1
0
mirror of https://github.com/ZetaKebab/MpcNET.git synced 2025-07-01 16:47:37 +00:00

src dir created

This commit is contained in:
glucaci
2017-04-12 11:21:29 +02:00
parent 21ceb47523
commit 4b1e3d7b70
61 changed files with 2869 additions and 2869 deletions

33
src/LibMpc.sln Normal file
View File

@ -0,0 +1,33 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26403.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Utilities", "Solution Utilities", "{83D06F7C-1336-4AC3-82BB-FDE7940D3C10}"
ProjectSection(SolutionItems) = preProject
.travis.yml = .travis.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibMpc", "LibMpc\LibMpc.csproj", "{8994C820-7BA9-4BB8-B9EA-C608B07C4A11}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibMpcTest", "LibMpcTest\LibMpcTest.csproj", "{69F1D68F-9CD5-4EA6-9B47-2A7A9BF8CED9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8994C820-7BA9-4BB8-B9EA-C608B07C4A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8994C820-7BA9-4BB8-B9EA-C608B07C4A11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8994C820-7BA9-4BB8-B9EA-C608B07C4A11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8994C820-7BA9-4BB8-B9EA-C608B07C4A11}.Release|Any CPU.Build.0 = Release|Any CPU
{69F1D68F-9CD5-4EA6-9B47-2A7A9BF8CED9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{69F1D68F-9CD5-4EA6-9B47-2A7A9BF8CED9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69F1D68F-9CD5-4EA6-9B47-2A7A9BF8CED9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69F1D68F-9CD5-4EA6-9B47-2A7A9BF8CED9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,123 @@
using System.Collections.Generic;
using System.Linq;
using LibMpc.Types;
namespace LibMpc
{
public partial class Commands
{
/// <summary>
/// https://www.musicpd.org/doc/protocol/database.html
/// </summary>
public class Database
{
// TODO: count
/// <summary>
/// Finds songs in the database that is exactly "searchText".
/// </summary>
public class Find : IMpcCommand<IEnumerable<IMpdFile>>
{
private readonly ITag _tag;
private readonly string _searchText;
public Find(ITag tag, string searchText)
{
_tag = tag;
_searchText = searchText;
}
public string Value => string.Join(" ", "find", _tag.Value, _searchText);
public IEnumerable<IMpdFile> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var results = new List<MpdFile>();
foreach (var line in response)
{
if (line.Key.Equals("file"))
{
results.Add(new MpdFile(line.Value));
}
else
{
results.Last().AddTag(line.Key, line.Value);
}
}
return results;
}
}
public class List : IMpcCommand<string>
{
private readonly ITag _tag;
public List(ITag tag)
{
_tag = tag;
}
public string Value => string.Join(" ", "list", _tag);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
// TODO:
return response.ToString();
}
}
// TODO: findadd
public class ListAll : IMpcCommand<IEnumerable<MpdDirectory>>
{
public string Value => "listall";
public IEnumerable<MpdDirectory> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var rootDirectory = new List<MpdDirectory>
{
new MpdDirectory("/") // Add by default the root directory
};
foreach (var line in response)
{
if (line.Key.Equals("file"))
{
rootDirectory.Last().AddFile(line.Value);
}
if (line.Key.Equals("directory"))
{
rootDirectory.Add(new MpdDirectory(line.Value));
}
}
return rootDirectory;
}
}
// TODO: listallinfo
// TODO: listfiles
// TODO: lsinfo
// TODO: readcomments
// TODO: search
// TODO: searchadd
// TODO: searchaddpl
public class Update : IMpcCommand<string>
{
// TODO: Extend command: < update [URI] >
public string Value => "update";
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
// TODO:
return response.ToString();
}
}
// TODO: rescan
}
}
}

View File

@ -0,0 +1,101 @@
using System.Collections.Generic;
using LibMpc.Types;
namespace LibMpc
{
public partial class Commands
{
/// <summary>
/// https://www.musicpd.org/doc/protocol/output_commands.html
/// </summary>
public class Output
{
/// <summary>
/// Turns an output off.
/// </summary>
public class DisableOutput : IMpcCommand<string>
{
private readonly int _outputId;
public DisableOutput(int outputId)
{
_outputId = outputId;
}
public string Value => string.Join(" ", "disableoutput", _outputId);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
// Response should be empty.
return string.Join(", ", response);
}
}
/// <summary>
/// Turns an output on.
/// </summary>
public class EnableOutput : IMpcCommand<string>
{
private readonly int _outputId;
public EnableOutput(int outputId)
{
_outputId = outputId;
}
public string Value => string.Join(" ", "enableoutput", _outputId);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
// Response should be empty.
return string.Join(", ", response);
}
}
/// <summary>
/// Turns an output on or off, depending on the current state.
/// </summary>
public class ToggleOutput : IMpcCommand<string>
{
private readonly int _outputId;
public ToggleOutput(int outputId)
{
_outputId = outputId;
}
public string Value => string.Join(" ", "toggleoutput", _outputId);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
// Response should be empty.
return string.Join(", ", response);
}
}
/// <summary>
/// Shows information about all outputs.
/// </summary>
public class Outputs : IMpcCommand<IEnumerable<MpdOutput>>
{
public string Value => "outputs";
public IEnumerable<MpdOutput> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var result = new List<MpdOutput>();
for (var i = 0; i < response.Count; i += 3)
{
var outputId = int.Parse(response[i].Value);
var outputName = response[i + 1].Value;
var outputEnabled = response[i + 2].Value == "1";
result.Add(new MpdOutput(outputId, outputName, outputEnabled));
}
return result;
}
}
}
}
}

View File

@ -0,0 +1,103 @@
using System.Collections.Generic;
using System.Linq;
using LibMpc.Types;
namespace LibMpc
{
public partial class Commands
{
public static class Playlists
{
/// <summary>
/// https://www.musicpd.org/doc/protocol/queue.html
/// </summary>
public static class Current
{
}
/// <summary>
/// https://www.musicpd.org/doc/protocol/playlist_files.html
/// </summary>
public static class Stored
{
public class ListPlaylist : IMpcCommand<IEnumerable<IMpdFilePath>>
{
private readonly string _playlistName;
public ListPlaylist(string playlistName)
{
_playlistName = playlistName;
}
public string Value => string.Join(" ", "listplaylist", $"\"{_playlistName}\"");
public IEnumerable<IMpdFilePath> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var results = response.Where(line => line.Key.Equals("file")).Select(line => new MpdFile(line.Value));
return results;
}
}
public class ListPlaylistInfo : IMpcCommand<IEnumerable<IMpdFile>>
{
private readonly string _playlistName;
public ListPlaylistInfo(string playlistName)
{
_playlistName = playlistName;
}
public string Value => string.Join(" ", "listplaylistinfo", $"\"{_playlistName}\"");
public IEnumerable<IMpdFile> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var results = new List<MpdFile>();
foreach (var line in response)
{
if (line.Key.Equals("file"))
{
results.Add(new MpdFile(line.Value));
}
else
{
results.Last().AddTag(line.Key, line.Value);
}
}
return results;
}
}
/// <summary>
/// Prints a list of the playlist directory.
/// </summary>
public class ListPlaylists : IMpcCommand<IEnumerable<MpdPlaylist>>
{
public string Value => "listplaylists";
public IEnumerable<MpdPlaylist> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var result = new List<MpdPlaylist>();
foreach (var line in response)
{
if (line.Key.Equals("playlist"))
{
result.Add(new MpdPlaylist(line.Value));
}
else if (line.Key.Equals("Last-Modified"))
{
result.Last().AddLastModified(line.Value);
}
}
return result;
}
}
}
}
}
}

View File

@ -0,0 +1,103 @@
using System.Collections.Generic;
using System.Linq;
using LibMpc.Types;
namespace LibMpc
{
public partial class Commands
{
/// <summary>
/// https://www.musicpd.org/doc/protocol/reflection_commands.html
/// </summary>
public static class Reflection
{
// config : This command is only permitted to "local" clients (connected via UNIX domain socket).
/// <summary>
/// Shows which commands the current user has access to.
/// </summary>
public class Commands : IMpcCommand<IEnumerable<string>>
{
public string Value => "commands";
public IEnumerable<string> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var result = response.Where(item => item.Key.Equals("command")).Select(item => item.Value);
return result;
}
}
// TODO: notcommands : Shows which commands the current user does not have access to.
/// <summary>
/// Shows a list of available song metadata.
/// </summary>
public class TagTypes : IMpcCommand<IEnumerable<string>>
{
public string Value => "tagtypes";
public IEnumerable<string> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var result = response.Where(item => item.Key.Equals("tagtype")).Select(item => item.Value);
return result;
}
}
/// <summary>
/// Gets a list of available URL handlers.
/// </summary>
public class UrlHandlers : IMpcCommand<IEnumerable<string>>
{
public string Value => "urlhandlers";
public IEnumerable<string> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var result = response.Where(item => item.Key.Equals("handler")).Select(item => item.Value);
return result;
}
}
/// <summary>
/// Print a list of decoder plugins, followed by their supported suffixes and MIME types.
/// </summary>
public class Decoders : IMpcCommand<IEnumerable<MpdDecoderPlugin>>
{
public string Value => "decoders";
public IEnumerable<MpdDecoderPlugin> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var result = new List<MpdDecoderPlugin>();
var mpdDecoderPlugin = MpdDecoderPlugin.Empty;
foreach (var line in response)
{
if (line.Key.Equals("plugin"))
{
if (mpdDecoderPlugin.IsInitialized)
{
result.Add(mpdDecoderPlugin);
}
mpdDecoderPlugin = new MpdDecoderPlugin(line.Value);
}
if (line.Key.Equals("suffix") && mpdDecoderPlugin.IsInitialized)
{
mpdDecoderPlugin.AddSuffix(line.Value);
}
if (line.Key.Equals("mime_type") && mpdDecoderPlugin.IsInitialized)
{
mpdDecoderPlugin.AddMediaType(line.Value);
}
}
return result;
}
}
}
}
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Linq;
namespace LibMpc
{
public interface IMpcCommand<out T>
{
string Value { get; }
T FormatResponse(IList<KeyValuePair<string, string>> response);
}
}

13
src/LibMpc/Constants.cs Normal file
View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LibMpc
{
public class Constants
{
public static readonly string Ok = "OK";
public static readonly string Ack = "ACK";
public static readonly string FirstLinePrefix = "OK MPD ";
}
}

69
src/LibMpc/Exception.cs Normal file
View File

@ -0,0 +1,69 @@
using System;
namespace LibMpc
{
/// <summary>
/// Is thrown when a command is to be executed on a disconnected <see cref="MpcConnection"/>
/// where the <see cref="MpcConnection.AutoConnect"/> property is set to false.
/// </summary>
public class NotConnectedException : InvalidOperationException
{
/// <summary>
/// Creates a new NotConnectedException.
/// </summary>
public NotConnectedException() : base("Not connected.") {}
}
/// <summary>
/// Is thrown when the connect method is invoked on an already connected <see cref="MpcConnection"/>.
/// </summary>
public class AlreadyConnectedException : InvalidOperationException
{
/// <summary>
/// Creates a new AlreadyConnectedException.
/// </summary>
public AlreadyConnectedException() : base("Connected already established.") { }
}
/// <summary>
/// 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.
/// </summary>
public class InvalidMpdResponseException : Exception
{
/// <summary>
/// Creates a new InvalidMpdResponseException.
/// </summary>
public InvalidMpdResponseException() : base( "Invalid Mpd Response." ) {}
/// <summary>
/// Creates a new InvalidMpdResponseException.
/// </summary>
/// <param name="message">A message describing the error.</param>
public InvalidMpdResponseException(string message) : base("Invalid Mpd Response: " + message) { }
}
/// <summary>
/// Is thrown when the MPD server returns an error to a command.
/// </summary>
public class MpdResponseException : Exception
{
private int errorCode;
private string errorMessage;
/// <summary>
/// The error code of the mpd server.
/// </summary>
public int ErrorCode { get { return this.errorCode; } }
/// <summary>
/// A message describing what went wrong.
/// </summary>
public string ErrorMessage { get { return this.errorMessage; } }
/// <summary>
/// Creates a new MpdResponseException.
/// </summary>
/// <param name="errorCode">The error code of the mpd server.</param>
/// <param name="errorMessage">A message describing what went wrong.</param>
public MpdResponseException(int errorCode, string errorMessage)
: base("MPD" + errorCode + " " + errorMessage)
{
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}
}

18
src/LibMpc/LibMpc.csproj Normal file
View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
<AssemblyName>LibMpc</AssemblyName>
<PackageId>LibMpc</PackageId>
<NetStandardImplicitPackageVersion>1.6.0</NetStandardImplicitPackageVersion>
<PackageTargetFallback>$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace LibMpc
{
public interface IMpdMessage<T>
{
IMpdRequest<T> Request { get; }
IMpdResponse<T> Response { get; }
}
[DebuggerDisplay("Request: {Request.Command.Value} | Response Status: {Response.State.Status}")]
public class MpdMessage<T> : IMpdMessage<T>
{
private readonly Regex _linePattern = new Regex("^(?<key>[A-Za-z_-]*):[ ]{0,1}(?<value>.*)$");
private readonly IList<string> _rawResponse;
public MpdMessage(IMpcCommand<T> command, bool connected, IReadOnlyCollection<string> response)
{
Request = new MpdRequest<T>(command);
var endLine = response.Skip(response.Count - 1).Single();
_rawResponse = response.Take(response.Count - 1).ToList();
var values = Request.Command.FormatResponse(GetValuesFromResponse());
Response = new MpdResponse<T>(endLine, values, connected);
}
public IMpdRequest<T> Request { get; }
public IMpdResponse<T> Response { get; }
private IList<KeyValuePair<string, string>> GetValuesFromResponse()
{
var result = new List<KeyValuePair<string, string>>();
foreach (var line in _rawResponse)
{
var match = _linePattern.Match(line);
if (match.Success)
{
var mpdKey = match.Result("${key}");
if (!string.IsNullOrEmpty(mpdKey))
{
var mpdValue = match.Result("${value}");
if (!string.IsNullOrEmpty(mpdValue))
{
result.Add(new KeyValuePair<string, string>(mpdKey, mpdValue));
}
}
}
}
return result;
}
public override string ToString()
{
return JsonConvert.SerializeObject(this, Formatting.Indented);
}
}
}

View File

@ -0,0 +1,17 @@
namespace LibMpc
{
public interface IMpdRequest<T>
{
IMpcCommand<T> Command { get; }
}
public class MpdRequest<T> : IMpdRequest<T>
{
public MpdRequest(IMpcCommand<T> command)
{
Command = command;
}
public IMpcCommand<T> Command { get; }
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
namespace LibMpc
{
public interface IMpdResponse<T>
{
IMpdResponseState State { get; }
T Body { get; }
}
public class MpdResponse<T> : IMpdResponse<T>
{
public MpdResponse(string endLine, T body, bool connected)
{
State = new MpdResponseState(endLine, connected);
Body = body;
}
public IMpdResponseState State { get; }
public T Body { get; }
}
public static class CheckNotNullExtension
{
public static void CheckNotNull(this object toBeChecked)
{
if (toBeChecked == null)
{
throw new ArgumentNullException(nameof(toBeChecked));
}
}
}
}

View File

@ -0,0 +1,66 @@
using System.Text.RegularExpressions;
namespace LibMpc
{
public interface IMpdResponseState
{
string Status { get; }
string ErrorMessage { get; }
string MpdError { get; }
bool Error { get; }
bool Connected { get; }
}
public class MpdResponseState : IMpdResponseState
{
private static readonly Regex ErrorPattern = new Regex("^ACK \\[(?<code>[0-9]*)@(?<nr>[0-9]*)] \\{(?<command>[a-z]*)} (?<message>.*)$");
private readonly string _endLine;
public MpdResponseState(string endLine, bool connected)
{
_endLine = endLine;
Connected = connected;
if (!string.IsNullOrEmpty(_endLine))
{
if (_endLine.Equals(Constants.Ok))
{
Status = _endLine;
Error = false;
}
else
{
ParseErrorResponse();
}
}
}
public bool Connected { get; } = false;
public bool Error { get; } = true;
public string Status { get; private set; } = "UNKNOWN";
public string ErrorMessage { get; private set; } = string.Empty;
public string MpdError { get; private set; } = string.Empty;
private void ParseErrorResponse()
{
Status = "ERROR";
MpdError = _endLine;
var match = ErrorPattern.Match(_endLine);
if (match.Groups.Count != 5)
{
ErrorMessage = "Unexpected response from server.";
}
else
{
var errorCode = match.Result("${code}");
var commandListItem = match.Result("${nr}");
var commandFailed = match.Result("${command}");
var errorMessage = match.Result("${message}");
ErrorMessage = $"ErrorCode: { errorCode }, CommandListItem: { commandListItem }, CommandFailed: { commandFailed }, ErrorMessage: { errorMessage }";
}
}
}
}

1114
src/LibMpc/Mpc.cs Normal file

File diff suppressed because it is too large Load Diff

137
src/LibMpc/MpcConnection.cs Normal file
View File

@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace LibMpc
{
/// <summary>
/// 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 <see cref="Mpc"/>
/// class.
/// </summary>
public class MpcConnection
{
private readonly IPEndPoint _server;
private TcpClient _tcpClient;
private NetworkStream _networkStream;
private StreamReader _reader;
private StreamWriter _writer;
private string _version;
public MpcConnection(IPEndPoint server)
{
if (IsConnected) return;
ClearConnectionFields();
_server = server;
}
public bool IsConnected => (_tcpClient != null) && _tcpClient.Connected;
public string Version => _version;
public async Task ConnectAsync()
{
if (_server == null)
throw new InvalidOperationException("Server IPEndPoint not set.");
if (IsConnected)
throw new AlreadyConnectedException();
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(_server.Address, _server.Port);
_networkStream = _tcpClient.GetStream();
// Encoding UTF8 has some problems with TcpClient: https://bugs.musicpd.org/view.php?id=4501
_reader = new StreamReader(_networkStream);
_writer = new StreamWriter(_networkStream) { NewLine = "\n" };
var firstLine = _reader.ReadLine();
if (!firstLine.StartsWith(Constants.FirstLinePrefix))
{
await DisconnectAsync();
throw new InvalidDataException("Response of mpd does not start with \"" + Constants.FirstLinePrefix + "\"." );
}
_version = firstLine.Substring(Constants.FirstLinePrefix.Length);
}
public Task DisconnectAsync()
{
if (_tcpClient == null)
{
return Task.CompletedTask;
}
_networkStream.Dispose();
ClearConnectionFields();
return Task.CompletedTask;
}
public async Task<IMpdMessage<T>> SendAsync<T>(IMpcCommand<T> command)
{
command.CheckNotNull();
var connected = await CheckConnectionAsync();
string[] response;
try
{
_writer.WriteLine(command.Value);
_writer.Flush();
response = await ReadResponseAsync();
}
catch (Exception)
{
try { await DisconnectAsync(); } catch (Exception) { }
return null; // TODO: Create Null Object for MpdResponse
}
return new MpdMessage<T>(command, connected, response);
}
private async Task<bool> CheckConnectionAsync()
{
if (!IsConnected)
{
await ConnectAsync();
}
return IsConnected;
}
private async Task<string[]> ReadResponseAsync()
{
var response = new List<string>();
// Read response untli reach end token (OK or ACK)
string responseLine;
do
{
responseLine = await _reader.ReadLineAsync();
response.Add(responseLine);
} while (!(responseLine.Equals(Constants.Ok) || responseLine.StartsWith(Constants.Ack)));
return response.Where(line => !string.IsNullOrEmpty(line)).ToArray();
}
private void ClearConnectionFields()
{
_writer?.Dispose();
_reader?.Dispose();
_networkStream?.Dispose();
_tcpClient?.Dispose();
_version = string.Empty;
}
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using LibMpc.Types;
namespace LibMpc
{
/// <summary>
/// The MpdDirectoryListing class contains the response of a MPD server to a list command.
/// </summary>
public class MpdDirectoryListing
{
private readonly ReadOnlyCollection<IMpdFile> file;
private readonly ReadOnlyCollection<string> directory;
private readonly ReadOnlyCollection<string> playlist;
/// <summary>
/// The list of files in the directory.
/// </summary>
public ReadOnlyCollection<IMpdFile> FileList { get { return this.file; } }
/// <summary>
/// The list of subdirectories in the directory.
/// </summary>
public ReadOnlyCollection<string> DirectoryList { get { return this.directory; } }
/// <summary>
/// The list of playlists in the directory.
/// </summary>
public ReadOnlyCollection<string> PlaylistList { get { return this.playlist; } }
/// <summary>
/// Creates a new MpdDirectoryListing.
/// </summary>
/// <param name="file">The list of files in the directory.</param>
/// <param name="directory">The list of subdirectories in the directory.</param>
/// <param name="playlist">The list of playlists in the directory.</param>
public MpdDirectoryListing(List<IMpdFile> file, List<string> directory, List<string> 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<IMpdFile>(file);
this.directory = new ReadOnlyCollection<string>(directory);
this.playlist = new ReadOnlyCollection<string>(playlist);
}
/// <summary>
/// Creates a new MpdDirectoryListing.
/// </summary>
/// <param name="file">The list of files in the directory.</param>
/// <param name="directory">The list of subdirectories in the directory.</param>
/// <param name="playlist">The list of playlists in the directory.</param>
public MpdDirectoryListing(ReadOnlyCollection<IMpdFile> file, ReadOnlyCollection<string> directory, ReadOnlyCollection<string> 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;
}
}
}

114
src/LibMpc/MpdStatistics.cs Normal file
View File

@ -0,0 +1,114 @@
using System.Text;
namespace LibMpc
{
/// <summary>
/// The MpdStatistics class contains statistics of the MPD file database.
/// </summary>
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;
/// <summary>
/// The number of artists in the MPD database.
/// </summary>
public int Artists { get { return this.artists; } }
/// <summary>
/// The number of albums in the MPD database.
/// </summary>
public int Albums { get { return this.albums; } }
/// <summary>
/// The number of songs in the MPD database.
/// </summary>
public int Songs { get { return this.songs; } }
/// <summary>
/// The time the MPD server is running in seconds.
/// </summary>
public int Uptime { get { return this.uptime; } }
/// <summary>
/// The number of seconds the MPD played so far.
/// </summary>
public int Playtime { get { return this.playtime; } }
/// <summary>
/// The total playtime of all songs in the MPD database.
/// </summary>
public int DbPlaytime { get { return this.db_playtime; } }
/// <summary>
/// The timestamp of the last MPD database update.
/// </summary>
public long DbUpdate { get { return this.db_update; } }
/// <summary>
/// Creates a new MpdStatistics object.
/// </summary>
/// <param name="artists">The number of artists in the MPD database.</param>
/// <param name="albums">The number of albums in the MPD database.</param>
/// <param name="songs">The number of songs in the MPD database.</param>
/// <param name="uptime">The time the MPD server is running in seconds.</param>
/// <param name="playtime">The number of seconds the MPD played so far.</param>
/// <param name="db_playtime">The total playtime of all songs in the MPD database.</param>
/// <param name="db_update">The timestamp of the last MPD database update.</param>
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;
}
/// <summary>
/// Returns a string representation of the object mainly for debugging purpuse.
/// </summary>
/// <returns>A string representation of the object.</returns>
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();
}
}
}

251
src/LibMpc/MpdStatus.cs Normal file
View File

@ -0,0 +1,251 @@
using System.Text;
namespace LibMpc
{
/// <summary>
/// The possible states of the MPD.
/// </summary>
public enum MpdState
{
/// <summary>
/// The state of the MPD could not be translated into this enumeration.
/// </summary>
Unknown,
/// <summary>
/// The MPD is playing a track.
/// </summary>
Play,
/// <summary>
/// The MPD is not playing a track.
/// </summary>
Stop,
/// <summary>
/// The playback of the MPD is currently paused.
/// </summary>
Pause
}
/// <summary>
/// The MpdStatus class contains all values describing the current status of the MPD.
/// </summary>
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;
/// <summary>
/// The current volume of the output.
/// </summary>
public int Volume { get { return this.volume; } }
/// <summary>
/// If the playlist is repeated after finish.
/// </summary>
public bool Repeat { get { return this.repeat; } }
/// <summary>
/// If the playlist is played in random order.
/// </summary>
public bool Random { get { return this.random; } }
/// <summary>
/// The version number of the playlist.
/// </summary>
public int Playlist { get { return this.playlist; } }
/// <summary>
/// The length of the playlist.
/// </summary>
public int PlaylistLength { get { return this.playlistLength; } }
/// <summary>
/// The number of seconds crossfaded between song changes.
/// </summary>
public int XFade { get { return this.xFade; } }
/// <summary>
/// The state of the MPD.
/// </summary>
public MpdState State { get { return this.state; } }
/// <summary>
/// The index of the currently played song in the playlist.
/// </summary>
public int Song { get { return this.song; } }
/// <summary>
/// The id of the song currently played.
/// </summary>
public int SongId { get { return this.songId; } }
/// <summary>
/// The number of seconds already played of the current song.
/// </summary>
public int TimeElapsed { get { return this.timeElapsed; } }
/// <summary>
/// The length of the current song in seconds.
/// </summary>
public int TimeTotal { get { return this.timeTotal; } }
/// <summary>
/// The bitrate of the current song.
/// </summary>
public int Bitrate { get { return this.bitrate; } }
/// <summary>
/// The audio sample rate of the current song.
/// </summary>
public int AudioSampleRate { get { return this.audioSampleRate; } }
/// <summary>
/// The audio bits of the current song.
/// </summary>
public int AudioBits { get { return this.audioBits; } }
/// <summary>
/// The number of audio channels of the current song.
/// </summary>
public int AudioChannels { get { return this.audioChannels; } }
/// <summary>
/// The number of the update on the MPD database currently running.
/// </summary>
public int UpdatingDb { get { return this.updatingDb; } }
/// <summary>
/// An error message, if there is an error.
/// </summary>
public string Error { get { return this.error; } }
/// <summary>
/// Creates a new MpdStatus object.
/// </summary>
/// <param name="volume">The current volume of the output.</param>
/// <param name="repeat">If the playlist is repeated after finish.</param>
/// <param name="random">If the playlist is played in random order.</param>
/// <param name="playlist">The version number of the playlist.</param>
/// <param name="playlistLength">The length of the playlist.</param>
/// <param name="xFade">The number of seconds crossfaded between song changes.</param>
/// <param name="state">The state of the MPD.</param>
/// <param name="song">The index of the currently played song in the playlist.</param>
/// <param name="songId">The id of the song currently played.</param>
/// <param name="timeElapsed">The number of seconds already played of the current song.</param>
/// <param name="timeTotal">The length of the current song in seconds.</param>
/// <param name="bitrate">The bitrate of the current song.</param>
/// <param name="audioSampleRate">The audio sample rate of the current song.</param>
/// <param name="audioBits">The audio bits of the current song.</param>
/// <param name="audioChannels">The number of audio channels of the current song.</param>
/// <param name="updatingDb">The number of the update on the MPD database currently running.</param>
/// <param name="error">An error message, if there is an error.</param>
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;
}
/// <summary>
/// Returns a string representation of the object maily for debugging purpuses.
/// </summary>
/// <returns>A string representation of the object.</returns>
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();
}
}
}

View File

@ -0,0 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LibMpc")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("8994c820-7ba9-4bb8-b9ea-c608b07c4a11")]

View File

@ -0,0 +1,13 @@
namespace LibMpc
{
/// <summary>
/// https://www.musicpd.org/doc/protocol/database.html : find {TYPE} {WHAT} [...] [window START:END]
/// </summary>
public class FindTags
{
public static ITag Any { get; } = new Tag("any");
public static ITag File { get; } = new Tag("file");
public static ITag Base { get; } = new Tag("base");
public static ITag ModifiedSince { get; } = new Tag("modified-since");
}
}

View File

@ -0,0 +1,24 @@
namespace LibMpc
{
/// <summary>
/// https://www.musicpd.org/doc/protocol/tags.html
/// </summary>
public class MpdTags
{
public static ITag Artist { get; } = new Tag("artist");
public static ITag ArtistSort { get; } = new Tag("artistsort");
public static ITag Album { get; } = new Tag("album");
public static ITag AlbumSort { get; } = new Tag("albumsort");
public static ITag AlbumArtist { get; } = new Tag("albumartist");
public static ITag AlbumArtistSort { get; } = new Tag("albumartistsort");
public static ITag Title { get; } = new Tag("title");
public static ITag Track { get; } = new Tag("track");
public static ITag Name { get; } = new Tag("name");
public static ITag Genre { get; } = new Tag("genre");
public static ITag Date { get; } = new Tag("date");
public static ITag Composer { get; } = new Tag("composer");
public static ITag Performer { get; } = new Tag("performer");
public static ITag Comment { get; } = new Tag("comment");
public static ITag Disc { get; } = new Tag("disc");
}
}

18
src/LibMpc/Tags/Tag.cs Normal file
View File

@ -0,0 +1,18 @@
namespace LibMpc
{
public interface ITag
{
string Value { get; }
}
internal class Tag : ITag
{
internal Tag(string value)
{
Value = value;
}
public string Value { get; }
}
}

View File

@ -0,0 +1,28 @@
using System.Collections.Generic;
namespace LibMpc.Types
{
public interface IMpdFilePath
{
string File { get; }
}
public interface IMpdFile : IMpdFilePath
{
int Time { get; }
string Album { get; }
string Artist { get; }
string Title { get; }
string Track { get; }
string Name { get; }
string Genre { get; }
string Date { get; }
string Composer { get; }
string Performer { get; }
string Comment { get; }
int Disc { get; }
int Pos { get; }
int Id { get; }
IDictionary<string, string> UnknownMetadata { get; }
}
}

View File

@ -0,0 +1,36 @@
using System.Collections.Generic;
namespace LibMpc.Types
{
public class MpdDecoderPlugin
{
public static readonly MpdDecoderPlugin Empty = new MpdDecoderPlugin(string.Empty);
private readonly IList<string> _suffixes = new List<string>();
private readonly IList<string> _mediaTypes = new List<string>();
public MpdDecoderPlugin(string name)
{
name.CheckNotNull();
Name = name;
IsInitialized = !string.IsNullOrEmpty(name);
}
public string Name { get; }
public IEnumerable<string> Suffixes => _suffixes;
public IEnumerable<string> MediaTypes => _mediaTypes;
internal bool IsInitialized { get; }
internal void AddSuffix(string suffix)
{
_suffixes.Add(suffix);
}
internal void AddMediaType(string type)
{
_mediaTypes.Add(type);
}
}
}

View File

@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Linq;
namespace LibMpc.Types
{
public class MpdDirectory
{
private readonly IList<IMpdFilePath> _files = new List<IMpdFilePath>();
public MpdDirectory(string path)
{
path.CheckNotNull();
Path = path;
var name = path.Split('/').Last();
Name = string.IsNullOrEmpty(name) ? "root" : name;
}
public string Path { get; }
public string Name { get; }
public IEnumerable<IMpdFilePath> Files => _files;
internal void AddFile(string file)
{
_files.Add(new MpdFile(file));
}
}
}

103
src/LibMpc/Types/MpdFile.cs Normal file
View File

@ -0,0 +1,103 @@
using System.Collections.Generic;
namespace LibMpc.Types
{
/// <summary>
/// The MpdFile class contains all meta data for a file of the MPD.
/// </summary>
internal class MpdFile : IMpdFile
{
private const string TagTime = "Time";
private const string TagArtist = "Artist";
private const string TagAlbum = "Album";
private const string TagTitle = "Title";
private const string TagTrack = "Track";
private const string TagName = "Name";
private const string TagGenre = "Genre";
private const string TagDate = "Date";
private const string TagComposer = "Composer";
private const string TagPerformer = "Performer";
private const string TagComment = "Comment";
private const string TagDisc = "Disc";
private const string TagPos = "Pos";
private const string TagId = "Id";
private readonly IDictionary<string, string> _unknownMetadata = new Dictionary<string, string>();
internal MpdFile(string file)
{
file.CheckNotNull();
File = file;
}
public string File { get; }
public int Time { get; private set; } = -1;
public string Album { get; private set; } = string.Empty;
public string Artist { get; private set; } = string.Empty;
public string Title { get; private set; } = string.Empty;
public string Track { get; private set; } = string.Empty;
public string Name { get; private set; } = string.Empty;
public string Genre { get; private set; } = string.Empty;
public string Date { get; private set; } = string.Empty;
public string Composer { get; private set; } = string.Empty;
public string Performer { get; private set; } = string.Empty;
public string Comment { get; private set; } = string.Empty;
public int Disc { get; private set; } = -1;
public int Pos { get; private set; } = -1;
public int Id { get; private set; } = -1;
public IDictionary<string, string> UnknownMetadata => _unknownMetadata;
internal void AddTag(string tag, string value)
{
switch (tag)
{
case TagTime:
Time = int.Parse(value);
break;
case TagArtist:
Artist = value;
break;
case TagAlbum:
Album = value;
break;
case TagTitle:
Title = value;
break;
case TagTrack:
Track = value;
break;
case TagName:
Name = value;
break;
case TagGenre:
Genre = value;
break;
case TagDate:
Date = value;
break;
case TagComposer:
Composer = value;
break;
case TagPerformer:
Performer = value;
break;
case TagComment:
Comment = value;
break;
case TagDisc:
Disc = int.Parse(value);
break;
case TagPos:
Pos = int.Parse(value);
break;
case TagId:
Id = int.Parse(value);
break;
default:
_unknownMetadata.Add(tag, value);
break;
}
}
}
}

View File

@ -0,0 +1,21 @@
namespace LibMpc.Types
{
/// <summary>
/// The MpdOutput class contains all attributes of an output device of the MPD.
/// </summary>
public class MpdOutput
{
public MpdOutput(int id, string name, bool enabled)
{
name.CheckNotNull();
Id = id;
Name = name;
IsEnabled = enabled;
}
public int Id { get; }
public string Name { get; }
public bool IsEnabled { get; }
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Globalization;
namespace LibMpc.Types
{
public class MpdPlaylist
{
public MpdPlaylist(string name)
{
name.CheckNotNull();
Name = name;
}
public string Name { get; }
public DateTime LastModified { get; private set; }
internal void AddLastModified(string lastModified)
{
LastModified = DateTime.Parse(lastModified, CultureInfo.InvariantCulture);
}
}
}

View File

@ -0,0 +1,29 @@
using LibMpc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace LibMpcTest
{
[TestClass]
public partial class LibMpcTest
{
private static MpdMock _mpdMock;
[ClassInitialize]
public static void Init(TestContext context)
{
_mpdMock = new MpdMock();
_mpdMock.Start();
Mpc = new MpcMock().Client;
}
[ClassCleanup]
public static void Cleanup()
{
_mpdMock.Dispose();
}
internal static Mpc Mpc { get; private set; }
}
}

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework>
<AssemblyName>LibMpcTest</AssemblyName>
<PackageId>LibMpcTest</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>
<ItemGroup>
<None Update="Server\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LibMpc\LibMpc.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.1.14" />
<PackageReference Include="MSTest.TestFramework" Version="1.1.14" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

26
src/LibMpcTest/MpcMock.cs Normal file
View File

@ -0,0 +1,26 @@
using LibMpc;
using System;
using System.Net;
using System.Threading.Tasks;
namespace LibMpcTest
{
public class MpcMock : IDisposable
{
public MpcMock()
{
Client = new Mpc(new IPEndPoint(IPAddress.Loopback, 6600));
var connected = Task.Run(async () => await Client.ConnectAsync()).Result;
TestOutput.WriteLine($"Connected to MPD : {connected}; Version: {Client.Version}");
}
public Mpc Client { get; }
public void Dispose()
{
Client?.DisconnectAsync().GetAwaiter().GetResult();
TestOutput.WriteLine($"Disconnected from MPD.");
}
}
}

57
src/LibMpcTest/MpdConf.cs Normal file
View File

@ -0,0 +1,57 @@
using System.IO;
using System.Text;
namespace LibMpcTest
{
public class MpdConf
{
private const string MPD_CONF_FILE = "mpd.conf";
private const string MPD_LOG_FILE = "mpd_log.txt";
private const string MPD_DB_FILE = "mpd.db";
public static void Create(string rootDirectory)
{
File.Create(Path.Combine(rootDirectory, MPD_LOG_FILE)).Dispose();
CreateConfFile(rootDirectory);
}
private static void CreateConfFile(string rootDirectory)
{
var builder = new StringBuilder();
builder.AppendLine($"log_file \"{Path.Combine(rootDirectory, MPD_LOG_FILE).Replace("\\", "\\\\")}\"");
builder.AppendLine($"db_file \"{Path.Combine(rootDirectory, MPD_DB_FILE).Replace("\\", "\\\\")}\"");
builder.AppendLine("bind_to_address \"any\"");
builder.AppendLine($"music_directory \"{Path.Combine(rootDirectory, "Music").Replace("\\", "\\\\")}\"");
builder.AppendLine($"playlist_directory \"{Path.Combine(rootDirectory, "Playlists").Replace("\\", "\\\\")}\"");
builder.AppendLine("port \"6600\"");
builder.AppendLine("audio_output {");
builder.AppendLine("type \"null\"");
builder.AppendLine("name \"Enabled output to be disabled\"");
builder.AppendLine("enabled \"true\"");
builder.AppendLine("mixer_type \"none\"");
builder.AppendLine("}");
builder.AppendLine("audio_output {");
builder.AppendLine("type \"null\"");
builder.AppendLine("name \"Disabled output to be enabled\"");
builder.AppendLine("enabled \"false\"");
builder.AppendLine("mixer_type \"none\"");
builder.AppendLine("}");
builder.AppendLine("audio_output {");
builder.AppendLine("type \"null\"");
builder.AppendLine("name \"Enabled output to be toggled\"");
builder.AppendLine("enabled \"true\"");
builder.AppendLine("mixer_type \"none\"");
builder.AppendLine("}");
var mpdConfContent = builder.ToString();
using (var file = File.CreateText(Path.Combine(rootDirectory, MPD_CONF_FILE)))
{
file.Write(mpdConfContent);
file.Flush();
}
}
}
}

119
src/LibMpcTest/MpdMock.cs Normal file
View File

@ -0,0 +1,119 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace LibMpcTest
{
public class MpdMock : IDisposable
{
public void Start()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
SendCommand("/usr/bin/pkill mpd");
}
MpdConf.Create(Path.Combine(AppContext.BaseDirectory, "Server"));
var server = GetServer();
Process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = server.FileName,
WorkingDirectory = server.WorkingDirectory,
Arguments = server.Arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
}
};
TestOutput.WriteLine($"Starting Server: {Process.StartInfo.FileName} {Process.StartInfo.Arguments}");
Process.Start();
TestOutput.WriteLine($"Output: {Process.StandardOutput.ReadToEnd()}");
TestOutput.WriteLine($"Error: {Process.StandardError.ReadToEnd()}");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
SendCommand("/bin/netstat -ntpl");
}
}
public Process Process { get; private set; }
private Server GetServer()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return Server.Linux;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Server.Windows;
}
throw new NotSupportedException("OS not supported");
}
private void SendCommand(string command)
{
var netcat = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
WorkingDirectory = "/bin/",
Arguments = $"-c \"sudo {command}\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
}
};
netcat.Start();
netcat.WaitForExit();
TestOutput.WriteLine(command);
TestOutput.WriteLine($"Output: {netcat.StandardOutput.ReadToEnd()}");
TestOutput.WriteLine($"Error: {netcat.StandardError.ReadToEnd()}");
}
public void Dispose()
{
Process?.Kill();
Process?.Dispose();
TestOutput.WriteLine("Server Stopped.");
}
private class Server
{
public static Server Linux = new Server(
fileName: "/bin/bash",
workingDirectory: "/bin/",
arguments: $"-c \"sudo /usr/bin/mpd {Path.Combine(AppContext.BaseDirectory, "Server", "mpd.conf")} -v\"");
public static Server Windows = new Server(
fileName: Path.Combine(AppContext.BaseDirectory, "Server", "mpd.exe"),
workingDirectory: Path.Combine(AppContext.BaseDirectory, "Server"),
arguments: $"{Path.Combine(AppContext.BaseDirectory, "Server", "mpd.conf")} -v");
private Server(string fileName, string workingDirectory, string arguments)
{
FileName = fileName;
WorkingDirectory = workingDirectory;
Arguments = arguments;
}
public string FileName { get; }
public string WorkingDirectory { get; }
public string Arguments { get; }
}
}
}

View File

@ -0,0 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LibMpcTest")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("69f1d68f-9cd5-4ea6-9b47-2a7a9bf8ced9")]

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
teaspoon-stirring-mug-of-coffee.mp3
whistle-kettle-boiling.mp3
wine-glass-double-chink-clink-cheers.mp3
Directory With Spaces/coin-spin-light.mp3
Directory With Spaces/finger-snap-click.mp3

View File

@ -0,0 +1,3 @@
A long name directory with some spaces/pouring-water-into-mug-of-coffee.mp3
A long name directory with some spaces/Sub Directory Two/short-trouser-pants-zip-closing.mp3
Directory/2-Kids-Laughing.mp3

View File

@ -0,0 +1,5 @@
A long name directory with some spaces/Ghost-Sounds.mp3
Directory/ambient-noise-server-room.mp3
Directory With Spaces/SubDirectory One/central-locking-Ford-Mondeo-Mk-3.mp3
_My Directory/gas-fire-lighting.mp3
A long name directory with some spaces/Sub Directory Two/starting-engine-Ford-Mondeo-Mk-3-diesel.mp3

View File

@ -0,0 +1,163 @@
info_begin
format: 1
mpd_version: 0.17.4
fs_charset: cp1252
tag: Artist
tag: ArtistSort
tag: Album
tag: AlbumArtist
tag: AlbumArtistSort
tag: Title
tag: Track
tag: Name
tag: Genre
tag: Date
tag: Composer
tag: Performer
tag: Disc
tag: MUSICBRAINZ_ARTISTID
tag: MUSICBRAINZ_ALBUMID
tag: MUSICBRAINZ_ALBUMARTISTID
tag: MUSICBRAINZ_TRACKID
info_end
directory: A long name directory with some spaces
mtime: 1482142041
begin: A long name directory with some spaces
directory: Sub Directory Two
mtime: 1482142041
begin: A long name directory with some spaces/Sub Directory Two
song_begin: short-trouser-pants-zip-closing.mp3
Time: 1
Artist: Geek & Dummy
Title: Sound effect: short trouser pants zip closing
Date: 2013
Date: 2013
mtime: 1481623577
song_end
song_begin: starting-engine-Ford-Mondeo-Mk-3-diesel.mp3
Time: 6
Artist: Geek & Dummy
Title: Sound effect: starting engine - Ford Mondeo Mk 3 diesel
Album: Geek & Dummy Sound Library
Date: 2014
Genre: soundfx
mtime: 1481623577
song_end
end: A long name directory with some spaces/Sub Directory Two
song_begin: pouring-water-into-mug-of-coffee.mp3
Time: 4
Artist: Geek & Dummy
Title: Sound effect: pouring water into mug of coffee
Date: 2013
Date: 2013
mtime: 1481623577
song_end
song_begin: Ghost-Sounds.mp3
Time: 95
Artist: Geek & Dummy
Title: Sound effect: ghostly haunted house (bells, ghostly laughs & knives sharpened)
Album: Geek & Dummy Sound Library
Date: 2014
Date: 2014
Genre: soundfx
mtime: 1481623577
song_end
end: A long name directory with some spaces
directory: Directory
mtime: 1482142041
begin: Directory
song_begin: 2-Kids-Laughing.mp3
Time: 30
Artist: Geek & Dummy
Title: Sound effect: two kids laughing
Album: Geek & Dummy Sound Library
Date: 2014
Date: 2014
Genre: soundfx
mtime: 1481623577
song_end
song_begin: ambient-noise-server-room.mp3
Time: 71
Artist: Geek & Dummy
Title: Sound effect: ambient noise - server room
Album: Geek & Dummy Sound Library
Date: 2014
Date: 2014
Genre: soundfx
mtime: 1481623577
song_end
end: Directory
directory: Directory With Spaces
mtime: 1482142041
begin: Directory With Spaces
directory: SubDirectory One
mtime: 1482142041
begin: Directory With Spaces/SubDirectory One
song_begin: central-locking-Ford-Mondeo-Mk-3.mp3
Time: 5
Artist: Geek & Dummy
Title: Sound effect: central locking - Ford Mondeo Mk 3
Album: Geek & Dummy Sound Library
Date: 2014
Date: 2014
Genre: soundfx
mtime: 1481623577
song_end
end: Directory With Spaces/SubDirectory One
song_begin: coin-spin-light.mp3
Time: 5
Artist: Geek & Dummy
Title: Sound effect: coin spin (light coin)
Date: 2013
Date: 2013
mtime: 1481623577
song_end
song_begin: finger-snap-click.mp3
Time: 0
Artist: Geek & Dummy
Title: Sound effect: finger snap/click
Album: Geek & Dummy Sound Library
Date: 2014
Date: 2014
Genre: soundfx
mtime: 1481623577
song_end
end: Directory With Spaces
directory: _My Directory
mtime: 1482142041
begin: _My Directory
song_begin: gas-fire-lighting.mp3
Time: 58
Artist: Geek & Dummy
Title: Sound effect: gas fire lighting and warming up
Album: Geek & Dummy Sound Library
Date: 2014
Date: 2014
Genre: soundfx
mtime: 1481623577
song_end
end: _My Directory
song_begin: teaspoon-stirring-mug-of-coffee.mp3
Time: 4
Artist: Geek & Dummy
Title: Sound effect: teaspoon stirring mug of coffee
Date: 2013
Date: 2013
mtime: 1481623577
song_end
song_begin: whistle-kettle-boiling.mp3
Time: 36
Artist: Geek & Dummy
Title: Sound effect: whistle kettle boiling
Date: 2013
Date: 2013
mtime: 1481623577
song_end
song_begin: wine-glass-double-chink-clink-cheers.mp3
Time: 1
Artist: Geek & Dummy
Title: Sound effect: wine glass double chink/clink/cheers
Date: 2013
Date: 2013
mtime: 1481623577
song_end

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,12 @@
using System;
namespace LibMpcTest
{
internal class TestOutput
{
internal static void WriteLine(string value)
{
Console.Out.WriteLine(value);
}
}
}

View File

@ -0,0 +1,33 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Linq;
using LibMpc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LibMpcTest
{
public partial class LibMpcTest
{
[TestMethod]
public async Task ListAllTest()
{
var response = await Mpc.SendAsync(new Commands.Database.ListAll());
TestOutput.WriteLine("ListAllTest Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Count().Equals(7));
}
[TestMethod]
public async Task FindGenreTest()
{
var response = await Mpc.SendAsync(new Commands.Database.Find(MpdTags.Genre, "soundfx"));
TestOutput.WriteLine("FindGenreTest Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Count().Equals(7));
}
}
}

View File

@ -0,0 +1,77 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using LibMpc;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LibMpcTest
{
public partial class LibMpcTest
{
[TestMethod]
public async Task DisableOutputTest()
{
var responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs());
Assert.IsTrue(responseOutputs.Response.Body.Single(output => output.Id.Equals(0)).IsEnabled);
var response = await Mpc.SendAsync(new Commands.Output.DisableOutput(0));
TestOutput.WriteLine("DisableOutputTest Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Equals(string.Empty));
Assert.IsTrue(response.Response.State.Status.Equals("OK"));
responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs());
Assert.IsFalse(responseOutputs.Response.Body.Single(output => output.Id.Equals(0)).IsEnabled);
}
[TestMethod]
public async Task EnableOutputTest()
{
var responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs());
// By default should be disable from mpd.config
Assert.IsFalse(responseOutputs.Response.Body.Single(output => output.Id.Equals(1)).IsEnabled);
var response = await Mpc.SendAsync(new Commands.Output.EnableOutput(1));
TestOutput.WriteLine("EnableOutputTest Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Equals(string.Empty));
Assert.IsTrue(response.Response.State.Status.Equals("OK"));
responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs());
Assert.IsTrue(responseOutputs.Response.Body.Single(output => output.Id.Equals(1)).IsEnabled);
}
[TestMethod]
public async Task ToggleOutputTest()
{
var responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs());
Assert.IsTrue(responseOutputs.Response.Body.Single(output => output.Id.Equals(2)).IsEnabled);
var response = await Mpc.SendAsync(new Commands.Output.ToggleOutput(2));
TestOutput.WriteLine("ToggleOutputTest Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Equals(string.Empty));
Assert.IsTrue(response.Response.State.Status.Equals("OK"));
responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs());
Assert.IsFalse(responseOutputs.Response.Body.Single(output => output.Id.Equals(2)).IsEnabled);
}
[TestMethod]
public async Task LisOutputsTest()
{
var response = await Mpc.SendAsync(new Commands.Output.Outputs());
TestOutput.WriteLine("LisOutputsTest Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Count().Equals(3));
}
}
}

View File

@ -0,0 +1,53 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using LibMpc;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LibMpcTest
{
public partial class LibMpcTest
{
[DataTestMethod]
[DataRow("Playlist One", 5)]
[DataRow("Playlist Two", 3)]
[DataRow("_My Playlist", 5)]
public async Task ListPlaylistTest(string playlistName, int numberOfFiles)
{
var response = await Mpc.SendAsync(new Commands.Playlists.Stored.ListPlaylist(playlistName));
TestOutput.WriteLine($"ListPlaylistTest (playlistName: {playlistName}) Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Count().Equals(numberOfFiles));
}
[DataTestMethod]
[DataRow("Playlist One", 5)]
[DataRow("Playlist Two", 3)]
[DataRow("_My Playlist", 5)]
public async Task ListPlaylistInfoTest(string playlistName, int numberOfFiles)
{
var response = await Mpc.SendAsync(new Commands.Playlists.Stored.ListPlaylistInfo(playlistName));
TestOutput.WriteLine($"ListPlaylistTest (playlistName: {playlistName}) Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Count().Equals(numberOfFiles));
Assert.IsTrue(response.Response.Body.All(item => !string.IsNullOrEmpty(item.Artist)));
Assert.IsTrue(response.Response.Body.All(item => !string.IsNullOrEmpty(item.Title)));
Assert.IsTrue(response.Response.Body.All(item => !string.IsNullOrEmpty(item.Date)));
}
[TestMethod]
public async Task ListPlaylistsTest()
{
var response = await Mpc.SendAsync(new Commands.Playlists.Stored.ListPlaylists());
TestOutput.WriteLine($"ListPlaylistsTest Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Count().Equals(3));
}
}
}

View File

@ -0,0 +1,80 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using LibMpc;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LibMpcTest
{
public partial class LibMpcTest
{
[TestMethod]
public async Task CommandsTest()
{
var response = await Mpc.SendAsync(new Commands.Reflection.Commands());
TestOutput.WriteLine($"CommandsTest (commands: {response.Response.Body.Count()}) Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
// Different answer from MPD on Windows and on Linux, beacuse of Version.
// Check some of the commands.
Assert.IsTrue(response.Response.Body.Any(command => command.Equals("listall")));
Assert.IsTrue(response.Response.Body.Any(command => command.Equals("outputs")));
Assert.IsTrue(response.Response.Body.Any(command => command.Equals("pause")));
Assert.IsTrue(response.Response.Body.Any(command => command.Equals("play")));
Assert.IsTrue(response.Response.Body.Any(command => command.Equals("setvol")));
Assert.IsTrue(response.Response.Body.Any(command => command.Equals("stop")));
}
[TestMethod]
public async Task TagTypesTest()
{
var response = await Mpc.SendAsync(new Commands.Reflection.TagTypes());
TestOutput.WriteLine("TagTypesTest Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
Assert.IsTrue(response.Response.Body.Count().Equals(17));
}
[TestMethod]
public async Task UrlHandlersTest()
{
var response = await Mpc.SendAsync(new Commands.Reflection.UrlHandlers());
TestOutput.WriteLine($"UrlHandlersTest (handlers: {response.Response.Body.Count()}) Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
// Different answer from MPD on Windows and on Linux.
// Check some of the handlers.
Assert.IsTrue(response.Response.Body.Any(handler => handler.Equals("http://")));
Assert.IsTrue(response.Response.Body.Any(handler => handler.Equals("mms://")));
Assert.IsTrue(response.Response.Body.Any(handler => handler.Equals("gopher://")));
Assert.IsTrue(response.Response.Body.Any(handler => handler.Equals("rtp://")));
}
[TestMethod]
public async Task DecodersTest()
{
var response = await Mpc.SendAsync(new Commands.Reflection.Decoders());
TestOutput.WriteLine($"DecodersTest (decoders: {response.Response.Body.Count()}) Result:");
TestOutput.WriteLine(JsonConvert.SerializeObject(response, Formatting.Indented));
// Different answer from MPD on Windows and on Linux.
// Check some of the decoders.
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.Name.Equals("mad")));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.Suffixes.Any(suffix => suffix.Equals("mp3"))));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.MediaTypes.Any(mediaType => mediaType.Equals("audio/mpeg"))));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.Name.Equals("flac")));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.Suffixes.Any(suffix => suffix.Equals("flac"))));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.MediaTypes.Any(mediaType => mediaType.Equals("audio/flac"))));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.MediaTypes.Any(mediaType => mediaType.Equals("audio/x-flac"))));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.Name.Equals("ffmpeg")));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.Suffixes.Any(suffix => suffix.Equals("aac"))));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.Suffixes.Any(suffix => suffix.Equals("mpeg"))));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.MediaTypes.Any(mediaType => mediaType.Equals("audio/aac"))));
Assert.IsTrue(response.Response.Body.Any(decoder => decoder.MediaTypes.Any(mediaType => mediaType.Equals("audio/mpeg"))));
}
}
}