Click here to Skip to main content
Click here to Skip to main content
 
Add your own
alternative version

LyricsFetcher - The Easiest Way to Find Lyrics for your Songs

, 29 Oct 2009
An article describing the development of a non-trivial C#/.NET application to fetch lyrics for songs.
LyricsFetcherCode-0.5.1.zip
CommandLine
Properties
docs
design.rst
index.rst
LyricsFetching.dia
SongManagement.dia
_static
_templates
LyricsFetcher
LyricsFetcher.csproj.user
LyricsFetcher.ico
LyricsFetching
ObjectListView.dll
Properties
Settings.settings
Resources
burn16.png
logo.png
network-offline.png
process-stop.png
process-working.png
splashscreen.png
stop24.png
trebleclef128.png
trebleclef32.png
trebleclef48.png
SongManagement
UserInterface
Utility
Web References
org.lyricwiki
LyricsResult.datasource
Reference.map
server.wsdl
SongResult.datasource
SOTDResult.datasource
setup
LyricsFetcher.iss
Readme.docx
Readme.pdf
Tests
LyricsFetcher.nunit
Properties
LyricsFetcherCode-0.6.zip
LyricsFetcherCode-0.6
LyricsFetcher
CommandLine
Properties
docs
_static
_templates
design.rst
index.rst
LyricsFetching.dia
SongManagement.dia
graphics
group.png
iTunes.png
logo.png
note16.png
note24.png
play24.png
splashscreen.png
stop24.png
Thumbs.db
Toolbar - Music.ico
trebleclef128.png
trebleclef32.png
trebleclef48.png
WMP.png
LyricsFetcher
AACTagReader.exe
genpuid.exe
libexpat.dll
LyricsFetcher.csproj.user
LyricsFetcher.ico
LyricsFetching
MetaDataFetching
mipcore.exe
ObjectListView.dll
Properties
Settings.settings
Resources
burn16.png
logo.png
network-offline.png
process-stop.png
process-working.png
splashscreen.png
stop24.png
trebleclef128.png
trebleclef32.png
trebleclef48.png
SongManagement
UserInterface
Utility
Web References
org.lyricwiki
LyricsResult.datasource
Reference.map
server.wsdl
SongResult.datasource
SOTDResult.datasource
setup
LyricsFetcher.iss
Readme.docx
Readme.pdf
Tests
LyricsFetcher.nunit
Properties
LyricsFetcherCode-0_6_1.zip
LyricsFetcherCode-0.6.1
LyricsFetcher
CommandLine
Properties
docs
design.rst
index.rst
LyricsFetching.dia
SongManagement.dia
_static
_templates
graphics
group.png
iTunes.png
logo.png
note16.png
note24.png
play24.png
splashscreen.png
stop24.png
Thumbs.db
Toolbar - Music.ico
trebleclef128.png
trebleclef32.png
trebleclef48.png
WMP.png
LyricsFetcher
AACTagReader.exe
genpuid.exe
libexpat.dll
LyricsFetcher.csproj.user
LyricsFetcher.ico
LyricsFetching
MetaDataFetching
mipcore.exe
ObjectListView.dll
Properties
Settings.settings
Resources
burn16.png
logo.png
network-offline.png
process-stop.png
process-working.png
splashscreen.png
stop24.png
Thumbs.db
trebleclef128.png
trebleclef32.png
trebleclef48.png
SongManagement
UserInterface
Utility
setup
LyricsFetcher.iss
Readme.docx
Readme.pdf
Tests
LyricsFetcher.nunit
Properties
LyricsFetcherCode.zip
bin
Release
design.rst
index.rst
LyricsFetching.dia
SongManagement.dia
bin
Release
LyricsFetcher.csproj.user
LyricsFetcher.ico
ObjectListView.dll
Settings.settings
burn16.png
logo.png
network-offline.png
process-stop.png
process-working.png
splashscreen.png
stop24.png
trebleclef128.png
trebleclef32.png
trebleclef48.png
LyricsResult.datasource
Reference.map
server.wsdl
SongResult.datasource
SOTDResult.datasource
LyricsFetcher.iss
Readme.docx
Readme.pdf
bin
Debug
LyricsFetcher.nunit
LyricsFetcherSetup-0.5.1.zip
LyricsFetcherSetup-0.5.1
LyricsFetcherSetup-0.5.1.EXE
LyricsFetcherSetup-0.5.zip
LyricsFetcherSetup-0.6.zip
LyricsFetcherSetup.zip
/*
 * AbstractFetchManager - A FetchManager is a thread pool like work manager, that allows
 * songs to be queued for some long running operation.
 *
 * Author: Phillip Piper
 * Date: 2009-03-25 10:51 PM
 *
 * Change log:
 * 2009-03-25  JPP  Initial version
 */
using System;
using System.Collections;
using System.Collections.Generic;

namespace LyricsFetcher
{
    /// <summary>
    /// An AbstractFetchManager provides a thread-pool like work manager. Songs can
    /// be queued for some long running operation; those operations can be waited upon, 
    /// or cancelled.
    /// </summary>
    abstract public class AbstractFetchManager
    {
        public AbstractFetchManager() {
        }

        #region Properties

        /// <summary>
        /// How many songs are currently fetching their lyrics?
        /// </summary>
        public int CountFetching {
            get {
                return this.fetchingSongs.Count;
            }
        }

        /// <summary>
        /// How many songs are waiting to start fetching their lyrics?
        /// </summary>
        public int CountWaiting {
            get {
                return this.waitingSongs.Count;
            }
        }

        /// <summary>
        /// Return true if any lyrics are currently being fetched or are waiting to be fetched.
        /// </summary>
        public bool IsFetching {
            get {
                return this.CountWaiting > 0 || this.CountFetching > 0;
            }
        }

        /// <summary>
        /// How many simultaneous threads will be used for fetching lyrics?
        /// </summary>
        public int MaxFetchingThreads {
            get { return this.maxFetching; }
            set { this.maxFetching = Math.Max(1, value); }
        }
        private int maxFetching = 5;

        /// <summary>
        /// Pause the fetching of lyrics. This does not stop existing threads --
        /// it simply prevents new threads from being launched
        /// </summary>
        public bool Paused {
            get { return this.paused; }
            set {
                this.paused = value;
                if (!this.paused)
                    this.PossibleStartNewThreads();
            }
        }
        private bool paused = true;

        #endregion

        #region Commands

        /// <summary>
        /// Start the process of fetching lyrics
        /// </summary>
        public void Start() {
            this.Paused = false;
        }

        /// <summary>
        /// Add the given collection of songs to those whose lyrics are being fetched
        /// </summary>
        /// <param name="songs"></param>
        public void Queue(IEnumerable<Song> songs) {
            foreach (Song song in songs) {
                // If the song is already being proceeded, ignore it
                if (!this.songStatusMap.ContainsKey(song)) {
                    this.waitingSongs.Add(song);
                    this.QueueInternal(song);
                }
            }
            this.PossibleStartNewThreads();
        }

        /// <summary>
        /// Add the song to the list of songs whose lyrics are being fetched
        /// </summary>
        /// <param name="song"></param>
        public void Queue(Song song) {
            this.Queue(new Song[] { song });
        }

        /// <summary>
        /// After new songs have been added, or old fetches have completed,
        /// possibly start a number of new threads up to our limit of concurrent
        /// fetches.
        /// </summary>
        protected void PossibleStartNewThreads() {
            if (this.Paused)
                return;

            // Prevent race conditions
            lock (this.thisLock) {
                while (this.CountWaiting > 0 && this.CountFetching < this.maxFetching) {
                    this.StartNewThread();
                }
            }
        }
        private Object thisLock = new Object();

        /// <summary>
        /// Start one new process for the first waiting song, removing
        /// that song from the wait queue
        /// </summary>
        protected void StartNewThread() {
            Song song = this.waitingSongs[0];
            this.waitingSongs.RemoveAt(0);
            this.fetchingSongs.Add(song);
            this.StartInternal(song);
        }

        /// <summary>
        /// Cancel the fetching of lyrics of the given song
        /// </summary>
        /// <param name="s"></param>
        public void Cancel(Song song) {
            this.CancelOne(song);
            this.PossibleStartNewThreads();
        }

        /// <summary>
        /// Cancel all fetches
        /// </summary>
        public void CancelAll() {
            foreach (Song song in this.waitingSongs.ToArray())
                this.CancelOne(song);
            foreach (Song song in this.fetchingSongs.ToArray())
                this.CancelOne(song);
        }

        /// <summary>
        /// Cancel the work on the given song
        /// </summary>
        /// <param name="song">The song to be cancelled</param>
        protected void CancelOne(Song song) {
            this.CancelInternal(song);
            this.waitingSongs.Remove(song);
            this.fetchingSongs.Remove(song);
            this.songStatusMap.Remove(song);
        }

        /// <summary>
        /// The given song has finished being worked on. Cleanup its 
        /// resources and start new tasks
        /// </summary>
        /// <param name="song"></param>
        protected void CleanupOne(Song song) {
            this.fetchingSongs.Remove(song);
            this.songStatusMap.Remove(song);
            this.PossibleStartNewThreads();
        }

        /// <summary>
        /// Wait until all lyrics have been fetched
        /// </summary>
        public void WaitUntilFinished() {
            while (this.IsFetching) {
                //System.Windows.Forms.Application.DoEvents();
                System.Threading.Thread.Sleep(10);
            }
        }
        #endregion

        #region Abstract methods

        /// <summary>
        /// Remove the given song from those being operated on.
        /// </summary>
        /// <param name="song">The song to be removed</param>
        protected abstract void CancelInternal(Song song);

        /// <summary>
        /// Add the song to the list of songs for whom some information is being fetched.
        /// </summary>
        /// <param name="song">The song to be queued</param>
        /// <remarks>This should add an entry into the songStatusMap to track
        /// the fetch status of the song</remarks>
        protected abstract void QueueInternal(Song song);

        /// <summary>
        /// Start a long running thread to process the given song
        /// </summary>
        protected abstract void StartInternal(Song song);

        #endregion

        protected List<Song> waitingSongs = new List<Song>();
        protected List<Song> fetchingSongs = new List<Song>();
        protected Hashtable songStatusMap = new Hashtable();

    }
}

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)

Share

About the Author

Phillip Piper
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 is currently developing high volume trading software for an Australian trading house.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 29 Oct 2009
Article Copyright 2009 by Phillip Piper
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid