Click here to Skip to main content
15,892,199 members
Articles / Programming Languages / C#

LyricsFetcher - The Easiest Way to Find Lyrics for your Songs

Rate me:
Please Sign up or sign in to vote.
4.93/5 (82 votes)
29 Oct 2009GPL325 min read 201.6K   2.4K   184  
An article describing the development of a non-trivial C#/.NET application to fetch lyrics for songs.
/*
 * [File purpose]
 * Author: Phillip Piper
 * Date: 8/01/2008 11:27 PM
 * 
 * CHANGE LOG:
 * when who what
 * 8/01/2008 JPP  Initial Version
 */

using System;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;

namespace LyricsFetcher.Tests
{
	[TestFixture]
    public class TestITunesLibrary
	{
		const int MAX_SONGS_TO_FETCH = 100;

        public virtual SongLibrary GetLibrary()
        {
            return new ITunesLibrary();
        }

		[Test]
		public void TestInitialState()
		{
			SongLibrary db = this.GetLibrary();
			Assert.IsFalse(db.HasSongs);
			Assert.AreEqual(0, db.MaxSongsToFetch);
		}
		
		[Test]
		public void TestBasicFetchAll()
		{
			SongLibrary db = this.GetLibrary();
			db.MaxSongsToFetch = MAX_SONGS_TO_FETCH;
			db.LoadSongs();
            db.WaitLoad();

			Assert.IsTrue(db.HasSongs);
			Assert.AreEqual(db.Songs.Count, db.MaxSongsToFetch);
		}
		
		[Test]
		public void TestFetchEvents()
		{
			SongLibrary db = this.GetLibrary();
            db.ProgessEvent += new EventHandler<ProgressEventArgs>(db_LoadingProgessEvent1);
            db.DoneEvent += new EventHandler<ProgressEventArgs>(db_LoadingDoneEvent1);

            this.doneEventCalled = false;
            for (int i=0; i<=100; i++) 
            	this.progressPercentages[i] = i;
            
            db.MaxSongsToFetch = Math.Max(100, MAX_SONGS_TO_FETCH);
            db.LoadSongs();
            db.WaitLoad();

            Assert.IsTrue(this.doneEventCalled);
            
            // We should have received one progress event for every value of percentage
            Assert.IsEmpty(this.progressPercentages.Keys);
		}
        bool doneEventCalled;
        Dictionary<int, int> progressPercentages = new Dictionary<int, int>();
        
        void db_LoadingDoneEvent1(object sender, ProgressEventArgs args)
        {
            this.doneEventCalled = true;
        }

        void db_LoadingProgessEvent1(object sender, ProgressEventArgs args)
        {
            this.progressPercentages.Remove(args.Percentage);
        }
		
		[Test]
		public void TestFetchCancelling()
		{
			SongLibrary db = this.GetLibrary();
            db.DoneEvent += new EventHandler<ProgressEventArgs>(db_LoadingDoneEvent2);
            this.doneEventCalled = false;

			db.MaxSongsToFetch = MAX_SONGS_TO_FETCH;
			db.LoadSongs();
			db.CancelLoad();
			
            Assert.IsTrue(doneEventCalled);
		}
		
		[Test]
		public void TestFetchCancelling2()
		{
			// This test isn't very reliable since it relies on timing. 
			// It is supposed to test that cancelling a fetch during a progress event does in fact cancel the fetch.
			// But progress events are triggered asynchronously, so the fetching may actually finish successfully
			// before the progress events are processed. 
			// We can "hide" this by making sure we fetch "enough" songs so that the progress events have a chance 
			// to cancel the fetching. On my current machine, 200 is usually enough.
			// JPP 2008/01/14
			
			SongLibrary db = this.GetLibrary();
            db.ProgessEvent += new EventHandler<ProgressEventArgs>(db_LoadingProgessEvent2);
            db.DoneEvent += new EventHandler<ProgressEventArgs>(db_LoadingDoneEvent2);

            this.doneEventCalled = false;

            db.MaxSongsToFetch = Math.Max(200, MAX_SONGS_TO_FETCH);
			db.LoadSongs();
            db.WaitLoad();

            Assert.IsTrue(doneEventCalled);
		}

        void db_LoadingDoneEvent2(object sender, ProgressEventArgs args)
        {
            this.doneEventCalled = true;
        }

        void db_LoadingProgessEvent2(object sender, ProgressEventArgs args)
        {
            args.IsCancelled = (args.Percentage >= 10);
        }
		
		[Test]
		public void TestFetchSongsWithoutLyrics()
		{
			SongLibrary db = this.GetLibrary();
			db.MaxSongsToFetch = MAX_SONGS_TO_FETCH;
            db.LoadSongs();
            db.WaitLoad();

			Assert.IsTrue(db.SongsWithoutLyrics.TrueForAll(
				delegate(Song s) {
					return (String.IsNullOrEmpty(s.Lyrics) || 
                        s.Lyrics.StartsWith("Failed") ||
                        s.Lyrics.StartsWith("[[LyricsFetcher failed"));
			    }
			));
		}
		
		[Test]
		public void TestFetchUntriedSongs()
		{
			SongLibrary db = this.GetLibrary();
			db.MaxSongsToFetch = MAX_SONGS_TO_FETCH;
            db.LoadSongs();
            db.WaitLoad();

			Assert.IsTrue(db.UntriedSongs.TrueForAll(
				delegate(Song s) {
					return (String.IsNullOrEmpty(s.Lyrics));
			    }
			));
		}
	}

    [TestFixture]
    public class TestWmpLibrary : TestITunesLibrary
    {
        public override SongLibrary GetLibrary()
        {
            return new WmpLibrary();
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Team Leader
Australia Australia
Phillip has been playing with computers since the Apple II was the hottest home computer available. He learned the fine art of C programming and Guru meditation on the Amiga.

C# and Python are his languages of choice. Smalltalk is his mentor for simplicity and beauty. C++ is to programming what drills are to visits to the dentist.

He worked for longer than he cares to remember as Lead Programmer and System Architect of the Objective document management system. (www.objective.com)

He has lived for 10 years in northern Mozambique, teaching in villages.

He has developed high volume trading software, low volume FX trading software, and is currently working for Atlassian on HipChat.

Comments and Discussions