修了一堆东西

This commit is contained in:
lanyi 2021-10-13 16:42:38 +02:00
parent 1f5f1c8e6c
commit 6fd0bf0046
5 changed files with 164 additions and 227 deletions

View File

@ -6,8 +6,20 @@
xmlns:local="clr-namespace:AnotherReplayReader" xmlns:local="clr-namespace:AnotherReplayReader"
mc:Ignorable="d" mc:Ignorable="d"
Title="Debug" Height="450" Width="800"> Title="Debug" Height="450" Width="800">
<Grid> <DockPanel Margin="10,10,10,10">
<TextBox x:Name="_textBox" Margin="10,34,10,10" TextWrapping="Wrap" Text="{Binding Path=DebugMessage, Mode=TwoWay}" ScrollViewer.VerticalScrollBarVisibility="Auto"/> <StackPanel DockPanel.Dock="Top"
<Button x:Name="_export" Content="导出日志" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75" Click="OnExport_Click"/> Orientation="Horizontal"
</Grid> Margin="0,0,0,10">
<Button Padding="8,2"
Margin="0,0,10,0"
Content="导出日志"
Click="OnExport_Click" />
<Button Padding="8,2"
Content="清空日志"
Click="OnClear_Click" />
</StackPanel>
<TextBox x:Name="_textBox"
TextWrapping="Wrap"
ScrollViewer.VerticalScrollBarVisibility="Auto" />
</DockPanel>
</Window> </Window>

View File

@ -1,37 +1,31 @@
using System; using Microsoft.Win32;
using System.Collections.Generic; using System;
using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Microsoft.Win32;
namespace AnotherReplayReader namespace AnotherReplayReader
{ {
public sealed class DebugMessageWrapper : INotifyPropertyChanged public sealed class DebugMessageWrapper
{ {
public event PropertyChangedEventHandler PropertyChanged; public readonly struct Proxy
public string DebugMessage
{ {
get => _debugMessage; public readonly string Payload;
set public Proxy(string text)
{ {
_debugMessage = value; Payload = text;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("DebugMessage")); }
public static Proxy operator +(Proxy p, string text)
{
return string.IsNullOrEmpty(p.Payload) ? new Proxy(text) : new Proxy(p.Payload + text);
} }
} }
private string _debugMessage; public event Action<string> NewText;
public Proxy DebugMessage
{
get => new Proxy();
set => NewText?.Invoke(value.Payload);
}
} }
/// <summary> /// <summary>
/// Debug.xaml 的交互逻辑 /// Debug.xaml 的交互逻辑
@ -42,8 +36,8 @@ namespace AnotherReplayReader
public Debug() public Debug()
{ {
DataContext = Instance;
InitializeComponent(); InitializeComponent();
Instance.NewText += t => Dispatcher.Invoke(() => _textBox.AppendText(t));
} }
private void OnExport_Click(object sender, RoutedEventArgs e) private void OnExport_Click(object sender, RoutedEventArgs e)
@ -64,5 +58,10 @@ namespace AnotherReplayReader
} }
} }
} }
private void OnClear_Click(object sender, RoutedEventArgs e)
{
_textBox.Clear();
}
} }
} }

View File

@ -35,7 +35,35 @@
<ColumnDefinition Width="145"/> <ColumnDefinition Width="145"/>
<ColumnDefinition Width="472*"/> <ColumnDefinition Width="472*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBox x:Name="_replayFilterBox" Grid.Row="0" Grid.Column="2" Margin="120,10,10,0" TextWrapping="Wrap" Height="20" VerticalAlignment="Top" /> <Grid Grid.Row="0"
Grid.Column="2"
Margin="120,10,10,0"
Height="20"
VerticalAlignment="Top">
<TextBox x:Name="_replayFilterBox"
TextChanged="ReplayFilterBox_TextChanged" />
<TextBlock IsHitTestVisible="False"
Text="输入录像名称或玩家名称可以筛选录像"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10,0,0,0"
Foreground="DarkGray">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility"
Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=_replayFilterBox}"
Value="">
<Setter Property="Visibility"
Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Button x:Name="_refreshButton" Content="刷新" Grid.Column="2" HorizontalAlignment="Left" Margin="0,11,0,0" VerticalAlignment="Top" Width="80" Click="OnReplayFolderPathBoxTextChanged"/> <Button x:Name="_refreshButton" Content="刷新" Grid.Column="2" HorizontalAlignment="Left" Margin="0,11,0,0" VerticalAlignment="Top" Width="80" Click="OnReplayFolderPathBoxTextChanged"/>
<DataGrid x:Name="_dataGrid" Grid.Row="0" Grid.Column="2" Grid.RowSpan="2" Margin="0,30,10,16" SelectionMode="Single" SelectionChanged="OnReplaySelectionChanged"> <DataGrid x:Name="_dataGrid" Grid.Row="0" Grid.Column="2" Grid.RowSpan="2" Margin="0,30,10,16" SelectionMode="Single" SelectionChanged="OnReplaySelectionChanged">
<DataGrid.Columns> <DataGrid.Columns>

View File

@ -9,6 +9,7 @@ using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Threading;
namespace AnotherReplayReader namespace AnotherReplayReader
{ {
@ -18,6 +19,8 @@ namespace AnotherReplayReader
{ {
string userDataLeafName = null; string userDataLeafName = null;
string replayFolderName = null; string replayFolderName = null;
try
{
using (var view32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) using (var view32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
using (var ra3Key = view32.OpenSubKey(@"Software\Electronic Arts\Electronic Arts\Red Alert 3", false)) using (var ra3Key = view32.OpenSubKey(@"Software\Electronic Arts\Electronic Arts\Red Alert 3", false))
{ {
@ -25,6 +28,11 @@ namespace AnotherReplayReader
userDataLeafName = ra3Key?.GetValue("UserDataLeafName") as string; userDataLeafName = ra3Key?.GetValue("UserDataLeafName") as string;
replayFolderName = ra3Key?.GetValue("ReplayFolderName") as string; replayFolderName = ra3Key?.GetValue("ReplayFolderName") as string;
} }
}
catch (Exception e)
{
Debug.Instance.DebugMessage += $"获取注册表项时出现错误:{e}\r\n";
}
if (string.IsNullOrWhiteSpace(userDataLeafName)) if (string.IsNullOrWhiteSpace(userDataLeafName))
{ {
@ -64,12 +72,6 @@ namespace AnotherReplayReader
set { _replayFolderPath = value; NotifyPropertyChanged(_replayFolderPath); } set { _replayFolderPath = value; NotifyPropertyChanged(_replayFolderPath); }
} }
public string ReplayFilterString
{
get { return _replayFilterString; }
set { _replayFilterString = value; NotifyPropertyChanged(_replayFilterString); }
}
public string ReplayDetails public string ReplayDetails
{ {
get { return _replayDetails; } get { return _replayDetails; }
@ -84,22 +86,21 @@ namespace AnotherReplayReader
public Replay CurrentReplay public Replay CurrentReplay
{ {
get { return _currentReplay; } get => _currentReplay;
set set
{ {
_currentReplay = value; _currentReplay = value;
NotifyPropertyChanged(_currentReplay); NotifyPropertyChanged(_currentReplay);
ReplayDetails = ""; ReplayDetails = "";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ReplayPlayable")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplayPlayable)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ReplaySelected")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplaySelected)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ReplayDamaged")); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplayDamaged)));
} }
} }
private volatile string _replayFolderPath; private string _replayFolderPath;
private volatile string _replayDetails; private string _replayDetails;
private volatile string _replayFilterString; private Replay _currentReplay;
private volatile Replay _currentReplay;
} }
/// <summary> /// <summary>
@ -107,12 +108,12 @@ namespace AnotherReplayReader
/// </summary> /// </summary>
public partial class MainWindow : Window public partial class MainWindow : Window
{ {
private MainWindowProperties _properties = new MainWindowProperties(); private readonly MainWindowProperties _properties = new MainWindowProperties();
private volatile List<Replay> _replayList; private readonly List<Replay> _replayList = new List<Replay>();
private Cache _cache = new Cache(); private readonly Cache _cache = new Cache();
private PlayerIdentity _playerIdentity; private readonly PlayerIdentity _playerIdentity;
private BigMinimapCache _minimapCache; private readonly BigMinimapCache _minimapCache;
private MinimapReader _minimapReader; private readonly MinimapReader _minimapReader;
private CancellationTokenSource _loadReplaysToken; private CancellationTokenSource _loadReplaysToken;
public MainWindow() public MainWindow()
@ -124,7 +125,7 @@ namespace AnotherReplayReader
var handling = new bool[1] { false }; var handling = new bool[1] { false };
Application.Current.Dispatcher.UnhandledException += (sender, eventArgs) => Application.Current.Dispatcher.UnhandledException += (sender, eventArgs) =>
{ {
if (handling == null || handling[0] == true) if (handling == null || handling[0])
{ {
return; return;
} }
@ -132,17 +133,17 @@ namespace AnotherReplayReader
Dispatcher.Invoke(() => MessageBox.Show($"错误:\r\n{eventArgs.Exception}")); Dispatcher.Invoke(() => MessageBox.Show($"错误:\r\n{eventArgs.Exception}"));
}; };
Closing += ((sender, eventArgs) => _cache.Save()); Closing += (sender, eventArgs) => _cache.Save();
_playerIdentity = new PlayerIdentity(_cache); _playerIdentity = new PlayerIdentity(_cache);
_minimapCache = new BigMinimapCache(_cache, _properties.RA3Directory); _minimapCache = new BigMinimapCache(_cache, _properties.RA3Directory);
_minimapReader = new MinimapReader(_minimapCache, _properties.RA3Directory, _properties.CustomMapsDirectory, _properties.ModsDirectory); _minimapReader = new MinimapReader(_minimapCache, _properties.RA3Directory, _properties.CustomMapsDirectory, _properties.ModsDirectory);
LoadReplays(); LoadReplays();
_ = AutoSaveReplays(); Task.Run(() => AutoSaveReplays(Dispatcher, _properties.RA3ReplayFolderPath));
} }
private async Task AutoSaveReplays() private static async Task AutoSaveReplays(Dispatcher dispatcher, string replayFolderPath)
{ {
const string ourPrefix = "自动保存"; const string ourPrefix = "自动保存";
var errorMessageCount = 0; var errorMessageCount = 0;
@ -155,7 +156,7 @@ namespace AnotherReplayReader
{ {
try try
{ {
var changed = (from fileName in Directory.GetFiles(_properties.RA3ReplayFolderPath, "*.RA3Replay") var changed = (from fileName in Directory.GetFiles(replayFolderPath, "*.RA3Replay")
let info = new FileInfo(fileName) let info = new FileInfo(fileName)
where !info.Name.StartsWith(ourPrefix) where !info.Name.StartsWith(ourPrefix)
where !previousFiles.ContainsKey(info.FullName) || previousFiles[info.FullName] != info.LastWriteTimeUtc where !previousFiles.ContainsKey(info.FullName) || previousFiles[info.FullName] != info.LastWriteTimeUtc
@ -238,7 +239,7 @@ namespace AnotherReplayReader
} }
var dateString = $"{date.Year}{date.Month:D2}{date.Day:D2}_{date.Hour:D2}{date.Minute:D2}{date.Second:D2}"; var dateString = $"{date.Year}{date.Month:D2}{date.Day:D2}_{date.Hour:D2}{date.Minute:D2}{date.Second:D2}";
var destinationPath = Path.Combine(_properties.RA3ReplayFolderPath, $"{ourPrefix}-{playerString}{dateString}.RA3Replay"); var destinationPath = Path.Combine(replayFolderPath, $"{ourPrefix}-{playerString}{dateString}.RA3Replay");
try try
{ {
File.Copy(replay.Path, destinationPath, true); File.Copy(replay.Path, destinationPath, true);
@ -253,21 +254,21 @@ namespace AnotherReplayReader
{ {
var errorString = $"自动保存录像时出现错误:\r\n{e}\r\n"; var errorString = $"自动保存录像时出现错误:\r\n{e}\r\n";
Debug.Instance.DebugMessage += errorString; Debug.Instance.DebugMessage += errorString;
_ = Dispatcher.InvokeAsync(() => if (Interlocked.Increment(ref errorMessageCount) == 1)
{
_ = dispatcher.InvokeAsync(() =>
{ {
try try
{
if (Interlocked.Increment(ref errorMessageCount) == 1)
{ {
MessageBox.Show(errorString); MessageBox.Show(errorString);
} }
}
finally finally
{ {
Interlocked.Decrement(ref errorMessageCount); Interlocked.Decrement(ref errorMessageCount);
} }
}); });
} }
}
await Task.Delay(10 * 1000); await Task.Delay(10 * 1000);
} }
@ -275,66 +276,59 @@ namespace AnotherReplayReader
private async void LoadReplays(string nextSelected = null) private async void LoadReplays(string nextSelected = null)
{ {
const string loadingString = "正在加载录像列表,请稍候"; const string loadingString = "正在加载录像列表,请稍候… 已加载 {0} 个录像";
Dispatcher.Invoke(() => try
{ {
_loadReplaysToken?.Cancel();
}
catch (AggregateException e)
{
Debug.Instance.DebugMessage += $"Cancellation failed: {e}";
}
_loadReplaysToken?.Dispose();
_loadReplaysToken = new CancellationTokenSource();
var cancelToken = _loadReplaysToken.Token;
var path = _properties.ReplayFolderPath;
if (_image != null) if (_image != null)
{ {
_image.Source = null; _image.Source = null;
} }
_dataGrid?.Items.Clear();
if (_dataGrid != null) _replayList.Clear();
{
_dataGrid.Items.Clear();
}
});
_loadReplaysToken?.Cancel();
_loadReplaysToken = new CancellationTokenSource();
var cancelToken = _loadReplaysToken.Token;
var path = _properties.ReplayFolderPath;
var task = Task.Run(async () =>
{
var messages = "";
var replayList = new List<Replay>();
if (!Directory.Exists(path)) if (!Directory.Exists(path))
{ {
messages = "这个文件夹并不存在。"; DisplayReplays("这个文件夹并不存在。", nextSelected);
return;
} }
else
var newList = await Task.Run(() =>
{ {
var replays = Directory.EnumerateFiles(path, "*.RA3Replay"); var list = new List<Replay>();
foreach (var replayPath in replays) foreach (var replayPath in Directory.EnumerateFiles(path, "*.RA3Replay"))
{ {
if (cancelToken.IsCancellationRequested)
{
break;
}
try try
{ {
var replay = await Task.Run(() => new Replay(replayPath)); list.Add(new Replay(replayPath));
replayList.Add(replay);
_properties.ReplayDetails = loadingString + $"\n已加载 {replayList.Count} 个录像";
} }
catch (Exception exception) catch (Exception exception)
{ {
Debug.Instance.DebugMessage += $"Uncaught exception when loading replay list: \r\n{exception}\r\n"; Debug.Instance.DebugMessage += $"Uncaught exception when loading replay list: \r\n{exception}\r\n";
continue;
} }
_ = Dispatcher.Invoke(() => _properties.ReplayDetails = string.Format(loadingString, _replayList.Count));
cancelToken.ThrowIfCancellationRequested();
} }
} return list;
return new { Replays = replayList, Messages = messages };
}); });
try _replayList.AddRange(newList);
{ DisplayReplays(string.Empty, nextSelected);
var result = await task;
_replayList = result.Replays;
cancelToken.ThrowIfCancellationRequested();
DisplayReplays(result.Messages, nextSelected);
}
catch (OperationCanceledException) { }
} }
private void DisplayReplays(string message = null, string nextSelected = null) private void DisplayReplays(string message = null, string nextSelected = null)
@ -351,13 +345,7 @@ namespace AnotherReplayReader
{ {
for (var i = 0; i < _dataGrid.Items.Count; ++i) for (var i = 0; i < _dataGrid.Items.Count; ++i)
{ {
var replay = _dataGrid.Items[i] as Replay; if (_dataGrid.Items[i] is Replay replay && replay.Path.Equals(nextSelected, StringComparison.OrdinalIgnoreCase))
if (replay == null)
{
continue;
}
if (replay.Path.Equals(nextSelected, StringComparison.OrdinalIgnoreCase))
{ {
_dataGrid.SelectedIndex = i; _dataGrid.SelectedIndex = i;
OnReplaySelectionChanged(null, null); OnReplaySelectionChanged(null, null);
@ -533,5 +521,10 @@ namespace AnotherReplayReader
LoadReplays(); LoadReplays();
} }
private void ReplayFilterBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
}
} }
} }

View File

@ -1,95 +0,0 @@
private async Task AutoSaveReplays()
{
const string ourPrefix = "自动保存";
// filename and last write time
Dictionary<string, DateTime> previousFiles = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);
// filename and file size
Dictionary<string, long> lastReplays = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase);
while(true)
{
try
{
var changed = from fileName in Directory.GetFiles(_properties.ReplayFolderPath, "*.RA3Replay")
let info = new FileInfo(fileName)
where !info.Name.StartsWith(ourPrefix)
where !previousFiles.ContainsKey(info.FullName) || previousFiles[info.FullName] != info.LastWriteTimeUtc
select info;
foreach (var info in changed)
{
previousFiles[info.FullName] = info.LastWriteTimeUtc;
}
var replays = changed.Select(info =>
{
try
{
return new Replay(info.FullName);
}
catch (Exception)
{
return null;
}
}).Where(replay => replay != null);
var newLastReplays = from replay in replays
let threshold = Math.Abs((DateTime.UtcNow - replay.Date).TotalSeconds)
where threshold < 20
select replay;
var toBeChecked = newLastReplays.ToDictionary(replay => replay.FileName, StringComparer.OrdinalIgnoreCase);
foreach (var savedLastReplay in lastReplays)
{
if (!toBeChecked.ContainsKey(savedLastReplay.Key))
{
try
{
toBeChecked.Add(savedLastReplay.Key, new Replay(savedLastReplay.Key));
}
catch(Exception)
{
}
}
}
foreach (var kv in toBeChecked)
{
var replay = kv.Value;
if (lastReplays.TryGetValue(kv.Key, out var fileSize))
{
if (fileSize == replay.Size)
{
// skip if size is not changed
continue;
}
}
lastReplays[kv.Key] = replay.Size;
var date = replay.Date;
var numberOfPlayers = replay.NumberOfPlayingPlayers;
var playerString = $"{numberOfPlayers}名玩家";
if (replay.NumberOfPlayingPlayers <= 2)
{
var playingPlayers = from player in replay.Players
let faction = ModData.GetFaction(replay.Mod, player.FactionID)
where faction.Kind != FactionKind.Observer
select $"{player}({faction.Name})";
playerString = playingPlayers.Aggregate((x, y) => x + y);
}
var dateString = $"{date.Year}-{date.Month}-{date.Day}_{date.Hour}:{date.Minute}";
File.Copy(replay.FileName, $"{_properties.ReplayFolderPath}/{ourPrefix}-{playerString}{dateString}.RA3Replay");
}
}
catch(Exception e)
{
_ = Dispatcher.InvokeAsync(() => MessageBox.Show($"自动保存录像时出现错误:\r\n{e}"));
}
await Task.Delay(10 * 1000);
}
}