BililiveRecorder/BililiveRecorder.WPF/Pages/RoomListPage.xaml.cs

441 lines
16 KiB
C#
Raw Normal View History

2020-11-27 18:51:02 +08:00
using System;
2020-12-05 18:30:04 +08:00
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
2020-12-10 17:02:56 +08:00
using System.Diagnostics;
2020-11-27 18:51:02 +08:00
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
2020-12-05 18:30:04 +08:00
using System.Text.RegularExpressions;
2020-12-20 18:42:46 +08:00
using System.Threading;
2020-12-05 18:30:04 +08:00
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
2020-11-27 18:51:02 +08:00
using BililiveRecorder.Core;
using BililiveRecorder.WPF.Controls;
2020-12-05 18:30:04 +08:00
using ModernWpf.Controls;
using NLog;
2020-11-27 18:51:02 +08:00
namespace BililiveRecorder.WPF.Pages
{
/// <summary>
/// Interaction logic for RoomList.xaml
/// </summary>
public partial class RoomListPage
{
private static readonly Regex RoomIdRegex
= new Regex(@"^(?:https?:\/\/)?live\.bilibili\.com\/(?:blanc\/|h5\/)?(\d*)(?:\?.*)?$",
RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
public RoomListPage()
{
2021-01-01 14:46:27 +08:00
this.InitializeComponent();
2020-12-05 18:30:04 +08:00
2021-01-01 14:46:27 +08:00
this.SortedRoomList = new SortedItemsSourceView(this.DataContext);
DataContextChanged += this.RoomListPage_DataContextChanged;
2020-12-05 18:30:04 +08:00
}
private void RoomListPage_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
2021-01-01 14:46:27 +08:00
(this.SortedRoomList as SortedItemsSourceView).Data = e.NewValue as ICollection<IRecordedRoom>;
2020-12-05 18:30:04 +08:00
}
public static readonly DependencyProperty SortedRoomListProperty =
DependencyProperty.Register(
nameof(SortedRoomList),
typeof(object),
typeof(RoomListPage),
new PropertyMetadata(OnPropertyChanged));
public object SortedRoomList
{
2021-01-01 14:46:27 +08:00
get => this.GetValue(SortedRoomListProperty);
set => this.SetValue(SortedRoomListProperty, value);
2020-12-05 18:30:04 +08:00
}
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
2020-11-27 18:51:02 +08:00
}
private async void RoomCard_DeleteRequested(object sender, EventArgs e)
{
2021-01-01 14:46:27 +08:00
if (this.DataContext is IRecorder rec && sender is IRecordedRoom room)
2020-11-27 18:51:02 +08:00
{
var dialog = new DeleteRoomConfirmDialog
{
DataContext = room
};
var result = await dialog.ShowAsync();
if (result == ModernWpf.Controls.ContentDialogResult.Primary)
{
rec.RemoveRoom(room);
2020-12-05 18:30:04 +08:00
rec.SaveConfigToFile();
2020-11-27 18:51:02 +08:00
}
}
}
2021-01-01 14:46:27 +08:00
private async void RoomCard_ShowSettingsRequested(object sender, EventArgs e)
{
try
{
await new PerRoomSettingsDialog { DataContext = sender }.ShowAsync();
}
catch (Exception) { }
}
2020-11-27 18:51:02 +08:00
private async void AddRoomCard_AddRoomRequested(object sender, string e)
{
var input = e.Trim();
2021-01-01 14:46:27 +08:00
if (string.IsNullOrWhiteSpace(input) || this.DataContext is not IRecorder rec) return;
2020-11-27 18:51:02 +08:00
if (!int.TryParse(input, out var roomid))
{
var m = RoomIdRegex.Match(input);
if (m.Success && m.Groups.Count > 1 && int.TryParse(m.Groups[1].Value, out var result2))
{
roomid = result2;
}
else
{
2021-01-04 14:01:11 +08:00
await new AddRoomFailedDialog { DataContext = AddRoomFailedDialog.AddRoomFailedErrorText.InvalidInput }.ShowAsync();
2020-11-27 18:51:02 +08:00
return;
}
}
if (roomid < 0)
{
2021-01-04 14:01:11 +08:00
await new AddRoomFailedDialog { DataContext = AddRoomFailedDialog.AddRoomFailedErrorText.RoomIdNegative }.ShowAsync();
2020-11-27 18:51:02 +08:00
return;
}
else if (roomid == 0)
{
2021-01-04 14:01:11 +08:00
await new AddRoomFailedDialog { DataContext = AddRoomFailedDialog.AddRoomFailedErrorText.RoomIdZero }.ShowAsync();
2020-11-27 18:51:02 +08:00
return;
}
if (rec.Any(x => x.RoomId == roomid || x.ShortRoomId == roomid))
{
2021-01-04 14:01:11 +08:00
await new AddRoomFailedDialog { DataContext = AddRoomFailedDialog.AddRoomFailedErrorText.Duplicate }.ShowAsync();
2020-11-27 18:51:02 +08:00
return;
}
rec.AddRoom(roomid);
2020-12-05 18:30:04 +08:00
rec.SaveConfigToFile();
}
private async void MenuItem_EnableAutoRecAll_Click(object sender, RoutedEventArgs e)
{
2021-01-01 14:46:27 +08:00
if (!(this.DataContext is IRecorder rec)) return;
2020-12-05 18:30:04 +08:00
await Task.WhenAll(rec.ToList().Select(rr => Task.Run(() => rr.Start())));
rec.SaveConfigToFile();
}
private async void MenuItem_DisableAutoRecAll_Click(object sender, RoutedEventArgs e)
{
2021-01-01 14:46:27 +08:00
if (!(this.DataContext is IRecorder rec)) return;
2020-12-05 18:30:04 +08:00
await Task.WhenAll(rec.ToList().Select(rr => Task.Run(() => rr.Stop())));
rec.SaveConfigToFile();
}
private void MenuItem_SortBy_Click(object sender, RoutedEventArgs e)
{
2021-01-01 14:46:27 +08:00
(this.SortedRoomList as SortedItemsSourceView).SortedBy = (SortedBy)((MenuItem)sender).Tag;
2020-12-05 18:30:04 +08:00
}
private void MenuItem_ShowLog_Click(object sender, RoutedEventArgs e)
{
2021-01-01 14:46:27 +08:00
this.Splitter.Visibility = Visibility.Visible;
this.LogElement.Visibility = Visibility.Visible;
this.RoomListRowDefinition.Height = new GridLength(1, GridUnitType.Star);
this.LogRowDefinition.Height = new GridLength(1, GridUnitType.Star);
2020-12-05 18:30:04 +08:00
}
private void MenuItem_HideLog_Click(object sender, RoutedEventArgs e)
{
2021-01-01 14:46:27 +08:00
this.Splitter.Visibility = Visibility.Collapsed;
this.LogElement.Visibility = Visibility.Collapsed;
this.RoomListRowDefinition.Height = new GridLength(1, GridUnitType.Star);
this.LogRowDefinition.Height = new GridLength(0);
2020-12-05 18:30:04 +08:00
}
private void Log_ScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
(sender as ScrollViewer)?.ScrollToEnd();
}
private void TextBlock_Copy_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
try
{
if (sender is TextBlock textBlock)
{
Clipboard.SetText(textBlock.Text);
}
}
catch (Exception)
{
}
2020-11-27 18:51:02 +08:00
}
2020-12-10 17:02:56 +08:00
private void MenuItem_OpenWorkDirectory_Click(object sender, RoutedEventArgs e)
{
try
{
2021-01-01 14:46:27 +08:00
if (this.DataContext is IRecorder rec)
Process.Start("explorer.exe", rec.Config.Global.WorkDirectory);
2020-12-10 17:02:56 +08:00
}
catch (Exception)
{
}
}
2020-11-27 18:51:02 +08:00
}
2020-12-05 18:30:04 +08:00
internal enum SortedBy
{
None = 0,
RoomId,
Status,
}
internal class SortedItemsSourceView : IList, IReadOnlyList<IRecordedRoom>, IKeyIndexMapping, INotifyCollectionChanged
{
2021-01-01 14:46:27 +08:00
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
2020-12-05 18:30:04 +08:00
private ICollection<IRecordedRoom> _data;
private SortedBy sortedBy;
private readonly IRecordedRoom[] NullRoom = new IRecordedRoom[] { null };
public event NotifyCollectionChangedEventHandler CollectionChanged;
public SortedItemsSourceView(object data)
{
if (data is not null)
{
if (data is IList<IRecordedRoom> list)
{
2021-01-01 14:46:27 +08:00
if (list is INotifyCollectionChanged n) n.CollectionChanged += this.Data_CollectionChanged;
this._data = list;
2020-12-05 18:30:04 +08:00
}
else
{
throw new ArgumentException("Type not supported.", nameof(data));
}
}
2021-01-01 14:46:27 +08:00
this.Sort();
2020-12-05 18:30:04 +08:00
}
2021-01-01 14:46:27 +08:00
private void Data_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => this.Sort();
2020-12-05 18:30:04 +08:00
public ICollection<IRecordedRoom> Data
{
2021-01-01 14:46:27 +08:00
get => this._data;
2020-12-05 18:30:04 +08:00
set
{
2021-01-01 14:46:27 +08:00
if (this._data is INotifyCollectionChanged n1) n1.CollectionChanged -= this.Data_CollectionChanged;
if (value is INotifyCollectionChanged n2) n2.CollectionChanged += this.Data_CollectionChanged;
this._data = value;
this.Sort();
2020-12-05 18:30:04 +08:00
}
}
2021-01-01 14:46:27 +08:00
public SortedBy SortedBy { get => this.sortedBy; set { this.sortedBy = value; this.Sort(); } }
2020-12-05 18:30:04 +08:00
public List<IRecordedRoom> Sorted { get; private set; }
2020-12-20 18:42:46 +08:00
private int sortSeboucneCount = int.MinValue;
2021-01-01 14:46:27 +08:00
private readonly SemaphoreSlim sortSemaphoreSlim = new SemaphoreSlim(1, 1);
2020-12-20 18:42:46 +08:00
private async void Sort()
{
// debounce && lock
logger.Debug("Sort called.");
2021-01-01 14:46:27 +08:00
var callCount = Interlocked.Increment(ref this.sortSeboucneCount);
2020-12-20 18:42:46 +08:00
await Task.Delay(200);
2021-01-01 14:46:27 +08:00
if (this.sortSeboucneCount != callCount)
2020-12-20 18:42:46 +08:00
{
logger.Debug("Sort cancelled by debounce.");
return;
}
2021-01-01 14:46:27 +08:00
await this.sortSemaphoreSlim.WaitAsync();
try { this.SortImpl(); }
finally { this.sortSemaphoreSlim.Release(); }
2020-12-20 18:42:46 +08:00
}
private void SortImpl()
2020-12-05 18:30:04 +08:00
{
2021-01-01 14:46:27 +08:00
logger.Debug("SortImpl called with {sortedBy} and {count} rooms.", this.SortedBy, this.Data?.Count ?? -1);
2021-01-01 14:46:27 +08:00
if (this.Data is null)
2020-12-05 18:30:04 +08:00
{
2021-01-01 14:46:27 +08:00
this.Sorted = this.NullRoom.ToList();
2020-12-20 18:42:46 +08:00
logger.Debug("SortImpl returned NullRoom.");
2020-12-05 18:30:04 +08:00
}
else
{
2021-01-01 14:46:27 +08:00
IEnumerable<IRecordedRoom> orderedData = this.SortedBy switch
2020-12-05 18:30:04 +08:00
{
2021-01-01 14:46:27 +08:00
SortedBy.RoomId => this.Data.OrderBy(x => x.ShortRoomId == 0 ? x.RoomId : x.ShortRoomId),
SortedBy.Status => this.Data.OrderByDescending(x => x.IsRecording).ThenByDescending(x => x.IsMonitoring),
_ => this.Data,
2020-12-05 18:30:04 +08:00
};
2021-01-01 14:46:27 +08:00
var result = orderedData.Concat(this.NullRoom).ToList();
2020-12-20 18:42:46 +08:00
logger.Debug("SortImpl sorted with {count} items.", result.Count);
{ // 崩溃问题信息收集。。虽然不觉得是这里的问题
var dup = result.GroupBy(x => x?.Guid ?? Guid.Empty).Where(x => x.Count() != 1);
if (dup.Any())
{
var sb = new StringBuilder("排序调试信息\n重复:\n");
foreach (var item in dup)
{
sb.Append("-Guid: ");
sb.AppendLine(item.Key.ToString());
foreach (var room in item)
{
sb.Append("RoomId: ");
sb.AppendLine(room?.RoomId.ToString());
}
}
sb.Append("原始:");
foreach (var room in result)
{
sb.Append("-Guid: ");
sb.AppendLine((room?.Guid ?? Guid.Empty).ToString());
sb.Append("RoomId: ");
sb.AppendLine(room?.RoomId.ToString());
}
logger.Debug(sb.ToString());
// trigger sentry
logger.Error(new SortedItemsSourceViewException(), "排序房间时发生了错误");
return;
}
}
2021-01-01 14:46:27 +08:00
this.Sorted = result;
2020-12-05 18:30:04 +08:00
}
// Instead of tossing out existing elements and re-creating them,
// ItemsRepeater will reuse the existing elements and match them up
// with the data again.
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
2021-01-01 14:46:27 +08:00
public IRecordedRoom this[int index] => this.Sorted != null ? this.Sorted[index] : throw new IndexOutOfRangeException();
public int Count => this.Sorted != null ? this.Sorted.Count : 0;
2020-12-05 18:30:04 +08:00
2021-01-01 14:46:27 +08:00
public bool IsReadOnly => ((IList)this.Sorted).IsReadOnly;
2020-12-05 18:30:04 +08:00
2021-01-01 14:46:27 +08:00
public bool IsFixedSize => ((IList)this.Sorted).IsFixedSize;
2020-12-05 18:30:04 +08:00
2021-01-01 14:46:27 +08:00
public object SyncRoot => ((ICollection)this.Sorted).SyncRoot;
2020-12-05 18:30:04 +08:00
2021-01-01 14:46:27 +08:00
public bool IsSynchronized => ((ICollection)this.Sorted).IsSynchronized;
2020-12-05 18:30:04 +08:00
2021-01-01 14:46:27 +08:00
object IList.this[int index] { get => ((IList)this.Sorted)[index]; set => ((IList)this.Sorted)[index] = value; }
2020-12-05 18:30:04 +08:00
2021-01-01 14:46:27 +08:00
public IEnumerator<IRecordedRoom> GetEnumerator() => this.Sorted.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
2020-12-05 18:30:04 +08:00
#region IKeyIndexMapping
private int lastRequestedIndex = IndexNotFound;
private const int IndexNotFound = -1;
// When UniqueIDs are supported, the ItemsRepeater caches the unique ID for each item
// with the matching UIElement that represents the item. When a reset occurs the
// ItemsRepeater pairs up the already generated UIElements with items in the data
// source.
// ItemsRepeater uses IndexForUniqueId after a reset to probe the data and identify
// the new index of an item to use as the anchor. If that item no
// longer exists in the data source it may try using another cached unique ID until
// either a match is found or it determines that all the previously visible items
// no longer exist.
public int IndexFromKey(string uniqueId)
{
// We'll try to increase our odds of finding a match sooner by starting from the
// position that we know was last requested and search forward.
2021-01-01 14:46:27 +08:00
var start = this.lastRequestedIndex;
for (var i = start; i < this.Count; i++)
2020-12-05 18:30:04 +08:00
{
if ((this[i]?.Guid ?? Guid.Empty).Equals(uniqueId))
return i;
}
// Then try searching backward.
2021-01-01 14:46:27 +08:00
start = Math.Min(this.Count - 1, this.lastRequestedIndex);
2020-12-05 18:30:04 +08:00
for (var i = start; i >= 0; i--)
{
if ((this[i]?.Guid ?? Guid.Empty).Equals(uniqueId))
return i;
}
return IndexNotFound;
}
public string KeyFromIndex(int index)
{
var key = this[index]?.Guid ?? Guid.Empty;
2021-01-01 14:46:27 +08:00
this.lastRequestedIndex = index;
2020-12-05 18:30:04 +08:00
return key.ToString();
}
public int Add(object value)
{
2021-01-01 14:46:27 +08:00
return ((IList)this.Sorted).Add(value);
2020-12-05 18:30:04 +08:00
}
public bool Contains(object value)
{
2021-01-01 14:46:27 +08:00
return ((IList)this.Sorted).Contains(value);
2020-12-05 18:30:04 +08:00
}
public void Clear()
{
2021-01-01 14:46:27 +08:00
((IList)this.Sorted).Clear();
2020-12-05 18:30:04 +08:00
}
public int IndexOf(object value)
{
2021-01-01 14:46:27 +08:00
return ((IList)this.Sorted).IndexOf(value);
2020-12-05 18:30:04 +08:00
}
public void Insert(int index, object value)
{
2021-01-01 14:46:27 +08:00
((IList)this.Sorted).Insert(index, value);
2020-12-05 18:30:04 +08:00
}
public void Remove(object value)
{
2021-01-01 14:46:27 +08:00
((IList)this.Sorted).Remove(value);
2020-12-05 18:30:04 +08:00
}
public void RemoveAt(int index)
{
2021-01-01 14:46:27 +08:00
((IList)this.Sorted).RemoveAt(index);
2020-12-05 18:30:04 +08:00
}
public void CopyTo(Array array, int index)
{
2021-01-01 14:46:27 +08:00
((ICollection)this.Sorted).CopyTo(array, index);
2020-12-05 18:30:04 +08:00
}
#endregion
public class SortedItemsSourceViewException : Exception
{
public SortedItemsSourceViewException() { }
public SortedItemsSourceViewException(string message) : base(message) { }
public SortedItemsSourceViewException(string message, Exception innerException) : base(message, innerException) { }
protected SortedItemsSourceViewException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
}
2020-11-27 18:51:02 +08:00
}