1
0
mirror of https://github.com/ZetaKebab/MpcNET.git synced 2024-09-16 05:30:09 +00:00

Various improvements

This commit is contained in:
Kim Hugener-Ohlsen 2018-01-05 10:18:57 +01:00
parent 6426d98fc5
commit a2c012dd7e
54 changed files with 1649 additions and 863 deletions

2
.gitignore vendored
View File

@ -14,3 +14,5 @@
/src/MpcNET.sln.DotSettings.user /src/MpcNET.sln.DotSettings.user
/src/MpcNET.Test/MpcNET.Test.csproj.DotSettings /src/MpcNET.Test/MpcNET.Test.csproj.DotSettings
/src/MpcNET/MpcNET.csproj.DotSettings /src/MpcNET/MpcNET.csproj.DotSettings
/.vs
*.user

View File

@ -1,11 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyName>MpcNET.Test</AssemblyName> <AssemblyName>MpcNET.Test</AssemblyName>
<PackageId>MpcNET.Test</PackageId> <PackageId>MpcNET.Test</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
@ -25,9 +24,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.1.14" /> <PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.1.14" /> <PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MpcNET.Commands;
using MpcNET.Tags; using MpcNET.Tags;
namespace MpcNET.Test namespace MpcNET.Test
@ -10,7 +11,7 @@ namespace MpcNET.Test
[TestMethod] [TestMethod]
public async Task ListAllTest() public async Task ListAllTest()
{ {
var response = await Mpc.SendAsync(new Commands.Database.ListAll()); var response = await Mpc.SendAsync(Command.Database.ListAll());
TestOutput.WriteLine("ListAllTest Result:"); TestOutput.WriteLine("ListAllTest Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -21,7 +22,7 @@ namespace MpcNET.Test
[TestMethod] [TestMethod]
public async Task FindGenreTest() public async Task FindGenreTest()
{ {
var response = await Mpc.SendAsync(new Commands.Database.Find(MpdTags.Genre, "soundfx")); var response = await Mpc.SendAsync(Command.Database.Find(MpdTags.Genre, "soundfx"));
TestOutput.WriteLine("FindGenreTest Result:"); TestOutput.WriteLine("FindGenreTest Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);

View File

@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MpcNET.Commands;
namespace MpcNET.Test namespace MpcNET.Test
{ {
@ -9,10 +10,10 @@ namespace MpcNET.Test
[TestMethod] [TestMethod]
public async Task DisableOutputTest() public async Task DisableOutputTest()
{ {
var responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs()); var responseOutputs = await Mpc.SendAsync(Command.Output.Outputs());
Assert.IsTrue(responseOutputs.Response.Body.Single(output => output.Id.Equals(0)).IsEnabled); Assert.IsTrue(responseOutputs.Response.Body.Single(output => output.Id.Equals(0)).IsEnabled);
var response = await Mpc.SendAsync(new Commands.Output.DisableOutput(0)); var response = await Mpc.SendAsync(Command.Output.DisableOutput(0));
TestOutput.WriteLine("DisableOutputTest Result:"); TestOutput.WriteLine("DisableOutputTest Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -20,18 +21,18 @@ namespace MpcNET.Test
Assert.IsTrue(response.Response.Body.Equals(string.Empty)); Assert.IsTrue(response.Response.Body.Equals(string.Empty));
Assert.IsTrue(response.Response.State.Status.Equals("OK")); Assert.IsTrue(response.Response.State.Status.Equals("OK"));
responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs()); responseOutputs = await Mpc.SendAsync(Command.Output.Outputs());
Assert.IsFalse(responseOutputs.Response.Body.Single(output => output.Id.Equals(0)).IsEnabled); Assert.IsFalse(responseOutputs.Response.Body.Single(output => output.Id.Equals(0)).IsEnabled);
} }
[TestMethod] [TestMethod]
public async Task EnableOutputTest() public async Task EnableOutputTest()
{ {
var responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs()); var responseOutputs = await Mpc.SendAsync(Command.Output.Outputs());
// By default should be disable from mpd.config // By default should be disable from mpd.config
Assert.IsFalse(responseOutputs.Response.Body.Single(output => output.Id.Equals(1)).IsEnabled); Assert.IsFalse(responseOutputs.Response.Body.Single(output => output.Id.Equals(1)).IsEnabled);
var response = await Mpc.SendAsync(new Commands.Output.EnableOutput(1)); var response = await Mpc.SendAsync(Command.Output.EnableOutput(1));
TestOutput.WriteLine("EnableOutputTest Result:"); TestOutput.WriteLine("EnableOutputTest Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -39,17 +40,17 @@ namespace MpcNET.Test
Assert.IsTrue(response.Response.Body.Equals(string.Empty)); Assert.IsTrue(response.Response.Body.Equals(string.Empty));
Assert.IsTrue(response.Response.State.Status.Equals("OK")); Assert.IsTrue(response.Response.State.Status.Equals("OK"));
responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs()); responseOutputs = await Mpc.SendAsync(Command.Output.Outputs());
Assert.IsTrue(responseOutputs.Response.Body.Single(output => output.Id.Equals(1)).IsEnabled); Assert.IsTrue(responseOutputs.Response.Body.Single(output => output.Id.Equals(1)).IsEnabled);
} }
[TestMethod] [TestMethod]
public async Task ToggleOutputTest() public async Task ToggleOutputTest()
{ {
var responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs()); var responseOutputs = await Mpc.SendAsync(Command.Output.Outputs());
Assert.IsTrue(responseOutputs.Response.Body.Single(output => output.Id.Equals(2)).IsEnabled); Assert.IsTrue(responseOutputs.Response.Body.Single(output => output.Id.Equals(2)).IsEnabled);
var response = await Mpc.SendAsync(new Commands.Output.ToggleOutput(2)); var response = await Mpc.SendAsync(Command.Output.ToggleOutput(2));
TestOutput.WriteLine("ToggleOutputTest Result:"); TestOutput.WriteLine("ToggleOutputTest Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -57,14 +58,14 @@ namespace MpcNET.Test
Assert.IsTrue(response.Response.Body.Equals(string.Empty)); Assert.IsTrue(response.Response.Body.Equals(string.Empty));
Assert.IsTrue(response.Response.State.Status.Equals("OK")); Assert.IsTrue(response.Response.State.Status.Equals("OK"));
responseOutputs = await Mpc.SendAsync(new Commands.Output.Outputs()); responseOutputs = await Mpc.SendAsync(Command.Output.Outputs());
Assert.IsFalse(responseOutputs.Response.Body.Single(output => output.Id.Equals(2)).IsEnabled); Assert.IsFalse(responseOutputs.Response.Body.Single(output => output.Id.Equals(2)).IsEnabled);
} }
[TestMethod] [TestMethod]
public async Task LisOutputsTest() public async Task LisOutputsTest()
{ {
var response = await Mpc.SendAsync(new Commands.Output.Outputs()); var response = await Mpc.SendAsync(Command.Output.Outputs());
TestOutput.WriteLine("LisOutputsTest Result:"); TestOutput.WriteLine("LisOutputsTest Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);

View File

@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MpcNET.Commands;
namespace MpcNET.Test namespace MpcNET.Test
{ {
@ -12,7 +13,7 @@ namespace MpcNET.Test
[DataRow("_My Playlist", 5)] [DataRow("_My Playlist", 5)]
public async Task ListPlaylistTest(string playlistName, int numberOfFiles) public async Task ListPlaylistTest(string playlistName, int numberOfFiles)
{ {
var response = await Mpc.SendAsync(Commands.Playlists.Stored.GetContent(playlistName)); var response = await Mpc.SendAsync(Command.Playlists.Stored.GetContent(playlistName));
TestOutput.WriteLine($"ListPlaylistTest (playlistName: {playlistName}) Result:"); TestOutput.WriteLine($"ListPlaylistTest (playlistName: {playlistName}) Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -26,7 +27,7 @@ namespace MpcNET.Test
[DataRow("_My Playlist", 5)] [DataRow("_My Playlist", 5)]
public async Task ListPlaylistInfoTest(string playlistName, int numberOfFiles) public async Task ListPlaylistInfoTest(string playlistName, int numberOfFiles)
{ {
var response = await Mpc.SendAsync(Commands.Playlists.Stored.GetContentWithMetadata(playlistName)); var response = await Mpc.SendAsync(Command.Playlists.Stored.GetContentWithMetadata(playlistName));
TestOutput.WriteLine($"ListPlaylistTest (playlistName: {playlistName}) Result:"); TestOutput.WriteLine($"ListPlaylistTest (playlistName: {playlistName}) Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -40,7 +41,7 @@ namespace MpcNET.Test
[TestMethod] [TestMethod]
public async Task ListPlaylistsTest() public async Task ListPlaylistsTest()
{ {
var response = await Mpc.SendAsync(Commands.Playlists.Stored.GetAll()); var response = await Mpc.SendAsync(Command.Playlists.Stored.GetAll());
TestOutput.WriteLine($"ListPlaylistsTest Result:"); TestOutput.WriteLine($"ListPlaylistsTest Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -116,57 +117,57 @@ namespace MpcNET.Test
private async Task Check_Empty_Queue() private async Task Check_Empty_Queue()
{ {
var message = await Mpc.SendAsync(Commands.Playlists.Current.GetAllSongsInfo()); var message = await Mpc.SendAsync(Command.Playlists.Current.GetAllSongsInfo());
Assert.IsTrue(message.HasSuccessResponse()); Assert.IsTrue(message.HasSuccessResponse());
Assert.IsFalse(message.Response.Body.Any()); Assert.IsFalse(message.Response.Body.Any());
} }
private async Task Load_Playlist(string playlistName) private async Task Load_Playlist(string playlistName)
{ {
var message = await Mpc.SendAsync(Commands.Playlists.Stored.Load(playlistName)); var message = await Mpc.SendAsync(Command.Playlists.Stored.Load(playlistName));
Assert.IsTrue(message.HasSuccessResponse()); Assert.IsTrue(message.HasSuccessResponse());
} }
private async Task Clear_Queue() private async Task Clear_Queue()
{ {
var message = await Mpc.SendAsync(Commands.Playlists.Current.Clear()); var message = await Mpc.SendAsync(Command.Playlists.Current.Clear());
Assert.IsTrue(message.HasSuccessResponse()); Assert.IsTrue(message.HasSuccessResponse());
} }
private async Task Check_Queue_HasSongs(int nrOfSongs) private async Task Check_Queue_HasSongs(int nrOfSongs)
{ {
var message = await Mpc.SendAsync(Commands.Playlists.Current.GetAllSongsInfo()); var message = await Mpc.SendAsync(Command.Playlists.Current.GetAllSongsInfo());
Assert.IsTrue(message.HasSuccessResponse()); Assert.IsTrue(message.HasSuccessResponse());
Assert.IsTrue(message.Response.Body.Count() == nrOfSongs); Assert.IsTrue(message.Response.Body.Count() == nrOfSongs);
} }
private async Task Add_Directory(string directory) private async Task Add_Directory(string directory)
{ {
var message = await Mpc.SendAsync(Commands.Playlists.Current.AddDirectory(directory)); var message = await Mpc.SendAsync(Command.Playlists.Current.AddDirectory(directory));
Assert.IsTrue(message.HasSuccessResponse()); Assert.IsTrue(message.HasSuccessResponse());
} }
private async Task Add_File(string file) private async Task Add_File(string file)
{ {
var message = await Mpc.SendAsync(Commands.Playlists.Current.AddSong(file)); var message = await Mpc.SendAsync(Command.Playlists.Current.AddSong(file));
Assert.IsTrue(message.HasSuccessResponse()); Assert.IsTrue(message.HasSuccessResponse());
} }
private async Task Remove_Position(int position) private async Task Remove_Position(int position)
{ {
var message = await Mpc.SendAsync(Commands.Playlists.Current.RemoveSongByPosition(position)); var message = await Mpc.SendAsync(Command.Playlists.Current.RemoveSongByPosition(position));
Assert.IsTrue(message.HasSuccessResponse()); Assert.IsTrue(message.HasSuccessResponse());
} }
private async Task Remove_Id(int songId) private async Task Remove_Id(int songId)
{ {
var message = await Mpc.SendAsync(Commands.Playlists.Current.RemoveSongById(songId)); var message = await Mpc.SendAsync(Command.Playlists.Current.RemoveSongById(songId));
Assert.IsTrue(message.HasSuccessResponse()); Assert.IsTrue(message.HasSuccessResponse());
} }
private async Task<int> Get_Song_Id() private async Task<int> Get_Song_Id()
{ {
var message = await Mpc.SendAsync(Commands.Playlists.Current.GetAllSongMetadata()); var message = await Mpc.SendAsync(Command.Playlists.Current.GetAllSongMetadata());
return message.Response.Body.Single().Id; return message.Response.Body.Single().Id;
} }
} }

View File

@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MpcNET.Commands;
namespace MpcNET.Test namespace MpcNET.Test
{ {
@ -9,7 +10,7 @@ namespace MpcNET.Test
[TestMethod] [TestMethod]
public async Task CommandsTest() public async Task CommandsTest()
{ {
var response = await Mpc.SendAsync(new Commands.Reflection.Commands()); var response = await Mpc.SendAsync(Command.Reflection.Commands());
TestOutput.WriteLine($"CommandsTest (commands: {response.Response.Body.Count()}) Result:"); TestOutput.WriteLine($"CommandsTest (commands: {response.Response.Body.Count()}) Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -27,7 +28,7 @@ namespace MpcNET.Test
[TestMethod] [TestMethod]
public async Task TagTypesTest() public async Task TagTypesTest()
{ {
var response = await Mpc.SendAsync(new Commands.Reflection.TagTypes()); var response = await Mpc.SendAsync(Command.Reflection.TagTypes());
TestOutput.WriteLine("TagTypesTest Result:"); TestOutput.WriteLine("TagTypesTest Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -38,7 +39,7 @@ namespace MpcNET.Test
[TestMethod] [TestMethod]
public async Task UrlHandlersTest() public async Task UrlHandlersTest()
{ {
var response = await Mpc.SendAsync(new Commands.Reflection.UrlHandlers()); var response = await Mpc.SendAsync(Command.Reflection.UrlHandlers());
TestOutput.WriteLine($"UrlHandlersTest (handlers: {response.Response.Body.Count()}) Result:"); TestOutput.WriteLine($"UrlHandlersTest (handlers: {response.Response.Body.Count()}) Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);
@ -54,7 +55,7 @@ namespace MpcNET.Test
[TestMethod] [TestMethod]
public async Task DecodersTest() public async Task DecodersTest()
{ {
var response = await Mpc.SendAsync(new Commands.Reflection.Decoders()); var response = await Mpc.SendAsync(Command.Reflection.Decoders());
TestOutput.WriteLine($"DecodersTest (decoders: {response.Response.Body.Count()}) Result:"); TestOutput.WriteLine($"DecodersTest (decoders: {response.Response.Body.Count()}) Result:");
TestOutput.WriteLine(response); TestOutput.WriteLine(response);

View File

@ -1,127 +1,33 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using MpcNET.Commands.Database;
using MpcNET.Tags; using MpcNET.Tags;
using MpcNET.Types; using MpcNET.Types;
namespace MpcNET namespace MpcNET.Commands
{ {
public static partial class Commands public static partial class Command
{ {
/// <summary> /// <summary>
/// https://www.musicpd.org/doc/protocol/database.html /// https://www.musicpd.org/doc/protocol/database.html
/// </summary> /// </summary>
public class Database public static class Database
{ {
public static IMpcCommand<IEnumerable<IMpdFile>> Find(ITag tag, string searchText)
{
return new FindCommand(tag, searchText);
}
public static IMpcCommand<string> Update(string uri = null)
{
return new UpdateCommand(uri);
}
// TODO: count // 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
/// <summary>
/// Lists all songs and directories in URI.
/// </summary>
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 // TODO: rescan
public static IMpcCommand<IEnumerable<MpdDirectory>> ListAll()
{
return new ListAllCommand();
}
} }
} }
} }

View File

@ -1,100 +1,34 @@
using System.Collections.Generic; using System.Collections.Generic;
using MpcNET.Commands.Output;
using MpcNET.Types; using MpcNET.Types;
namespace MpcNET namespace MpcNET.Commands
{ {
public static partial class Commands public static partial class Command
{ {
/// <summary> /// <summary>
/// https://www.musicpd.org/doc/protocol/output_commands.html /// https://www.musicpd.org/doc/protocol/output_commands.html
/// </summary> /// </summary>
public class Output public class Output
{ {
/// <summary> public static IMpcCommand<IEnumerable<MpdOutput>> Outputs()
/// Turns an output off.
/// </summary>
public class DisableOutput : IMpcCommand<string>
{ {
private readonly int _outputId; return new OutputsCommand();
public DisableOutput(int outputId)
{
_outputId = outputId;
} }
public string Value => string.Join(" ", "disableoutput", _outputId); public static IMpcCommand<string> DisableOutput(int outputId)
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{ {
// Response should be empty. return new DisableOutputCommand(outputId);
return string.Join(", ", response);
}
} }
/// <summary> public static IMpcCommand<string> EnableOutput(int outputId)
/// Turns an output on.
/// </summary>
public class EnableOutput : IMpcCommand<string>
{ {
private readonly int _outputId; return new EnableOutputCommand(outputId);
public EnableOutput(int outputId)
{
_outputId = outputId;
} }
public string Value => string.Join(" ", "enableoutput", _outputId); public static IMpcCommand<string> ToggleOutput(int outputId)
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{ {
// Response should be empty. return new ToggleOutputCommand(outputId);
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,24 @@
using MpcNET.Commands.Playback;
using MpcNET.Types;
namespace MpcNET.Commands
{
public partial class Command
{
/// <summary>
/// https://www.musicpd.org/doc/protocol/playback_commands.html
/// </summary>
public static class Playback
{
public static IMpcCommand<string> Next() => new NextCommand();
public static IMpcCommand<string> Previous() => new PreviousCommand();
public static IMpcCommand<string> PlayPause() => new PlayPauseCommand();
public static IMpcCommand<string> Play(IMpdFile mpdFile) => new PlayCommand(mpdFile);
public static IMpcCommand<string> Stop() => new StopCommand();
}
}
}

View File

@ -1,10 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using MpcNET.Commands.Playlist;
using MpcNET.Types; using MpcNET.Types;
namespace MpcNET namespace MpcNET.Commands
{ {
public static partial class Commands public static partial class Command
{ {
public static class Playlists public static class Playlists
{ {
@ -13,214 +13,45 @@ namespace MpcNET
/// </summary> /// </summary>
public static class Current public static class Current
{ {
/// <summary>
/// Adds the file URI to the playlist (directories add recursively). URI can also be a single file.
/// </summary>
private class AddImpl : IMpcCommand<string>
{
private readonly string _uri;
public AddImpl(string uri)
{
_uri = uri;
}
public string Value => string.Join(" ", "add", $"\"{_uri}\"");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
/// <summary> /// <summary>
/// Command: add /// Command: add
/// </summary> /// </summary>
public static IMpcCommand<string> AddDirectory(string directory) { return new AddImpl(directory); } public static IMpcCommand<string> AddDirectory(string directory) { return new AddCommand(directory); }
/// <summary>
/// Adds a song to the playlist (non-recursive) and returns the song id.
/// </summary>
private class AddIdImpl : IMpcCommand<string>
{
private readonly string _uri;
public AddIdImpl(string uri)
{
_uri = uri;
}
public string Value => string.Join(" ", "addid", $"\"{_uri}\"");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
/// <summary> /// <summary>
/// Command: addid /// Command: addid
/// </summary> /// </summary>
public static IMpcCommand<string> AddSong(string songPath) { return new AddIdImpl(songPath); } public static IMpcCommand<string> AddSong(string songPath) { return new AddIdCommand(songPath); }
/// <summary>
/// Clears the current playlist.
/// </summary>
private class ClearImpl : IMpcCommand<string>
{
public string Value => "clear";
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
/// <summary> /// <summary>
/// Command: clear /// Command: clear
/// </summary> /// </summary>
public static IMpcCommand<string> Clear() { return new ClearImpl(); } public static IMpcCommand<string> Clear() { return new ClearCommand(); }
/// <summary>
/// Displays the current playlist.
/// </summary>
private class PlaylistImpl : IMpcCommand<IEnumerable<IMpdFile>>
{
public string Value => "playlist";
public IEnumerable<IMpdFile> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var results = response.Select(line => new MpdFile(line.Value) { Pos = int.Parse(line.Key) });
return results;
}
}
/// <summary> /// <summary>
/// Command: playlist /// Command: playlist
/// </summary> /// </summary>
public static IMpcCommand<IEnumerable<IMpdFile>> GetAllSongsInfo() { return new PlaylistImpl(); } public static IMpcCommand<IEnumerable<IMpdFile>> GetAllSongsInfo() { return new PlaylistCommand(); }
/// <summary>
/// Deletes a song from the playlist.
/// </summary>
private class DeleteImpl : IMpcCommand<string>
{
private readonly int _position;
public DeleteImpl(int position)
{
_position = position;
}
public string Value => string.Join(" ", "delete", _position);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
/// <summary> /// <summary>
/// Command: delete /// Command: delete
/// </summary> /// </summary>
public static IMpcCommand<string> RemoveSongByPosition(int position) { return new DeleteImpl(position); } public static IMpcCommand<string> RemoveSongByPosition(int position) { return new DeleteCommand(position); }
/// <summary>
/// Deletes the song SONGID from the playlist
/// </summary>
private class DeleteIdImpl : IMpcCommand<string>
{
private readonly int _songId;
public DeleteIdImpl(int songId)
{
_songId = songId;
}
public string Value => string.Join(" ", "deleteid", _songId);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
/// <summary> /// <summary>
/// Command: deleteid /// Command: deleteid
/// </summary> /// </summary>
public static IMpcCommand<string> RemoveSongById(int songId) { return new DeleteIdImpl(songId); } public static IMpcCommand<string> RemoveSongById(int songId) { return new DeleteIdCommand(songId); }
/// <summary>
/// Displays song ID in the playlist.
/// </summary>
private class PlaylistIdImpl : IMpcCommand<IEnumerable<IMpdFile>>
{
private readonly int _songId;
public PlaylistIdImpl(int songId)
{
_songId = songId;
}
public string Value => string.Join(" ", "playlistid", _songId);
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> /// <summary>
/// Command: playlistid /// Command: playlistid
/// </summary> /// </summary>
public static IMpcCommand<IEnumerable<IMpdFile>> GetSongMetadata(int songId) { return new PlaylistIdImpl(songId); } public static IMpcCommand<IEnumerable<IMpdFile>> GetSongMetadata(int songId) { return new PlaylistIdCommand(songId); }
/// <summary>
/// Displays a list of all songs in the playlist,
/// </summary>
private class PlaylistInfoImpl : IMpcCommand<IEnumerable<IMpdFile>>
{
public string Value => "playlistinfo";
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> /// <summary>
/// Command: playlistinfo /// Command: playlistinfo
/// </summary> /// </summary>
public static IMpcCommand<IEnumerable<IMpdFile>> GetAllSongMetadata() { return new PlaylistInfoImpl(); } public static IMpcCommand<IEnumerable<IMpdFile>> GetAllSongMetadata() { return new PlaylistInfoCommand(); }
} }
/// <summary> /// <summary>
@ -228,128 +59,26 @@ namespace MpcNET
/// </summary> /// </summary>
public static class Stored public static class Stored
{ {
/// <summary>
/// Loads the playlist into the current queue.
/// </summary>
private class LoadImpl : IMpcCommand<string>
{
private readonly string _playlistName;
public LoadImpl(string playlistName)
{
_playlistName = playlistName;
}
public string Value => string.Join(" ", "load", $"\"{_playlistName}\"");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
/// <summary> /// <summary>
/// Command: load /// Command: load
/// </summary> /// </summary>
public static IMpcCommand<string> Load(string playlistName) { return new LoadImpl(playlistName); } public static IMpcCommand<string> Load(string playlistName) { return new LoadCommand(playlistName); }
/// <summary>
/// Lists the songs in the playlist.
/// </summary>
private class ListPlaylistImpl : IMpcCommand<IEnumerable<IMpdFilePath>>
{
private readonly string _playlistName;
public ListPlaylistImpl(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;
}
}
/// <summary> /// <summary>
/// Command: listplaylist /// Command: listplaylist
/// </summary> /// </summary>
public static IMpcCommand<IEnumerable<IMpdFilePath>> GetContent(string playlistName) { return new ListPlaylistImpl(playlistName); } public static IMpcCommand<IEnumerable<IMpdFilePath>> GetContent(string playlistName) { return new ListPlaylistCommand(playlistName); }
/// <summary>
/// Lists the songs with metadata in the playlist.
/// </summary>
private class ListPlaylistInfoImpl : IMpcCommand<IEnumerable<IMpdFile>>
{
private readonly string _playlistName;
public ListPlaylistInfoImpl(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> /// <summary>
/// Command: listplaylistinfo /// Command: listplaylistinfo
/// </summary> /// </summary>
public static IMpcCommand<IEnumerable<IMpdFile>> GetContentWithMetadata(string playlistName) { return new ListPlaylistInfoImpl(playlistName); } public static IMpcCommand<IEnumerable<IMpdFile>> GetContentWithMetadata(string playlistName) { return new ListPlaylistInfoCommand(playlistName); }
/// <summary>
/// Prints a list of the playlist directory.
/// </summary>
private class ListPlaylistsImpl : 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;
}
}
/// <summary> /// <summary>
/// Command: listplaylists /// Command: listplaylists
/// </summary> /// </summary>
public static IMpcCommand<IEnumerable<MpdPlaylist>> GetAll() { return new ListPlaylistsImpl(); } public static IMpcCommand<IEnumerable<MpdPlaylist>> GetAll() { return new ListPlaylistsCommand(); }
} }
} }
} }

View File

@ -1,102 +1,34 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using MpcNET.Commands.Reflection;
using MpcNET.Types; using MpcNET.Types;
namespace MpcNET namespace MpcNET.Commands
{ {
public static partial class Commands public static partial class Command
{ {
/// <summary> /// <summary>
/// https://www.musicpd.org/doc/protocol/reflection_commands.html /// https://www.musicpd.org/doc/protocol/reflection_commands.html
/// </summary> /// </summary>
public static class Reflection public static class Reflection
{ {
// config : This command is only permitted to "local" clients (connected via UNIX domain socket). public static IMpcCommand<IEnumerable<string>> Commands()
/// <summary>
/// Shows which commands the current user has access to.
/// </summary>
public class Commands : IMpcCommand<IEnumerable<string>>
{ {
public string Value => "commands"; return new CommandsCommand();
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. public static IMpcCommand<IEnumerable<string>> TagTypes()
/// <summary>
/// Shows a list of available song metadata.
/// </summary>
public class TagTypes : IMpcCommand<IEnumerable<string>>
{ {
public string Value => "tagtypes"; return new TagTypesCommand();
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> public static IMpcCommand<IEnumerable<string>> UrlHandlers()
/// Gets a list of available URL handlers.
/// </summary>
public class UrlHandlers : IMpcCommand<IEnumerable<string>>
{ {
public string Value => "urlhandlers"; return new UrlHandlersCommand();
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> public static IMpcCommand<IEnumerable<MpdDecoderPlugin>> Decoders()
/// 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"; return new DecodersCommand();
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,24 @@
using MpcNET.Commands.Status;
using MpcNET.Types;
namespace MpcNET.Commands
{
public static partial class Command
{
/// <summary>
/// https://www.musicpd.org/doc/protocol/command_reference.html#status_commands
/// </summary>
public static class Status
{
public static IMpcCommand<MpdStatus> GetStatus()
{
return new StatusCommand();
}
public static IMpcCommand<IMpdFile> GetCurrentSong()
{
return new CurrentSongCommand();
}
}
}
}

View File

@ -0,0 +1,30 @@
using System.Collections.Generic;
using MpcNET.Tags;
using MpcNET.Types;
namespace MpcNET.Commands.Database
{
/// <summary>
/// Finds songs in the database that is exactly "searchText".
/// </summary>
internal class FindCommand : IMpcCommand<IEnumerable<IMpdFile>>
{
private readonly ITag _tag;
private readonly string _searchText;
public FindCommand(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)
{
return MpdFile.CreateList(response);
}
}
// TODO: rescan
}

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using MpcNET.Types;
namespace MpcNET.Commands.Database
{
/// <summary>
/// Lists all songs and directories in URI.
/// </summary>
internal class ListAllCommand : 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: findadd
// TODO: rescan
}

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using MpcNET.Tags;
namespace MpcNET.Commands.Database
{
internal class ListCommand : IMpcCommand<string>
{
private readonly ITag _tag;
public ListCommand(ITag tag)
{
_tag = tag;
}
public string Value => string.Join(" ", "list", _tag);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
// TODO:
return response.ToString();
}
}
// TODO: rescan
}

View File

@ -0,0 +1,54 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Database
{
// TODO: listallinfo
// TODO: listfiles
// TODO: lsinfo
// TODO: readcomments
// TODO: search
// TODO: searchadd
// TODO: searchaddpl
public class UpdateCommand : IMpcCommand<string>
{
private readonly string uri;
public UpdateCommand(string uri)
{
this.uri = uri;
}
// TODO: Extend command: < update [URI] >
public string Value
{
get
{
if (string.IsNullOrEmpty(this.uri))
{
return "update";
}
var newUri = this.uri;
if (this.uri.StartsWith(@""""))
{
newUri = @"""" + this.uri;
}
if (this.uri.EndsWith(@""""))
{
newUri = this.uri + @"""";
}
return string.Join(" ", "update", newUri);
}
}
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
// TODO: rescan
}

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Output
{
/// <summary>
/// Turns an output off.
/// </summary>
public class DisableOutputCommand : IMpcCommand<string>
{
private readonly int outputId;
public DisableOutputCommand(int outputId)
{
this.outputId = outputId;
}
public string Value => string.Join(" ", "disableoutput", this.outputId);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
// Response should be empty.
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Output
{
/// <summary>
/// Turns an output on.
/// </summary>
internal class EnableOutputCommand : IMpcCommand<string>
{
private readonly int _outputId;
public EnableOutputCommand(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);
}
}
}

View File

@ -0,0 +1,29 @@
using System.Collections.Generic;
using MpcNET.Types;
namespace MpcNET.Commands.Output
{
/// <summary>
/// Shows information about all outputs.
/// </summary>
public class OutputsCommand : 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,24 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Output
{
/// <summary>
/// Turns an output on or off, depending on the current state.
/// </summary>
internal class ToggleOutputCommand : IMpcCommand<string>
{
private readonly int outputId;
public ToggleOutputCommand(int outputId)
{
this.outputId = outputId;
}
public string Value => string.Join(" ", "toggleoutput", outputId);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playback
{
internal class NextCommand : IMpcCommand<string>
{
public string Value => string.Join(" ", "next");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,33 @@
using System.Collections.Generic;
using MpcNET.Types;
namespace MpcNET.Commands.Playback
{
internal class PlayCommand : IMpcCommand<string>
{
private readonly IMpdFile mpdFile;
public PlayCommand(IMpdFile mpdFile)
{
this.mpdFile = mpdFile;
}
public string Value
{
get
{
if (this.mpdFile.HasId)
{
return string.Join(" ", "playid", this.mpdFile.Id);
}
return string.Join(" ", "play", this.mpdFile.Pos);
}
}
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playback
{
internal class PlayPauseCommand : IMpcCommand<string>
{
public string Value => string.Join(" ", "pause");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playback
{
internal class PreviousCommand : IMpcCommand<string>
{
public string Value => string.Join(" ", "previous");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playback
{
internal class StopCommand : IMpcCommand<string>
{
public string Value => string.Join(" ", "stop");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Adds the file URI to the playlist (directories add recursively). URI can also be a single file.
/// </summary>
internal class AddCommand : IMpcCommand<string>
{
private readonly string _uri;
public AddCommand(string uri)
{
_uri = uri;
}
public string Value => string.Join(" ", "add", $"\"{_uri}\"");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Adds a song to the playlist (non-recursive) and returns the song id.
/// </summary>
internal class AddIdCommand : IMpcCommand<string>
{
private readonly string _uri;
public AddIdCommand(string uri)
{
_uri = uri;
}
public string Value => string.Join(" ", "addid", $"\"{_uri}\"");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Clears the current playlist.
/// </summary>
internal class ClearCommand : IMpcCommand<string>
{
public string Value => "clear";
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Deletes a song from the playlist.
/// </summary>
internal class DeleteCommand : IMpcCommand<string>
{
private readonly int position;
public DeleteCommand(int position)
{
this.position = position;
}
public string Value => string.Join(" ", "delete", this.position);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Deletes the song SONGID from the playlist
/// </summary>
internal class DeleteIdCommand : IMpcCommand<string>
{
private readonly int songId;
public DeleteIdCommand(int songId)
{
this.songId = songId;
}
public string Value => string.Join(" ", "deleteid", this.songId);
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Linq;
using MpcNET.Types;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Lists the songs in the playlist.
/// </summary>
internal class ListPlaylistCommand : IMpcCommand<IEnumerable<IMpdFilePath>>
{
private readonly string _playlistName;
public ListPlaylistCommand(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;
}
}
}

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using MpcNET.Types;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Lists the songs with metadata in the playlist.
/// </summary>
internal class ListPlaylistInfoCommand : IMpcCommand<IEnumerable<IMpdFile>>
{
private readonly string _playlistName;
public ListPlaylistInfoCommand(string playlistName)
{
_playlistName = playlistName;
}
public string Value => string.Join(" ", "listplaylistinfo", $"\"{_playlistName}\"");
public IEnumerable<IMpdFile> FormatResponse(IList<KeyValuePair<string, string>> response)
{
return MpdFile.CreateList(response);
}
}
}

View File

@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Linq;
using MpcNET.Types;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Prints a list of the playlist directory.
/// </summary>
internal class ListPlaylistsCommand : 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,24 @@
using System.Collections.Generic;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Loads the playlist into the current queue.
/// </summary>
internal class LoadCommand : IMpcCommand<string>
{
private readonly string _playlistName;
public LoadCommand(string playlistName)
{
_playlistName = playlistName;
}
public string Value => string.Join(" ", "load", $"\"{_playlistName}\"");
public string FormatResponse(IList<KeyValuePair<string, string>> response)
{
return string.Join(", ", response);
}
}
}

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Linq;
using MpcNET.Types;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Displays the current playlist.
/// </summary>
internal class PlaylistCommand : IMpcCommand<IEnumerable<IMpdFile>>
{
public string Value => "playlist";
public IEnumerable<IMpdFile> FormatResponse(IList<KeyValuePair<string, string>> response)
{
var results = response.Select(line => MpdFile.Create(line.Value, int.Parse(line.Key)));
return results;
}
}
}

View File

@ -0,0 +1,25 @@
using System.Collections.Generic;
using MpcNET.Types;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Displays song ID in the playlist.
/// </summary>
internal class PlaylistIdCommand : IMpcCommand<IEnumerable<IMpdFile>>
{
private readonly int _songId;
public PlaylistIdCommand(int songId)
{
_songId = songId;
}
public string Value => string.Join((string) " ", new[] {"playlistid"}, _songId);
public IEnumerable<IMpdFile> FormatResponse(IList<KeyValuePair<string, string>> response)
{
return MpdFile.CreateList(response);
}
}
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
using MpcNET.Types;
namespace MpcNET.Commands.Playlist
{
/// <summary>
/// Displays a list of all songs in the playlist,
/// </summary>
internal class PlaylistInfoCommand : IMpcCommand<IEnumerable<IMpdFile>>
{
public string Value => "playlistinfo";
public IEnumerable<IMpdFile> FormatResponse(IList<KeyValuePair<string, string>> response)
{
return MpdFile.CreateList(response);
}
}
}

View File

@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Linq;
namespace MpcNET.Commands.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 CommandsCommand : 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;
}
}
}

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using MpcNET.Types;
namespace MpcNET.Commands.Reflection
{
/// <summary>
/// Print a list of decoder plugins, followed by their supported suffixes and MIME types.
/// </summary>
public class DecodersCommand : 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,22 @@
using System.Collections.Generic;
using System.Linq;
namespace MpcNET.Commands.Reflection
{
// TODO: notcommands : Shows which commands the current user does not have access to.
/// <summary>
/// Shows a list of available song metadata.
/// </summary>
public class TagTypesCommand : 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;
}
}
}

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
namespace MpcNET.Commands.Reflection
{
/// <summary>
/// Gets a list of available URL handlers.
/// </summary>
public class UrlHandlersCommand : 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;
}
}
}

View File

@ -0,0 +1,15 @@
using System.Collections.Generic;
using MpcNET.Types;
namespace MpcNET.Commands.Status
{
internal class CurrentSongCommand : IMpcCommand<IMpdFile>
{
public string Value => "currentsong";
public IMpdFile FormatResponse(IList<KeyValuePair<string, string>> response)
{
return MpdFile.Create(response, 0).mpdFile;
}
}
}

View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace MpcNET.Commands.Status
{
internal class StatusCommand : IMpcCommand<MpdStatus>
{
public string Value => "status";
public MpdStatus FormatResponse(IList<KeyValuePair<string, string>> response)
{
int volume = -1;
bool repeat = false;
bool random = false;
bool single = false;
bool consume = false;
int playlist = -1;
int playlistLength = 0;
int playlistSong = -1;
int playlistSongId = -1;
int playlistNextSong = -1;
int playlistNextSongId = -1;
int bitrate = 0;
int audioSampleRate = -1;
int audioBits = -1;
int audioChannels = -1;
int crossfade = -1;
MpdState mpdState = MpdState.Unknown;
TimeSpan elapsed;
TimeSpan duration;
int updatingDb = -1;
string error = string.Empty;
foreach (var keyValuePair in response)
{
var value = keyValuePair.Value;
switch (keyValuePair.Key)
{
case "volume":
int.TryParse(value, out volume);
break;
case "repeat":
repeat = "1" == value;
break;
case "random":
random = "1" == value;
break;
case "single":
single = "1" == value;
break;
case "consume":
consume = "1" == value;
break;
case "playlist":
int.TryParse(value, out playlist);
break;
case "playlistlength":
int.TryParse(value, out playlistLength);
break;
case "song":
int.TryParse(value, out playlistSong);
break;
case "songid":
int.TryParse(value, out playlistSongId);
break;
case "nextsong":
int.TryParse(value, out playlistNextSong);
break;
case "nextsongid":
int.TryParse(value, out playlistNextSongId);
break;
case "bitrate":
int.TryParse(value, out bitrate);
break;
case "audio":
var audioFormat = value.Split(':');
int.TryParse(audioFormat[0], out audioSampleRate);
int.TryParse(audioFormat[1], out audioBits);
int.TryParse(audioFormat[2], out audioChannels);
break;
case "xfade":
int.TryParse(value, out crossfade);
break;
case "state":
Enum.TryParse(value, true, out mpdState);
break;
case "elapsed":
elapsed = ParseTime(value);
break;
case "duration":
duration = ParseTime(value);
break;
case "updating_db":
int.TryParse(value, out updatingDb);
break;
default:
Debug.WriteLine($"Unprocessed status: {keyValuePair.Key} - {keyValuePair.Value}");
break;
}
}
return new MpdStatus(
volume,
repeat,
random,
playlist,
playlistLength,
crossfade,
mpdState,
playlistSong,
playlistSongId,
playlistNextSong,
playlistNextSongId,
elapsed,
duration,
bitrate,
audioSampleRate,
audioBits,
audioChannels,
updatingDb,
error);
}
private static TimeSpan ParseTime(string value)
{
var timeParts = value.Split(new[] { '.' }, 2);
int.TryParse(timeParts[0], out var seconds);
int.TryParse(timeParts[1], out var milliseconds);
return TimeSpan.FromSeconds(seconds) + TimeSpan.FromMilliseconds(milliseconds);
}
}
}

View File

@ -0,0 +1,10 @@
namespace MpcNET.Message
{
public interface IMpdMessage<T>
{
IMpdRequest<T> Request { get; }
IMpdResponse<T> Response { get; }
bool IsResponseValid { get; }
}
}

View File

@ -6,12 +6,6 @@ using Newtonsoft.Json;
namespace MpcNET.Message namespace MpcNET.Message
{ {
public interface IMpdMessage<T>
{
IMpdRequest<T> Request { get; }
IMpdResponse<T> Response { get; }
}
[DebuggerDisplay("Request: {Request.Command.Value} | Response Status: {Response.State.Status}")] [DebuggerDisplay("Request: {Request.Command.Value} | Response Status: {Response.State.Status}")]
public class MpdMessage<T> : IMpdMessage<T> public class MpdMessage<T> : IMpdMessage<T>
{ {
@ -30,8 +24,11 @@ namespace MpcNET.Message
} }
public IMpdRequest<T> Request { get; } public IMpdRequest<T> Request { get; }
public IMpdResponse<T> Response { get; } public IMpdResponse<T> Response { get; }
public bool IsResponseValid => this.Response.State.Status == "OK";
private IList<KeyValuePair<string, string>> GetValuesFromResponse() private IList<KeyValuePair<string, string>> GetValuesFromResponse()
{ {
var result = new List<KeyValuePair<string, string>>(); var result = new List<KeyValuePair<string, string>>();

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MpcNET.Message; using MpcNET.Message;
using MpcNET.Utils; using MpcNET.Utils;
@ -17,18 +18,20 @@ namespace MpcNET
/// </summary> /// </summary>
public class MpcConnection public class MpcConnection
{ {
private readonly Encoding encoding = new UTF8Encoding();
private readonly IPEndPoint _server; private readonly IPEndPoint _server;
private TcpClient _tcpClient; private TcpClient _tcpClient;
private NetworkStream _networkStream; private NetworkStream _networkStream;
private StreamReader _reader;
private StreamWriter _writer;
private string _version; private string _version;
public MpcConnection(IPEndPoint server) public MpcConnection(IPEndPoint server)
{ {
if (IsConnected) return; if (this.IsConnected)
{
return;
}
ClearConnectionFields(); ClearConnectionFields();
_server = server; _server = server;
@ -52,17 +55,18 @@ namespace MpcNET
_networkStream = _tcpClient.GetStream(); _networkStream = _tcpClient.GetStream();
// Encoding UTF8 has some problems with TcpClient: https://bugs.musicpd.org/view.php?id=4501 // Encoding UTF8 has some problems with TcpClient: https://bugs.musicpd.org/view.php?id=4501
_reader = new StreamReader(_networkStream); using (var reader = new StreamReader(_networkStream, this.encoding, true, 512, true))
_writer = new StreamWriter(_networkStream) { NewLine = "\n" }; {
var firstLine = await reader.ReadLineAsync();
var firstLine = _reader.ReadLine();
if (!firstLine.StartsWith(Constants.FirstLinePrefix)) if (!firstLine.StartsWith(Constants.FirstLinePrefix))
{ {
await DisconnectAsync(); await DisconnectAsync();
throw new InvalidDataException("Response of mpd does not start with \"" + Constants.FirstLinePrefix + "\"." ); throw new InvalidDataException("Response of mpd does not start with \"" + Constants.FirstLinePrefix + "\".");
} }
_version = firstLine.Substring(Constants.FirstLinePrefix.Length); _version = firstLine.Substring(Constants.FirstLinePrefix.Length);
} }
}
public Task DisconnectAsync() public Task DisconnectAsync()
{ {
@ -87,14 +91,28 @@ namespace MpcNET
try try
{ {
_writer.WriteLine(command.Value); Console.WriteLine("Command: " + command.Value);
_writer.Flush(); // Encoding UTF8 has some problems with TcpClient: https://bugs.musicpd.org/view.php?id=4501
using (var writer = new StreamWriter(_networkStream, this.encoding, 512, true) { NewLine = "\n" })
{
writer.WriteLine(command.Value);
writer.Flush();
}
response = await ReadResponseAsync(); response = await ReadResponseAsync();
} }
catch (Exception exception)
{
Console.WriteLine("Exception:" + exception);
try
{
await DisconnectAsync();
}
catch (Exception) catch (Exception)
{ {
try { await DisconnectAsync(); } catch (Exception) { } }
return null; // TODO: Create Null Object for MpdResponse return null; // TODO: Create Null Object for MpdResponse
} }
@ -116,20 +134,28 @@ namespace MpcNET
var response = new List<string>(); var response = new List<string>();
// Read response untli reach end token (OK or ACK) // Read response untli reach end token (OK or ACK)
using (var reader = new StreamReader(_networkStream, this.encoding, true, 512, true))
{
string responseLine; string responseLine;
do do
{ {
responseLine = await _reader.ReadLineAsync(); responseLine = await reader.ReadLineAsync();
Console.WriteLine("ReadLine:|" + responseLine + "|");
if (responseLine == null)
{
Console.WriteLine("Received null");
responseLine = string.Empty;
continue;
}
response.Add(responseLine); response.Add(responseLine);
} while (!(responseLine.Equals(Constants.Ok) || responseLine.StartsWith(Constants.Ack))); } while (!(responseLine.Equals(Constants.Ok) || responseLine.StartsWith(Constants.Ack)));
}
return response.Where(line => !string.IsNullOrEmpty(line)).ToArray(); return response.Where(line => !string.IsNullOrEmpty(line)).ToArray();
} }
private void ClearConnectionFields() private void ClearConnectionFields()
{ {
_writer?.Dispose();
_reader?.Dispose();
_networkStream?.Dispose(); _networkStream?.Dispose();
_tcpClient?.Dispose(); _tcpClient?.Dispose();
_version = string.Empty; _version = string.Empty;

View File

@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>MpcNET</AssemblyName> <AssemblyName>MpcNET</AssemblyName>
<PackageId>MpcNET</PackageId> <PackageId>MpcNET</PackageId>
<NetStandardImplicitPackageVersion>1.6.0</NetStandardImplicitPackageVersion>
<PackageTargetFallback>$(PackageTargetFallback);dnxcore50</PackageTargetFallback> <PackageTargetFallback>$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
@ -17,7 +16,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" /> <None Include="Commands\Commands.Status.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

25
src/MpcNET/MpdState.cs Normal file
View File

@ -0,0 +1,25 @@
namespace MpcNET
{
/// <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
}
}

View File

@ -1,119 +1,13 @@
using System.Text; using System;
using System.Text;
namespace MpcNET namespace MpcNET
{ {
/// <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> /// <summary>
/// The MpdStatus class contains all values describing the current status of the MPD. /// The MpdStatus class contains all values describing the current status of the MPD.
/// </summary> /// </summary>
public class MpdStatus 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> /// <summary>
/// Creates a new MpdStatus object. /// Creates a new MpdStatus object.
/// </summary> /// </summary>
@ -126,8 +20,10 @@ namespace MpcNET
/// <param name="state">The state of the MPD.</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="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="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="nextSong">The next song.</param>
/// <param name="timeTotal">The length of the current song in seconds.</param> /// <param name="nextSongId">The next song identifier.</param>
/// <param name="elapsed">The elapsed.</param>
/// <param name="duration">The duration.</param>
/// <param name="bitrate">The bitrate of the current song.</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="audioSampleRate">The audio sample rate of the current song.</param>
/// <param name="audioBits">The audio bits of the current song.</param> /// <param name="audioBits">The audio bits of the current song.</param>
@ -144,34 +40,126 @@ namespace MpcNET
MpdState state, MpdState state,
int song, int song,
int songId, int songId,
int timeElapsed, int nextSong,
int timeTotal, int nextSongId,
TimeSpan elapsed,
TimeSpan duration,
int bitrate, int bitrate,
int audioSampleRate, int audioSampleRate,
int audioBits, int audioBits,
int audioChannels, int audioChannels,
int updatingDb, int updatingDb,
string error string error)
)
{ {
this.volume = volume; this.Volume = volume;
this.repeat = repeat; this.Repeat = repeat;
this.random = random; this.Random = random;
this.playlist = playlist; this.Playlist = playlist;
this.playlistLength = playlistLength; this.PlaylistLength = playlistLength;
this.xFade = xFade; this.XFade = xFade;
this.state = state; this.State = state;
this.song = song; this.Song = song;
this.songId = songId; this.SongId = songId;
this.timeElapsed = timeElapsed; NextSong = nextSong;
this.timeTotal = timeTotal; NextSongId = nextSongId;
this.bitrate = bitrate; this.Elapsed = elapsed;
this.audioSampleRate = audioSampleRate; this.Duration = duration;
this.audioBits = audioBits; this.Bitrate = bitrate;
this.audioChannels = audioChannels; this.AudioSampleRate = audioSampleRate;
this.updatingDb = updatingDb; this.AudioBits = audioBits;
this.error = error; this.AudioChannels = audioChannels;
this.UpdatingDb = updatingDb;
this.Error = error;
} }
/// <summary>
/// The current volume of the output.
/// </summary>
public int Volume { get; }
/// <summary>
/// If the playlist is repeated after finish.
/// </summary>
public bool Repeat { get; }
/// <summary>
/// If the playlist is played in random order.
/// </summary>
public bool Random { get; }
/// <summary>
/// The version number of the playlist.
/// </summary>
public int Playlist { get; }
/// <summary>
/// The length of the playlist.
/// </summary>
public int PlaylistLength { get; }
/// <summary>
/// The number of seconds crossfaded between song changes.
/// </summary>
public int XFade { get; }
/// <summary>
/// The state of the MPD.
/// </summary>
public MpdState State { get; }
/// <summary>
/// The index of the currently played song in the playlist.
/// </summary>
public int Song { get; }
/// <summary>
/// The id of the song currently played.
/// </summary>
public int SongId { get; }
public int NextSong { get; }
public int NextSongId { get; }
/// <summary>
/// The number of seconds already played of the current song.
/// </summary>
public TimeSpan Elapsed { get; }
/// <summary>
/// The length of the current song in seconds.
/// </summary>
public TimeSpan Duration { get; }
/// <summary>
/// The bitrate of the current song.
/// </summary>
public int Bitrate { get; }
/// <summary>
/// The audio sample rate of the current song.
/// </summary>
public int AudioSampleRate { get; }
/// <summary>
/// The audio bits of the current song.
/// </summary>
public int AudioBits { get; }
/// <summary>
/// The number of audio channels of the current song.
/// </summary>
public int AudioChannels { get; }
/// <summary>
/// The number of the update on the MPD database currently running.
/// </summary>
public int UpdatingDb { get; }
/// <summary>
/// An error message, if there is an error.
/// </summary>
public string Error { get; }
/// <summary> /// <summary>
/// Returns a string representation of the object maily for debugging purpuses. /// Returns a string representation of the object maily for debugging purpuses.
/// </summary> /// </summary>
@ -180,13 +168,13 @@ namespace MpcNET
{ {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
appendInt(builder, "volume", this.volume); AppendInt(builder, "volume", this.Volume);
appendBool(builder, "repeat", this.repeat); AppendBool(builder, "repeat", this.Repeat);
appendBool(builder, "random", this.random); AppendBool(builder, "random", this.Random);
appendInt(builder, "playlist", this.playlist); AppendInt(builder, "playlist", this.Playlist);
appendInt(builder, "playlistlength", this.playlistLength); AppendInt(builder, "playlistlength", this.PlaylistLength);
appendInt(builder, "xfade", this.xFade); AppendInt(builder, "xfade", this.XFade);
switch (this.state) switch (this.State)
{ {
case MpdState.Play: case MpdState.Play:
builder.AppendLine("state: play"); builder.AppendLine("state: play");
@ -198,38 +186,39 @@ namespace MpcNET
builder.AppendLine("state: stop"); builder.AppendLine("state: stop");
break; break;
} }
appendInt(builder, "song", this.song);
appendInt(builder, "songid", this.songId); AppendInt(builder, "song", this.Song);
if ((this.timeElapsed >= 0) || (this.timeTotal >= 0)) AppendInt(builder, "songid", this.SongId);
if (this.Elapsed > TimeSpan.Zero || this.Duration > TimeSpan.Zero)
{ {
builder.Append("time: "); builder.Append("time: ");
builder.Append(this.timeElapsed); builder.Append(this.Elapsed);
builder.Append(":"); builder.Append(":");
builder.Append(this.timeTotal); builder.Append(this.Duration);
builder.AppendLine(); builder.AppendLine();
} }
appendInt(builder, "bitrate", this.bitrate); AppendInt(builder, "bitrate", this.Bitrate);
if ((this.audioSampleRate >= 0) || (this.audioBits >= 0) || (this.audioChannels >= 0)) if ((AudioSampleRate >= 0) || (this.AudioBits >= 0) || (this.AudioChannels >= 0))
{ {
builder.Append("audio: "); builder.Append("audio: ");
builder.Append(this.audioSampleRate); builder.Append(this.AudioSampleRate);
builder.Append(":"); builder.Append(":");
builder.Append(this.audioBits); builder.Append(this.AudioBits);
builder.Append(":"); builder.Append(":");
builder.Append(this.audioChannels); builder.Append(this.AudioChannels);
builder.AppendLine(); builder.AppendLine();
} }
appendInt(builder, "updating_db", this.updatingDb); AppendInt(builder, "updating_db", this.UpdatingDb);
if (this.error != null) if (this.Error != null)
{ {
builder.Append("error: "); builder.Append("error: ");
builder.AppendLine(this.error); builder.AppendLine(this.Error);
} }
return builder.ToString(); return builder.ToString();
} }
private static void appendInt(StringBuilder builder, string name, int value) private static void AppendInt(StringBuilder builder, string name, int value)
{ {
if (value < 0) if (value < 0)
return; return;
@ -240,7 +229,7 @@ namespace MpcNET
builder.AppendLine(); builder.AppendLine();
} }
private static void appendBool(StringBuilder builder, string name, bool value) private static void AppendBool(StringBuilder builder, string name, bool value)
{ {
builder.Append(name); builder.Append(name);
builder.Append(": "); builder.Append(": ");

View File

@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following

View File

@ -2,11 +2,6 @@
namespace MpcNET.Types namespace MpcNET.Types
{ {
public interface IMpdFilePath
{
string Path { get; }
}
public interface IMpdFile : IMpdFilePath public interface IMpdFile : IMpdFilePath
{ {
int Time { get; } int Time { get; }
@ -23,6 +18,76 @@ namespace MpcNET.Types
int Disc { get; } int Disc { get; }
int Pos { get; } int Pos { get; }
int Id { get; } int Id { get; }
IDictionary<string, string> UnknownMetadata { get; } IReadOnlyDictionary<string, string> UnknownMetadata { get; }
/// <summary>
/// If the MpdFile has the <see cref="Time"/> property set.
/// </summary>
bool HasTime { get; }
/// <summary>
/// If the MpdFile has the <see cref="Album"/> property set.
/// </summary>
bool HasAlbum { get; }
/// <summary>
/// If the MpdFile has the <see cref="Artist"/> property set.
/// </summary>
bool HasArtist { get; }
/// <summary>
/// If the MpdFile has the <see cref="Title"/> property set.
/// </summary>
bool HasTitle { get; }
/// <summary>
/// If the MpdFile has the <see cref="Track"/> property set.
/// </summary>
bool HasTrack { get; }
/// <summary>
/// If the MpdFile has the <see cref="Name"/> property set.
/// </summary>
bool HasName { get; }
/// <summary>
/// If the MpdFile has the <see cref="Genre"/> property set.
/// </summary>
bool HasGenre { get; }
/// <summary>
/// If the MpdFile has the <see cref="Date"/> property set.
/// </summary>
bool HasDate { get; }
/// <summary>
/// If the MpdFile has the <see cref="Composer"/> property set.
/// </summary>
bool HasComposer { get; }
/// <summary>
/// If the MpdFile has the <see cref="Performer"/> property set.
/// </summary>
bool HasPerformer { get; }
/// <summary>
/// If the MpdFile has the <see cref="Comment"/> property set.
/// </summary>
bool HasComment { get; }
/// <summary>
/// If the MpdFile has the <see cref="Disc"/> property set.
/// </summary>
bool HasDisc { get; }
/// <summary>
/// If the MpdFile has the <see cref="Pos"/> property set.
/// </summary>
bool HasPos { get; }
/// <summary>
/// If the MpdFile has the <see cref="Id"/> property set.
/// </summary>
bool HasId { get; }
} }
} }

View File

@ -0,0 +1,7 @@
namespace MpcNET.Types
{
public interface IMpdFilePath
{
string Path { get; }
}
}

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using MpcNET.Utils;
namespace MpcNET.Types namespace MpcNET.Types
{ {
@ -8,97 +7,324 @@ namespace MpcNET.Types
/// </summary> /// </summary>
internal class MpdFile : IMpdFile internal class MpdFile : IMpdFile
{ {
private const string TagTime = "Time"; internal const string TagFile = "file";
private const string TagArtist = "Artist"; internal const string TagTime = "Time";
private const string TagAlbum = "Album"; internal const string TagArtist = "Artist";
private const string TagTitle = "Title"; internal const string TagAlbum = "Album";
private const string TagTrack = "Track"; internal const string TagTitle = "Title";
private const string TagName = "Name"; internal const string TagTrack = "Track";
private const string TagGenre = "Genre"; internal const string TagName = "Name";
private const string TagDate = "Date"; internal const string TagGenre = "Genre";
private const string TagComposer = "Composer"; internal const string TagDate = "Date";
private const string TagPerformer = "Performer"; internal const string TagComposer = "Composer";
private const string TagComment = "Comment"; internal const string TagPerformer = "Performer";
private const string TagDisc = "Disc"; internal const string TagComment = "Comment";
private const string TagPos = "Pos"; internal const string TagDisc = "Disc";
private const string TagId = "Id"; internal const string TagPos = "Pos";
internal const string TagId = "Id";
private readonly IDictionary<string, string> _unknownMetadata = new Dictionary<string, string>(); internal const int NoTime = -1;
internal const string NoAlbum = null;
internal const string NoArtist = null;
internal const string NoTitle = null;
internal const string NoTrack = null;
internal const string NoName = null;
internal const string NoGenre = null;
internal const string NoDate = null;
internal const string NoComposer = null;
internal const string NoPerformer = null;
internal const string NoComment = null;
internal const int NoDisc = -1;
internal const int NoPos = -1;
internal const int NoId = -1;
internal MpdFile(string file) internal MpdFile(
string path,
int time = NoTime,
string album = NoAlbum,
string artist = NoArtist,
string title = NoTitle,
string track = NoTrack,
string name = NoName,
string genre = NoGenre,
string date = NoDate,
string composer = NoComposer,
string performer = NoPerformer,
string comment = NoComment,
int disc = NoDisc,
int pos = NoPos,
int id = NoId,
IReadOnlyDictionary<string, string> unknownMetadata = null)
{ {
file.CheckNotNull(); this.Path = path;
this.Time = time;
Path = file; 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;
this.UnknownMetadata = unknownMetadata ?? new Dictionary<string, string>();
} }
public string Path { get; } public string Path { get; }
public int Time { get; private set; } = -1; public int Time { get; }
public string Album { get; private set; } = string.Empty; public string Album { get; }
public string Artist { get; private set; } = string.Empty; public string Artist { get; }
public string Title { get; private set; } = string.Empty; public string Title { get; }
public string Track { get; private set; } = string.Empty; public string Track { get; }
public string Name { get; private set; } = string.Empty; public string Name { get; }
public string Genre { get; private set; } = string.Empty; public string Genre { get; }
public string Date { get; private set; } = string.Empty; public string Date { get; }
public string Composer { get; private set; } = string.Empty; public string Composer { get; }
public string Performer { get; private set; } = string.Empty; public string Performer { get; }
public string Comment { get; private set; } = string.Empty; public string Comment { get; }
public int Disc { get; private set; } = -1; public int Disc { get; }
public int Pos { get; set; } = -1; public int Pos { get; set; }
public int Id { get; private set; } = -1; public int Id { get; }
public IDictionary<string, string> UnknownMetadata => _unknownMetadata; public IReadOnlyDictionary<string, string> UnknownMetadata { get; }
internal void AddTag(string tag, string value)
/// <summary>
/// If the MpdFile has the <see cref="Time"/> property set.
/// </summary>
public bool HasTime => this.Time != NoTime;
/// <summary>
/// If the MpdFile has the <see cref="Album"/> property set.
/// </summary>
public bool HasAlbum => this.Album != NoAlbum;
/// <summary>
/// If the MpdFile has the <see cref="Artist"/> property set.
/// </summary>
public bool HasArtist => this.Artist != NoArtist;
/// <summary>
/// If the MpdFile has the <see cref="Title"/> property set.
/// </summary>
public bool HasTitle => this.Title != NoTitle;
/// <summary>
/// If the MpdFile has the <see cref="Track"/> property set.
/// </summary>
public bool HasTrack => this.Track != NoTrack;
/// <summary>
/// If the MpdFile has the <see cref="Name"/> property set.
/// </summary>
public bool HasName => this.Name != NoName;
/// <summary>
/// If the MpdFile has the <see cref="Genre"/> property set.
/// </summary>
public bool HasGenre => this.Genre != NoGenre;
/// <summary>
/// If the MpdFile has the <see cref="Date"/> property set.
/// </summary>
public bool HasDate => this.Date != NoDate;
/// <summary>
/// If the MpdFile has the <see cref="Composer"/> property set.
/// </summary>
public bool HasComposer => this.Composer != NoComposer;
/// <summary>
/// If the MpdFile has the <see cref="Performer"/> property set.
/// </summary>
public bool HasPerformer => this.Performer != NoPerformer;
/// <summary>
/// If the MpdFile has the <see cref="Comment"/> property set.
/// </summary>
public bool HasComment => this.Comment != NoComment;
/// <summary>
/// If the MpdFile has the <see cref="Disc"/> property set.
/// </summary>
public bool HasDisc => this.Disc != NoDisc;
/// <summary>
/// If the MpdFile has the <see cref="Pos"/> property set.
/// </summary>
public bool HasPos => this.Pos != NoPos;
/// <summary>
/// If the MpdFile has the <see cref="Id"/> property set.
/// </summary>
public bool HasId => this.Id != NoId;
internal static MpdFile Create(string path, int pos)
{ {
switch (tag) return new MpdFile(path, pos: pos);
}
internal static (IMpdFile mpdFile, int index) Create(IList<KeyValuePair<string, string>> response, int startIndex)
{ {
string file;
if (response.Count <= startIndex)
{
return (null, -1);
}
var fileKeyValuePair = response[startIndex];
if (fileKeyValuePair.Key == "file")
{
file = fileKeyValuePair.Value;
}
else
{
return (null, -1);
}
int time = NoTime;
string album = NoAlbum;
string artist = NoArtist;
string title = NoTitle;
string track = NoTrack;
string name = NoName;
string genre = NoGenre;
string date = NoDate;
string composer = NoComposer;
string performer = NoPerformer;
string comment = NoComment;
int disc = NoDisc;
int pos = NoPos;
int id = NoId;
var unknownMetadata = new Dictionary<string, string>();
for (var index = startIndex + 1; index < response.Count; index++)
{
var line = response[index];
if (line.Key != null)
{
switch (line.Key)
{
case TagFile:
return (new MpdFile(
file,
time,
album,
artist,
title,
track,
name,
genre,
date,
composer,
performer,
comment,
disc,
pos,
id), index - 1);
case TagTime: case TagTime:
Time = int.Parse(value); if (int.TryParse(line.Value, out int tryTime))
break; {
case TagArtist: time = tryTime;
Artist = value; }
break; break;
case TagAlbum: case TagAlbum:
Album = value; album = line.Value;
break;
case TagArtist:
artist = line.Value;
break; break;
case TagTitle: case TagTitle:
Title = value; title = line.Value;
break; break;
case TagTrack: case TagTrack:
Track = value; track = line.Value;
break; break;
case TagName: case TagName:
Name = value; name = line.Value;
break; break;
case TagGenre: case TagGenre:
Genre = value; genre = line.Value;
break; break;
case TagDate: case TagDate:
Date = value; date = line.Value;
break; break;
case TagComposer: case TagComposer:
Composer = value; composer = line.Value;
break; break;
case TagPerformer: case TagPerformer:
Performer = value; performer = line.Value;
break; break;
case TagComment: case TagComment:
Comment = value; comment = line.Value;
break; break;
case TagDisc: case TagDisc:
Disc = int.Parse(value); if (int.TryParse(line.Value, out var tryDisc))
{
disc = tryDisc;
}
break; break;
case TagPos: case TagPos:
Pos = int.Parse(value); if (int.TryParse(line.Value, out var tryPos))
{
pos = tryPos;
}
break; break;
case TagId: case TagId:
Id = int.Parse(value); if (int.TryParse(line.Value, out var tryId))
{
id = tryId;
}
break; break;
default: default:
_unknownMetadata.Add(tag, value); unknownMetadata.Add(line.Key, line.Value);
break; break;
} }
} }
} }
return (new MpdFile(
file,
time,
album,
artist,
title,
track,
name,
genre,
date,
composer,
performer,
comment,
disc,
pos,
id), response.Count - 1);
}
public static IEnumerable<IMpdFile> CreateList(IList<KeyValuePair<string, string>> response)
{
var mpdFiles = new List<IMpdFile>();
for (var index = 0; index < response.Count; index++)
{
var (mpdFile, lastIndex) = Create(response, index);
if (mpdFile != null)
{
mpdFiles.Add(mpdFile);
index = lastIndex;
}
else
{
break;
}
}
return mpdFiles;
}
}
} }