diff --git a/.gitignore b/.gitignore index baf3686..33fb3c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .hg/ .hgignore - +*.userprefs +bin/ diff --git a/SubsonicMono/SubsonicAPI/SubsonicAPI.pidb b/SubsonicMono/SubsonicAPI/SubsonicAPI.pidb index 1e55b11..80081c7 100644 Binary files a/SubsonicMono/SubsonicAPI/SubsonicAPI.pidb and b/SubsonicMono/SubsonicAPI/SubsonicAPI.pidb differ diff --git a/SubsonicMono/SubsonicMono/SubsonicPlayer.pidb b/SubsonicMono/SubsonicMono/SubsonicPlayer.pidb index 52bd816..f0a1804 100644 Binary files a/SubsonicMono/SubsonicMono/SubsonicPlayer.pidb and b/SubsonicMono/SubsonicMono/SubsonicPlayer.pidb differ diff --git a/SubsonicPlayer/SubsonicAPI/SubsonicAPI.cs b/SubsonicPlayer/SubsonicAPI/SubsonicAPI.cs index 471f558..d30245f 100644 --- a/SubsonicPlayer/SubsonicAPI/SubsonicAPI.cs +++ b/SubsonicPlayer/SubsonicAPI/SubsonicAPI.cs @@ -1,4 +1,4 @@ -/************************************************************************** +/************************************************************************** Subsonic Csharp Copyright (C) 2010 Ian Fijolek @@ -29,118 +29,164 @@ namespace SubsonicAPI public class SubsonicItem { - public string Name; - public string id; - public enum SubsonicItemType { - Folder, Song + Folder, Song, Artist, Library, Playlist + } + + public string name; + public string id; + public string lastModified; + public string lastAccessed; + public SubsonicItemType itemType; + public SubsonicItem parent; + private List _children; + + public List children + { + get + { + if (_children == null) + { + if (this.itemType == SubsonicItemType.Song) + _children = null; + else + _children = Subsonic.GetItemChildren(this, ""); + } + return _children; + } + set + { + _children = value; + } + } + + public SubsonicItem() + { + this.name = ""; + this.id = ""; + this.lastAccessed = DateTime.Now.ToString(); + } + + public SubsonicItem(string name, string id) + { + this.name = name; + this.id = name; + this.lastAccessed = DateTime.Now.ToString(); } - public SubsonicItemType ItemType; - + public SubsonicItem(string name, string id, SubsonicItemType itemType, SubsonicItem parent) + { + this.name = name; + this.id = id; + this.itemType = itemType; + this.parent = parent; + this.lastAccessed = DateTime.Now.ToString(); + } + public override string ToString() { - return Name; + return name; + } + + public SubsonicItem FindItemById(string id) + { + SubsonicItem foundItem = null; + + // If the current item is the item we are looking for, return it + if (this.id == id) + foundItem = this; + // Otherwise, we check the children if they exist + else if (_children != null) + { + foreach(SubsonicItem child in _children) + { + // If this child is the item we are looking for, return it + if (child.id == id) + { + foundItem = child; + break; + } + else + { + foundItem = child.FindItemById(id); + if (foundItem != null) + break; + } + } + } + + return foundItem; + } + + public SubsonicItem GetChildByName(string childName) + { + SubsonicItem theItem = null; + + if (_children != null) + { + theItem = _children.Find( + delegate(SubsonicItem itm) + { + return itm.name == childName ; + } + ); + } + + return theItem; } } - - 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 string artist; + public string album; + public string title; + public Song() { - base.ItemType = SubsonicItemType.Song; + this.artist = ""; + this.title = ""; + this.album = ""; + this.name = ""; + this.id = ""; + this.itemType = SubsonicItem.SubsonicItemType.Song; + this.parent = null; + this.lastAccessed = DateTime.Now.ToString(); } - - public Song(string theTitle, string theId) + + public Song(string title,string artist, string album, string id) { - Name = theTitle; - id = theId; - - base.ItemType = SubsonicItemType.Song; + this.artist = artist; + this.title = title; + this.album = album; + this.name = title; + this.id = id; + this.itemType = SubsonicItem.SubsonicItemType.Song; + this.parent = null; + this.lastAccessed = DateTime.Now.ToString(); + } + + public Song(string title, string artist, string album, string id, SubsonicItem parent) + { + this.artist = artist; + this.title = title; + this.album = album; + this.name = title; + this.id = id; + this.itemType = SubsonicItem.SubsonicItemType.Song; + this.parent = parent; + this.lastAccessed = DateTime.Now.ToString(); + } + + public Stream getStream() + { + return Subsonic.StreamSong(this.id); + } + + public override string ToString() + { + return artist + " - " + title; } } @@ -152,15 +198,36 @@ namespace SubsonicAPI /// public static class Subsonic { + private static SubsonicItem _MyLibrary; + + /// + /// Public Property that can be used for auto-retrieving children + /// + public static SubsonicItem MyLibrary + { + get + { + return _MyLibrary; + } + set + { + _MyLibrary = value; + } + } + // Should be set from application layer when the application is loaded public static string appName; - // Version of the REST API implemented + // Min version of the REST API implemented private static string apiVersion = "1.3.0"; // Set with the login method static string server; static string authHeader; + + // Used for generating direct URLS + static string encPass; + static string username; /// /// Takes parameters for server, username and password to generate an auth header @@ -177,7 +244,13 @@ namespace SubsonicAPI server = theServer; authHeader = user + ":" + password; authHeader = Convert.ToBase64String(Encoding.Default.GetBytes(authHeader)); - + + // Store user and encoded password for alternate authentication + username = user; + Byte[] passwordBytes = Encoding.Default.GetBytes(password); + for (int i = 0; i < passwordBytes.Length; i++) + encPass += passwordBytes[i].ToString("x2"); + Stream theStream = MakeGenericRequest("ping", null); StreamReader sr = new StreamReader(theStream); @@ -186,6 +259,8 @@ namespace SubsonicAPI /// TODO: Parse the result and determine if logged in or not + _MyLibrary = new SubsonicItem("LibraryRoot", "-1", SubsonicItem.SubsonicItemType.Library, null); + return result; } @@ -244,13 +319,122 @@ namespace SubsonicAPI return requestURL; } + /// + /// Creates a URL for a command with username and encoded pass in the URL + /// + /// + /// + /// URL for streaming a song or retrieving the results of a call + public static string BuildDirectURL(string method, Dictionary parameters) + { + string callURL = "http://" + server + "/rest/" + method + "?v=" + apiVersion + "&c=" + appName + + "&u=" + username + "&p=enc:" + encPass; + if (parameters != null) + { + foreach (KeyValuePair parameter in parameters) + { + callURL += "&" + parameter.Key + "=" + parameter.Value; + } + } + return callURL; + } + + /// + /// Returns a list of SubsonicItems that fall inside the parent object + /// + /// + /// A + /// + /// + /// A + /// + /// + /// A + /// + public static List GetItemChildren(SubsonicItem parent, string ifModifiedSince) + { + Dictionary parameters = new Dictionary(); + + // Generate the proper request for the parent type + string requestType, musicFolderId; + if (parent.itemType == SubsonicItem.SubsonicItemType.Library) + { + requestType = "getIndexes"; + if (parent.id != "-1") + parameters.Add("musicFolderId", parent.id); + } + else + { + requestType = "getMusicDirectory"; + parameters.Add("id", parent.id); + } + + // Load the parameters if provided + if (!string.IsNullOrEmpty(ifModifiedSince)) + parameters.Add("ifModifiedSince", ifModifiedSince); + + // Make the request + Stream theStream = MakeGenericRequest(requestType, 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); + + List children = new List(); + + // Parse the artist + if (parent.itemType == SubsonicItem.SubsonicItemType.Library) + { + if (myXML.ChildNodes[1].Name == "subsonic-response") + { + if (myXML.ChildNodes[1].FirstChild.Name == "indexes") + { + for (int i = 0; i < myXML.ChildNodes[1].FirstChild.ChildNodes.Count; i++) + { + for (int 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; + + children.Add(new SubsonicItem(artist, id, SubsonicItem.SubsonicItemType.Folder, parent)); + } + } + } + } + } + // Parse the directory + else if (parent.itemType == SubsonicItem.SubsonicItemType.Folder) + { + if (myXML.ChildNodes[1].Name == "subsonic-response") + { + if (myXML.ChildNodes[1].FirstChild.Name == "directory") + { + for (int 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 id = myXML.ChildNodes[1].FirstChild.ChildNodes[i].Attributes["id"].Value; + + SubsonicItem theItem = new SubsonicItem(title, id, (isDir ? SubsonicItem.SubsonicItemType.Folder : SubsonicItem.SubsonicItemType.Song), parent); + children.Add(theItem); + } + } + } + } + + return children; + } + /// /// 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 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 = "") + public static List GetIndexes(string musicFolderId, string ifModifiedSince) { // Load the parameters if provided Dictionary parameters = new Dictionary(); @@ -270,8 +454,8 @@ namespace SubsonicAPI XmlDocument myXML = new XmlDocument(); myXML.LoadXml(result); - // Parse the XML document into a Dictionary - Dictionary artists = new Dictionary(); + // Parse the XML document into a List + List artists = new List(); if (myXML.ChildNodes[1].Name == "subsonic-response") { if (myXML.ChildNodes[1].FirstChild.Name == "indexes") @@ -285,7 +469,7 @@ namespace SubsonicAPI 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); + artists.Add(new SubsonicItem(artist, id)); } } } @@ -293,6 +477,16 @@ namespace SubsonicAPI return artists; } + + public static List GetIndexes(string musicFolderId) + { + return GetIndexes(musicFolderId, ""); + } + + public static List GetIndexes() + { + return GetIndexes("", ""); + } /// /// Streams a given music file. (Renamed from request name "stream") @@ -303,7 +497,7 @@ namespace SubsonicAPI /// 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) + public static Stream StreamSong(string id, int? maxBitRate) { // Reades the id of the song and sets it as a parameter Dictionary theParameters = new Dictionary(); @@ -317,13 +511,17 @@ namespace SubsonicAPI return theStream; } + public static Stream StreamSong(string id) + { + return StreamSong(id, null); + } /// /// 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) + public static List GetMusicDirectory(string id) { Dictionary theParameters = new Dictionary(); theParameters.Add("id", id); @@ -336,14 +534,15 @@ namespace SubsonicAPI XmlDocument myXML = new XmlDocument(); myXML.LoadXml(result); - MusicFolder theFolder = new MusicFolder("ArtistFolder", id); + List theContents = new List(); 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; + SubsonicItem theParent = new SubsonicItem(); + theParent.name = myXML.ChildNodes[1].FirstChild.Attributes["name"].Value; + theParent.id = myXML.ChildNodes[1].FirstChild.Attributes["id"].Value; int i = 0; for (i = 0; i < myXML.ChildNodes[1].FirstChild.ChildNodes.Count; i++) @@ -352,32 +551,145 @@ namespace SubsonicAPI 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); + SubsonicItem theItem = new SubsonicItem(title, theId, (isDir ? SubsonicItem.SubsonicItemType.Folder : SubsonicItem.SubsonicItemType.Song), theParent); + theContents.Add(theItem); } } } - return theFolder; + return theContents; } - - /// - /// 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(); + + /// + /// 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; - } + /// TODO: Parse result to list + + return nowPlaying; + } + + /// + /// Performs a search valid for the current version of the subsonic server + /// If version is >= 1.4.0 search2 + /// Else search + /// + /// The Term you want to search for + /// A List of SubsonicItem objects + public static List Search(string query) + { + Dictionary parameters = new Dictionary(); + Version apiV = new Version(apiVersion); + Version Search2Min = new Version("1.4.0"); + string request = ""; + // Use search for the server version + if (apiV >= Search2Min) + { + request = "search2"; + parameters.Add("query", query); + } + else + { + request = "search"; + parameters.Add("any", query); + } + + // Make the request + Stream theStream = MakeGenericRequest(request, 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); + + List searchResults = new List(); + + // Parse the artist + if (myXML.ChildNodes[1].Name == "subsonic-response") + { + if (myXML.ChildNodes[1].FirstChild.Name == "searchResult") + { + for (int 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; + string artist = ""; + string album = ""; + + if (!isDir) + { + artist = myXML.ChildNodes[1].FirstChild.ChildNodes[i].Attributes["artist"].Value; + album = myXML.ChildNodes[1].FirstChild.ChildNodes[i].Attributes["album"].Value; + } + + SubsonicItem theItem; + if (isDir) + theItem = new SubsonicItem(title, theId, SubsonicItem.SubsonicItemType.Folder, null); + else + theItem = new Song(title, artist, album, theId); + + searchResults.Add(theItem); + } + } + } + + return searchResults; + } + + /// + /// Returns a list of all playlists on server + /// + public static List GetPlaylists() + { + List playlists = new List(); + + Dictionary theParameters = new Dictionary(); + Stream theStream = MakeGenericRequest("getPlaylists", theParameters); + StreamReader sr = new StreamReader(theStream); + string result = sr.ReadToEnd(); + + /// TODO: Parse result into list + + return playlists; + } + + /// + /// Returns a list of all SubsonicItems in playlist of given ID + /// + /// + /// ID of playlist to be fetched [retreive from GetPlaylists()] + /// + /// + /// Returns list of SubsonicItems + /// + public static List GetPlaylist(string playlistId) + { + List playlist = new List(); + + Dictionary theParameters = new Dictionary(); + theParameters.Add("id", playlistId); + Stream theStream = MakeGenericRequest("getPlaylist", theParameters); + StreamReader sr = new StreamReader(theStream); + string result = sr.ReadToEnd(); + + /// TODO: Parse result into list + + return playlist; + } + + + } -} \ No newline at end of file +}