From dd938883e56b03977e7a7036675a2439d5fba2ce Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 30 Oct 2016 23:54:45 +0100 Subject: [PATCH] Original code base --- LibMpc/Enum.cs | 80 ++ LibMpc/Exception.cs | 90 +++ LibMpc/Mpc.cs | 1337 +++++++++++++++++++++++++++++++++ LibMpc/MpcConnection.cs | 316 ++++++++ LibMpc/MpdDirectoryListing.cs | 84 +++ LibMpc/MpdFile.cs | 559 ++++++++++++++ LibMpc/MpdOutput.cs | 69 ++ LibMpc/MpdResponse.cs | 326 ++++++++ LibMpc/MpdStatistics.cs | 134 ++++ LibMpc/MpdStatus.cs | 271 +++++++ 10 files changed, 3266 insertions(+) create mode 100644 LibMpc/Enum.cs create mode 100644 LibMpc/Exception.cs create mode 100644 LibMpc/Mpc.cs create mode 100644 LibMpc/MpcConnection.cs create mode 100644 LibMpc/MpdDirectoryListing.cs create mode 100644 LibMpc/MpdFile.cs create mode 100644 LibMpc/MpdOutput.cs create mode 100644 LibMpc/MpdResponse.cs create mode 100644 LibMpc/MpdStatistics.cs create mode 100644 LibMpc/MpdStatus.cs diff --git a/LibMpc/Enum.cs b/LibMpc/Enum.cs new file mode 100644 index 0000000..769d85a --- /dev/null +++ b/LibMpc/Enum.cs @@ -0,0 +1,80 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +namespace Libmpc +{ + /// + /// The scope specifier for search commands. + /// + public enum ScopeSpecifier + { + /// + /// Any attribute of a file. + /// + Any, + /// + /// The path and name of the file. + /// + Filename, + /// + /// The artist of the track. + /// + Artist, + /// + /// The album the track appears on. + /// + Album, + /// + /// The title of the track. + /// + Title, + /// + /// The index of the track on its album. + /// + Track, + /// + /// The name of the track. + /// + Name, + /// + /// The genre of the song. + /// + Genre, + /// + /// The date the track was released. + /// + Date, + /// + /// The composer of the song. + /// + Composer, + /// + /// The performer of the song. + /// + Performer, + /// + /// A comment for the track. + /// + Comment, + /// + /// The disc of a multidisc album the track is on. + /// + Disc + } + +} \ No newline at end of file diff --git a/LibMpc/Exception.cs b/LibMpc/Exception.cs new file mode 100644 index 0000000..221ad1b --- /dev/null +++ b/LibMpc/Exception.cs @@ -0,0 +1,90 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Libmpc +{ + /// + /// Is thrown when a command is to be executed on a disconnected + /// where the property is set to false. + /// + public class NotConnectedException : InvalidOperationException + { + /// + /// Creates a new NotConnectedException. + /// + public NotConnectedException() : base("Not connected.") {} + } + /// + /// Is thrown when the connect method is invoked on an already connected . + /// + public class AlreadyConnectedException : InvalidOperationException + { + /// + /// Creates a new AlreadyConnectedException. + /// + public AlreadyConnectedException() : base("Connected already established.") { } + } + /// + /// Is thrown if the response from a MPD server is not as expected. This should never happen when + /// working with a tested version of the MPD server. + /// + public class InvalidMpdResponseException : Exception + { + /// + /// Creates a new InvalidMpdResponseException. + /// + public InvalidMpdResponseException() : base( "Invalid Mpd Response." ) {} + /// + /// Creates a new InvalidMpdResponseException. + /// + /// A message describing the error. + public InvalidMpdResponseException(string message) : base("Invalid Mpd Response: " + message) { } + } + /// + /// Is thrown when the MPD server returns an error to a command. + /// + public class MpdResponseException : Exception + { + private int errorCode; + private string errorMessage; + /// + /// The error code of the mpd server. + /// + public int ErrorCode { get { return this.errorCode; } } + /// + /// A message describing what went wrong. + /// + public string ErrorMessage { get { return this.errorMessage; } } + /// + /// Creates a new MpdResponseException. + /// + /// The error code of the mpd server. + /// A message describing what went wrong. + public MpdResponseException(int errorCode, string errorMessage) + : base("MPD" + errorCode + " " + errorMessage) + { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + } +} diff --git a/LibMpc/Mpc.cs b/LibMpc/Mpc.cs new file mode 100644 index 0000000..4bb5f22 --- /dev/null +++ b/LibMpc/Mpc.cs @@ -0,0 +1,1337 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using System.Text.RegularExpressions; + +namespace Libmpc +{ + /// + /// The delegate for the and events. + /// + /// The connection firing the event. + public delegate void MpcEventDelegate(Mpc connection); + /// + /// The Mpc class implements all commands for the MPD. It takes care of command building + /// and parsing the response into .net objects. + /// + public class Mpc + { + private const string TAG_ANY = "any"; + private const string TAG_FILENAME = "filename"; + + private const string TAG_ARTIST = "artist"; + private const string TAG_ALBUM = "album"; + private const string TAG_TITLE = "title"; + private const string TAG_TRACK = "track"; + private const string TAG_NAME = "name"; + private const string TAG_GENRE = "genre"; + private const string TAG_DATE = "date"; + private const string TAG_COMPOSER = "composer"; + private const string TAG_PERFORMER = "performer"; + private const string TAG_COMMENT = "comment"; + private const string TAG_DISC = "disc"; + + private static readonly Regex STATUS_AUDIO_REGEX = new Regex("^(?[0-9]*):(?[0-9]*):(?[0-9]*)$"); + + private readonly MpcConnectionEventDelegate onMpcConnectionConnectedDelegate; + private readonly MpcConnectionEventDelegate onMpcConnectionDisconnectedDelegate; + + private MpcConnection connection = null; + + /// + /// Creates a new Mpc. + /// + public Mpc() + { + this.onMpcConnectionConnectedDelegate = new MpcConnectionEventDelegate(this.onMpcConnectionConnected); + this.onMpcConnectionDisconnectedDelegate = new MpcConnectionEventDelegate(this.onMpcConnectionDisconnected); + } + /// + /// Is fired when a connection to a MPD server is established. + /// + public event MpcEventDelegate OnConnected; + /// + /// Is fired when the connection to the MPD server is closed. + /// + public event MpcEventDelegate OnDisconnected; + /// + /// If the Mpc object has a connection that is connected to an MPD. + /// + public bool Connected + { + get { return this.connection == null ? false : this.connection.Connected; } + } + /// + /// The MpcConnection used to talk to the server. + /// + public MpcConnection Connection + { + get { return this.connection; } + set + { + if (this.connection != null) + { + this.connection.OnConnected -= this.onMpcConnectionConnectedDelegate; + this.connection.OnDisconnected -= this.onMpcConnectionDisconnectedDelegate; + + if (this.connection.Connected) + this.onMpcConnectionDisconnected( this.connection ); + } + + this.connection = value; + + if (this.connection != null) + { + this.connection.OnConnected += this.onMpcConnectionConnectedDelegate; + this.connection.OnDisconnected += this.onMpcConnectionDisconnectedDelegate; + + if (this.connection.Connected) + this.onMpcConnectionConnected(this.connection); + } + } + } + + private MpcConnection getConnection() + { + MpcConnection ret = this.connection; + if (ret == null) + throw new NotConnectedException(); + else + return ret; + } + + + private void onMpcConnectionConnected(MpcConnection connection) + { + if (this.OnConnected != null) + this.OnConnected.Invoke(this); + } + + private void onMpcConnectionDisconnected(MpcConnection connection) + { + if (this.OnDisconnected != null) + this.OnDisconnected.Invoke(this); + } + + #region Admin Commands + /// + /// Disables an MPD output. + /// + /// The id of the output. + /// If the action was successful. + public bool DisableOutput(int id) + { + return !this.getConnection().Exec("disableoutput", new string[] { id.ToString() }).IsError; + } + /// + /// Enables an MPD output. + /// + /// The id of the output. + /// If the action was successful. + public bool EnableOutput(int id) + { + return !this.getConnection().Exec("enableoutput", new string[] { id.ToString() }).IsError; + } + /// + /// Lists all outputs of the MPD. + /// + /// The list of all MPD outputs. + public MpdOutput[] Outputs() + { + MpdResponse response = this.getConnection().Exec("outputs"); + if (response.Message.Count % 3 != 0) + throw new InvalidMpdResponseException(); + + MpdOutput[] ret = new MpdOutput[response.Message.Count / 3]; + + for (int i = 0; i < ret.Length; i++) + { + int id; + string name; + int enabled; + + KeyValuePair idLine = response[i * 3]; + if (idLine.Key == null) + throw new InvalidMpdResponseException("Invalid form of line " + (i * 3)); + if (!idLine.Key.Equals("outputid")) + throw new InvalidMpdResponseException("Key of line " + (i * 3) + " is not 'outputid'"); + if( !int.TryParse(idLine.Value, out id) ) + throw new InvalidMpdResponseException("Value of line " + (i * 3) + " is not a number"); + + KeyValuePair nameLine = response[i * 3 + 1]; + if (nameLine.Key == null) + throw new InvalidMpdResponseException("Invalid form of line " + (i * 3 + 1)); + if (!nameLine.Key.Equals("outputname")) + throw new InvalidMpdResponseException("Key of line " + (i * 3 + 1) + " is not 'outputname'"); + name = nameLine.Value; + + KeyValuePair enabledLine = response[i * 3 + 2]; + if (enabledLine.Key == null) + throw new InvalidMpdResponseException("Invalid form of line " + (i * 3 + 2)); + if (!enabledLine.Key.Equals("outputenabled")) + throw new InvalidMpdResponseException("Key of line " + (i * 3 + 2) + " is not 'outputenabled'"); + if (!int.TryParse(enabledLine.Value, out enabled)) + throw new InvalidMpdResponseException("Value of line " + (i * 3 + 2) + " is not a number"); + + ret[i] = new MpdOutput(id, name, enabled > 0); + } + + return ret; + } + /// + /// Returns the list of tag types the MPD supports. + /// + /// The list of tag types the MPD supports. + public string[] TagTypes() + { + MpdResponse response = this.getConnection().Exec("tagtypes"); + + string[] ret = new string[response.Message.Count]; + + for (int i = 0; i < ret.Length; i++) + { + KeyValuePair line = response[i]; + if (!line.Key.Equals("tagtype")) + throw new InvalidMpdResponseException("Key of line " + (i) + " is not 'tagtype'"); + ret[i] = line.Value; + } + + return ret; + } + /// + /// Starts an update of the MPD database. + /// + /// An sequential number of the update process. + public int Update() + { + MpdResponse response = this.getConnection().Exec("update"); + + if( response.Message.Count != 1 ) + throw new InvalidMpdResponseException("Respose message has more than one line."); + + int ret; + + KeyValuePair line = response[0]; + if (!line.Key.Equals("updating_db")) + throw new InvalidMpdResponseException("Key of line 0 is not 'updating_db'"); + if (!int.TryParse(line.Value, out ret)) + throw new InvalidMpdResponseException("Value of line 0 is not a number"); + + return ret; + } + + #endregion + + #region Database Commands + /// + /// Returns all files in the database who's attribute matches the given token. Works like the Search command but is case sensitive. + /// + /// Specifies the attribute to search for. + /// The value the files attribute must have to be included in the result. + /// All files in the database who's attribute matches the given token. + public List Find(ScopeSpecifier scopeSpecifier, string token) + { + if (token == null) + throw new ArgumentNullException("token"); + + MpdResponse response = this.getConnection().Exec("find", new string[] { this.toTag(scopeSpecifier), token }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.buildList(response); + } + /// + /// Returns all values found in files of the MPD for the given attribute. + /// + /// The attribute who's values are requested. + /// All values found in files of the MPD for the given attribute. + public List List(ScopeSpecifier scopeSpecifier) + { + MpdResponse response = this.getConnection().Exec("list", new string[] { this.toTag(scopeSpecifier) }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return response.getValueList(); + } + /// + /// Returns all values for the given attribute found in files of the MPD where another attribute matches a given value. + /// + /// The attribute whos values are returns. + /// The attribute whos value should match a given value for the file to be included in the result. + /// The value the searchTag attribute must match for the file to be included in the result. + /// All values found in files of the MPD for the given attribute. + public List List(ScopeSpecifier resultTag, ScopeSpecifier searchTag, string searchValue) + { + if (searchValue == null) + throw new ArgumentNullException("searchValue"); + + MpdResponse response = this.getConnection().Exec("list", new string[] { this.toTag(resultTag), this.toTag(searchTag), searchValue }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return response.getValueList(); + } + /// + /// Returns the names of all files and directory found under the given path. + /// + /// The path whos subdirectories and their files are requested. + /// The names of all files and directory found under the given path. + public List ListAll(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + MpdResponse response = this.getConnection().Exec("listall", new string[] { path }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return response.getValueList(); + } + /// + /// Returns the information of all files found in the given path and its subdirectories. + /// + /// The path of which the file information is requested. + /// The information of all files found in the given path and its subdirectories. + public List ListAllInfo(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + + MpdResponse response = this.getConnection().Exec("listallinfo", new string[] { path }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.buildList(response); + } + /// + /// Returns the directory listing of the root directory. + /// + /// The listing of the root directory. + public MpdDirectoryListing LsInfo() + { + return this.LsInfo(null); + } + /// + /// Returns the directory listing of the given path. + /// + /// The path whos listing is requested. + /// The directory listing of the given path. + public MpdDirectoryListing LsInfo(string path) + { + MpdResponse response; + if (path == null) + response = this.getConnection().Exec("lsinfo"); + else + response = this.getConnection().Exec("lsinfo", new string[] { path }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return new MpdDirectoryListing( + MpdFile.buildList(response), + response.getAttributeValueList("directory"), + response.getAttributeValueList("playlist")); + } + /// + /// Returns all files in the database who's attribute matches the given token. Works like the Find command but is case insensitive. + /// + /// Specifies the attribute to search for. + /// The value the files attribute must have to be included in the result. + /// All files in the database who's attribute matches the given token. + public List Search(ScopeSpecifier scopeSpecifier, string token) + { + if (token == null) + throw new ArgumentNullException("token"); + + MpdResponse response = this.getConnection().Exec("search", new string[] { this.toTag(scopeSpecifier), token }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.buildList(response); + } + + #endregion + + #region Playlist Commands + /// + /// Adds a file to the playlist. + /// + /// The name and path of the file to add. + public void Add(string filename) + { + if (filename == null) + throw new ArgumentNullException("filename"); + + MpdResponse response = this.getConnection().Exec("add", new string[] { filename }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Adds a file to the playlist and returns the id. + /// + /// The name and path of the file to add. + /// The id of the file in the playlist. + public int AddId(string filename) + { + if (filename == null) + throw new ArgumentNullException("filename"); + + MpdResponse response = this.getConnection().Exec("add", new string[] { filename }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + if (response.Count != 1) + throw new InvalidMpdResponseException("Returned more than one line for command addid."); + + string id = response["Id"]; + if( id == null ) + throw new InvalidMpdResponseException("Tag Id missing in response to command addid."); + int tryId = -1; + if( !int.TryParse(id, out tryId) ) + throw new InvalidMpdResponseException("Tag Id in response to command addid does not contain an number."); + + return tryId; + } + /// + /// Clears the playlist. + /// + public void Clear() + { + MpdResponse response = this.getConnection().Exec("clear"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Returns the information of the current song. + /// + /// The information of the current song. + public MpdFile CurrentSong() + { + MpdResponse response = this.getConnection().Exec("currentsong"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.build(response); + } + /// + /// Deletes the track with the given index from the current playlist. + /// + /// The index of the track to remove from the playlist. + public void Delete(int nr) + { + MpdResponse response = this.getConnection().Exec("delete", new string[] { nr.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Deletes the track with the given id from the current playlist. + /// + /// The id of the track to remove from the playlist. + public void DeleteId(int id) + { + MpdResponse response = this.getConnection().Exec("deleteid", new string[] { id.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Loads the playlist with the given name. + /// + /// The name of the playlist to load. + public void Load( string name ) + { + if (name == null) + throw new ArgumentNullException("name"); + + MpdResponse response = this.getConnection().Exec("load", new string[] { name }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Renames a playlist. + /// + /// The old name of the playlist. + /// The new name of the playlist. + public void Rename(string oldName, string newName) + { + if (oldName == null) + throw new ArgumentNullException("oldName"); + if (newName == null) + throw new ArgumentNullException("newName"); + + MpdResponse response = this.getConnection().Exec("rename", new string[] { oldName, newName }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Moves a track within the playlist. + /// + /// The old index of the track in the playlist. + /// The new index of the track in the playlist. + public void Move(int oldNr, int newNr) + { + MpdResponse response = this.getConnection().Exec("move", new string[] { oldNr.ToString(), newNr.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Moves a track within the playlist. + /// + /// The id of the track to move. + /// The new index of the track in the playlist. + public void MoveId(int id, int nr) + { + MpdResponse response = this.getConnection().Exec("moveid", new string[] { id.ToString(), nr.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Returns the meta data of the items in the current playlist. + /// + /// The meta data of the items in the current playlist. + public List PlaylistInfo() + { + MpdResponse response = this.getConnection().Exec("playlistinfo"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.buildList(response); + } + /// + /// Returns the meta data of a track in the current playlist. + /// + /// The index of the track in the playlist. + /// The meta data of the track in the current playlist. + public MpdFile PlaylistInfo( int nr ) + { + MpdResponse response = this.getConnection().Exec("playlistinfo", new string[] { nr.ToString() } ); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.build(response); + } + /// + /// Returns the meta data of the items in the current playlist. + /// + /// The meta data of the items in the current playlist. + public List PlaylistId() + { + MpdResponse response = this.getConnection().Exec("playlistid"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.buildList(response); + } + /// + /// Returns the meta data of a track in the current playlist. + /// + /// The id of the track in the playlist. + /// The meta data of the track in the current playlist. + public MpdFile PlaylistId(int id) + { + MpdResponse response = this.getConnection().Exec("playlistid", new string[] { id.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.build(response); + } + /// + /// Returns all changed tracks in the playlist since the given version. + /// + /// The version number. + /// All changed songs in the playlist since the given version. + public List Plchanges( int version ) + { + MpdResponse response = this.getConnection().Exec("plchanges", new string[] {version.ToString()}); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.buildList(response); + } + /// + /// Returns the ids and positions of the changed tracks in the playlist since the given version. + /// + /// + /// + /// The ids and positions of the changed tracks in the playlist since the given version as KeyValuePairs. + /// The key is the index and the id is the value. + /// + public List> PlChangesPosId( int version ) + { + MpdResponse response = this.getConnection().Exec("plchangesposid", new string[] { version.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + if (response.Count % 2 != 0) + throw new InvalidMpdResponseException("Response to command plchangesposid contains an odd number of lines!"); + + List> ret = new List>(); + + for (int i = 0; i < response.Count; i += 2) + { + KeyValuePair posLine = response[i]; + KeyValuePair idLine = response[i+1]; + + if ((posLine.Key == null) || (posLine.Value == null)) + throw new InvalidMpdResponseException("Invalid format of line " + i + "!"); + if ((idLine.Key == null) || (idLine.Value == null)) + throw new InvalidMpdResponseException("Invalid format of line " + (i + 1) + "!"); + + if (!posLine.Key.Equals("cpos")) + throw new InvalidMpdResponseException("Line " + i + " does not start with \"cpos\"!"); + if (!idLine.Key.Equals("Id")) + throw new InvalidMpdResponseException("Line " + (i + 1) + " does not start with \"Id\"!"); + + int tryPos = -1; + if (!int.TryParse(posLine.Value, out tryPos)) + throw new InvalidMpdResponseException("Tag value on line " + i + " is not a number."); + int tryId = -1; + if (!int.TryParse(idLine.Value, out tryId)) + throw new InvalidMpdResponseException("Tag value on line " + (i + 1) + " is not a number."); + + ret.Add(new KeyValuePair(tryPos, tryId)); + } + + + return ret; + } + /// + /// Removes the playlist with the given name. + /// + /// The name of the playlist to remove. + public void Rm(string name) + { + if (name == null) + throw new ArgumentNullException("name"); + + MpdResponse response = this.getConnection().Exec("rm", new string[] { name }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Saves the current playlist with the given name. + /// + /// The name to the save the currenty playlist. + public void Save(string name) + { + if (name == null) + throw new ArgumentNullException("name"); + + MpdResponse response = this.getConnection().Exec("save", new string[] { name }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Shuffles the current playlist. + /// + public void Shuffle() + { + MpdResponse response = this.getConnection().Exec("shuffle"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Swaps the to tracks in the current playlist. + /// + /// The index of the first track. + /// The index of the second track. + public void Swap(int nr1, int nr2) + { + MpdResponse response = this.getConnection().Exec("swap", new string[] { nr1.ToString(), nr2.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Swaps the to tracks in the current playlist. + /// + /// The id of the first track. + /// The id of the second track. + public void SwapId(int id1, int id2) + { + MpdResponse response = this.getConnection().Exec("swapid", new string[] { id1.ToString(), id2.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Returns the filenames of the tracks in the given playlist. + /// + /// The playlist whos filename are requested. + /// The filenames of the tracks in the given playlist. + public List ListPlaylist(string name) + { + if (name == null) + throw new ArgumentNullException("name"); + + MpdResponse response = this.getConnection().Exec("listplaylist", new string[] { name }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return response.getValueList(); + } + /// + /// Return the meta data of the tracks in the given playlist. + /// + /// The playlist whos files meta data are requested. + /// The meta data of the tracks in the given playlist. + public List ListPlaylistInfo(string name) + { + if (name == null) + throw new ArgumentNullException("name"); + + MpdResponse response = this.getConnection().Exec("listplaylistinfo", new string[] { name }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.buildList(response); + } + /// + /// Add a file to a playlist. + /// + /// The name of the playlist. + /// The path and name of the file to add. + public void PlaylistAdd(string name, string file) + { + if (name == null) + throw new ArgumentNullException("name"); + if (file == null) + throw new ArgumentNullException("file"); + + MpdResponse response = this.getConnection().Exec("playlistadd", new string[] { name, file }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Clears all tracks from a playlist. + /// + /// The name of the playlist to clear. + public void PlaylistClear(string name) + { + if (name == null) + throw new ArgumentNullException("name"); + + MpdResponse response = this.getConnection().Exec("playlistclear", new string[] { name }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Delete a file from a playlist. + /// + /// The name of the playlist + /// The id of the track to delete. + public void PlaylistDelete(string name, int id) + { + if (name == null) + throw new ArgumentNullException("name"); + + MpdResponse response = this.getConnection().Exec("playlistdelete", new string[] { name, id.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Moves a track in a playlist. + /// + /// The name of the playlist. + /// The id of the track to move. + /// The position to move the track to. + public void PlaylistMove(string name, int id, int nr) + { + if (name == null) + throw new ArgumentNullException("name"); + + MpdResponse response = this.getConnection().Exec("playlistmove", new string[] { id.ToString(), nr.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Returns the meta data for all tracks in the current playlist whos attribute equals the given value. + /// + /// The attribute to search for the given value. + /// The value to search for in the given attribute. + /// The meta data for all tracks in the current playlist whos attribute equals the given value. + public List PlaylistFind(ScopeSpecifier scopeSpecifier, string token) + { + if (token == null) + throw new ArgumentNullException("token"); + + MpdResponse response = this.getConnection().Exec("playlistfind", new string[] { this.toTag(scopeSpecifier), token }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.buildList(response); + } + /// + /// Returns the meta data for all tracks in the current playlist whos attribute contains the given value. + /// + /// The attribute to search for the given value. + /// The value to search for in the given attribute. + /// The meta data for all tracks in the current playlist whos attribute contains the given value. + public List PlaylistSearch(ScopeSpecifier scopeSpecifier, string token) + { + if (token == null) + throw new ArgumentNullException("token"); + + MpdResponse response = this.getConnection().Exec("playlistsearch", new string[] { this.toTag(scopeSpecifier), token }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return MpdFile.buildList(response); + } + #endregion + + #region Playback Commands + /// + /// Sets the seconds to crossfade between songs. + /// + /// The seconds to crossfade between songs. + public void Crossfade(int seconds) + { + MpdResponse response = this.getConnection().Exec("crossfade", new string[] { seconds.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Starts the playback of the next song in the playlist- + /// + public void Next() + { + MpdResponse response = this.getConnection().Exec("next"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Sets the MPD to pause or resume the playback. + /// + /// If the playback should be paused or resumed. + public void Pause(bool pause) + { + MpdResponse response = this.getConnection().Exec("pause", new string[] { pause ? "1" : "0" }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Starts the playback of the current item in the playlist. + /// + public void Play() + { + MpdResponse response = this.getConnection().Exec("play"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Starts the playback of the item with the given index in the playlist. + /// + /// The index of the track in the playlist to start playing. + public void Play(int nr) + { + MpdResponse response = this.getConnection().Exec("play", new string[] { nr.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Starts the playback of the track in the playlist with the id 0. + /// + public void PlayId() + { + MpdResponse response = this.getConnection().Exec("playid"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Starts the playback of the track in the playlist with the given id. + /// + /// The id of the track to start playing. + public void PlayId(int id) + { + MpdResponse response = this.getConnection().Exec("playid", new string[] { id.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Starts the playback of the previous track in the playlist. + /// + public void Previous() + { + MpdResponse response = this.getConnection().Exec("previous"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Sets the MPD to random or sequential playback. + /// + /// If the MPD playlist should be played randomly. + public void Random(bool random) + { + MpdResponse response = this.getConnection().Exec("random", new string[] { random ? "1" : "0" }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Sets if the MPD should repeat the playlist. + /// + /// If the MPD should repeat the playlist. + public void Repeat(bool repeat) + { + MpdResponse response = this.getConnection().Exec("repeat", new string[] { repeat ? "1" : "0" }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Starts playback of a given song at the give position. + /// + /// The index of the song in the playlist. + /// The number of seconds to start playback on. + public void Seek(int nr, int time) + { + MpdResponse response = this.getConnection().Exec("seek", new string[] { nr.ToString(), time.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Starts playback of a given song at the give position. + /// + /// The id of the song in the playlist. + /// The number of seconds to start playback on. + public void SeekId(int id, int time) + { + MpdResponse response = this.getConnection().Exec("seekid", new string[] { id.ToString(), time.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Sets the output volume of the MPD. + /// + /// The output volume of the MPD between 0 and 100. + public void SetVol(int vol) + { + if (vol < 0) + throw new ArgumentException("vol < 0"); + if (vol > 100) + throw new ArgumentException("vol > 100"); + + MpdResponse response = this.getConnection().Exec("setvol", new string[] { vol.ToString() }); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + /// + /// Stops the playback of the MPD. + /// + public void Stop() + { + MpdResponse response = this.getConnection().Exec("stop"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + } + + #endregion + + #region Misc Commands + /// + /// Clears the error message set in the MPD. + /// + public void ClearError() + { + this.getConnection().Exec("clearerror"); + } + /// + /// Returns which commands the current user has access to. + /// + /// The commands the current user has access to. + public List Commands() + { + MpdResponse response = this.getConnection().Exec("commands"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return response.getValueList(); + } + /// + /// Returns which commands the current user does has access to. + /// + /// The commands the current user does has access to. + public List NotCommands() + { + MpdResponse response = this.getConnection().Exec("notcommands"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + return response.getValueList(); + } + /// + /// Send the password to the server allow access to the server if enabled in the MPD. + /// + /// The password to authorize to the server. + /// If the password is valid. + public bool Password(string password) + { + if (password == null) + throw new ArgumentNullException("password"); + + return this.getConnection().Exec("password", new string[] { password }).IsError; + } + /// + /// Sends a ping command to the server and waits for the response. + /// + public void Ping() + { + this.getConnection().Exec("ping"); + } + /// + /// Requests the current statistics from the MPD, + /// + /// The current statistics fromt the MPD. + public MpdStatistics Stats() + { + MpdResponse response = this.getConnection().Exec("stats"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + int artists = -1; + int albums = -1; + int songs = -1; + int uptime = -1; + int playtime = -1; + int db_playtime = -1; + int db_update = -1; + + foreach (KeyValuePair line in response) + { + if( ( line.Key != null ) && ( line.Value!=null ) ) + switch (line.Key) + { + case "artists": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + artists = tryValue; + } + break; + case "albums": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + albums = tryValue; + } + break; + case "songs": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + songs = tryValue; + } + break; + case "uptime": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + uptime = tryValue; + } + break; + case "playtime": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + playtime = tryValue; + } + break; + case "db_playtime": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + db_playtime = tryValue; + } + break; + case "db_update": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + db_update = tryValue; + } + break; + } + } + + return new MpdStatistics(artists, albums, songs, uptime, playtime, db_playtime, db_update); + } + /// + /// Returns the current status of the MPD. + /// + /// The current status of the MPD. + public MpdStatus Status() + { + MpdResponse response = this.getConnection().Exec("status"); + + if (response.IsError) + throw new MpdResponseException(response.ErrorCode, response.ErrorMessage); + + int volume = -1; + bool repeat = false; + bool random = false; + int playlist = -1; + int playlistLength = -1; + int playlistQueue = -1; + int xFade = -1; + MpdState state = MpdState.Unknown; + int song = -1; + int songId = -1; + int timeElapsed = -1; + int timeTotal = -1; + int bitrate = -1; + int audioSampleRate = -1; + int audioBits = -1; + int audioChannels = -1; + int updatingDb = -1; + string error = null; + + foreach (KeyValuePair line in response) + { + if ( (line.Key != null) && (line.Value!=null) ) + switch (line.Key) + { + case "volume": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + { + volume = tryValue; + if (volume < 0) + volume = 0; + if (volume > 100) + volume = 100; + } + } + break; + case "repeat": + repeat = (line.Value != null) && (line.Value.Equals("1")); + break; + case "random": + random = (line.Value != null) && (line.Value.Equals("1")); + break; + case "playlist": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + playlist = tryValue; + } + break; + case "playlistlength": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + playlistLength = tryValue; + } + break; + case "playlistqueue": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + playlistQueue = tryValue; + } + break; + case "xfade": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + xFade = tryValue; + } + break; + case "state": + switch (line.Value) + { + case "play": + state = MpdState.Play; + break; + case "pause": + state = MpdState.Pause; + break; + case "stop": + state = MpdState.Stop; + break; + } + break; + case "song": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + song = tryValue; + } + break; + case "songid": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + songId = tryValue; + } + break; + case "time": + int index = line.Value.IndexOf(':'); + if (index >= 0) + { + int tryValue; + if (int.TryParse(line.Value.Substring(0, index), out tryValue)) + timeElapsed = tryValue; + if (int.TryParse(line.Value.Substring(index+1), out tryValue)) + timeTotal = tryValue; + } + break; + case "bitrate": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + bitrate = tryValue; + } + break; + case "audio": + Match match = STATUS_AUDIO_REGEX.Match(line.Value); + if (match.Success) + { + int tryValue; + if (int.TryParse(match.Result("$sampleRate"), out tryValue)) + audioSampleRate = tryValue; + if (int.TryParse(match.Result("$bits"), out tryValue)) + audioBits = tryValue; + if (int.TryParse(match.Result("$channels"), out tryValue)) + audioChannels = tryValue; + } + break; + case "updating_db": + { + int tryValue; + if (int.TryParse(line.Value, out tryValue)) + updatingDb = tryValue; + } + break; + case "error": + error = line.Value; + break; + } + } + + return new MpdStatus( + volume, + repeat, + random, + playlist, + playlistLength, + xFade, + state, + song, + songId, + timeElapsed, + timeTotal, + bitrate, + audioSampleRate, + audioBits, + audioChannels, + updatingDb, + error + ); + } + + #endregion + + private string toTag(ScopeSpecifier scopeSpecifier) + { + switch (scopeSpecifier) + { + default: + throw new ArgumentException("scopeSpecifier"); + case ScopeSpecifier.Any: + return TAG_ANY; + case ScopeSpecifier.Filename: + return TAG_FILENAME; + case ScopeSpecifier.Artist: + return TAG_ARTIST; + case ScopeSpecifier.Album: + return TAG_ALBUM; + case ScopeSpecifier.Title: + return TAG_TITLE; + case ScopeSpecifier.Track: + return TAG_TRACK; + case ScopeSpecifier.Name: + return TAG_NAME; + case ScopeSpecifier.Genre: + return TAG_GENRE; + case ScopeSpecifier.Date: + return TAG_DATE; + case ScopeSpecifier.Composer: + return TAG_COMPOSER; + case ScopeSpecifier.Performer: + return TAG_PERFORMER; + case ScopeSpecifier.Comment: + return TAG_COMMENT; + case ScopeSpecifier.Disc: + return TAG_DISC; + } + } + } +} diff --git a/LibMpc/MpcConnection.cs b/LibMpc/MpcConnection.cs new file mode 100644 index 0000000..1a5d2d4 --- /dev/null +++ b/LibMpc/MpcConnection.cs @@ -0,0 +1,316 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; + +namespace Libmpc +{ + /// + /// The delegate for the and events. + /// + /// The connection firing the event. + public delegate void MpcConnectionEventDelegate( MpcConnection connection ); + /// + /// Keeps the connection to the MPD server and handels the most basic structure of the + /// MPD protocol. The high level commands are handeled in the + /// class. + /// + public class MpcConnection + { + /// + /// Is fired when a connection to a MPD server is established. + /// + public event MpcConnectionEventDelegate OnConnected; + /// + /// Is fired when the connection to the MPD server is closed. + /// + public event MpcConnectionEventDelegate OnDisconnected; + + private static readonly string FIRST_LINE_PREFIX = "OK MPD "; + + private static readonly string OK = "OK"; + private static readonly string ACK = "ACK"; + + private static readonly Regex ACK_REGEX = new Regex("^ACK \\[(?[0-9]*)@(?[0-9]*)] \\{(?[a-z]*)} (?.*)$"); + + private IPEndPoint ipEndPoint = null; + + private TcpClient tcpClient = null; + private NetworkStream networkStream = null; + + private StreamReader reader; + private StreamWriter writer; + + private string version; + /// + /// If the connection to the MPD is connected. + /// + public bool Connected { get { return (this.tcpClient != null) && this.tcpClient.Connected; } } + /// + /// The version of the MPD. + /// + public string Version { get { return this.version; } } + + private bool autoConnect = false; + /// + /// If a connection should be established when a command is to be + /// executed in disconnected state. + /// + public bool AutoConnect + { + get{ return this.autoConnect; } + set { this.autoConnect = value; } + } + /// + /// Creates a new MpdConnection. + /// + public MpcConnection() {} + /// + /// Creates a new MpdConnection. + /// + /// The IPEndPoint of the MPD server. + public MpcConnection(IPEndPoint server) { this.Connect(server); } + /// + /// The IPEndPoint of the MPD server. + /// + /// When a conenction to a MPD server is already established. + public IPEndPoint Server + { + get { return this.ipEndPoint; } + set + { + if (this.Connected) + throw new AlreadyConnectedException(); + + this.ipEndPoint = value; + + this.ClearConnectionFields(); + } + } + /// + /// Connects to a MPD server. + /// + /// The IPEndPoint of the server. + public void Connect(IPEndPoint server) + { + this.Server = server; + this.Connect(); + } + /// + /// Connects to the MPD server who's IPEndPoint was set in the Server property. + /// + /// If no IPEndPoint was set to the Server property. + public void Connect() + { + if (this.ipEndPoint == null) + throw new InvalidOperationException("Server IPEndPoint not set."); + + if (this.Connected) + throw new AlreadyConnectedException(); + + this.tcpClient = new TcpClient( + this.ipEndPoint.Address.ToString(), + this.ipEndPoint.Port); + this.networkStream = this.tcpClient.GetStream(); + + this.reader = new StreamReader(this.networkStream, Encoding.UTF8); + this.writer = new StreamWriter(this.networkStream, Encoding.UTF8); + this.writer.NewLine = "\n"; + + string firstLine = this.reader.ReadLine(); + if( !firstLine.StartsWith( FIRST_LINE_PREFIX ) ) + { + this.Disconnect(); + throw new InvalidDataException("Response of mpd does not start with \"" + FIRST_LINE_PREFIX + "\"." ); + } + this.version = firstLine.Substring(FIRST_LINE_PREFIX.Length); + + this.writer.WriteLine(); + this.writer.Flush(); + + this.readResponse(); + + if( this.OnConnected != null ) + this.OnConnected.Invoke( this ); + } + /// + /// Disconnects from the current MPD server. + /// + public void Disconnect() + { + if (this.tcpClient == null) + return; + + this.networkStream.Close(); + + this.ClearConnectionFields(); + + if( this.OnDisconnected != null ) + this.OnDisconnected.Invoke( this ); + } + /// + /// Executes a simple command without arguments on the MPD server and returns the response. + /// + /// The command to execute. + /// The MPD server response parsed into a basic object. + /// If the command contains a space of a newline charakter. + public MpdResponse Exec(string command) + { + if (command == null) + throw new ArgumentNullException("command"); + if (command.Contains(" ")) + throw new ArgumentException("command contains space"); + if (command.Contains("\n")) + throw new ArgumentException("command contains newline"); + + this.CheckConnected(); + + try + { + this.writer.WriteLine(command); + this.writer.Flush(); + + return this.readResponse(); + } + catch (Exception) + { + try { this.Disconnect(); } + catch (Exception) { } + throw; + } + } + /// + /// Executes a MPD command with arguments on the MPD server. + /// + /// The command to execute. + /// The arguments of the command. + /// The MPD server response parsed into a basic object. + /// If the command contains a space of a newline charakter. + public MpdResponse Exec(string command, string[] argument) + { + if (command == null) + throw new ArgumentNullException("command"); + if (command.Contains(" ")) + throw new ArgumentException("command contains space"); + if (command.Contains("\n")) + throw new ArgumentException("command contains newline"); + + if (argument == null) + throw new ArgumentNullException("argument"); + for (int i = 0; i < argument.Length; i++) + { + if (argument[i] == null) + throw new ArgumentNullException("argument[" + i + "]"); + if (argument[i].Contains("\n")) + throw new ArgumentException("argument[" + i + "] contains newline"); + } + + this.CheckConnected(); + + try + { + this.writer.Write(command); + foreach (string arg in argument) + { + this.writer.Write(' '); + this.WriteToken(arg); + } + this.writer.WriteLine(); + this.writer.Flush(); + + return this.readResponse(); + } + catch (Exception) + { + try { this.Disconnect(); } catch (Exception) { } + throw; + } + } + + private void CheckConnected() + { + if (!this.Connected) + { + if (this.autoConnect) + this.Connect(); + else + throw new NotConnectedException(); + } + + } + + private void WriteToken(string token) + { + if (token.Contains(" ")) + { + this.writer.Write("\""); + foreach (char chr in token) + if (chr == '"') + this.writer.Write("\\\""); + else + this.writer.Write(chr); + } + else + this.writer.Write(token); + } + + private MpdResponse readResponse() + { + List ret = new List(); + string line = this.reader.ReadLine(); + while (!(line.Equals(OK) || line.StartsWith(ACK))) + { + ret.Add(line); + line = this.reader.ReadLine(); + } + if (line.Equals(OK)) + return new MpdResponse(new ReadOnlyCollection(ret)); + else + { + Match match = ACK_REGEX.Match(line); + + if (match.Groups.Count != 5) + throw new InvalidDataException( "Error response not as expected" ); + + return new MpdResponse( + int.Parse( match.Result("${code}") ), + int.Parse( match.Result("${nr}") ), + match.Result("${command}"), + match.Result("${message}"), + new ReadOnlyCollection(ret) + ); + } + } + + private void ClearConnectionFields() + { + this.tcpClient = null; + this.networkStream = null; + this.reader = null; + this.writer = null; + this.version = null; + } + } +} diff --git a/LibMpc/MpdDirectoryListing.cs b/LibMpc/MpdDirectoryListing.cs new file mode 100644 index 0000000..8949557 --- /dev/null +++ b/LibMpc/MpdDirectoryListing.cs @@ -0,0 +1,84 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +using System; +using System.Collections.ObjectModel; +using System.Collections.Generic; + +namespace Libmpc +{ + /// + /// The MpdDirectoryListing class contains the response of a MPD server to a list command. + /// + public class MpdDirectoryListing + { + private readonly ReadOnlyCollection file; + private readonly ReadOnlyCollection directory; + private readonly ReadOnlyCollection playlist; + /// + /// The list of files in the directory. + /// + public ReadOnlyCollection FileList { get { return this.file; } } + /// + /// The list of subdirectories in the directory. + /// + public ReadOnlyCollection DirectoryList { get { return this.directory; } } + /// + /// The list of playlists in the directory. + /// + public ReadOnlyCollection PlaylistList { get { return this.playlist; } } + /// + /// Creates a new MpdDirectoryListing. + /// + /// The list of files in the directory. + /// The list of subdirectories in the directory. + /// The list of playlists in the directory. + public MpdDirectoryListing(List file, List directory, List playlist) + { + if (file == null) + throw new ArgumentNullException("file"); + if (directory == null) + throw new ArgumentNullException("directory"); + if (playlist == null) + throw new ArgumentNullException("playlist"); + + this.file = new ReadOnlyCollection(file); + this.directory = new ReadOnlyCollection(directory); + this.playlist = new ReadOnlyCollection(playlist); + } + /// + /// Creates a new MpdDirectoryListing. + /// + /// The list of files in the directory. + /// The list of subdirectories in the directory. + /// The list of playlists in the directory. + public MpdDirectoryListing(ReadOnlyCollection file, ReadOnlyCollection directory, ReadOnlyCollection playlist) + { + if (file == null) + throw new ArgumentNullException("file"); + if (directory == null) + throw new ArgumentNullException("directory"); + if (playlist == null) + throw new ArgumentNullException("playlist"); + + this.file = file; + this.directory = directory; + this.playlist = playlist; + } + } +} diff --git a/LibMpc/MpdFile.cs b/LibMpc/MpdFile.cs new file mode 100644 index 0000000..9785e79 --- /dev/null +++ b/LibMpc/MpdFile.cs @@ -0,0 +1,559 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Libmpc +{ + /// + /// The MpdFile class contains all meta data for a file of the MPD. + /// + public class MpdFile + { + private const string TAG_FILE = "file"; + private const string TAG_TIME = "Time"; + private const string TAG_ARTIST = "Artist"; + private const string TAG_ALBUM = "Album"; + private const string TAG_TITLE = "Title"; + private const string TAG_TRACK = "Track"; + private const string TAG_NAME = "Name"; + private const string TAG_GENRE = "Genre"; + private const string TAG_DATE = "Date"; + private const string TAG_COMPOSER = "Composer"; + private const string TAG_PERFORMER = "Performer"; + private const string TAG_COMMENT = "Comment"; + private const string TAG_DISC = "Disc"; + private const string TAG_POS = "Pos"; + private const string TAG_ID = "Id"; + + private const int NO_TIME = -1; + private const string NO_ALBUM = null; + private const string NO_ARTIST = null; + private const string NO_TITLE = null; + private const string NO_TRACK = null; + private const string NO_NAME = null; + private const string NO_GENRE = null; + private const string NO_DATE = null; + private const string NO_COMPOSER = null; + private const string NO_PERFORMER = null; + private const string NO_COMMENT = null; + private const int NO_DISC = -1; + private const int NO_POS = -1; + private const int NO_ID = -1; + + private readonly string file; + private readonly int time; + private readonly string album; + private readonly string artist; + private readonly string title; + private readonly string track; + private readonly string name; + private readonly string genre; + private readonly string date; + private readonly string composer; + private readonly string performer; + private readonly string comment; + private readonly int disc; + private readonly int pos; + private readonly int id; + /// + /// The name and path of the file. + /// + public string File { get { return this.file; } } + /// + /// The length of the file in seconds. + /// + public int Time { get { return this.time; } } + /// + /// The album of the file. + /// + public string Album { get { return this.album; } } + /// + /// The artist of the file. + /// + public string Artist { get { return this.artist; } } + /// + /// The title of the file. + /// + public string Title { get { return this.title; } } + /// + /// The value of the track property of the file. + /// + public string Track { get { return this.track; } } + /// + /// The name of the song. + /// + public string Name { get { return this.name; } } + /// + /// The genre of the song. + /// + public string Genre { get { return this.genre; } } + /// + /// The date the song was released. + /// + public string Date { get { return this.date; } } + /// + /// The composer of the song. + /// + public string Composer { get { return this.composer; } } + /// + /// The performer of the song. + /// + public string Performer { get { return this.performer; } } + /// + /// A comment to the file. + /// + public string Comment { get { return this.comment; } } + /// + /// The number of the disc on a multidisc album. + /// + public int Disc { get { return this.disc; } } + /// + /// The index of the file in a playlist. + /// + public int Pos { get { return this.pos; } } + /// + /// The id of the file in a playlist. + /// + public int Id { get { return this.id; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasTime { get { return this.time != NO_TIME; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasAlbum { get { return this.album != NO_ALBUM; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasArtist { get { return this.artist != NO_ARTIST; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasTitle { get { return this.title != NO_TITLE; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasTrack { get { return this.track != NO_TRACK; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasName { get { return this.name != NO_NAME; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasGenre { get { return this.genre != NO_GENRE; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasDate { get { return this.date != NO_DATE; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasComposer { get { return this.composer != NO_COMPOSER; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasPerformer { get { return this.performer != NO_PERFORMER; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasComment { get { return this.comment != NO_COMMENT; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasDisc { get { return this.disc != NO_DISC; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasPos { get { return this.pos != NO_POS; } } + /// + /// If the MpdFile has the property set. + /// + public bool HasId { get { return this.id != NO_ID; } } + /// + /// Creates a new MpdFile. + /// + /// The name and path of the file. + /// The length of the file in seconds. + /// The album of the file. + /// The artist of the file. + /// The title of the file. + /// The value of the track property of the file. + /// The name of the song. + /// The genre of the song. + /// The date the song was released. + /// The composer of the song. + /// The performer of the song. + /// A comment to the file. + /// The number of the disc on a multidisc album. + /// The index of the file in a playlist. + /// The id of the file in a playlist. + public MpdFile(string file, + int time, + string album, + string artist, + string title, + string track, + string name, + string genre, + string date, + string composer, + string performer, + string comment, + int disc, + int pos, + int id) + { + if (file == null) + throw new ArgumentNullException("file"); + + this.file = file; + this.time = time; + this.album = album; + this.artist = artist; + this.title = title; + this.track = track; + this.name = name; + this.genre = genre; + this.date = date; + this.composer = composer; + this.performer = performer; + this.comment = comment; + this.disc = disc; + this.pos = pos; + this.id = id; + } + /// + /// A string containing all the properties of the file. + /// + /// + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + + appendString(builder, TAG_FILE, this.file); + if (this.HasTime) + appendInt(builder, TAG_TIME, this.time); + if (this.HasAlbum) + appendString(builder, TAG_ALBUM, this.album); + if (this.HasArtist) + appendString(builder, TAG_ARTIST, this.artist); + if (this.HasTitle) + appendString(builder, TAG_TITLE, this.title); + if (this.HasTrack) + appendString(builder, TAG_TRACK, this.track); + if (this.HasName) + appendString(builder, TAG_NAME, this.name); + if (this.HasGenre) + appendString(builder, TAG_GENRE, this.genre); + if (this.HasDate) + appendString(builder, TAG_DATE, this.date); + if (this.HasComposer) + appendString(builder, TAG_COMPOSER, this.composer); + if (this.HasPerformer) + appendString(builder, TAG_PERFORMER, this.performer); + if (this.HasComment) + appendString(builder, TAG_COMMENT, this.comment); + if (this.HasDisc) + appendInt(builder, TAG_DISC, this.disc); + if (this.HasPos) + appendInt(builder, TAG_POS, this.pos); + if (this.HasId) + appendInt(builder, TAG_ID, this.id); + + return builder.ToString(); + } + + private static void appendString(StringBuilder builder, string name, string value) + { + builder.Append(name); + builder.Append(": "); + builder.Append(value); + builder.AppendLine(); + } + + private static void appendInt(StringBuilder builder, string name, int value) + { + builder.Append(name); + builder.Append(": "); + builder.Append(value); + builder.AppendLine(); + } + /// + /// Returns a MpdFile object from a MpdResponse object. + /// + /// The response of the MPD server. + /// A new MpdFile object build from the MpdResponse object. + public static MpdFile build(MpdResponse response) + { + if (response == null) + throw new ArgumentNullException("response"); + + string file = null; + int time = NO_TIME; + string album = NO_ALBUM; + string artist = NO_ARTIST; + string title = NO_TITLE; + string track = NO_TRACK; + string name = NO_NAME; + string genre = NO_GENRE; + string date = NO_DATE; + string composer = NO_COMPOSER; + string performer = NO_PERFORMER; + string comment = NO_COMMENT; + int disc = NO_DISC; + int pos = NO_POS; + int id = NO_ID; + + + foreach (KeyValuePair line in response) + { + if( line.Key != null ) + switch (line.Key) + { + case TAG_FILE: + file = line.Value; + break; + case TAG_TIME: + int tryTime; + if( int.TryParse( line.Value, out tryTime ) ) + time = tryTime; + break; + case TAG_ALBUM: + album = line.Value; + break; + case TAG_ARTIST: + artist = line.Value; + break; + case TAG_TITLE: + title = line.Value; + break; + case TAG_TRACK: + track = line.Value; + break; + case TAG_NAME: + name = line.Value; + break; + case TAG_GENRE: + genre = line.Value; + break; + case TAG_DATE: + date = line.Value; + break; + case TAG_COMPOSER: + composer = line.Value; + break; + case TAG_PERFORMER: + performer = line.Value; + break; + case TAG_COMMENT: + comment = line.Value; + break; + case TAG_DISC: + int tryDisc; + if (int.TryParse(line.Value, out tryDisc)) + disc = tryDisc; + break; + case TAG_POS: + int tryPos; + if (int.TryParse(line.Value, out tryPos)) + pos = tryPos; + break; + case TAG_ID: + int tryId; + if (int.TryParse(line.Value, out tryId)) + id = tryId; + break; + } + } + + if (file == null) + return null; + else + return new MpdFile( + file, + time, + album, + artist, + title, + track, + name, + genre, + date, + composer, + performer, + comment, + disc, + pos, + id); + } + /// + /// Builds a list of MpdFile objects from a MpdResponse object. + /// + /// The MpdResponse object to build the list of MpdFiles from. + /// A list ob MpdFiles built from the MpdResponse object. + public static List buildList(MpdResponse response) + { + if (response == null) + throw new ArgumentNullException("response"); + + List ret = new List(); + + string file = null; + int time = NO_TIME; + string album = NO_ALBUM; + string artist = NO_ARTIST; + string title = NO_TITLE; + string track = NO_TRACK; + string name = NO_NAME; + string genre = NO_GENRE; + string date = NO_DATE; + string composer = NO_COMPOSER; + string performer = NO_PERFORMER; + string comment = NO_COMMENT; + int disc = NO_DISC; + int pos = NO_POS; + int id = NO_ID; + + + foreach (KeyValuePair line in response) + { + if( line.Key != null ) + switch (line.Key) + { + case TAG_FILE: + if( file != null ) + ret.Add( new MpdFile( + file, + time, + album, + artist, + title, + track, + name, + genre, + date, + composer, + performer, + comment, + disc, + pos, + id ) ); + + file = line.Value; + + time = NO_TIME; + album = NO_ALBUM; + artist = NO_ARTIST; + title = NO_TITLE; + track = NO_TRACK; + name = NO_NAME; + genre = NO_GENRE; + date = NO_DATE; + composer = NO_COMPOSER; + performer = NO_PERFORMER; + comment = NO_COMMENT; + disc = NO_DISC; + pos = NO_POS; + id = NO_ID; + + break; + case TAG_TIME: + int tryTime; + if( int.TryParse( line.Value, out tryTime ) ) + time = tryTime; + break; + case TAG_ALBUM: + album = line.Value; + break; + case TAG_ARTIST: + artist = line.Value; + break; + case TAG_TITLE: + title = line.Value; + break; + case TAG_TRACK: + track = line.Value; + /* + int tryTrack; + if (int.TryParse(line.Value, out tryTrack)) + track = tryTrack; + */ + break; + case TAG_NAME: + name = line.Value; + break; + case TAG_GENRE: + genre = line.Value; + break; + case TAG_DATE: + date = line.Value; + break; + case TAG_COMPOSER: + composer = line.Value; + break; + case TAG_PERFORMER: + performer = line.Value; + break; + case TAG_COMMENT: + comment = line.Value; + break; + case TAG_DISC: + int tryDisc; + if (int.TryParse(line.Value, out tryDisc)) + disc = tryDisc; + break; + case TAG_POS: + int tryPos; + if (int.TryParse(line.Value, out tryPos)) + pos = tryPos; + break; + case TAG_ID: + int tryId; + if (int.TryParse(line.Value, out tryId)) + id = tryId; + break; + } + } + + if (file != null) + ret.Add(new MpdFile( + file, + time, + album, + artist, + title, + track, + name, + genre, + date, + composer, + performer, + comment, + disc, + pos, + id )); + + return ret; + } + } +} diff --git a/LibMpc/MpdOutput.cs b/LibMpc/MpdOutput.cs new file mode 100644 index 0000000..8108355 --- /dev/null +++ b/LibMpc/MpdOutput.cs @@ -0,0 +1,69 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Libmpc +{ + /// + /// The MpdOutput class contains all attributes of an output device of the MPD. + /// + public class MpdOutput + { + private readonly int id; + private readonly string name; + private readonly bool enabled; + /// + /// The id of the output. + /// + public int Id { get { return this.id; } } + /// + /// The name of the output. + /// + public string Name { get { return this.name; } } + /// + /// If the output is enabled. + /// + public bool IsEnabled { get { return this.enabled; } } + /// + /// Creates a new MpdOutput object. + /// + /// The id of the output. + /// The name of the output. + /// If the output is enabled. + public MpdOutput(int id, string name, bool enabled) + { + if (name == null) + throw new ArgumentNullException("name"); + + this.id = id; + this.name = name; + this.enabled = enabled; + } + /// + /// Returns a string representation of the object mainly for debuging purpose. + /// + /// A string representation of the object. + public override string ToString() + { + return this.id + "::" + this.name + "::" + this.enabled; + } + } +} diff --git a/LibMpc/MpdResponse.cs b/LibMpc/MpdResponse.cs new file mode 100644 index 0000000..3a3415f --- /dev/null +++ b/LibMpc/MpdResponse.cs @@ -0,0 +1,326 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using System.Text.RegularExpressions; + +namespace Libmpc +{ + /// + /// The MpdResponse class parses the response to an MPD command in it's most + /// basic structure. + /// + public class MpdResponse : IEnumerable> + { + private const string OK = "OK"; + private const string ACK = "ACK"; + + private static readonly Regex LINE_REGEX = new Regex("^(?[A-Za-z]*):[ ]{0,1}(?.*)$"); + + private readonly bool isError; + private readonly int errorCode; + private readonly int commandListNum; + private readonly string currentCommand; + private readonly string errorMessage; + private readonly ReadOnlyCollection message; + + private Dictionary dictionary = null; + /// + /// If the response denotes an error in the last command. + /// + public bool IsError { get { return this.isError; } } + /// + /// The error code if an error occured. + /// + public int ErrorCode { get { return this.errorCode; } } + /// + /// If an error occured the index of the invalid command in a command list. + /// + public int CommandListNum { get { return this.commandListNum; } } + /// + /// The command executed. + /// + public string CurrentCommand { get { return this.currentCommand; } } + /// + /// The description of the error, if occured. + /// + public string ErrorMessage { get { return this.errorMessage; } } + /// + /// The lines of the response message. + /// + public ReadOnlyCollection Message { get { return this.message; } } + /// + /// The value of an attribute in the MPD response. + /// + /// The name of the attribute. + /// The value of the attribute + public string this[string key] + { + get + { + if (this.dictionary == null) + { + this.dictionary = new Dictionary(); + + foreach (string line in this.message) + { + Match match = LINE_REGEX.Match(line); + if (match.Success) + this.dictionary[match.Result("$key")] = match.Result("$value"); + } + } + + return this.dictionary[key]; + } + } + /// + /// The number of lines in the response message. + /// + public int Count { get { return this.message.Count; } } + /// + /// A line in the MPD response as KeyValuePair. If the message cannot be separated + /// into key and value according to the MPD protocol spec, a KeyValuePair is returned + /// with the key null and the value the whole text of the line. + /// + /// The index of the line. + /// The requested line as KeyValuePair. + public KeyValuePair this[int line] + { + get + { + Match match = LINE_REGEX.Match(this.message[line]); + if (match.Success) + return new KeyValuePair(match.Result("${key}"), match.Result("${value}")); + else + return new KeyValuePair(null, this.message[line]); + } + } + /// + /// Creates a new MpdResponse from a list of lines in case no error occured. + /// + /// The response to an MPD command. + public MpdResponse( ReadOnlyCollection message ) + { + if (message == null) + throw new ArgumentNullException("message"); + + this.isError = false; + this.errorCode = -1; + this.commandListNum = 0; + this.currentCommand = null; + this.errorMessage = null; + this.message = message; + } + /// + /// Creates a new MpdResponse in case an error occured. + /// + /// The code of the error. + /// The index of the command which raised the error. + /// The command that raised the error. + /// The message describing the error. + /// The text of the standard MPD response. + public MpdResponse( int errorCode, int commandListNum, string currentCommand, string errorMessage, ReadOnlyCollection message) + { + if (currentCommand == null) + throw new ArgumentNullException("currentCommand"); + if (errorMessage == null) + throw new ArgumentNullException("errorMessage"); + if (message == null) + throw new ArgumentNullException("message"); + + this.isError = true; + this.errorCode = errorCode; + this.commandListNum = commandListNum; + this.currentCommand = currentCommand; + this.errorMessage = errorMessage; + this.message = message; + } + /// + /// Returns the values in all lines with the given attribute. + /// + /// The attribute who's values are reguested. + /// The values in all lines with the given attribute. + public List getAttributeValueList(string attribute) + { + List ret = new List(); + + foreach (string line in this.message) + { + Match match = LINE_REGEX.Match(line); + if (match.Success) + { + string key = match.Result("${key}"); + if( ( key != null ) && key.Equals( attribute ) ) + { + string value = match.Result("${value}"); + if( value != null ) + ret.Add( value ); + } + } + } + + return ret; + } + /// + /// Returns only the value parts in all key/value pairs in the response. + /// + /// The list of values in all key/value pairs in the response. + public List getValueList() + { + List ret = new List(); + + foreach (string line in this.message) + { + Match match = LINE_REGEX.Match(line); + if (match.Success) + { + string value = match.Result("${value}"); + if (value != null) + ret.Add(value); + } + } + + return ret; + } + /// + /// Builds the response text of the MPD server from the object. + /// + /// The response text of the MPD server from the object. + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + foreach (string line in this.message) + builder.AppendLine(line); + + if (this.isError) + { + builder.Append(ACK); + builder.Append(" ["); + builder.Append(this.errorMessage); + builder.Append('@'); + builder.Append(this.commandListNum); + builder.Append("] {"); + builder.Append(this.currentCommand); + builder.Append("} "); + builder.Append(this.errorMessage); + //ACK [50@1] {play} song doesn't exist: "10240" + } + else + builder.Append(OK); + + return builder.ToString(); + } + + #region IEnumerable> Members + /// + /// Returns an enumerator for all KeyValuePairs in the MPD response. + /// + /// An enumerator for all KeyValuePairs in the MPD response. + IEnumerator> IEnumerable>.GetEnumerator() + { + return new MpdResponseEnumerator(this); + } + + #endregion + + #region IEnumerable Members + /// + /// Returns an enumerator for all KeyValuePairs in the MPD response. + /// + /// An enumerator for all KeyValuePairs in the MPD response. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return new MpdResponseEnumerator(this); + } + + #endregion + } + /// + /// A class for enumerating over the KeyValuePairs in the response. + /// + public class MpdResponseEnumerator :IEnumerator> + { + private readonly MpdResponse response; + private int position = -1; + private KeyValuePair current; + /// + /// Creates a new MpdResponseEnumerator. + /// + /// The response to enumerate over. + protected internal MpdResponseEnumerator(MpdResponse response) + { + this.response = response; + } + + #region IEnumerator> Members + /// + /// Returns the current element of the enumerator. + /// + KeyValuePair IEnumerator>.Current + { + get { return this.current; } + } + + #endregion + + #region IDisposable Members + + void IDisposable.Dispose() + { + this.position = -1; + } + + #endregion + + #region IEnumerator Members + /// + /// Returns the current element of the enumerator. + /// + object System.Collections.IEnumerator.Current + { + get { return this.current; } + } + /// + /// Moves the enumerator to the next KeyValuePair in the MPD response. + /// + /// If the enumerator has any values left. + bool System.Collections.IEnumerator.MoveNext() + { + this.position++; + if (this.position < this.response.Count) + { + this.current = this.response[this.position]; + return true; + } + else + return false; + } + /// + /// Sets the enumerator to it's initial state. + /// + void System.Collections.IEnumerator.Reset() + { + this.position = -1; + } + + #endregion + } +} diff --git a/LibMpc/MpdStatistics.cs b/LibMpc/MpdStatistics.cs new file mode 100644 index 0000000..e2fe9ca --- /dev/null +++ b/LibMpc/MpdStatistics.cs @@ -0,0 +1,134 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Libmpc +{ + /// + /// The MpdStatistics class contains statistics of the MPD file database. + /// + public class MpdStatistics + { + private readonly int artists; + private readonly int albums; + private readonly int songs; + private readonly int uptime; + private readonly int playtime; + private readonly int db_playtime; + private readonly long db_update; + /// + /// The number of artists in the MPD database. + /// + public int Artists { get { return this.artists; } } + /// + /// The number of albums in the MPD database. + /// + public int Albums { get { return this.albums; } } + /// + /// The number of songs in the MPD database. + /// + public int Songs { get { return this.songs; } } + /// + /// The time the MPD server is running in seconds. + /// + public int Uptime { get { return this.uptime; } } + /// + /// The number of seconds the MPD played so far. + /// + public int Playtime { get { return this.playtime; } } + /// + /// The total playtime of all songs in the MPD database. + /// + public int DbPlaytime { get { return this.db_playtime; } } + /// + /// The timestamp of the last MPD database update. + /// + public long DbUpdate { get { return this.db_update; } } + /// + /// Creates a new MpdStatistics object. + /// + /// The number of artists in the MPD database. + /// The number of albums in the MPD database. + /// The number of songs in the MPD database. + /// The time the MPD server is running in seconds. + /// The number of seconds the MPD played so far. + /// The total playtime of all songs in the MPD database. + /// The timestamp of the last MPD database update. + public MpdStatistics( + int artists, + int albums, + int songs, + int uptime, + int playtime, + int db_playtime, + long db_update + ) + { + this.artists = artists; + this.albums = albums; + this.songs = songs; + this.uptime = uptime; + this.playtime = playtime; + this.db_playtime = db_playtime; + this.db_update = db_update; + } + /// + /// Returns a string representation of the object mainly for debugging purpuse. + /// + /// A string representation of the object. + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + + appendInt(builder, "artists", this.artists); + appendInt(builder, "songs", this.songs); + appendInt(builder, "uptime", this.uptime); + appendInt(builder, "playtime", this.playtime); + appendInt(builder, "db_playtime", this.db_playtime); + appendLong(builder, "db_update", this.db_update); + + return builder.ToString(); + } + + private static void appendInt(StringBuilder builder, string name, int value) + { + if (value < 0) + return; + + builder.Append(name); + builder.Append(": "); + builder.Append(value); + builder.AppendLine(); + } + + private static void appendLong(StringBuilder builder, string name, long value) + { + if (value < 0) + return; + + builder.Append(name); + builder.Append(": "); + builder.Append(value); + builder.AppendLine(); + } + + } +} diff --git a/LibMpc/MpdStatus.cs b/LibMpc/MpdStatus.cs new file mode 100644 index 0000000..52e268a --- /dev/null +++ b/LibMpc/MpdStatus.cs @@ -0,0 +1,271 @@ +/* + * Copyright 2008 Matthias Sessler + * + * This file is part of LibMpc.net. + * + * LibMpc.net is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * LibMpc.net 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with LibMpc.net. If not, see . + */ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Libmpc +{ + /// + /// The possible states of the MPD. + /// + public enum MpdState + { + /// + /// The state of the MPD could not be translated into this enumeration. + /// + Unknown, + /// + /// The MPD is playing a track. + /// + Play, + /// + /// The MPD is not playing a track. + /// + Stop, + /// + /// The playback of the MPD is currently paused. + /// + Pause + } + /// + /// The MpdStatus class contains all values describing the current status of the MPD. + /// + public class MpdStatus + { + private int volume; + private bool repeat; + private bool random; + private int playlist; + private int playlistLength; + private int xFade; + private MpdState state; + private int song; + private int songId; + private int timeElapsed; + private int timeTotal; + private int bitrate; + private int audioSampleRate; + private int audioBits; + private int audioChannels; + private int updatingDb; + private string error; + /// + /// The current volume of the output. + /// + public int Volume { get { return this.volume; } } + /// + /// If the playlist is repeated after finish. + /// + public bool Repeat { get { return this.repeat; } } + /// + /// If the playlist is played in random order. + /// + public bool Random { get { return this.random; } } + /// + /// The version number of the playlist. + /// + public int Playlist { get { return this.playlist; } } + /// + /// The length of the playlist. + /// + public int PlaylistLength { get { return this.playlistLength; } } + /// + /// The number of seconds crossfaded between song changes. + /// + public int XFade { get { return this.xFade; } } + /// + /// The state of the MPD. + /// + public MpdState State { get { return this.state; } } + /// + /// The index of the currently played song in the playlist. + /// + public int Song { get { return this.song; } } + /// + /// The id of the song currently played. + /// + public int SongId { get { return this.songId; } } + /// + /// The number of seconds already played of the current song. + /// + public int TimeElapsed { get { return this.timeElapsed; } } + /// + /// The length of the current song in seconds. + /// + public int TimeTotal { get { return this.timeTotal; } } + /// + /// The bitrate of the current song. + /// + public int Bitrate { get { return this.bitrate; } } + /// + /// The audio sample rate of the current song. + /// + public int AudioSampleRate { get { return this.audioSampleRate; } } + /// + /// The audio bits of the current song. + /// + public int AudioBits { get { return this.audioBits; } } + /// + /// The number of audio channels of the current song. + /// + public int AudioChannels { get { return this.audioChannels; } } + /// + /// The number of the update on the MPD database currently running. + /// + public int UpdatingDb { get { return this.updatingDb; } } + /// + /// An error message, if there is an error. + /// + public string Error { get { return this.error; } } + /// + /// Creates a new MpdStatus object. + /// + /// The current volume of the output. + /// If the playlist is repeated after finish. + /// If the playlist is played in random order. + /// The version number of the playlist. + /// The length of the playlist. + /// The number of seconds crossfaded between song changes. + /// The state of the MPD. + /// The index of the currently played song in the playlist. + /// The id of the song currently played. + /// The number of seconds already played of the current song. + /// The length of the current song in seconds. + /// The bitrate of the current song. + /// The audio sample rate of the current song. + /// The audio bits of the current song. + /// The number of audio channels of the current song. + /// The number of the update on the MPD database currently running. + /// An error message, if there is an error. + public MpdStatus( + int volume, + bool repeat, + bool random, + int playlist, + int playlistLength, + int xFade, + MpdState state, + int song, + int songId, + int timeElapsed, + int timeTotal, + int bitrate, + int audioSampleRate, + int audioBits, + int audioChannels, + int updatingDb, + string error + ) + { + this.volume = volume; + this.repeat = repeat; + this.random = random; + this.playlist = playlist; + this.playlistLength = playlistLength; + this.xFade = xFade; + this.state = state; + this.song = song; + this.songId = songId; + this.timeElapsed = timeElapsed; + this.timeTotal = timeTotal; + this.bitrate = bitrate; + this.audioSampleRate = audioSampleRate; + this.audioBits = audioBits; + this.audioChannels = audioChannels; + this.updatingDb = updatingDb; + this.error = error; + } + /// + /// Returns a string representation of the object maily for debugging purpuses. + /// + /// A string representation of the object. + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + + appendInt(builder, "volume", this.volume); + appendBool(builder, "repeat", this.repeat); + appendBool(builder, "random", this.random); + appendInt(builder, "playlist", this.playlist); + appendInt(builder, "playlistlength", this.playlistLength); + appendInt(builder, "xfade", this.xFade); + switch (this.state) + { + case MpdState.Play: + builder.AppendLine("state: play"); + break; + case MpdState.Pause: + builder.AppendLine("state: pause"); + break; + case MpdState.Stop: + builder.AppendLine("state: stop"); + break; + } + appendInt(builder, "song", this.song); + appendInt(builder, "songid", this.songId); + if ((this.timeElapsed >= 0) || (this.timeTotal >= 0)) + { + builder.Append("time: "); + builder.Append(this.timeElapsed); + builder.Append(":"); + builder.Append(this.timeTotal); + builder.AppendLine(); + } + appendInt(builder, "bitrate", this.bitrate); + if ((this.audioSampleRate >= 0) || (this.audioBits >= 0) || (this.audioChannels >= 0)) + { + builder.Append("audio: "); + builder.Append(this.audioSampleRate); + builder.Append(":"); + builder.Append(this.audioBits); + builder.Append(":"); + builder.Append(this.audioChannels); + builder.AppendLine(); + } + appendInt(builder, "updating_db", this.updatingDb); + if (this.error != null) + { + builder.Append("error: "); + builder.AppendLine(this.error); + } + + return builder.ToString(); + } + + private static void appendInt(StringBuilder builder, string name, int value) + { + if (value < 0) + return; + + builder.Append(name); + builder.Append(": "); + builder.Append(value); + builder.AppendLine(); + } + + private static void appendBool(StringBuilder builder, string name, bool value) + { + builder.Append(name); + builder.Append(": "); + builder.Append(value ? '1' : '0'); + builder.AppendLine(); + } + } +}