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 ;
2021-08-09 22:58:30 +08:00
using System.IO ;
2020-11-27 18:51:02 +08:00
using System.Linq ;
2020-12-05 18:30:04 +08:00
using System.Text.RegularExpressions ;
2022-06-04 02:34:09 +08:00
using System.Threading.Tasks ;
2020-12-05 18:30:04 +08:00
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 ;
2021-02-23 18:03:37 +08:00
using Serilog ;
2020-11-27 18:51:02 +08:00
2021-02-23 18:03:37 +08:00
#nullable enable
2020-11-27 18:51:02 +08:00
namespace BililiveRecorder.WPF.Pages
{
/// <summary>
/// Interaction logic for RoomList.xaml
/// </summary>
public partial class RoomListPage
{
2021-02-23 18:03:37 +08:00
private static readonly ILogger logger = Log . ForContext < RoomListPage > ( ) ;
2020-11-27 18:51:02 +08:00
private static readonly Regex RoomIdRegex
= new Regex ( @"^(?:https?:\/\/)?live\.bilibili\.com\/(?:blanc\/|h5\/)?(\d*)(?:\?.*)?$" ,
RegexOptions . IgnoreCase | RegexOptions . Singleline | RegexOptions . Compiled ) ;
2021-02-23 18:03:37 +08:00
private readonly IRoom ? [ ] NullRoom = new IRoom ? [ ] { null } ;
2021-01-15 17:41:33 +08:00
private readonly KeyIndexMappingReadOnlyList NullRoomWithMapping ;
2020-11-27 18:51:02 +08:00
public RoomListPage ( )
{
2021-01-15 17:41:33 +08:00
this . NullRoomWithMapping = new KeyIndexMappingReadOnlyList ( this . NullRoom ) ;
this . DataContextChanged + = this . RoomListPage_DataContextChanged ;
2021-01-01 14:46:27 +08:00
this . InitializeComponent ( ) ;
2021-01-15 17:41:33 +08:00
}
2020-12-05 18:30:04 +08:00
2021-01-15 17:41:33 +08:00
private void RoomListPage_DataContextChanged ( object sender , DependencyPropertyChangedEventArgs e )
{
2021-02-23 18:03:37 +08:00
if ( e . OldValue is IRecorder data_old ) ( ( INotifyCollectionChanged ) data_old . Rooms ) . CollectionChanged - = this . DataSource_CollectionChanged ;
if ( e . NewValue is IRecorder data_new ) ( ( INotifyCollectionChanged ) data_new . Rooms ) . CollectionChanged + = this . DataSource_CollectionChanged ;
2021-01-15 17:41:33 +08:00
this . ApplySort ( ) ;
2020-12-05 18:30:04 +08:00
}
2021-01-15 17:41:33 +08:00
public static readonly DependencyProperty RoomListProperty =
DependencyProperty . Register (
nameof ( RoomList ) ,
typeof ( object ) ,
typeof ( RoomListPage ) ,
new PropertyMetadata ( OnPropertyChanged ) ) ;
public object RoomList
{
get = > this . GetValue ( RoomListProperty ) ;
set = > this . SetValue ( RoomListProperty , value ) ;
}
2020-12-05 18:30:04 +08:00
2021-01-15 17:41:33 +08:00
public static readonly DependencyProperty SortByProperty =
DependencyProperty . Register (
nameof ( SortBy ) ,
typeof ( SortedBy ) ,
typeof ( RoomListPage ) ,
new PropertyMetadata ( OnPropertyChanged ) ) ;
2020-12-05 18:30:04 +08:00
2021-01-15 17:41:33 +08:00
public SortedBy SortBy
2020-12-05 18:30:04 +08:00
{
2021-01-15 17:41:33 +08:00
get = > ( SortedBy ) this . GetValue ( SortByProperty ) ;
2021-01-18 15:02:42 +08:00
set
2021-01-15 17:41:33 +08:00
{
2021-01-18 15:02:42 +08:00
this . SetValue ( SortByProperty , value ) ;
2021-01-15 17:41:33 +08:00
this . ApplySort ( ) ;
}
}
2020-12-05 18:30:04 +08:00
2021-01-18 15:02:42 +08:00
private static void OnPropertyChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e ) = > ( ( RoomListPage ) d ) . PrivateOnPropertyChanged ( e ) ;
private void PrivateOnPropertyChanged ( DependencyPropertyChangedEventArgs e ) { }
2021-01-15 17:41:33 +08:00
private void DataSource_CollectionChanged ( object sender , NotifyCollectionChangedEventArgs e ) = > this . ApplySort ( ) ;
private void ApplySort ( )
{
try
{
2021-02-23 18:03:37 +08:00
if ( this . DataContext is not IRecorder recorder | | recorder . Rooms . Count = = 0 )
2021-01-15 17:41:33 +08:00
{
this . RoomList = this . NullRoomWithMapping ;
}
else
{
2021-02-23 18:03:37 +08:00
var data = recorder . Rooms ;
IEnumerable < IRoom > orderedData = this . SortBy switch
2021-01-15 17:41:33 +08:00
{
2021-02-23 18:03:37 +08:00
SortedBy . RoomId = > data . OrderBy ( x = > x . ShortId = = 0 ? x . RoomConfig . RoomId : x . ShortId ) ,
SortedBy . Status = > from x in data orderby x . Recording descending , x . RoomConfig . AutoRecord descending , x . Streaming descending select x ,
2021-01-15 17:41:33 +08:00
_ = > data ,
} ;
var result = new KeyIndexMappingReadOnlyList ( orderedData . Concat ( this . NullRoom ) . ToArray ( ) ) ;
this . RoomList = result ;
}
}
catch ( Exception ex )
{
logger . Error ( ex , "Error Sorting" ) ;
}
2020-11-27 18:51:02 +08:00
}
2021-02-23 18:03:37 +08:00
#pragma warning disable VSTHRD100 // Avoid async void methods
2020-11-27 18:51:02 +08:00
private async void RoomCard_DeleteRequested ( object sender , EventArgs e )
2021-02-23 18:03:37 +08:00
#pragma warning restore VSTHRD100 // Avoid async void methods
2020-11-27 18:51:02 +08:00
{
2021-02-23 18:03:37 +08:00
if ( this . DataContext is IRecorder rec & & sender is IRoom room )
2020-11-27 18:51:02 +08:00
{
2021-01-29 20:31:25 +08:00
try
2020-11-27 18:51:02 +08:00
{
2021-01-29 20:31:25 +08:00
var dialog = new DeleteRoomConfirmDialog
{
2022-05-16 21:06:49 +08:00
DataContext = room ,
Owner = Application . Current . MainWindow
2021-01-29 20:31:25 +08:00
} ;
2020-11-27 18:51:02 +08:00
2022-05-16 21:06:49 +08:00
var result = await dialog . ShowAndDisableMinimizeToTrayAsync ( ) ;
2020-11-27 18:51:02 +08:00
2021-01-29 20:31:25 +08:00
if ( result = = ContentDialogResult . Primary )
{
rec . RemoveRoom ( room ) ;
}
2020-11-27 18:51:02 +08:00
}
2021-01-29 20:31:25 +08:00
catch ( Exception ) { }
2020-11-27 18:51:02 +08:00
}
}
2021-02-23 18:03:37 +08:00
#pragma warning disable VSTHRD100 // Avoid async void methods
2021-01-01 14:46:27 +08:00
private async void RoomCard_ShowSettingsRequested ( object sender , EventArgs e )
2021-02-23 18:03:37 +08:00
#pragma warning restore VSTHRD100 // Avoid async void methods
2021-01-01 14:46:27 +08:00
{
try
{
2022-05-16 21:06:49 +08:00
await new PerRoomSettingsDialog
{
DataContext = sender ,
Owner = Application . Current . MainWindow
} . ShowAndDisableMinimizeToTrayAsync ( ) ;
2021-01-01 14:46:27 +08:00
}
catch ( Exception ) { }
}
2021-02-23 18:03:37 +08:00
#pragma warning disable VSTHRD100 // Avoid async void methods
2020-11-27 18:51:02 +08:00
private async void AddRoomCard_AddRoomRequested ( object sender , string e )
2021-02-23 18:03:37 +08:00
#pragma warning restore VSTHRD100 // Avoid async void methods
2020-11-27 18:51:02 +08:00
{
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-29 20:31:25 +08:00
try
{
2022-05-16 21:06:49 +08:00
await new AddRoomFailedDialog
{
DataContext = AddRoomFailedDialog . AddRoomFailedErrorText . InvalidInput ,
Owner = Application . Current . MainWindow
} . ShowAndDisableMinimizeToTrayAsync ( ) ;
2021-01-29 20:31:25 +08:00
}
catch ( Exception ) { }
2020-11-27 18:51:02 +08:00
return ;
}
}
if ( roomid < 0 )
{
2021-01-29 20:31:25 +08:00
try
{
2022-05-16 21:06:49 +08:00
await new AddRoomFailedDialog
{
DataContext = AddRoomFailedDialog . AddRoomFailedErrorText . RoomIdNegative ,
Owner = Application . Current . MainWindow
} . ShowAndDisableMinimizeToTrayAsync ( ) ;
2021-01-29 20:31:25 +08:00
}
catch ( Exception ) { }
2020-11-27 18:51:02 +08:00
return ;
}
else if ( roomid = = 0 )
{
2021-01-29 20:31:25 +08:00
try
{
2022-05-16 21:06:49 +08:00
await new AddRoomFailedDialog
{
DataContext = AddRoomFailedDialog . AddRoomFailedErrorText . RoomIdZero ,
Owner = Application . Current . MainWindow
} . ShowAndDisableMinimizeToTrayAsync ( ) ;
2021-01-29 20:31:25 +08:00
}
catch ( Exception ) { }
2020-11-27 18:51:02 +08:00
return ;
}
2021-02-23 18:03:37 +08:00
if ( rec . Rooms . Any ( x = > x . RoomConfig . RoomId = = roomid | | x . ShortId = = roomid ) )
2020-11-27 18:51:02 +08:00
{
2021-01-29 20:31:25 +08:00
try
{
2022-05-16 21:06:49 +08:00
await new AddRoomFailedDialog
{
DataContext = AddRoomFailedDialog . AddRoomFailedErrorText . Duplicate ,
Owner = Application . Current . MainWindow
} . ShowAndDisableMinimizeToTrayAsync ( ) ;
2021-01-29 20:31:25 +08:00
}
catch ( Exception ) { }
2020-11-27 18:51:02 +08:00
return ;
}
rec . AddRoom ( roomid ) ;
2020-12-05 18:30:04 +08:00
}
2021-02-23 18:03:37 +08:00
private void MenuItem_EnableAutoRecAll_Click ( object sender , RoutedEventArgs e )
2020-12-05 18:30:04 +08:00
{
2021-01-08 12:17:31 +08:00
if ( this . DataContext is not IRecorder rec ) return ;
2020-12-05 18:30:04 +08:00
2021-02-23 18:03:37 +08:00
foreach ( var room in rec . Rooms )
room . RoomConfig . AutoRecord = true ;
rec . SaveConfig ( ) ;
2020-12-05 18:30:04 +08:00
}
2021-02-23 18:03:37 +08:00
private void MenuItem_DisableAutoRecAll_Click ( object sender , RoutedEventArgs e )
2020-12-05 18:30:04 +08:00
{
2021-01-08 12:17:31 +08:00
if ( this . DataContext is not IRecorder rec ) return ;
2020-12-05 18:30:04 +08:00
2021-02-23 18:03:37 +08:00
foreach ( var room in rec . Rooms )
room . RoomConfig . AutoRecord = false ;
rec . SaveConfig ( ) ;
2020-12-05 18:30:04 +08:00
}
2021-01-15 17:41:33 +08:00
private void MenuItem_SortBy_Click ( object sender , RoutedEventArgs e ) = > this . SortBy = ( 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
}
2021-01-08 12:17:31 +08:00
private void Log_ScrollViewer_Loaded ( object sender , RoutedEventArgs e ) = > ( sender as ScrollViewer ) ? . ScrollToEnd ( ) ;
2020-12-05 18:30:04 +08:00
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 )
{
}
}
2021-01-08 18:54:50 +08:00
2021-06-14 15:14:10 +08:00
private void MenuItem_SaveConfig_Click ( object sender , RoutedEventArgs e )
{
try
{
if ( this . DataContext is IRecorder rec )
rec . SaveConfig ( ) ;
}
catch ( Exception )
{
}
}
private void MenuItem_ChangeWorkPath_Click ( object sender , RoutedEventArgs e )
{
try
{
logger . Debug ( "ChangeWorkPath menu button invoked" ) ;
Process . Start ( typeof ( RoomListPage ) . Assembly . Location , "run --ask-path" ) ;
( Application . Current . MainWindow as NewMainWindow ) ? . CloseWithoutConfirmAction ( ) ;
}
catch ( Exception )
{ }
}
2021-01-08 18:54:50 +08:00
private void MenuItem_ShowHideTitleArea_Click ( object sender , RoutedEventArgs e )
{
if ( ( ( MenuItem ) sender ) . Tag is bool b & & this . DataContext is IRecorder rec )
rec . Config . Global . WpfShowTitleAndArea = b ;
}
2021-08-09 22:58:30 +08:00
private void MenuItem_ShowLogFilesInExplorer_Click ( object sender , RoutedEventArgs e )
{
try
{
var logPath = Path . Combine ( Path . GetDirectoryName ( typeof ( RoomListPage ) . Assembly . Location ) , "logs" ) ;
Process . Start ( "explorer.exe" , logPath ) ;
}
catch ( Exception )
{ }
}
2022-06-04 02:34:09 +08:00
private void MenuItem_RefreshAllRoomInfo_Click ( object sender , RoutedEventArgs e )
{
2022-06-11 17:52:34 +08:00
if ( this . DataContext is IRecorder rec )
2022-06-04 02:34:09 +08:00
{
2022-06-11 17:52:34 +08:00
_ = Task . Run ( async ( ) = >
{
await Task . Delay ( 200 ) ;
2022-06-04 02:34:09 +08:00
2022-06-11 17:52:34 +08:00
if ( MessageBox . Show ( "录播姬会自动检测直播间状态,不需要手动刷新。\n录播姬主要通过接收B站服务器的推送来更新状态, 直播服务器会给录播姬实时发送开播通知, 延迟极低。\n\n" +
"频繁刷新直播间状态、短时间内大量请求B站直播 API 可能会导致你的 IP 被屏蔽,完全无法录播。\n\n本功能是特殊情况下确实需要刷新所有直播间信息时使用的。\n\n是否要刷新所有直播间的信息? \n( 每个直播间会发送一个请求) " , "B站录播姬" , MessageBoxButton . YesNo , MessageBoxImage . Warning ) ! = MessageBoxResult . Yes )
return ;
2022-06-04 02:34:09 +08:00
foreach ( var room in rec . Rooms . ToArray ( ) )
{
await room . RefreshRoomInfoAsync ( ) ;
await Task . Delay ( 500 ) ;
}
2022-06-11 17:52:34 +08:00
} ) ;
}
2022-06-04 02:34:09 +08:00
}
2020-11-27 18:51:02 +08:00
}
2020-12-05 18:30:04 +08:00
2021-01-15 17:41:33 +08:00
public enum SortedBy
2020-12-05 18:30:04 +08:00
{
None = 0 ,
RoomId ,
Status ,
}
2021-02-23 18:03:37 +08:00
internal class KeyIndexMappingReadOnlyList : IReadOnlyList < IRoom ? > , IKeyIndexMapping
2020-12-05 18:30:04 +08:00
{
2021-02-23 18:03:37 +08:00
private readonly IReadOnlyList < IRoom ? > data ;
2020-12-05 18:30:04 +08:00
2021-02-23 18:03:37 +08:00
public KeyIndexMappingReadOnlyList ( IReadOnlyList < IRoom ? > data )
2020-12-05 18:30:04 +08:00
{
2021-01-15 17:41:33 +08:00
this . data = data ;
2020-12-05 18:30:04 +08:00
}
2021-02-23 18:03:37 +08:00
public IRoom ? this [ int index ] = > this . data [ index ] ;
2020-12-05 18:30:04 +08:00
2021-01-15 17:41:33 +08:00
public int Count = > this . data . Count ;
2020-12-05 18:30:04 +08:00
2021-02-23 18:03:37 +08:00
public IEnumerator < IRoom ? > GetEnumerator ( ) = > this . data . GetEnumerator ( ) ;
2021-01-15 17:41:33 +08:00
IEnumerator IEnumerable . GetEnumerator ( ) = > ( ( IEnumerable ) this . data ) . 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
{
2021-02-23 18:03:37 +08:00
if ( ( this [ i ] ? . ObjectId ? ? Guid . Empty ) . Equals ( uniqueId ) )
2020-12-05 18:30:04 +08:00
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 - - )
{
2021-02-23 18:03:37 +08:00
if ( ( this [ i ] ? . ObjectId ? ? Guid . Empty ) . Equals ( uniqueId ) )
2020-12-05 18:30:04 +08:00
return i ;
}
return IndexNotFound ;
}
public string KeyFromIndex ( int index )
{
2021-02-23 18:03:37 +08:00
var key = this [ index ] ? . ObjectId ? ? Guid . Empty ;
2021-01-01 14:46:27 +08:00
this . lastRequestedIndex = index ;
2020-12-05 18:30:04 +08:00
return key . ToString ( ) ;
}
#endregion
2020-12-19 17:27:45 +08:00
}
2020-11-27 18:51:02 +08:00
}