Skip to content

Commit

Permalink
Merge pull request #40 from rioil/refactoring
Browse files Browse the repository at this point in the history
リファクタリング
  • Loading branch information
rioil authored Sep 29, 2024
2 parents 99ed158 + 60d832b commit 3f6a98e
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 96 deletions.
10 changes: 1 addition & 9 deletions VRChatLifelog/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Livet;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -48,13 +47,6 @@ public static IHostBuilder CreateHostBuilder()
services.AddHostedService(p => p.GetRequiredService<LogWatcherService>());
services.AddOptions<LogWatchOption>();
services.AddHostedService<NotifyIconService>();

//services.AddSingleton<MainWindow>();
})
.ConfigureHostConfiguration(config =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config.AddJsonFile("appsettings.json", true, true);
})
.ConfigureLogging(logging =>
{
Expand Down Expand Up @@ -152,7 +144,7 @@ private async Task MigrateDbData(LifelogContext context)
}

int historyId = 1;
foreach(var history in context.JoinLeaveHistories.OrderBy(h => h.Joined))
foreach (var history in context.JoinLeaveHistories.OrderBy(h => h.Joined))
{
history.Id = historyId++;
}
Expand Down
10 changes: 10 additions & 0 deletions VRChatLifelog/Data/FilteredData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace VRChatLifelog.Data
{
/// <summary>
/// フィルター条件に一致したデータ
/// </summary>
/// <param name="MatchedUserNames"> あいまい検索によって一致したユーザー名 </param>
/// <param name="MatchedWorldNames"> あいまい検索によって一致したワールド名 </param>
/// <param name="LocationHistories"> 場所の履歴 </param>
internal record FilteredData(string[] MatchedUserNames, string[] MatchedWorldNames, LocationHistory[] LocationHistories);
}
126 changes: 126 additions & 0 deletions VRChatLifelog/Data/LocationHistoryFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace VRChatLifelog.Data
{
/// <summary>
/// 場所の履歴をフィルタリングするための条件
/// </summary>
internal class LocationHistoryFilter
{
/// <summary>
/// 日付による絞り込みを行うかどうか
/// </summary>
public bool FilterByDate { get; set; }

/// <summary>
/// 人物による絞り込みを行うかどうか
/// </summary>
public bool FilterByPerson { get; set; }

/// <summary>
/// ワールド名による絞り込みを行うかどうか
/// </summary>
public bool FilterByWorldName { get; set; }

/// <summary>
/// 日付の範囲の開始日時
/// </summary>
public DateTime? DateFirst { get; set; }

/// <summary>
/// 日付の範囲の終了日時
/// </summary>
public DateTime? DateLast { get; set; }

/// <summary>
/// 人物による絞り込みのクエリ
/// </summary>
public string? PersonQuery { get; set; }

/// <summary>
/// ワールド名に夜絞り込みのクエリ
/// </summary>
public string? WorldNameQuery { get; set; }

/// <summary>
/// 場所の履歴リストからフィルター条件に一致する項目を取得します.
/// </summary>
/// <param name="context">履歴データ</param>
/// <returns>条件に一致したデータ</returns>
public async ValueTask<FilteredData> ApplyAsync(LifelogContext context)
{
IQueryable<LocationHistory> result;
string[] userNames = [];
string[] worldNames = [];

// 対象人物による絞り込み
if (FilterByPerson && !string.IsNullOrEmpty(PersonQuery))
{
// 対象人物のJoin/Leave履歴を取得
var joinLeaveHistories = context.JoinLeaveHistories
.Where(h => h.PlayerName.Contains(PersonQuery));
/*
* MEMO:
* 文字列一致のクエリが複数回実行されることになるが,配列化してキャッシュしてはいけない
* 配列はIAsyncEnumerableを実装していないため,LocationHistoriesを作成する最後のToArrayAsyncで例外が発生してしまう
*/

// TODO 期間指定の反映
userNames = await joinLeaveHistories.Select(h => h.PlayerName)
.Distinct()
.OrderBy(name => name)
.ToArrayAsync();

// Join/Leave情報から対応するインスタンス情報を取得
result = joinLeaveHistories.Include(h => h.LocationHistory)
.Select(joinleave => joinleave.LocationHistory)
.AsQueryable();
}
else
{
result = context.LocationHistories.AsQueryable();
}

// 対象ワールドによる絞り込み
if (FilterByWorldName && !string.IsNullOrEmpty(WorldNameQuery))
{
result = result.Where(l => l.WorldName.Contains(WorldNameQuery));

// TODO 期間指定の反映
worldNames = await result.Select(l => l.WorldName)
.Distinct()
.OrderBy(name => name)
.ToArrayAsync();
}

// 日付による絞り込み
if (FilterByDate && (DateFirst is not null || DateLast is not null))
{
if (DateFirst is not null)
{
result = result.Where(h => DateFirst <= h.Joined);
}

var end = DateLast + TimeSpan.FromDays(1);
if (end is not null)
{
if (end < DateTime.Now)
{
result = result.Where(h => h.Left < end);
}
else
{
result = result.Where(h => h.Left == null || h.Left < end);
}
}
}

var locationHistories = await result.OrderByDescending(h => h.Joined).ToArrayAsync();

return new FilteredData(userNames, worldNames, locationHistories);
}
}
}
122 changes: 35 additions & 87 deletions VRChatLifelog/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Livet.Commands;
using Livet.EventListeners.WeakEvents;
using Livet.Messaging;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Reactive.Bindings;
using System;
Expand All @@ -21,13 +20,14 @@ public class MainWindowViewModel : ViewModel
{
internal MainWindowViewModel(IServiceProvider serviceProvider)
{
_logWatcher = serviceProvider.GetRequiredService<LogWatcherService>();
var logWatcher = serviceProvider.GetRequiredService<LogWatcherService>();

CompositeDisposable.Add(new LivetWeakEventListener<WatchingFileCountChangedEventHandler, WatchingFileCountChangedEventArgs>(
h => new WatchingFileCountChangedEventHandler(h),
a => _logWatcher.WatchingFileCountChanged += a,
a => _logWatcher.WatchingFileCountChanged -= a,
a => logWatcher.WatchingFileCountChanged += a,
a => logWatcher.WatchingFileCountChanged -= a,
(sender, args) => DispatcherHelper.UIDispatcher.Invoke(() => WatchingFileCount.Value = args.Count)));
WatchingFileCount.Value = _logWatcher.WatchingFileCount;
WatchingFileCount.Value = logWatcher.WatchingFileCount;

SelectedLocationHistory = new ReactivePropertySlim<LocationHistory?>();
CompositeDisposable.Add(SelectedLocationHistory.Subscribe(x =>
Expand All @@ -36,6 +36,10 @@ internal MainWindowViewModel(IServiceProvider serviceProvider)
{
UpdateJoinLeaveHistory(SelectedLocationHistory.Value);
}
else
{
JoinLeaveHistories.Value = [];
}
}));

SelectedJoinLeaveHistory = new();
Expand Down Expand Up @@ -64,11 +68,6 @@ public void Initialize()
/// </summary>
private const string TransitionMessageKey = "Transition";

/// <summary>
/// ログ監視サービス
/// </summary>
private readonly LogWatcherService _logWatcher;

#region 変更通知プロパティ
/// <summary>
/// 表示期間の開始日
Expand All @@ -95,11 +94,6 @@ public void Initialize()
/// </summary>
public ReactivePropertySlim<bool> FilterByPerson { get; } = new();

/// <summary>
/// あいまい検索によって一致したユーザー名
/// </summary>
public ReactivePropertySlim<string[]?> MatchedUserNames { get; } = new();

/// <summary>
/// ワールド名のクエリ
/// </summary>
Expand All @@ -110,6 +104,11 @@ public void Initialize()
/// </summary>
public ReactivePropertySlim<bool> FilterByWorldName { get; } = new();

/// <summary>
/// あいまい検索によって一致したユーザー名
/// </summary>
public ReactivePropertySlim<string[]?> MatchedUserNames { get; } = new();

/// <summary>
/// あいまい検索によって一致したワールド名
/// </summary>
Expand All @@ -126,14 +125,14 @@ public void Initialize()
public ReactivePropertySlim<LocationHistory?> SelectedLocationHistory { get; }

/// <summary>
/// 選択されたプレイヤー
/// 滞在プレイヤーの履歴
/// </summary>
public ReactivePropertySlim<JoinLeaveHistory?> SelectedJoinLeaveHistory { get; }
public ReactivePropertySlim<JoinLeaveHistory[]?> JoinLeaveHistories { get; } = new();

/// <summary>
/// 滞在プレイヤーの履歴
/// 選択されたプレイヤー
/// </summary>
public ReactivePropertySlim<JoinLeaveHistory[]?> JoinLeaveHistories { get; } = new();
public ReactivePropertySlim<JoinLeaveHistory?> SelectedJoinLeaveHistory { get; }

/// <summary>
/// 監視中のファイル数
Expand All @@ -147,76 +146,25 @@ public void Initialize()
/// </summary>
public async void ApplyFilter()
{
IQueryable<LocationHistory> result;
MatchedUserNames.Value = null;
MatchedWorldNames.Value = null;
JoinLeaveHistories.Value = null;

using var dbContext = new LifelogContext();
// 対象人物による絞り込み
if (FilterByPerson.Value && !string.IsNullOrEmpty(PersonQuery.Value))
var filter = new LocationHistoryFilter
{
// 対象人物のJoin/Leave履歴を取得
var joinLeaveHistories = dbContext.JoinLeaveHistories
.Where(h => h.PlayerName.Contains(PersonQuery.Value));
/*
* MEMO:
* 文字列一致のクエリが複数回実行されることになるが,配列化してキャッシュしてはいけない
* 配列はIAsyncEnumerableを実装していないため,LocationHistoriesを作成する最後のToArrayAsyncで例外が発生してしまう
*/

// TODO 期間指定の反映
var userNames = joinLeaveHistories.Select(h => h.PlayerName).Distinct().OrderBy(name => name);
MatchedUserNames.Value = [.. userNames];

// Join/Leave情報から対応するインスタンス情報を取得
result = joinLeaveHistories
.Include(h => h.LocationHistory)
.Select(joinleave => joinleave.LocationHistory)
.AsQueryable();
}
else
{
result = dbContext.LocationHistories.AsQueryable();
}

// 対象ワールドによる絞り込み
if (FilterByWorldName.Value && !string.IsNullOrEmpty(WorldNameQuery.Value))
{
result = result.Where(l => l.WorldName.Contains(WorldNameQuery.Value));

// TODO 期間指定の反映
var worldNames = result.Select(l => l.WorldName).Distinct().OrderBy(name => name);
MatchedWorldNames.Value = [.. worldNames];
}

// 日付による絞り込み
if (FilterByDate.Value && (First.Value is not null || Last.Value is not null))
{
if (First.Value is not null)
{
result = result.Where(h => First.Value <= h.Joined);
}

var end = Last.Value + TimeSpan.FromDays(1);
if (end is not null)
{
if (end < DateTime.Now)
{
result = result.Where(h => h.Left < end);
}
else
{
result = result.Where(h => h.Left == null || h.Left < end);
}
}
}

result = result.OrderByDescending(h => h.Joined);
LocationHistories.Value = await result.ToArrayAsync();
if (SelectedLocationHistory.Value is not null && LocationHistories.Value.Contains(SelectedLocationHistory.Value))
FilterByDate = FilterByDate.Value,
FilterByPerson = FilterByPerson.Value,
FilterByWorldName = FilterByWorldName.Value,
DateFirst = First.Value,
DateLast = Last.Value,
PersonQuery = PersonQuery.Value,
WorldNameQuery = WorldNameQuery.Value,
};
var filteredData = await filter.ApplyAsync(new LifelogContext());

LocationHistories.Value = filteredData.LocationHistories;
MatchedUserNames.Value = filteredData.MatchedUserNames;
MatchedWorldNames.Value = filteredData.MatchedWorldNames;

if (SelectedLocationHistory.Value is not null && LocationHistories.Value.All(x => x.Id != SelectedLocationHistory.Value.Id))
{
UpdateJoinLeaveHistory(SelectedLocationHistory.Value);
SelectedLocationHistory.Value = null;
}
}
private ViewModelCommand? _applyFilterCommand;
Expand Down

0 comments on commit 3f6a98e

Please sign in to comment.