diff --git a/Sources/MpcNET/Commands/Database/FindAndSearchCommands.cs b/Sources/MpcNET/Commands/Database/FindAndSearchCommands.cs index 26abfbb..23a06e3 100644 --- a/Sources/MpcNET/Commands/Database/FindAndSearchCommands.cs +++ b/Sources/MpcNET/Commands/Database/FindAndSearchCommands.cs @@ -7,10 +7,244 @@ namespace MpcNET.Commands.Database { using System.Collections.Generic; + using System.ComponentModel; using System.Linq; using MpcNET.Tags; using MpcNET.Types; + /// + /// Operators available for filters in the protocol + /// + public enum FilterOperator + { + /// + /// Equal (==) + /// + [Description("==")] + Equal, + /// + /// Different (!=) + /// + [Description("!=")] + Different, + /// + /// Contains (contains) + /// + [Description("contains")] + Contains, + /// + /// Mask (=~) + /// + [Description("=~")] + Mask, + /// + /// None ("") + /// + [Description("")] + None, + } + + /// + /// + public static class EnumHelper + { + /// + /// of the enum + /// + /// + public static string GetDescription(this T enumValue) + where T : struct, System.IConvertible + { + if (!typeof(T).IsEnum) + return null; + + string description = enumValue.ToString(); + System.Reflection.FieldInfo fieldInfo = enumValue.GetType().GetField(enumValue.ToString()); + + if (fieldInfo != null) + { + object[] attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), true); + if (attributes != null && attributes.Length > 0) + description = ((DescriptionAttribute)attributes[0]).Description; + } + + return description; + } + } + + /// + /// Filter interface specialized by filters in the protocol + /// + public abstract class IFilter + { + /// + /// Name of the filter + /// + public string Name; + /// + /// Operator of the filter + /// + public FilterOperator Operator; + /// + /// Value to test + /// + public string Value; + /// + /// If the expression is negated + /// + public bool Negation; + + /// + /// Initializes a new instance of the class. + /// + /// Name of the filter + /// Operator of the filter + /// Value to test + /// If the expression is negated + public IFilter(string Name, FilterOperator Operator, string Value, bool Negation = false) + { + this.Name = Name; + this.Operator = Operator; + this.Value = Value; + this.Negation = Negation; + } + + /// + /// Gets the formatted command + /// + public string GetFormattedCommand() + { + string make = ""; + + if (Negation) + make += "(!"; + + make += "(" + Name + " "; + make += Operator.GetDescription(); + make += " " + "\\\"" + Value + "\\\"" + ")"; + + if (Negation) + make += ")"; + + return make; + } + } + + /// + /// Filter "file" + /// + public class FilterFile : IFilter + { + /// + /// Initializes a new instance of the class. + /// + /// Operator of the filter + /// Value to test + /// If the expression is negated + public FilterFile(string Value, FilterOperator Operator, bool Negation = false) : base("file", Operator, Value, Negation) + { + if (Operator != FilterOperator.Equal) + throw new System.ArgumentException("Operator is not compatible: for \"File\" use FilterOperator.Equal."); + + this.Name = "file"; + this.Value = Value; + this.Operator = Operator; + this.Negation = Negation; + } + } + + /// + /// Filter "TAG" + /// + public class FilterTag : IFilter + { + /// + /// Initializes a new instance of the class. + /// + /// The tag. + /// Operator of the filter + /// Value to test + /// If the expression is negated + public FilterTag(ITag Tag, string Value, FilterOperator Operator, bool Negation = false) : base(Tag.Value, Operator, Value, Negation) + { + if (Operator != FilterOperator.Equal && Operator != FilterOperator.Different && Operator != FilterOperator.Contains) + throw new System.ArgumentException("Operator is not compatible: for \"TAG\" use FilterOperator.Equal, FilterOperator.Different or FilterOperator.Contains."); + + this.Value = Value; + this.Operator = Operator; + this.Negation = Negation; + } + } + + /// + /// Filter "base" (not the interface base class, the base command: restrict search songs to a directory) + /// + public class FilterBase : IFilter + { + /// + /// Initializes a new instance of the class. + /// + /// Operator of the filter + /// Value to test + /// If the expression is negated + public FilterBase(string Value, FilterOperator Operator, bool Negation = false) : base("base", Operator, Value, Negation) + { + if (Operator != FilterOperator.None) + throw new System.ArgumentException("Operator is not compatible: for \"base\" use FilterOperator.None."); + + this.Name = "base"; + this.Value = Value; + this.Operator = Operator; + this.Negation = Negation; + } + } + + /// + /// Filter "modified-since" + /// + public class FilterModifiedSince : IFilter + { + /// + /// Initializes a new instance of the class. + /// + /// Operator of the filter + /// Value to test + /// If the expression is negated + public FilterModifiedSince(string Value, FilterOperator Operator, bool Negation = false) : base("modified-since", Operator, Value, Negation) + { + if (Operator != FilterOperator.None) + throw new System.ArgumentException("Operator is not compatible: for \"ModifiedSince\" use FilterOperator.None."); + + this.Name = "modified-since"; + this.Value = Value; + this.Operator = Operator; + this.Negation = Negation; + } + } + + /// + /// Filter "AudioFormat" + /// + public class FilterAudioFormat : IFilter + { + /// + /// Initializes a new instance of the class. + /// + /// Operator of the filter + /// Value to test + /// If the expression is negated + public FilterAudioFormat(string Value, FilterOperator Operator, bool Negation = false) : base("AudioFormat", Operator, Value, Negation) + { + if (Operator != FilterOperator.Equal && Operator != FilterOperator.Mask) + throw new System.ArgumentException("Operator is not compatible: for \"AudioFormat\" use FilterOperator.Equal or FilterOperator.Mask."); + + this.Name = "AudioFormat"; + this.Value = Value; + this.Operator = Operator; + this.Negation = Negation; + } + } + /// /// Finds songs in the database that contain "searchText". /// Since MPD 0.21, search syntax is now (TAG == 'VALUE'). @@ -44,6 +278,22 @@ namespace MpcNET.Commands.Database /// End of the portion of the results desired public SearchCommand(List> filters, int windowStart = -1, int windowEnd = -1) : base(filters, windowStart, windowEnd) { } + /// + /// Initializes a new instance of the class. + /// + /// Filter + /// Start of the portion of the results desired + /// End of the portion of the results desired + public SearchCommand(IFilter filter, int windowStart = -1, int windowEnd = -1) : base(filter, windowStart, windowEnd) { } + + /// + /// Initializes a new instance of the class. + /// + /// List of filters + /// Start of the portion of the results desired + /// End of the portion of the results desired + public SearchCommand(List filters, int windowStart = -1, int windowEnd = -1) : base(filters, windowStart, windowEnd) { } + } /// @@ -79,6 +329,21 @@ namespace MpcNET.Commands.Database /// End of the portion of the results desired public SearchAddCommand(List> filters, int windowStart = -1, int windowEnd = -1) : base(filters, windowStart, windowEnd) { } + /// + /// Initializes a new instance of the class. + /// + /// Filter + /// Start of the portion of the results desired + /// End of the portion of the results desired + public SearchAddCommand(IFilter filter, int windowStart = -1, int windowEnd = -1) : base(filter, windowStart, windowEnd) { } + + /// + /// Initializes a new instance of the class. + /// + /// List of filters + /// Start of the portion of the results desired + /// End of the portion of the results desired + public SearchAddCommand(List filters, int windowStart = -1, int windowEnd = -1) : base(filters, windowStart, windowEnd) { } } /// @@ -114,15 +379,31 @@ namespace MpcNET.Commands.Database /// End of the portion of the results desired public FindCommand(List> filters, int windowStart = -1, int windowEnd = -1) : base(filters, windowStart, windowEnd) { } + /// + /// Initializes a new instance of the class. + /// + /// Filter + /// Start of the portion of the results desired + /// End of the portion of the results desired + public FindCommand(IFilter filter, int windowStart = -1, int windowEnd = -1) : base(filter, windowStart, windowEnd) { } + + /// + /// Initializes a new instance of the class. + /// + /// List of filters + /// Start of the portion of the results desired + /// End of the portion of the results desired + public FindCommand(List filters, int windowStart = -1, int windowEnd = -1) : base(filters, windowStart, windowEnd) { } + } - /// /// Base class for find/search commands. /// public abstract class BaseFilterCommand : IMpcCommand> { - private readonly List> filters; + private readonly List> tagFilters; + private readonly List completeFilters; private readonly int _start; private readonly int _end; @@ -145,8 +426,8 @@ namespace MpcNET.Commands.Database /// End of the portion of the results desired public BaseFilterCommand(ITag tag, string searchText, int windowStart = -1, int windowEnd = -1) { - this.filters = new List>(); - this.filters.Add(new KeyValuePair(tag, searchText)); + tagFilters = new List>(); + tagFilters.Add(new KeyValuePair(tag, searchText)); _start = windowStart; _end = windowEnd; @@ -160,7 +441,36 @@ namespace MpcNET.Commands.Database /// End of the portion of the results desired public BaseFilterCommand(List> filters, int windowStart = -1, int windowEnd = -1) { - this.filters = filters; + tagFilters = filters; + + _start = windowStart; + _end = windowEnd; + } + + /// + /// Initializes a new instance of the class. + /// + /// Filter + /// Start of the portion of the results desired + /// End of the portion of the results desired + public BaseFilterCommand(IFilter filters, int windowStart = -1, int windowEnd = -1) + { + completeFilters = new List(); + completeFilters.Add(filters); + + _start = windowStart; + _end = windowEnd; + } + + /// + /// Initializes a new instance of the class. + /// + /// List of filters + /// Start of the portion of the results desired + /// End of the portion of the results desired + public BaseFilterCommand(List filters, int windowStart = -1, int windowEnd = -1) + { + completeFilters = filters; _start = windowStart; _end = windowEnd; @@ -170,14 +480,27 @@ namespace MpcNET.Commands.Database /// Serializes the command. /// /// - /// The serialize command. + /// The serialized command. /// public string Serialize() { - var serializedFilters = string.Join(" AND ", - filters.Select(x => $"({x.Key.Value} {Operand} {escape(x.Value)})") - ); - var cmd = $@"{CommandName} ""({serializedFilters})"""; + string cmd = ""; + + if (tagFilters != null) + { + var serializedFilters = string.Join(" AND ", + tagFilters.Select(x => $"({x.Key.Value} {Operand} {escape(x.Value)})") + ); + cmd = $@"{CommandName} ""({serializedFilters})"""; + } + + if (completeFilters != null) + { + var serializedFilters = string.Join(" AND ", + completeFilters.Select(x => $"{x.GetFormattedCommand()}") + ); + cmd = $@"{CommandName} ""({serializedFilters})"""; + } if (_start > -1) {