diff --git a/AnotherReplayReader.csproj b/AnotherReplayReader.csproj
index 3bf032b..22be21c 100644
--- a/AnotherReplayReader.csproj
+++ b/AnotherReplayReader.csproj
@@ -91,6 +91,9 @@
1.0.7
+
+ 3.0.0
+
1.0.0
@@ -118,6 +121,7 @@
+
diff --git a/MainWindow.xaml b/MainWindow.xaml
index 66367fc..4051df8 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -43,7 +43,7 @@
-
+
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index 2e8e066..517fd37 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -55,9 +55,15 @@ namespace AnotherReplayReader
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
- private void NotifyPropertyChanged(T value, [CallerMemberName] string propertyName = "")
+ private void SetAndNotifyPropertyChanged(ref T target, T newValue, [CallerMemberName] string propertyName = "")
{
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ var equals = Equals(target, newValue);
+ target = newValue;
+ if (!equals)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
}
public string RA3Directory { get; }
@@ -68,14 +74,14 @@ namespace AnotherReplayReader
public string ReplayFolderPath
{
- get { return _replayFolderPath; }
- set { _replayFolderPath = value; NotifyPropertyChanged(_replayFolderPath); }
+ get => _replayFolderPath;
+ set => SetAndNotifyPropertyChanged(ref _replayFolderPath, value);
}
public string ReplayDetails
{
- get { return _replayDetails; }
- set { _replayDetails = value; NotifyPropertyChanged(_replayDetails); }
+ get => _replayDetails;
+ set => SetAndNotifyPropertyChanged(ref _replayDetails, value);
}
public bool ReplaySelected => _currentReplay != null;
@@ -89,8 +95,7 @@ namespace AnotherReplayReader
get => _currentReplay;
set
{
- _currentReplay = value;
- NotifyPropertyChanged(_currentReplay);
+ SetAndNotifyPropertyChanged(ref _currentReplay, value);
ReplayDetails = "";
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplayPlayable)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ReplaySelected)));
@@ -109,19 +114,18 @@ namespace AnotherReplayReader
public partial class MainWindow : Window
{
private readonly MainWindowProperties _properties = new MainWindowProperties();
- private readonly List _replayList = new List();
private readonly Cache _cache = new Cache();
private readonly PlayerIdentity _playerIdentity;
private readonly BigMinimapCache _minimapCache;
private readonly MinimapReader _minimapReader;
+ private List _replayList = new List();
+ private List _pinyinList = new List();
private CancellationTokenSource _loadReplaysToken;
public MainWindow()
{
DataContext = _properties;
- InitializeComponent();
-
var handling = new bool[1] { false };
Application.Current.Dispatcher.UnhandledException += (sender, eventArgs) =>
{
@@ -133,11 +137,11 @@ namespace AnotherReplayReader
Dispatcher.Invoke(() => MessageBox.Show($"错误:\r\n{eventArgs.Exception}"));
};
- Closing += (sender, eventArgs) => _cache.Save();
-
_playerIdentity = new PlayerIdentity(_cache);
_minimapCache = new BigMinimapCache(_cache, _properties.RA3Directory);
_minimapReader = new MinimapReader(_minimapCache, _properties.RA3Directory, _properties.CustomMapsDirectory, _properties.ModsDirectory);
+ InitializeComponent();
+ Closing += (sender, eventArgs) => _cache.Save();
LoadReplays();
Task.Run(() => AutoSaveReplays(Dispatcher, _properties.RA3ReplayFolderPath));
@@ -295,8 +299,13 @@ namespace AnotherReplayReader
{
_image.Source = null;
}
- _dataGrid?.Items.Clear();
+ if (_dataGrid != null)
+ {
+ _dataGrid.ItemsSource = Array.Empty();
+ _dataGrid.Items.Refresh();
+ }
_replayList.Clear();
+ _pinyinList.Clear();
if (!Directory.Exists(path))
{
@@ -304,9 +313,10 @@ namespace AnotherReplayReader
return;
}
- var newList = await Task.Run(() =>
+ var (newList, newPinyinList) = await Task.Run(() =>
{
var list = new List();
+ var pinyinList = new List();
foreach (var replayPath in Directory.EnumerateFiles(path, "*.RA3Replay"))
{
if (cancelToken.IsCancellationRequested)
@@ -315,7 +325,9 @@ namespace AnotherReplayReader
}
try
{
- list.Add(new Replay(replayPath));
+ var replay = new Replay(replayPath);
+ list.Add(replay);
+ pinyinList.Add(replay.ToPinyin(_playerIdentity));
}
catch (Exception exception)
{
@@ -324,21 +336,22 @@ namespace AnotherReplayReader
}
_ = Dispatcher.Invoke(() => _properties.ReplayDetails = string.Format(loadingString, _replayList.Count));
}
- return list;
+ return (list, pinyinList);
});
- _replayList.AddRange(newList);
+ _replayList = newList;
+ _pinyinList = newPinyinList;
DisplayReplays(string.Empty, nextSelected);
}
- private void DisplayReplays(string message = null, string nextSelected = null)
+ private void DisplayReplays(string message = null, string nextSelected = null, Replay[] filtered = null)
{
- var filtered = _replayList;
Dispatcher.Invoke(() =>
{
+ filtered = filtered ?? _replayList.ToArray();
_properties.CurrentReplay = null;
- _dataGrid.Items.Clear();
- filtered.ForEach(x => _dataGrid.Items.Add(x));
+ _dataGrid.ItemsSource = filtered;
+ _dataGrid.Items.Refresh();
_properties.ReplayDetails = message;
if (nextSelected != null)
@@ -410,7 +423,7 @@ namespace AnotherReplayReader
break;
}
var factionName = ModData.GetFaction(replay.Mod, player.FactionID).Name;
- var realName = replay.Type == ReplayType.Lan ? _playerIdentity.QueryRealName(player.PlayerIP) : string.Empty;
+ var realName = replay.Type == ReplayType.Lan ? _playerIdentity.FormatRealName(player.PlayerIP) : string.Empty;
_properties.ReplayDetails += $"{player.PlayerName + realName},{factionName}\n";
}
@@ -522,9 +535,39 @@ namespace AnotherReplayReader
LoadReplays();
}
- private void ReplayFilterBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
+ private async void ReplayFilterBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
-
+ try
+ {
+ var pinyins = _replayFilterBox.Text.Split(',', ' ', ',')
+ .Select(x => x.ToPinyin())
+ .Where(x => !string.IsNullOrEmpty(x))
+ .ToArray();
+ if (!pinyins.Any())
+ {
+ DisplayReplays();
+ return;
+ }
+ var tokenSource = _loadReplaysToken;
+ var token = tokenSource.Token;
+ var list = _pinyinList.ToArray();
+ var result = await Task.Run(() =>
+ {
+ var query = from replay in list.AsParallel()
+ where pinyins.Any(pinyin => replay.MatchPinyin(pinyin))
+ select replay.Replay;
+ return query.ToArray();
+ });
+ if (token.IsCancellationRequested || _loadReplaysToken != tokenSource)
+ {
+ return;
+ }
+ DisplayReplays(null, null, result);
+ }
+ catch (Exception ex)
+ {
+ Debug.Instance.DebugMessage += $"Exception when filtering replays: {ex}";
+ }
}
}
}
diff --git a/PinyinExtensions.cs b/PinyinExtensions.cs
new file mode 100644
index 0000000..cc9a6af
--- /dev/null
+++ b/PinyinExtensions.cs
@@ -0,0 +1,76 @@
+using NPinyin;
+using System;
+using System.Collections.Generic;
+
+namespace AnotherReplayReader
+{
+ class PinyinReplayData
+ {
+ public Replay Replay { get; }
+ public string Name { get; }
+ public string Map { get; }
+ public string Mod { get; }
+ public List Players { get; } = new List();
+ public List RealNames { get; } = new List();
+ public List Factions { get; } = new List();
+
+ public PinyinReplayData(Replay replay, PlayerIdentity playerIdentity)
+ {
+ Replay = replay;
+ Name = replay.FileName.ToPinyin();
+ Map = replay.MapName.ToPinyin();
+ Mod = replay.Mod.ModName.ToPinyin();
+ foreach (var player in replay.Players)
+ {
+ void AddIfNotEmpty(List target, string s)
+ {
+ s = s.ToPinyin();
+ if (!string.IsNullOrEmpty(s))
+ {
+ target.Add(s);
+ }
+ }
+ AddIfNotEmpty(Players, player.PlayerName);
+ AddIfNotEmpty(RealNames, playerIdentity.GetRealName(player.PlayerIP));
+ AddIfNotEmpty(Factions, ModData.GetFaction(replay.Mod, player.FactionID).Name);
+ }
+ }
+
+ public bool MatchPinyin(string pinyin)
+ {
+ return Name.ContainsIgnoreCase(pinyin)
+ || Players.FindIndex(s => s.ContainsIgnoreCase(pinyin)) != -1
+ || RealNames.FindIndex(s => s.ContainsIgnoreCase(pinyin)) != -1
+ || Map.ContainsIgnoreCase(pinyin)
+ || Mod.ContainsIgnoreCase(pinyin)
+ || Factions.FindIndex(s => s.ContainsIgnoreCase(pinyin)) != -1;
+ }
+ }
+
+ static class PinyinExtensions
+ {
+ public static bool ContainsIgnoreCase(this string self, string s)
+ {
+ return s != null && self.IndexOf(s, StringComparison.CurrentCultureIgnoreCase) != -1;
+ }
+
+ public static string ToPinyin(this string self)
+ {
+ string pinyin;
+ try
+ {
+ pinyin = Pinyin.GetPinyin(self);
+ }
+ catch
+ {
+ return null;
+ }
+ return pinyin.Replace(" ", "");
+ }
+
+ public static PinyinReplayData ToPinyin(this Replay replay, PlayerIdentity playerIdentity)
+ {
+ return new PinyinReplayData(replay, playerIdentity);
+ }
+ }
+}
diff --git a/PlayerIdentity.cs b/PlayerIdentity.cs
index 7fcdfdd..d368fd3 100644
--- a/PlayerIdentity.cs
+++ b/PlayerIdentity.cs
@@ -1,12 +1,12 @@
using System;
-using System.IO;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Net;
-using System.Web;
-using System.Web.Script.Serialization;
using System.Text;
using System.Threading.Tasks;
+using System.Web;
+using System.Web.Script.Serialization;
namespace AnotherReplayReader
{
@@ -14,12 +14,12 @@ namespace AnotherReplayReader
{
public static string SimpleIPToString(uint ip)
{
- return $"{ip / 256 / 256 / 256}.{(ip / 256 / 256) % 256}.{(ip / 256) % 256}.{ip % 256}";
+ return $"{ip / 256 / 256 / 256}.{ip / 256 / 256 % 256}.{ip / 256 % 256}.{ip % 256}";
}
public uint IP
{
- get { return _ip; }
+ get => _ip;
set
{
_ip = value;
@@ -27,9 +27,20 @@ namespace AnotherReplayReader
}
}
public string IPString { get; private set; }
- public string ID { get; set; }
+ public string ID
+ {
+ get => _id;
+ set
+ {
+ _id = value;
+ _pinyin = _id.ToPinyin();
+ }
+ }
+ public string PinyinID => _pinyin;
private uint _ip;
+ private string _id;
+ private string _pinyin;
}
internal class PlayerIdentity
@@ -38,7 +49,7 @@ namespace AnotherReplayReader
private Cache _cache;
private volatile IReadOnlyDictionary _list;
-
+
public PlayerIdentity(Cache cache)
{
_cache = cache;
@@ -57,7 +68,7 @@ namespace AnotherReplayReader
var data = Encoding.UTF8.GetString(bytes.Select((x, i) => (byte)(x ^ id[i % id.Length])).ToArray());
var serializer = new JavaScriptSerializer();
var cachedTable = serializer.Deserialize>(data);
- if(cachedTable == null)
+ if (cachedTable == null)
{
_list = null;
return;
@@ -90,7 +101,7 @@ namespace AnotherReplayReader
var response = reader.ReadToEnd();
var serializer = new JavaScriptSerializer();
var temp = serializer.Deserialize>(response);
- if(temp == null)
+ if (temp == null)
{
_list = null;
return;
@@ -112,7 +123,7 @@ namespace AnotherReplayReader
{
var list = _list;
- if(!IsListUsable(list))
+ if (!IsListUsable(list))
{
return new List();
}
@@ -120,7 +131,7 @@ namespace AnotherReplayReader
return _list.Select((kv) => new IPAndPlayer { IP = kv.Key, ID = kv.Value }).OrderBy(x => x.IP).ToList();
}
- public string QueryRealName(uint ip)
+ public string GetRealName(uint ip)
{
var list = _list;
@@ -129,12 +140,26 @@ namespace AnotherReplayReader
return string.Empty;
}
- if(ip == 0)
+ if (ip == 0)
{
return string.Empty;
}
- var name = list.TryGetValue(ip, out var realName) ? realName : IPAndPlayer.SimpleIPToString(ip);
+ if (!list.TryGetValue(ip, out var name))
+ {
+ return string.Empty;
+ }
+
+ return name;
+ }
+
+ public string FormatRealName(uint ip)
+ {
+ var name = GetRealName(ip);
+ if (string.IsNullOrEmpty(name))
+ {
+ name = IPAndPlayer.SimpleIPToString(ip);
+ }
return $"({name})";
}
diff --git a/Replay.cs b/Replay.cs
index 74f672f..4a04cee 100644
--- a/Replay.cs
+++ b/Replay.cs
@@ -19,7 +19,6 @@ namespace AnotherReplayReader
public string PlayerName { get; private set; }
public uint PlayerIP { get; private set; }
- public string PlayerRealName { get; private set; }
public int FactionID { get; private set; }
public int Team { get; private set; }
diff --git a/Window1.xaml.cs b/Window1.xaml.cs
index d4960ff..ed294bb 100644
--- a/Window1.xaml.cs
+++ b/Window1.xaml.cs
@@ -1,4 +1,5 @@
-using System;
+using NPinyin;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -49,9 +50,21 @@ namespace AnotherReplayReader
private void Display(string filter = "", string nameFilter = "")
{
+ var pinyin = nameFilter.ToPinyin();
var newList = _identity
.AsSortedList()
- .Where(x => x.IPString.StartsWith(filter) && x.ID.StartsWith(nameFilter, StringComparison.CurrentCultureIgnoreCase))
+ .Where(x =>
+ {
+ if (!x.IPString.StartsWith(filter))
+ {
+ return false;
+ }
+ if (x.PinyinID?.ContainsIgnoreCase(pinyin) is true)
+ {
+ return true;
+ }
+ return x.ID.ContainsIgnoreCase(nameFilter);
+ })
.ToArray();
Dispatcher.Invoke(() =>
{