Click here to Skip to main content
15,892,575 members
Articles / Programming Languages / XML

Retrieving CD Information from a remote Freedb database

Rate me:
Please Sign up or sign in to vote.
4.92/5 (32 votes)
29 Jul 2004CPOL10 min read 220.3K   4.3K   125  
Describes a library that can be used to retrieve Audio CD information from the CDDB compatible freedb database.
#region COPYRIGHT (c) 2004 by Brian Weeres
/* Copyright (c) 2004 by Brian Weeres
 * 
 * Email: bweeres@protegra.com; bweeres@hotmail.com
 * 
 * Permission to use, copy, modify, and distribute this software for any
 * purpose is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * If you modify it then please indicate so. 
 *
 * The software is provided "AS IS" and there are no warranties or implied warranties.
 * In no event shall Brian Weeres and/or Protegra Technology Group be liable for any special, 
 * direct, indirect, or consequential damages or any damages whatsoever resulting for any reason 
 * out of the use or performance of this software
 * 
 */
#endregion
using System;
using System.Text;
using System.Net;
using System.IO;
using System.Collections.Specialized;
using System.Diagnostics;

namespace Freedb
{
	/// <summary>
	/// Summary description for FreedbHelper.
	/// </summary>
	public class FreedbHelper
	{
		public const string MAIN_FREEDB_ADDRESS = "freedb.freedb.org";
		public const string DEFAULT_ADDITIONAL_URL_INFO = "/~cddb/cddb.cgi";
		private Site m_mainSite = new Site(MAIN_FREEDB_ADDRESS,"http",DEFAULT_ADDITIONAL_URL_INFO);
		private string m_UserName;
		private string m_Hostname;
		private string m_ClientName;
		private string m_Version;
		private string m_ProtocolLevel = "6"; // default to level 6 support
		private Site m_CurrentSite = null;
		

		#region Constants for Freedb commands
		public class Commands
		{
			public const string CMD_HELLO	= "hello";
			public const string CMD_READ	= "cddb+read";
			public const string CMD_QUERY	= "cddb+query";
			public const string CMD_SITES	= "sites";
			public const string CMD_PROTO	= "proto";
			public const string CMD_CATEGORIES	= "cddb+lscat";
			public const string CMD	= "cmd="; // will never use without the equals so put it here
			public const string CMD_TERMINATOR	= "."; 
		}
		#endregion

		#region Constants for Freedb ResponseCodes
		public class ResponseCodes
		{
			public const string CODE_210 = "210"; // Okay // or in a query multiple exact matches
			public const string CODE_401 = "401"; // sites: no site information available
			public const string CODE_402 = "402"; // Server Error
			
			public const string CODE_500 = "500"; // Invalid command, invalid parameters, etc.
			//query codes
			public const string CODE_200 = "200"; // Exact match 
			public const string CODE_211 = "211"; // InExact matches found - list follows
			public const string CODE_202 = "202"; // No match 
			public const string CODE_403 = "403"; // Database entry is corrupt
			public const string CODE_409 = "409"; // No Handshake

			// our own code
			public const string CODE_INVALID = "-1"; // Invalid code 
		}
		#endregion

		
		#region Public Properties
		/// <summary>
		/// Property Version (string)
		/// </summary>
		public string Version
		{
			get
			{
				return this.m_Version;
			}
			set
			{
				this.m_Version = value;
			}
		}

		




		/// <summary>
		/// Property MainSite(string)
		/// </summary>
		public Site MainSite
		{
			get
			{
				return this.m_mainSite 		;
			}
		}

		/// <summary>
		/// Property ClientName (string)
		/// </summary>
		public string ClientName
		{
			get
			{
				return this.m_ClientName;
			}
			set
			{
				this.m_ClientName = value;
			}
		}
		
		/// <summary>
		/// Property Hostname (string)
		/// </summary>
		public string Hostname
		{
			get
			{
				return this.m_Hostname;
			}
			set
			{
				this.m_Hostname = value;
			}
		}
		
		/// <summary>
		/// Property UserName (string)
		/// </summary>
		public string UserName
		{
			get
			{
				return this.m_UserName;
			}
			set
			{
				this.m_UserName = value;
			}
		}

		/// <summary>
		/// Property ProtocolLevel (string)
		/// </summary>
		public string ProtocolLevel
		{
			get
			{
				return this.m_ProtocolLevel;
			}
			set
			{
				this.m_ProtocolLevel = value;
			}
		}

		/// <summary>
		/// Property CurrentSite (Site)
		/// </summary>
		public Site CurrentSite
		{
			get
			{
				return this.m_CurrentSite;
			}
			set
			{
				this.m_CurrentSite = value;
			}
		}


		
		
		public FreedbHelper()
		{
			m_ProtocolLevel = "6"; // default it
		}



		/// <summary>
		/// Retrieve all Freedb servers from the main server site
		/// </summary>
		/// <param name="sites">SiteCollection that is populated with the site information</param>
		/// <returns>Response Code</returns>
		public string GetSites(out SiteCollection sites)
		{
			return GetSites(Site.PROTOCOLS.ALL, out sites);
		}
		
		#endregion


		/// <summary>
		/// Get the Freedb sites
		/// </summary>
		/// <param name="protocol"></param>
		/// <param name="sites">SiteCollection that is populated with the site information</param>
		/// <returns>Response Code</returns>
		/// 
		public string GetSites(string protocol, out SiteCollection sites)
		{
			if (protocol != Site.PROTOCOLS.CDDBP  && protocol != Site.PROTOCOLS.HTTP)
				protocol = Site.PROTOCOLS.ALL;

			StringCollection coll;

			try
			{
				coll= Call(Commands.CMD_SITES,m_mainSite.GetUrl());
			}
			
			catch (Exception ex)
			{
				Debug.WriteLine("Error retrieving Sites." + ex.Message);
				Exception newEx = new Exception("FreedbHelper.GetSites: Error retrieving Sites.",ex);
				throw newEx;
			}
			
			sites = null;

			// check if results came back
			if (coll.Count < 0)
			{
				string msg = "No results returned from sites request.";
				Exception ex = new Exception(msg,null);
				throw ex;
			}

			string code = GetCode(coll[0]);
			if (code == ResponseCodes.CODE_INVALID)
			{
				string msg = "Unable to process results Sites Request. Returned Data: " + coll[0];
				Exception ex = new Exception(msg,null);
				throw ex;
			}

			switch (code)
			{
				case ResponseCodes.CODE_500:
					return ResponseCodes.CODE_500;

				case ResponseCodes.CODE_401:
					return ResponseCodes.CODE_401;

				case ResponseCodes.CODE_210:
				{
					coll.RemoveAt(0);
					sites = new SiteCollection();
					foreach (String line in coll)
					{
						Debug.WriteLine("line: " + line);
						Site site = new Site(line);
						if (protocol == Site.PROTOCOLS.ALL)
							sites.Add(new Site(line));
						else if (site.Protocol == protocol)
							sites.Add(new Site(line));
					}

					return ResponseCodes.CODE_210;
				}

				default: 
					return ResponseCodes.CODE_500;
			}

		}
	

		/// <summary>
		/// Read Entry from the database. 
		/// </summary>
		/// <param name="qr">A QueryResult object that is created by performing a query</param>
		/// <param name="cdEntry">out parameter - CDEntry object</param>
		/// <returns></returns>
		public string Read(QueryResult qr, out CDEntry cdEntry)
		{
			Debug.Assert(qr != null);
			cdEntry = null;
			
			StringCollection coll = null;
			StringBuilder builder = new StringBuilder(FreedbHelper.Commands.CMD_READ);
			builder.Append("+");
			builder.Append(qr.Category);
			builder.Append("+");
			builder.Append(qr.Discid);

			//make call
			try
			{
				coll = Call(builder.ToString());
			}
			
			catch (Exception ex)
			{
				string msg = "Error performing cddb read.";
				Exception newex = new Exception(msg,ex);
				throw newex ;
			}

			// check if results came back
			if (coll.Count < 0)
			{
				string msg = "No results returned from cddb read.";
				Exception ex = new Exception(msg,null);
				throw ex;
			}


			string code = GetCode(coll[0]);
			if (code == ResponseCodes.CODE_INVALID)
			{
				string msg = "Unable to process results for cddb read. Returned Data: " + coll[0];
				Exception ex = new Exception(msg,null);
				throw ex;
			}


			switch (code)
			{
				case ResponseCodes.CODE_500:
					return ResponseCodes.CODE_500;

				case ResponseCodes.CODE_401: // entry not found
				case ResponseCodes.CODE_402: // server error
				case ResponseCodes.CODE_403: // Database entry is corrupt
				case ResponseCodes.CODE_409: // No handshake
					return code;

				case ResponseCodes.CODE_210: // good 
				{
					coll.RemoveAt(0); // remove the 210
					cdEntry = new CDEntry(coll);
					return ResponseCodes.CODE_210;
				}
				default:
					return ResponseCodes.CODE_500;
			}
		}


		/// <summary>
		/// Query the freedb server to see if there is information on this cd
		/// </summary>
		/// <param name="querystring"></param>
		/// <param name="queryResult"></param>
		/// <param name="queryResultsColl"></param>
		/// <returns></returns>
		public string Query(string querystring, out QueryResult queryResult, out QueryResultCollection queryResultsColl)
		{
			queryResult = null;
			queryResultsColl = null;
			StringCollection coll = null;

			StringBuilder builder = new StringBuilder(FreedbHelper.Commands.CMD_QUERY);
			builder.Append("+");
			builder.Append(querystring);
			
			//make call
			try
			{
				coll = Call(builder.ToString());
			}
			
			catch (Exception ex)
			{
				string msg = "Unable to perform cddb query.";
				Exception newex = new Exception(msg,ex);
				throw newex ;
			}
			
			// check if results came back
			if (coll.Count < 0)
			{
				string msg = "No results returned from cddb query.";
				Exception ex = new Exception(msg,null);
				throw ex;
			}

			string code = GetCode(coll[0]);
			if (code == ResponseCodes.CODE_INVALID)
			{
				string msg = "Unable to process results returned for query: Data returned: " + coll[0];
				Exception ex = new Exception (msg,null);
				throw ex;
			}


			switch (code)
			{
				case ResponseCodes.CODE_500:
					return ResponseCodes.CODE_500;
			
				// Multiple results were returned
				// Put them into a queryResultCollection object
				case ResponseCodes.CODE_211:
				case ResponseCodes.CODE_210:
				{
					queryResultsColl = new QueryResultCollection();
					//remove the 210 or 211
					coll.RemoveAt(0);
					foreach (string line in coll)
					{
						QueryResult result = new QueryResult(line,true);
						queryResultsColl.Add(result);
					}
				
					return ResponseCodes.CODE_211;
				}
			
			
				// exact match 
				case ResponseCodes.CODE_200:
				{
					queryResult = new QueryResult(coll[0]);
					return ResponseCodes.CODE_200;
				}
			

				//not found
				case ResponseCodes.CODE_202:
					return ResponseCodes.CODE_202;

				//Database entry is corrupt
				case ResponseCodes.CODE_403:
					return ResponseCodes.CODE_403;

					//no handshake
				case ResponseCodes.CODE_409:
					return ResponseCodes.CODE_409;
					
				default:
					return ResponseCodes.CODE_500;
			
			} // end of switch


		}


		/// <summary>
		/// Retrieve the categories
		/// </summary>
		/// <param name="strings"></param>
		/// <returns></returns>
		public string GetCategories(out StringCollection strings)
		{

			StringCollection coll;
			strings = null;

			try
			{
				coll = Call(FreedbHelper.Commands.CMD_CATEGORIES);
			}
			
			catch (Exception ex)
			{
				string msg = "Unable to retrieve Categories.";
				Exception newex = new Exception(msg,ex);
				throw newex;
			}
			
			// check if results came back
			if (coll.Count < 0)
			{
				string msg = "No results returned from categories request.";
				Exception ex = new Exception(msg,null);
				throw ex;
			}

			string code = GetCode(coll[0]);
			if (code == ResponseCodes.CODE_INVALID)
			{
				string msg = "Unable to retrieve Categories. Data Returned: " + coll[0];
				Exception ex = new Exception(msg,null);
				throw ex;
			}

			switch (code)
			{
				case ResponseCodes.CODE_500:
					return ResponseCodes.CODE_500;

				case ResponseCodes.CODE_210:
				{
					strings = coll;
					coll.RemoveAt(0);
					return ResponseCodes.CODE_210;
				}

				default:
				{
					string msg = "Unknown code returned from GetCategories: " + coll[0];
					Exception ex = new Exception(msg,null);
					throw ex;
				}
					
					
			}

		}


		/// <summary>
		/// Call the Freedb server using the specified command and the current site
		/// If the current site is null use the default server
		/// </summary>
		/// <param name="command">The command to be exectued</param>
		/// <returns>StringCollection</returns>
		private StringCollection Call(string command)
		{
			if (m_CurrentSite != null)
				return Call(command,m_CurrentSite.GetUrl());
			else
				return Call(command,m_mainSite.GetUrl());
		}

		/// <summary>
		/// Call the Freedb server using the specified command and the specified url
		/// The command should not include the cmd= and hello and proto parameters.
		/// They will be added automatically
		/// </summary>
		/// <param name="command">The command to be exectued</param>
		/// <param name="url">The Freedb server to use</param>
		/// <returns>StringCollection</returns>
		private StringCollection Call(string commandIn, string url)
		{
			StreamReader reader = null;
			HttpWebResponse response = null;
			StringCollection coll = new StringCollection();
			
			try
			{
				//create our HttpWebRequest which we use to call the freedb server
				HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
				req.ContentType = "text/plain";
				// we are using th POST method of calling the http server. We could have also used the GET method
				req.Method="POST";
				//add the hello and proto commands to the request
				string command = BuildCommand(Commands.CMD + commandIn);
				//using Unicode
				byte[] byteArray = Encoding.UTF8.GetBytes(command);
				//get our request stream
				Stream newStream= req.GetRequestStream();
				//write our command data to it
				newStream.Write(byteArray,0,byteArray.Length);
				newStream.Close();
				//Make the call. Note this is a synchronous call
				response = (HttpWebResponse) req.GetResponse();
				//put the results into a StreamReader
				reader = new StreamReader(response.GetResponseStream(),System.Text.Encoding.UTF8);
				// add each line to the StringCollection until we get the terminator
				string line;
				while ((line = reader.ReadLine()) != null) 
				{
					if (line.StartsWith(Commands.CMD_TERMINATOR))
						break;
					else
						coll.Add(line);
				}
			}
			
			catch (Exception ex)
			{
				throw ex;
			}

			finally
			{
				if (response != null)
					response.Close();
				if (reader != null)
					reader.Close();
			}
			
			return coll;
		}





		/// <summary>
		/// Given a specific command add on the hello and proto which are requied for an http call
		/// </summary>
		/// <param name="command"></param>
		/// <returns></returns>
		private string BuildCommand(string command)
		{
			StringBuilder builder = new StringBuilder(command);
			builder.Append("&");
			builder.Append(Hello());
			builder.Append("&");
			builder.Append(Proto());
			return builder.ToString();
		}

		/// <summary>
		/// Build the hello part of the command 
		/// </summary>
		/// <returns></returns>
		public string Hello()
		{
			StringBuilder builder = new StringBuilder(Commands.CMD_HELLO);
			builder.Append("=");
			builder.Append(m_UserName);
			builder.Append("+");
			builder.Append(this.m_Hostname);
			builder.Append("+");
			builder.Append(this.ClientName);
			builder.Append("+");
			builder.Append(this.m_Version);
			return builder.ToString();
		}

		/// <summary>
		/// Build the Proto part of the command
		/// </summary>
		/// <returns></returns>
		public string Proto()
		{
			StringBuilder builder = new StringBuilder(Commands.CMD_PROTO);
			builder.Append("=");
			builder.Append(m_ProtocolLevel );
			return builder.ToString();
		}


		/// <summary>
		/// given the first line of a result set return the CDDB code
		/// </summary>
		/// <param name="firstLine"></param>
		/// <returns></returns>
		private string GetCode(string firstLine)
		{
			firstLine = firstLine.Trim();
			
			//find first white space after start
			int index = firstLine.IndexOf(' ');
			if (index != -1)
				firstLine = firstLine.Substring(0,index);
			else
			{
				return ResponseCodes.CODE_INVALID;
			}

			return firstLine;
		}



		/// <summary>
		/// If a different default site address is preferred over "freedb.freedb.org"
		/// set it here
		/// NOTE: Only set the ip address
		/// </summary>
		/// <param name="ipAddress"></param>
		public void SetDefaultSiteAddress(string siteAddress)
		{
			//sanity check on the url
			if (siteAddress.IndexOf("http") != -1 ||
				siteAddress.IndexOf("cgi") != -1)
				throw new Exception("Invalid Site Address specified");

			this.m_mainSite.SiteAddress = siteAddress;
		}

	}
}

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 Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions