493 lines
16 KiB
C#
493 lines
16 KiB
C#
using MpcNET;
|
|
using MpcNET.Commands.Database;
|
|
using MpcNET.Commands.Reflection;
|
|
using MpcNET.Tags;
|
|
using MpcNET.Types;
|
|
using MpcNET.Types.Filters;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Interop;
|
|
using System.Windows.Media;
|
|
using System.Linq;
|
|
using MpcNET.Commands.Queue;
|
|
using System.Windows.Input;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace unison
|
|
{
|
|
public partial class Shuffle : Window
|
|
{
|
|
private MPDHandler _mpd;
|
|
bool _continuous = false;
|
|
List<string> _songList { get; }
|
|
List<string> _genreList { get; }
|
|
List<string> _folderList { get; }
|
|
List<IFilter> _filters { get; }
|
|
|
|
public Shuffle()
|
|
{
|
|
InitializeComponent();
|
|
_songList = new();
|
|
_genreList = new();
|
|
_folderList = new();
|
|
_filters = new();
|
|
SongFilterNumber.Text = "0";
|
|
|
|
_mpd = (MPDHandler)Application.Current.Properties["mpd"];
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
ListGenre();
|
|
ListFolder();
|
|
}
|
|
|
|
public async void ListGenre()
|
|
{
|
|
if (_genreList.Count != 0)
|
|
return;
|
|
|
|
List<string> Response = await _mpd.SafelySendCommandAsync(new ListCommand(MpdTags.Genre, null, null));
|
|
|
|
if (Response == null)
|
|
return;
|
|
|
|
foreach (string genre in Response)
|
|
_genreList.Add(genre);
|
|
}
|
|
|
|
public async void ListFolder()
|
|
{
|
|
if (_folderList.Count != 0)
|
|
return;
|
|
|
|
IEnumerable<IMpdFilePath> Response = await _mpd.SafelySendCommandAsync(new LsInfoCommand(""));
|
|
|
|
if (Response == null)
|
|
return;
|
|
|
|
foreach (IMpdFilePath folder in Response)
|
|
_folderList.Add(folder.Name);
|
|
}
|
|
|
|
private bool IsFilterEmpty()
|
|
{
|
|
if (_filters.Count() == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
private void Window_Closing(object sender, CancelEventArgs e)
|
|
{
|
|
e.Cancel = true;
|
|
WindowState = WindowState.Minimized;
|
|
Hide();
|
|
}
|
|
|
|
public void InitHwnd()
|
|
{
|
|
WindowInteropHelper helper = new(this);
|
|
helper.EnsureHandle();
|
|
}
|
|
|
|
private bool IsOnMainThread()
|
|
{
|
|
return App.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread;
|
|
}
|
|
|
|
private T FindParent<T>(DependencyObject child) where T : DependencyObject
|
|
{
|
|
var parent = VisualTreeHelper.GetParent(child);
|
|
|
|
if (parent == null)
|
|
return null;
|
|
|
|
if (parent is T)
|
|
return parent as T;
|
|
else
|
|
return FindParent<T>(parent);
|
|
}
|
|
|
|
|
|
|
|
private void AddFilter_Clicked(object sender, RoutedEventArgs e)
|
|
{
|
|
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
|
|
SongFilterNumber.Text = "0";
|
|
}
|
|
|
|
private void RemoveFilter_Clicked(object sender, RoutedEventArgs e)
|
|
{
|
|
if (FilterPanel.Children.Count > 1)
|
|
FilterPanel.Children.Remove(FindParent<ContentPresenter>(sender as Button));
|
|
else
|
|
Reset_Clicked(null, null);
|
|
SongFilterNumber.Text = "0";
|
|
}
|
|
|
|
private void Reset_Clicked(object sender, RoutedEventArgs e)
|
|
{
|
|
FilterPanel.Children.RemoveRange(0, FilterPanel.Children.Count);
|
|
FilterPanel.Children.Add(new ContentPresenter { ContentTemplate = (DataTemplate)FindResource("FilterPanel") });
|
|
SongFilterNumber.Text = "0";
|
|
}
|
|
|
|
private ITag FilterEquivalence_Type(string value)
|
|
{
|
|
if (value == "Song")
|
|
return MpdTags.Title;
|
|
else if (value == "Artist")
|
|
return MpdTags.Artist;
|
|
else if (value == "Album")
|
|
return MpdTags.Album;
|
|
else if (value == "Year")
|
|
return MpdTags.Date;
|
|
else if (value == "Genre")
|
|
return MpdTags.Genre;
|
|
return MpdTags.Title;
|
|
}
|
|
|
|
private FilterOperator FilterEquivalence_Operator(string value)
|
|
{
|
|
if (value == "contains")
|
|
return FilterOperator.Contains;
|
|
else if (value == "is")
|
|
return FilterOperator.Equal;
|
|
else if (value == "is not")
|
|
return FilterOperator.Different;
|
|
return FilterOperator.Equal;
|
|
}
|
|
|
|
private async void UpdateFilter_Clicked(object sender, RoutedEventArgs e)
|
|
{
|
|
_filters.Clear();
|
|
|
|
Debug.WriteLine("is on main thread => " + IsOnMainThread());
|
|
|
|
foreach (ContentPresenter superChild in FilterPanel.Children)
|
|
{
|
|
ITag tag = MpdTags.Title;
|
|
FilterOperator op = FilterOperator.None;
|
|
string value = "";
|
|
bool isDir = false;
|
|
|
|
StackPanel stackPanel = VisualTreeHelper.GetChild(superChild, 0) as StackPanel;
|
|
foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
|
|
{
|
|
if (child.Name == "FilterValue")
|
|
value = child.Text;
|
|
}
|
|
foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
|
|
{
|
|
if (child.Name == "FilterType")
|
|
{
|
|
if (child.SelectedItem.ToString() == "Directory")
|
|
isDir = true;
|
|
else
|
|
tag = FilterEquivalence_Type(child.SelectedItem.ToString());
|
|
}
|
|
|
|
if (child.Name == "FilterOperator")
|
|
op = FilterEquivalence_Operator(child.SelectedItem.ToString());
|
|
|
|
if (child.Name == "FilterList" && child.Visibility == Visibility.Visible)
|
|
value = child.SelectedItem.ToString();
|
|
}
|
|
|
|
if (value != "")
|
|
{
|
|
if (!isDir)
|
|
_filters.Add(new FilterTag(tag, value, op));
|
|
else
|
|
_filters.Add(new FilterBase(value, FilterOperator.None));
|
|
|
|
await GetSongsFromFilter();
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task GetSongsFromFilter()
|
|
{
|
|
Debug.WriteLine("get songs from filter => start");
|
|
|
|
Debug.WriteLine("is on main thread => " + IsOnMainThread());
|
|
|
|
_songList.Clear();
|
|
SongFilterPanel.Visibility = Visibility.Visible;
|
|
|
|
int song = _mpd.GetStats().Songs;
|
|
|
|
Debug.WriteLine("get songs from filter => before command");
|
|
|
|
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(_filters, 0, song + 1) });
|
|
string Response = await _mpd.SafelySendCommandAsync(commandList);
|
|
|
|
Debug.WriteLine("get songs from filter => after command");
|
|
|
|
// create a list of the file url
|
|
string[] value = Response.Split(", [file, ");
|
|
|
|
// there are no song in this filter
|
|
if (value[0] == "")
|
|
{
|
|
SongFilterNumber.Text = _songList.Count.ToString();
|
|
return;
|
|
}
|
|
|
|
Debug.WriteLine("get songs from filter => before adding to list");
|
|
|
|
foreach (string file in value)
|
|
{
|
|
int start = 0;
|
|
int end = file.IndexOf("],");
|
|
string filePath = file.Substring(start, end - start);
|
|
|
|
_songList.Add(filePath);
|
|
SongFilterNumber.Text = _songList.Count.ToString();
|
|
}
|
|
|
|
Debug.WriteLine("get songs from filter => after adding to list");
|
|
|
|
// remove characters from first file
|
|
_songList[0] = _songList[0].Substring(7, _songList[0].Length - 7);
|
|
|
|
SongFilterPanel.Visibility = Visibility.Visible;
|
|
SongFilterNumber.Text = _songList.Count.ToString();
|
|
|
|
Debug.WriteLine("get songs from filter => finish");
|
|
}
|
|
|
|
private void FilterType_Change(object sender, string Operator, List<string> Listing)
|
|
{
|
|
ComboBox comboBox = sender as ComboBox;
|
|
StackPanel stackPanel = comboBox.Parent as StackPanel;
|
|
foreach (ComboBox child in stackPanel.Children.OfType<ComboBox>())
|
|
{
|
|
if (child.Name == "FilterOperator")
|
|
{
|
|
child.ItemsSource = (Array)FindResource(Operator);
|
|
child.SelectedItem = child.Items[0];
|
|
}
|
|
|
|
if (child.Name == "FilterList")
|
|
{
|
|
child.Visibility = Visibility.Visible;
|
|
child.ItemsSource = Listing;
|
|
child.SelectedItem = child.Items[0];
|
|
}
|
|
}
|
|
foreach (TextBox child in stackPanel.Children.OfType<TextBox>())
|
|
{
|
|
if (child.Name == "FilterValue")
|
|
child.Visibility = Visibility.Collapsed;
|
|
}
|
|
SongFilterNumber.Text = "0";
|
|
}
|
|
|
|
private void FilterType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
string item = e.AddedItems[0].ToString();
|
|
if (item == "Genre")
|
|
FilterType_Change(sender, "OperatorTypeB", _genreList);
|
|
else if (item == "Directory")
|
|
FilterType_Change(sender, "OperatorTypeC", _folderList);
|
|
else
|
|
{
|
|
ComboBox combobox = sender as ComboBox;
|
|
StackPanel stackpanel = combobox.Parent as StackPanel;
|
|
foreach (ComboBox child in stackpanel.Children.OfType<ComboBox>())
|
|
{
|
|
if (child.Name == "FilterOperator")
|
|
{
|
|
child.ItemsSource = (Array)FindResource("OperatorTypeA");
|
|
child.SelectedItem = child.Items[0];
|
|
}
|
|
|
|
if (child.Name == "FilterList")
|
|
child.Visibility = Visibility.Collapsed;
|
|
}
|
|
foreach (TextBox child in stackpanel.Children.OfType<TextBox>())
|
|
{
|
|
if (child.Name == "FilterValue")
|
|
child.Visibility = Visibility.Visible;
|
|
}
|
|
}
|
|
SongFilterNumber.Text = "0";
|
|
}
|
|
|
|
private void OperatorType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
Debug.WriteLine("selection changed => operator type");
|
|
SongFilterNumber.Text = "0";
|
|
}
|
|
|
|
private void QueueValidationTextBox(object sender, TextCompositionEventArgs e)
|
|
{
|
|
Regex regex = new Regex("[^0-9]+");
|
|
e.Handled = regex.IsMatch(e.Text);
|
|
}
|
|
|
|
private void QueueValidationNumber()
|
|
{
|
|
if (int.Parse(SongNumber.Text) < 1)
|
|
SongNumber.Text = "1";
|
|
if (int.Parse(SongNumber.Text) > 1000)
|
|
SongNumber.Text = "1000";
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private async void AddToQueue_Clicked(object sender, RoutedEventArgs e)
|
|
{
|
|
QueueValidationNumber();
|
|
|
|
if (_mpd.GetStats() == null)
|
|
return;
|
|
|
|
if (IsFilterEmpty())
|
|
await AddToQueueRandom(int.Parse(SongNumber.Text)); // @TODO await or not???
|
|
else
|
|
{
|
|
UpdateFilter_Clicked(null, null);
|
|
await AddToQueueFilter(int.Parse(SongNumber.Text)); // @TODO await or not???
|
|
}
|
|
}
|
|
|
|
private async /*void*/ Task AddToQueueRandom(int SongNumber)
|
|
{
|
|
int AddedSongs = 0;
|
|
NumberAddedSongs.Text = AddedSongs.ToString();
|
|
SearchStatus.Visibility = Visibility.Visible;
|
|
|
|
Debug.WriteLine("song to add => " + SongNumber);
|
|
for (int i = 0; i < SongNumber; i++)
|
|
{
|
|
// generate random number
|
|
int song = new Random().Next(0, _mpd.GetStats().Songs - 1);
|
|
|
|
// query random song
|
|
CommandList commandList = new CommandList(new IMpcCommand<object>[] { new SearchCommand(new FilterTag(MpdTags.Title, "", FilterOperator.Contains), song, song + 1) });
|
|
string Response = await _mpd.SafelySendCommandAsync(commandList);
|
|
|
|
await Task.Delay(1);
|
|
if (Response.Length > 0)
|
|
{
|
|
// parse song and add it to queue
|
|
int start = Response.IndexOf("[file, ");
|
|
int end = Response.IndexOf("],");
|
|
string filePath = Response.Substring(start + 7, end - (start + 7));
|
|
_mpd.AddSong(filePath);
|
|
Debug.WriteLine("song path => " + filePath);
|
|
|
|
AddedSongs++;
|
|
NumberAddedSongs.Text = AddedSongs.ToString();
|
|
}
|
|
}
|
|
|
|
// TODO make play at first position added, as soon as possible
|
|
if (!_mpd.IsPlaying())
|
|
_mpd.Play(0);
|
|
|
|
SearchStatus.Visibility = Visibility.Collapsed;
|
|
}
|
|
|
|
private async /*void*/ Task AddToQueueFilter(int SongNumber)
|
|
{
|
|
int AddedSongs = 0;
|
|
NumberAddedSongs.Text = AddedSongs.ToString();
|
|
SearchStatus.Visibility = Visibility.Visible;
|
|
|
|
Debug.WriteLine("song to add => " + SongNumber);
|
|
|
|
// more requested songs than available => add everything
|
|
if (SongNumber > _songList.Count)
|
|
{
|
|
foreach (string path in _songList)
|
|
{
|
|
await Task.Delay(1);
|
|
_mpd.AddSong(path);
|
|
Debug.WriteLine("song path => " + path);
|
|
|
|
AddedSongs++;
|
|
NumberAddedSongs.Text = AddedSongs.ToString();
|
|
}
|
|
}
|
|
// more available songs than requested =>
|
|
// we add unique indexes until we reach the requested amount
|
|
else
|
|
{
|
|
HashSet<int> SongIndex = new();
|
|
while (SongIndex.Count < SongNumber)
|
|
{
|
|
int MaxIndex = new Random().Next(0, _songList.Count - 1);
|
|
SongIndex.Add(MaxIndex);
|
|
}
|
|
|
|
foreach (int index in SongIndex)
|
|
_mpd.AddSong(_songList[index]);
|
|
}
|
|
|
|
SearchStatus.Visibility = Visibility.Collapsed;
|
|
}
|
|
|
|
|
|
public bool GetContinuous()
|
|
{
|
|
return _continuous;
|
|
}
|
|
|
|
public async Task HandleContinuous()
|
|
{
|
|
if (!GetContinuous())
|
|
return;
|
|
|
|
Debug.WriteLine("is on main thread => " + IsOnMainThread());
|
|
|
|
IEnumerable<IMpdFile> Playlist = await _mpd.SafelySendCommandAsync(new PlaylistCommand());
|
|
int queueSize = 0;
|
|
foreach (IMpdFile file in Playlist)
|
|
{
|
|
Debug.WriteLine(file.Path);
|
|
queueSize++;
|
|
}
|
|
Debug.WriteLine("queue size is: " + queueSize);
|
|
|
|
if (queueSize < 5)
|
|
{
|
|
if (_mpd.GetStats() == null)
|
|
return;
|
|
|
|
if (IsFilterEmpty())
|
|
await AddToQueueRandom(5); // @TODO await or not?
|
|
else
|
|
await AddToQueueFilter(5); // @TODO await or not?
|
|
}
|
|
}
|
|
|
|
private async void ContinuousShuffle_Checked(object sender, RoutedEventArgs e)
|
|
{
|
|
if (ContinuousShuffle.IsChecked == true)
|
|
{
|
|
_continuous = true;
|
|
_songList.Clear();
|
|
if (!IsFilterEmpty())
|
|
UpdateFilter_Clicked(null, null);
|
|
}
|
|
else
|
|
_continuous = false;
|
|
|
|
await HandleContinuous();
|
|
}
|
|
}
|
|
} |