/************************************************************************** Subsonic Csharp Copyright (C) 2010 Ian Fijolek This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************/ using System; using System.Collections.Generic; using System.Text; using System.Net; using System.IO; using System.Xml; namespace SubsonicAPI { #region Classes public class SubsonicItem { public string Name; public string id; public enum SubsonicItemType { Folder, Song } public SubsonicItemType ItemType; public override string ToString() { return Name; } } public class MusicFolder : SubsonicItem { #region private vars private List _Folders; private List _Songs; #endregion private vars #region properties public List Folders { get { return _Folders; } set { _Folders = value; } } public List Songs { get { return _Songs; } set { _Songs = value; } } #endregion properties public MusicFolder() { _Folders = new List(); _Songs = new List(); base.ItemType = SubsonicItemType.Folder; } public MusicFolder(string theName, string theId) { _Folders = new List(); _Songs = new List(); base.Name = theName; base.id = theId; base.ItemType = SubsonicItemType.Folder; } ~MusicFolder() { } public void AddSong(string title, string id) { Song newSong = new Song(title, id); _Songs.Add(newSong); } public void AddFolder(string name, string id) { MusicFolder newFolder = new MusicFolder(name, id); _Folders.Add(newFolder); } public Song FindSong(string theTitle) { Song theSong = _Songs.Find( delegate(Song sng) { return sng.Name == theTitle; } ); return theSong; } public MusicFolder FindFolder(string theFolderName) { MusicFolder theFolder = _Folders.Find( delegate(MusicFolder fldr) { return fldr.Name == theFolderName; } ); return theFolder; } } public class Song : SubsonicItem { public Song() { base.ItemType = SubsonicItemType.Song; } public Song(string theTitle, string theId) { Name = theTitle; id = theId; base.ItemType = SubsonicItemType.Song; } } #endregion Classes /// /// Open Source C# Implementation of the Subsonic API /// http://www.subsonic.org/pages/api.jsp /// public static class Subsonic { // Should be set from application layer when the application is loaded public static string appName; // Version of the REST API implemented private static string apiVersion = "1.3.0"; // Set with the login method static string server; static string authHeader; /// /// Takes parameters for server, username and password to generate an auth header /// and Pings the server /// /// /// /// /// Resulting XML (Future boolean) public static string LogIn(string theServer, string user, string password) { string result = "Nothing Happened"; server = theServer; authHeader = user + ":" + password; authHeader = Convert.ToBase64String(Encoding.Default.GetBytes(authHeader)); Stream theStream = MakeGenericRequest("ping", null); StreamReader sr = new StreamReader(theStream); result = sr.ReadToEnd(); /// TODO: Parse the result and determine if logged in or not return result; } /// /// Uses the Auth Header for logged in user to make an HTTP request to the server /// with the given Subsonic API method and parameters /// /// /// /// Datastream of the server response public static Stream MakeGenericRequest(string method, Dictionary parameters) { // Check to see if Logged In yet if (string.IsNullOrEmpty(authHeader)) { // Throw a Not Logged In exception Exception e = new Exception("No Authorization header. Must Log In first"); return null; } else { if (!method.EndsWith(".view")) method += ".view"; string requestURL = BuildRequestURL(method, parameters); WebRequest theRequest = WebRequest.Create(requestURL); theRequest.Method = "GET"; theRequest.Headers["Authorization"] = "Basic " + authHeader; WebResponse response = theRequest.GetResponse(); Stream dataStream = response.GetResponseStream(); return dataStream; } } /// /// Creates a URL for a request but does not make the actual request using set login credentials an dmethod and parameters /// /// /// /// Proper Subsonic API URL for a request public static string BuildRequestURL(string method, Dictionary parameters) { string requestURL = "http://" + server + "/rest/" + method + "?v=" + apiVersion + "&c=" + appName; if (parameters != null) { foreach (KeyValuePair parameter in parameters) { requestURL += "&" + parameter.Key + "=" + parameter.Value; } } return requestURL; } /// /// Returns an indexed structure of all artists. /// /// Required: No; If specified, only return artists in the music folder with the given ID. /// Required: No; If specified, only return a result if the artist collection has changed since the given time. /// Dictionary, Key = Artist and Value = id public static Dictionary GetIndexes(string musicFolderId = "", string ifModifiedSince = "") { // Load the parameters if provided Dictionary parameters = new Dictionary(); if (!string.IsNullOrEmpty(musicFolderId)) parameters.Add("musicFolderId", musicFolderId); if (!string.IsNullOrEmpty(ifModifiedSince)) parameters.Add("ifModifiedSince", ifModifiedSince); // Make the request Stream theStream = MakeGenericRequest("getIndexes", parameters); // Read the response as a string StreamReader sr = new StreamReader(theStream); string result = sr.ReadToEnd(); // Parse the resulting XML string into an XmlDocument XmlDocument myXML = new XmlDocument(); myXML.LoadXml(result); // Parse the XML document into a Dictionary Dictionary artists = new Dictionary(); if (myXML.ChildNodes[1].Name == "subsonic-response") { if (myXML.ChildNodes[1].FirstChild.Name == "indexes") { int i = 0; for (i = 0; i < myXML.ChildNodes[1].FirstChild.ChildNodes.Count; i++) { int j = 0; for (j = 0; j < myXML.ChildNodes[1].FirstChild.ChildNodes[i].ChildNodes.Count; j++) { string artist = myXML.ChildNodes[1].FirstChild.ChildNodes[i].ChildNodes[j].Attributes["name"].Value; string id = myXML.ChildNodes[1].FirstChild.ChildNodes[i].ChildNodes[j].Attributes["id"].Value; artists.Add(artist, id); } } } } return artists; } /// /// Streams a given music file. (Renamed from request name "stream") /// /// Required: Yes; A string which uniquely identifies the file to stream. /// Obtained by calls to getMusicDirectory. /// Required: No; If specified, the server will attempt to /// limit the bitrate to this value, in kilobits per second. If set to zero, no limit /// is imposed. Legal values are: 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256 and 320. /// public static Stream StreamSong(string id, int? maxBitRate = null) { // Reades the id of the song and sets it as a parameter Dictionary theParameters = new Dictionary(); theParameters.Add("id", id); if (maxBitRate.HasValue) theParameters.Add("maxBitRate", maxBitRate.ToString()); // Makes the request Stream theStream = MakeGenericRequest("stream", theParameters); return theStream; } /// /// Returns a listing of all files in a music directory. Typically used to get list of albums for an artist, or list of songs for an album. /// /// A string which uniquely identifies the music folder. Obtained by calls to getIndexes or getMusicDirectory. /// MusicFolder object containing info for the specified directory public static MusicFolder GetMusicDirectory(string id) { Dictionary theParameters = new Dictionary(); theParameters.Add("id", id); Stream theStream = MakeGenericRequest("getMusicDirectory", theParameters); StreamReader sr = new StreamReader(theStream); string result = sr.ReadToEnd(); XmlDocument myXML = new XmlDocument(); myXML.LoadXml(result); MusicFolder theFolder = new MusicFolder("ArtistFolder", id); if (myXML.ChildNodes[1].Name == "subsonic-response") { if (myXML.ChildNodes[1].FirstChild.Name == "directory") { theFolder.Name = myXML.ChildNodes[1].FirstChild.Attributes["name"].Value; theFolder.id = myXML.ChildNodes[1].FirstChild.Attributes["id"].Value; int i = 0; for (i = 0; i < myXML.ChildNodes[1].FirstChild.ChildNodes.Count; i++) { bool isDir = bool.Parse(myXML.ChildNodes[1].FirstChild.ChildNodes[i].Attributes["isDir"].Value); string title = myXML.ChildNodes[1].FirstChild.ChildNodes[i].Attributes["title"].Value; string theId = myXML.ChildNodes[1].FirstChild.ChildNodes[i].Attributes["id"].Value; if (isDir) theFolder.AddFolder(title, theId); else theFolder.AddSong(title, theId); } } } return theFolder; } /// /// Returns what is currently being played by all users. Takes no extra parameters. /// public static List GetNowPlaying() { List nowPlaying = new List(); Dictionary theParameters = new Dictionary(); Stream theStream = MakeGenericRequest("getNowPlaying", theParameters); StreamReader sr = new StreamReader(theStream); string result = sr.ReadToEnd(); return nowPlaying; } } }