From 7f089cbefdaa906d53945d38a4aad2921c74fc47 Mon Sep 17 00:00:00 2001 From: belbetQ <145178901+creepiez@users.noreply.github.com> Date: Tue, 27 May 2025 23:38:42 +0900 Subject: [PATCH] =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E3=81=99=E3=82=8BDBMS?= =?UTF-8?q?=E3=81=ABMySQL=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + README.md | 110 +++++++++- .../MainWindow.xaml.cs | 38 +++- .../Properties/launchSettings.json | 10 + .../VRChatActivityLogModel.cs | 4 +- .../VRChatActivityLogViewer.csproj | 13 ++ .../VRChatActivityLogViewer/appsettings.json | 7 + .../VRChatActivityLogger/Program.cs | 30 ++- .../PublishProfiles/PublishProfile.pubxml | 28 +-- .../Properties/launchSettings.json | 17 +- .../VRChatActivityLogger.cs | 90 ++++---- .../VRChatActivityLogger.csproj | 10 + .../VRChatActivityLogger/appsettings.json | 10 + ...abaseContext.cs => ActivityContextBase.cs} | 34 +-- .../Database/ActivityContextMariaDb.cs | 76 +++++++ .../Database/ActivityContextSQLite.cs | 111 ++++++++++ .../Database/DatabaseMigration.cs | 205 ------------------ .../Database/DbConfig.cs | 46 ++++ .../Database/DbKind.cs | 14 ++ .../Database/DbMigrationBase.cs | 121 +++++++++++ .../Database/DbMigrationMariaDb.cs | 24 ++ .../Database/DbMigrationSQLite.cs | 23 ++ .../Database/DbOperatorFactory.cs | 77 +++++++ .../Database/IDbMigration.cs | 32 +++ .../VRChatActivityToolsShared.csproj | 1 + 25 files changed, 836 insertions(+), 297 deletions(-) create mode 100644 .gitignore create mode 100644 VRChatActivityLogViewer/VRChatActivityLogViewer/Properties/launchSettings.json create mode 100644 VRChatActivityLogViewer/VRChatActivityLogViewer/appsettings.json create mode 100644 VRChatActivityLogger/VRChatActivityLogger/appsettings.json rename VRChatActivityToolsShared/VRChatActivityToolsShared/Database/{DatabaseContext.cs => ActivityContextBase.cs} (52%) create mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextMariaDb.cs create mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextSQLite.cs delete mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DatabaseMigration.cs create mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbConfig.cs create mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbKind.cs create mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationBase.cs create mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationMariaDb.cs create mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationSQLite.cs create mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbOperatorFactory.cs create mode 100644 VRChatActivityToolsShared/VRChatActivityToolsShared/Database/IDbMigration.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b340bd8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/VRChatActivityLogViewer/VRChatActivityLogViewer/appsettings.Development.json +/VRChatActivityLogger/VRChatActivityLogger/appsettings.Development.json diff --git a/README.md b/README.md index fb2aaac..cdd4f1c 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ VRChatのログを解析し、活動履歴のデータベースを作成しま コンソール画面を表示しないで実行したい場合は以下の起動オプションを使用してください。 +Windows以外では本起動オプションは無効となります。 ``` VRChatActivityLogger.exe -console false ``` @@ -68,15 +69,68 @@ VRChatActivityLogger.exe -console false 画面上のLoggerボタンをクリックする事でも実行する事ができます。 +### appsettings.json + +記入例は下記です。 +``` json +{ + "ConnectionStrings": { + "VRChatActivityLog": "Server=localhost;Database=VRChatActivityLog;Username=usr;Password=pswd;" + }, + "DbKind": "MariaDB", + + "VRChat": { + "LogFileDir": "" + } +} +``` +#### `.ConnectionStrings.VRChatActivityLog` +DBへの接続文字列です。 + +SQLiteモードのみ、空文字を指定すると`(カレントディレクトリ)\VRChatActivityLog.db`となります。 + +詳細は各EFCoreのドキュメントをご覧ください。 + +[MySQL .NET Connection String Options](https://mysqlconnector.net/connection-options/) + +[Entity Framework Core > Microsoft.Data.Sqlite > Basic usage](https://github.com/dotnet/efcore?tab=readme-ov-file#basic-usage-1) + +#### `.ConnectionStrings.DbKind` +使用するDBの種類です。下記をお使いください。 + +- `"MariaDB"` +- `"SQLite"` + +Note: MySQLでも`"MariaDB"`を指定してください。相違はEFCoreが自動調整します。 + +#### `.VRChat.LogFileDir` +VRChatのログディレクトリです。 + +空文字にすると自動的にローカルマシンのアプリケーションデータフォルダとなります。 + +出力先を変えている方だけご利用ください。 + +#### 開発時 +ローカル環境では`appsettings.json`ではなく`appsettings.Development.json`を使用してください。 +`appsettings.Development.json`が存在する場合は`appsettings.json`よりも`appsettings.Development.json`が優先されます。 + +1. `appsettings.json`をプログラムディレクトリに`appsettings.Development.json`としてコピーする +2. `appsettings.Development.json`を各自環境の設定に書き換える +3. バイナリを実行する + ### VRChatActivityLog.db -VRChatActivityLogger.exeを実行すると作成されるデータベースファイルです。 +SQLiteモードでVRChatActivityLogger.exeを実行すると作成されるデータベースファイルです。 中身はSQLite3のデータなので、他のアプリと連携したりもできると思います。 +ただし、接続文字列の特性上本ファイルのファイル名は可変です。 + ## 既知の問題 inviteの送信履歴などから送信先となるユーザ名を表示する事はできません。VRChatのログにユーザ名が記録されない為です。 +ネットワーク上のDBへ接続出来るか確認ができません。EFCoreの`CanConnect()`が「DBが存在するかを返す」為です。 + ## ライセンス このプログラムにはMITライセンスが適用されます。 @@ -147,11 +201,65 @@ If you have not yet created a database, run VRChatActivityLogger.exe first. You can also run it by clicking the Logger button on the screen. +### appsettings.json + +The sample is below. + +``` json +{ + "ConnectionStrings": { + "VRChatActivityLog": "Server=localhost;Database=VRChatActivityLog;Username=usr;Password=pswd;" + }, + "DbKind": "MariaDB", + + "VRChat": { + "LogFileDir": "" + } +} +``` + +#### `.ConnectionStrings.VRChatActivityLog` + +Connection string to your DB. + +In only SQLite mode, if you set empty string, this path set to `(current dir.)\VRChatActivityLog.db`. + +For more details, see also each EFCore documents. + +[MySQL .NET Connection String Options](https://mysqlconnector.net/connection-options/) + +[Entity Framework Core > Microsoft.Data.Sqlite > Basic usage](https://github.com/dotnet/efcore?tab=readme-ov-file#basic-usage-1) + +#### `.ConnectionStrings.DbKind` + +Kind for your DB. Please use below. + +- `"MariaDB"` +- `"SQLite"` + +Note: If you want to use MySQL, specify `"MariaDB"`. The deference will be fixed by EFCore. + +#### `.VRChat.LogFileDir` + +Path for VRChat log directory. + +If empty specified, automatically set your local machine's application data folder. + +Only who changes log output folder can use this option. + +#### Development +In local develop enviroment, you can use `appsettings.Development.json` instead of `appsettings.json`. +If `appsettings.Development.json` exists in the directory, `appsettings.Development.json` will be prioritized than `appsettings.json`. + +1. Copy `appsettings.json` as `appsettings.Development.json` to your program directory. +2. Edit `appsettings.Development.json` as your enviroment. +3. Run or debug your binary. ### VRChatActivityLog.db This is the database file that is created when you run VRChatActivityLogger.exe. The contents are SQLite3 data, so it can be used in conjunction with other applications. +This file name/path are variable due to the connection string. ## Known issues It is not possible to display the name of the user to whom invitations are sent from the invitations sending history, because the user name is not recorded in the VRChat log. diff --git a/VRChatActivityLogViewer/VRChatActivityLogViewer/MainWindow.xaml.cs b/VRChatActivityLogViewer/VRChatActivityLogViewer/MainWindow.xaml.cs index a8bf1ad..b965068 100644 --- a/VRChatActivityLogViewer/VRChatActivityLogViewer/MainWindow.xaml.cs +++ b/VRChatActivityLogViewer/VRChatActivityLogViewer/MainWindow.xaml.cs @@ -9,6 +9,7 @@ using System.Windows.Controls; using System.Windows.Input; using VRChatActivityToolsShared.Database; +using Microsoft.Extensions.Configuration; namespace VRChatActivityLogViewer { @@ -21,12 +22,23 @@ public partial class MainWindow : Window private readonly string errorFilePath = "./Logs/VRChatActivityLogger/errorfile.txt"; + private readonly IConfigurationRoot _configuration; + + private readonly DbOperatorFactory _dbOperatorFactory; + /// /// コンストラクタ /// public MainWindow() { InitializeComponent(); + + var env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? string.Empty; + _configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{env}.json", true) + .Build(); + _dbOperatorFactory = new DbOperatorFactory(new DbConfig(_configuration)); } /// @@ -68,9 +80,12 @@ private async Task ExecuteSearch() EnableProcessingMode(); // DBが古い場合はアップグレードする - if (DatabaseMigration.GetCurrentVersion() < DatabaseContext.Version) + using (var context = _dbOperatorFactory.GetDbContext()) { - DatabaseMigration.UpgradeDatabase(); + if (_dbOperatorFactory.DbMigration.GetCurrentVersion(context) < ActivityContextBase.Version) + { + _dbOperatorFactory.DbMigration.UpgradeDatabase(context); + } } // 検索期間の計算 @@ -136,7 +151,7 @@ private async Task ExecuteSearch() IsAcceptInvite = acptInvCheckBox.IsChecked ?? false, IsAcceptRequestInvite = acptReqInvCheckBox.IsChecked ?? false, }; - var activityLogs = await VRChatActivityLogModel.SearchActivityLogs(parameter); + var activityLogs = await VRChatActivityLogModel.SearchActivityLogs(parameter, _dbOperatorFactory); // 選択アイテムの保存 var selectedItem = ActivityLogGrid.SelectedItem as ActivityLogGridModel; @@ -417,14 +432,17 @@ private void DisableProcessingMode() taskbarInfo.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None; Mouse.OverrideCursor = null; loggerButton.IsEnabled = true; - - if (File.Exists(DatabaseContext.DBFilePath)) + + using (var context = _dbOperatorFactory.GetDbContext()) { - searchButton.IsEnabled = true; - } - else - { - searchButton.IsEnabled = false; + if (context.Database.CanConnect()) + { + searchButton.IsEnabled = true; + } + else + { + searchButton.IsEnabled = false; + } } ActivityLogGrid.Focus(); diff --git a/VRChatActivityLogViewer/VRChatActivityLogViewer/Properties/launchSettings.json b/VRChatActivityLogViewer/VRChatActivityLogViewer/Properties/launchSettings.json new file mode 100644 index 0000000..44811e7 --- /dev/null +++ b/VRChatActivityLogViewer/VRChatActivityLogViewer/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "VRChatActivityLogViewer": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogModel.cs b/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogModel.cs index 791ec39..ca5487e 100644 --- a/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogModel.cs +++ b/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogModel.cs @@ -15,7 +15,7 @@ class VRChatActivityLogModel /// /// /// - public static async Task> SearchActivityLogs(ActivityLogSearchParameter parameter) + public static async Task> SearchActivityLogs(ActivityLogSearchParameter parameter, DbOperatorFactory factory) { return await Task.Run(() => { @@ -50,7 +50,7 @@ public static async Task> SearchActivityLogs(ActivityLogSearch searchActivityTypes.Add(ActivityType.AcceptRequestInvite); List activityLogs = new List(); - using (var db = new DatabaseContext()) + using (var db = factory.GetDbContext()) { activityLogs = db.ActivityLogs .Where(a => searchActivityTypes.Contains(a.ActivityType)) diff --git a/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogViewer.csproj b/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogViewer.csproj index 0c4e835..8d6d62a 100644 --- a/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogViewer.csproj +++ b/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogViewer.csproj @@ -17,6 +17,10 @@ + + + + @@ -25,4 +29,13 @@ + + + PreserveNewest + + + PreserveNewest + + + \ No newline at end of file diff --git a/VRChatActivityLogViewer/VRChatActivityLogViewer/appsettings.json b/VRChatActivityLogViewer/VRChatActivityLogViewer/appsettings.json new file mode 100644 index 0000000..5a07085 --- /dev/null +++ b/VRChatActivityLogViewer/VRChatActivityLogViewer/appsettings.json @@ -0,0 +1,7 @@ +{ + "ConnectionStrings": { + "VRChatActivityLog": "" + }, + "DbKind": "SQLite", + "DbVersion": "" +} \ No newline at end of file diff --git a/VRChatActivityLogger/VRChatActivityLogger/Program.cs b/VRChatActivityLogger/VRChatActivityLogger/Program.cs index 52aedec..d1f906c 100644 --- a/VRChatActivityLogger/VRChatActivityLogger/Program.cs +++ b/VRChatActivityLogger/VRChatActivityLogger/Program.cs @@ -1,10 +1,14 @@ -using System; +using Microsoft.Extensions.Configuration; +using System; using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; namespace VRChatActivityLogger { class Program { + // 遅延ロードなのでWindows以外でもコールしない限り例外にはならない模様 [System.Runtime.InteropServices.DllImport("kernel32.dll")] private static extern bool AllocConsole(); @@ -13,23 +17,35 @@ class Program /// /// static int Main(string[] rawArgs) - { - + { + var logger = Logger.GetLogger(); var args = new Argument(rawArgs) { - NamedParameters = new Dictionary { + NamedParameters = new Dictionary + { { "console", "true" }, } }; - if (args.NamedParameters["console"].ToLower() != "false") + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && args.NamedParameters["console"].ToLower() != "false") { AllocConsole(); } - var logger = Logger.GetLogger(); logger.Info("VRChatActivityLoggerを実行します。"); - var app = new VRChatActivityLogger(); + var env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? string.Empty; + var config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .AddJsonFile($"appsettings.{env}.json", true) + .Build(); + var app = new VRChatActivityLogger(new VRChatActivityToolsShared.Database.DbConfig(config)); + + // VRCログファイルの検索先指定があればそちらを使う + var vrc = config.GetSection("VRChat"); + if (vrc != null && !string.IsNullOrEmpty(vrc["LogFileDir"])) + { + app.VRChatLogFilePath = vrc["LogFileDir"]; + } var returnCode = app.Run(); logger.Info("VRChatActivityLoggerを終了します。"); diff --git a/VRChatActivityLogger/VRChatActivityLogger/Properties/PublishProfiles/PublishProfile.pubxml b/VRChatActivityLogger/VRChatActivityLogger/Properties/PublishProfiles/PublishProfile.pubxml index 84c73fe..29fbbc8 100644 --- a/VRChatActivityLogger/VRChatActivityLogger/Properties/PublishProfiles/PublishProfile.pubxml +++ b/VRChatActivityLogger/VRChatActivityLogger/Properties/PublishProfiles/PublishProfile.pubxml @@ -1,17 +1,17 @@ - + - - - FileSystem - Release - Any CPU - netcoreapp3.1 - bin\Publish\netcoreapp3.1\ - win-x64 - false - true - true - +--> + + + FileSystem + Release + Any CPU + netcoreapp3.1 + bin\Publish\netcoreapp3.1\ + win-x64 + false + true + true + \ No newline at end of file diff --git a/VRChatActivityLogger/VRChatActivityLogger/Properties/launchSettings.json b/VRChatActivityLogger/VRChatActivityLogger/Properties/launchSettings.json index 8e17287..0c999e1 100644 --- a/VRChatActivityLogger/VRChatActivityLogger/Properties/launchSettings.json +++ b/VRChatActivityLogger/VRChatActivityLogger/Properties/launchSettings.json @@ -1,8 +1,11 @@ -{ - "profiles": { - "VRChatActivityLogger": { - "commandName": "Project", - "commandLineArgs": "-console true" - } - } +{ + "profiles": { + "VRChatActivityLogger": { + "commandName": "Project", + "commandLineArgs": "-console true", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } } \ No newline at end of file diff --git a/VRChatActivityLogger/VRChatActivityLogger/VRChatActivityLogger.cs b/VRChatActivityLogger/VRChatActivityLogger/VRChatActivityLogger.cs index cdce81b..91b7ed7 100644 --- a/VRChatActivityLogger/VRChatActivityLogger/VRChatActivityLogger.cs +++ b/VRChatActivityLogger/VRChatActivityLogger/VRChatActivityLogger.cs @@ -17,13 +17,26 @@ class VRChatActivityLogger /// /// ロガー /// - private NLog.Logger logger = Logger.GetLogger(); + private readonly NLog.Logger logger = Logger.GetLogger(); /// /// VRChatのログの保存場所 + /// 規定値はローカルマシンのフォルダ /// public string VRChatLogFilePath { get; set; } = - Regex.Replace(GetFolderPath(SpecialFolder.LocalApplicationData), @"\\[^\\]+$", "") + @"\LocalLow\VRChat\VRChat\"; + Path.Combine(Regex.Replace(GetFolderPath(SpecialFolder.LocalApplicationData), @"\\[^\\]+$", ""), "LocalLow", "VRChat", "VRChat"); + + /// + /// DB接続設定 + /// + private readonly DbConfig _configuration; + + public VRChatActivityLogger(DbConfig configuration) + { + _configuration = configuration; + var pgm = Path.GetFullPath(System.AppDomain.CurrentDomain.BaseDirectory); + errorFilePath = Path.Combine(pgm, "Logs", "VRChatActivityLogger", "errorfile.txt"); + } /// /// 処理を実行します。 @@ -34,7 +47,10 @@ public int Run() var logger = Logger.GetLogger(); try { - ClearErrorInfoFile(); + logger.Info($"エラーログ出力先: {errorFilePath}"); + ClearErrorInfoFile(); + + var factory = new DbOperatorFactory(_configuration); // ログ解析 var activityLogs = new List(); @@ -43,36 +59,39 @@ public int Run() logger.Debug("ログを解析中 " + file); activityLogs.AddRange(ParseVRChatLog(file)); } - activityLogs = activityLogs.OrderBy(a => a.Timestamp).ToList(); - - // DBファイルチェック - if (!File.Exists(DatabaseContext.DBFilePath)) - { - logger.Info("データベースファイルが見つかりませんでした。新しく作成します。"); - - DatabaseMigration.CreateDatabase(); - - logger.Info("データベースファイルを作成しました。"); - } - - // DBバージョンチェック - var currentVersion = DatabaseMigration.GetCurrentVersion(); - - if (currentVersion < DatabaseContext.Version) - { - logger.Info("古いバージョンのデータベースを使用しています。データベースのアップグレードを行います。"); - - DatabaseMigration.UpgradeDatabase(); - - logger.Info("データベースをアップグレードしました。"); - } - else if (DatabaseContext.Version < currentVersion) - { - throw new InvalidOperationException("新しいバージョンのアプリで作成されたデータベースが存在するため、処理を中断します。"); - } + activityLogs = activityLogs.OrderBy(a => a.Timestamp).ToList(); + + // DB確認 + using (var context = factory.GetDbContext()) + { + if (!context.Database.CanConnect()) + { + logger.Info("データベースが見つかりませんでした。新しく作成します。"); + + factory.DbMigration.CreateDbAndTables(context); + + logger.Info("データベースを作成しました。"); + } + + // DBバージョンチェック + var currentVersion = factory.DbMigration.GetCurrentVersion(context); + + if (currentVersion < ActivityContextBase.Version) + { + logger.Info("古いバージョンのデータベースを使用しています。データベースのアップグレードを行います。"); + + factory.DbMigration.UpgradeDatabase(context); + + logger.Info("データベースをアップグレードしました。"); + } + else if (ActivityContextBase.Version < currentVersion) + { + throw new InvalidOperationException("新しいバージョンのアプリで作成されたデータベースが存在するため、処理を中断します。"); + } + } // DB更新 - using (var db = new DatabaseContext()) + using (var db = factory.GetDbContext()) { // 既にDBに登録されているログは登録対象から削除する var lastActivity = db.ActivityLogs.Find(db.ActivityLogs.Max(a => a.ID)); @@ -121,11 +140,8 @@ public int Run() { try { - foreach (var activityLog in activityLogs) - { - db.Add(activityLog); - db.SaveChanges(); - } + db.AddRange(activityLogs); + db.SaveChanges(); transaction.Commit(); } catch (Exception) @@ -496,7 +512,7 @@ private List ParseVRChatLog(string filePath) private string processingLine = string.Empty; - private readonly string errorFilePath = "./Logs/VRChatActivityLogger/errorfile.txt"; + private readonly string errorFilePath; /// /// エラーファイルをクリアします。 diff --git a/VRChatActivityLogger/VRChatActivityLogger/VRChatActivityLogger.csproj b/VRChatActivityLogger/VRChatActivityLogger/VRChatActivityLogger.csproj index a1afb02..d14a535 100644 --- a/VRChatActivityLogger/VRChatActivityLogger/VRChatActivityLogger.csproj +++ b/VRChatActivityLogger/VRChatActivityLogger/VRChatActivityLogger.csproj @@ -15,6 +15,7 @@ + @@ -47,4 +48,13 @@ + + + Always + + + PreserveNewest + + + diff --git a/VRChatActivityLogger/VRChatActivityLogger/appsettings.json b/VRChatActivityLogger/VRChatActivityLogger/appsettings.json new file mode 100644 index 0000000..8bf9356 --- /dev/null +++ b/VRChatActivityLogger/VRChatActivityLogger/appsettings.json @@ -0,0 +1,10 @@ +{ + "ConnectionStrings": { + "VRChatActivityLog": "Server=localhost;Database=VRChatActivityLog;Username=user;Password=password;" + }, + "DbKind": "MariaDB", + + "VRChat": { + "LogFileDir": "" + } +} \ No newline at end of file diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DatabaseContext.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextBase.cs similarity index 52% rename from VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DatabaseContext.cs rename to VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextBase.cs index c2ee007..3accdb4 100644 --- a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DatabaseContext.cs +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextBase.cs @@ -9,18 +9,13 @@ namespace VRChatActivityToolsShared.Database /// /// データベースコンテキスト /// - public class DatabaseContext : DbContext + public abstract class ActivityContextBase : DbContext { /// /// データベースのバージョン /// public static int Version { get; } = 3; - /// - /// データベースのファイルパス - /// - public static string DBFilePath { get; set; } = @"VRChatActivityLog.db"; - /// /// ActivityLogsテーブル /// @@ -31,16 +26,27 @@ public class DatabaseContext : DbContext /// public DbSet Information { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - var connectionString = new SqliteConnectionStringBuilder { DataSource = DBFilePath }.ToString(); - optionsBuilder.UseSqlite(new SqliteConnection(connectionString)); - } + /// + /// Dispose()がコールされた後か? + /// + public bool Disposed { get; protected set; } + + /// + /// DBにテーブルがあるか確認する + /// + /// 確認するテーブル名 + /// + public abstract bool HasTable(string tableName); + + public ActivityContextBase(DbContextOptions options) : base(options) { } - protected override void OnModelCreating(ModelBuilder modelBuilder) + /// + public override void Dispose() { - modelBuilder.Entity().HasKey(a => new { a.ID }); - modelBuilder.Entity().HasKey(a => new { a.ID }); + if (Disposed) + { return; } + base.Dispose(); + Disposed = true; } } } diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextMariaDb.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextMariaDb.cs new file mode 100644 index 0000000..93f35f8 --- /dev/null +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextMariaDb.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Pomelo.EntityFrameworkCore.MySql; +using Microsoft.EntityFrameworkCore; +using Microsoft.Data.Sqlite; +using System.Data; + +namespace VRChatActivityToolsShared.Database +{ + public class ActivityContextMariaDb : ActivityContextBase + { + public ActivityContextMariaDb(DbContextOptions options) : base(options) { } + + public override bool HasTable(string tableName) + { + var con = Database.GetDbConnection(); + var info = con.GetSchema("Tables"); + return info.AsEnumerable().Any(a => a["TABLE_NAME"].ToString() == tableName); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(e => + { + e.ToTable("ActivityLogs") + .HasKey(a => a.ID) + .HasName("PK_ActivityLogs"); + + e.Property("ID") + .ValueGeneratedOnAdd() + .IsRequired(); + + e.Property("ActivityType") + .IsRequired(); + + e.Property("Timestamp") + .HasColumnType("datetime"); + + e.Property("NotificationID") + .HasColumnType("text"); + + e.Property("UserID") + .HasColumnType("text"); + + e.Property("UserName") + .HasColumnType("text"); + + e.Property("WorldID") + .HasColumnType("text"); + + e.Property("WorldName") + .HasColumnType("text"); + + e.Property("Message") + .HasColumnType("text"); + + e.Property("Url") + .HasColumnType("text"); + }); + + modelBuilder.Entity(e => + { + e.ToTable("Information") + .HasKey(a => a.ID) + .HasName("PK_Information"); + + e.Property(a => a.ID) + .ValueGeneratedOnAdd() + .IsRequired(); + }); + } + } +} diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextSQLite.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextSQLite.cs new file mode 100644 index 0000000..998343c --- /dev/null +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityContextSQLite.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using System.Data; +using System.Linq; + +namespace VRChatActivityToolsShared.Database +{ + /// + /// データベースコンテキスト + /// + public class ActivityContextSQLite : ActivityContextBase + { + /// + /// データベースのファイルパス + /// + public static string DBFilePath { get; } = @"VRChatActivityLog.db"; + + public static string ParseConnectionString(string con) + { + if (string.IsNullOrEmpty(con)) + { + return new SqliteConnectionStringBuilder { DataSource = DBFilePath }.ToString(); + } + return con; + } + + public ActivityContextSQLite(DbContextOptions options) : base(options) { } + + public override bool HasTable(string tableName) + { + using var db = Database.GetDbConnection(); + db.Open(); + + #region var existsTableSql = "..."; + var existsTableSql = +@" + SELECT + COUNT(*) + FROM + sqlite_master + WHERE + TYPE = 'table' AND + " + $"name = '{tableName}';"; + #endregion + using var command = new SqliteCommand(existsTableSql, (SqliteConnection)db); + using var reader = command.ExecuteReader(); + if (!reader.Read()) + { + return false; + } + + return 0 < reader.GetInt32(0); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(e => + { + e.ToTable("ActivityLogs") + .HasKey(a => a.ID) + .HasName("PK_ActivityLogs"); + + e.Property("ID") + .ValueGeneratedOnAdd() + .IsRequired(); + + e.Property("ActivityType") + .IsRequired(); + + e.Property("Timestamp") + .HasColumnType("datetime"); + + e.Property("NotificationID") + .HasColumnType("text"); + + e.Property("UserID") + .HasColumnType("text"); + + e.Property("UserName") + .HasColumnType("text"); + + e.Property("WorldID") + .HasColumnType("text"); + + e.Property("WorldName") + .HasColumnType("text"); + + e.Property("Message") + .HasColumnType("text"); + + e.Property("Url") + .HasColumnType("text"); + }); + + modelBuilder.Entity(e => + { + e.ToTable("Information") + .HasKey(a => a.ID) + .HasName("PK_Information"); + + e.Property(a => a.ID) + .ValueGeneratedOnAdd() + .IsRequired(); + }); + } + } +} diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DatabaseMigration.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DatabaseMigration.cs deleted file mode 100644 index 42652dc..0000000 --- a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DatabaseMigration.cs +++ /dev/null @@ -1,205 +0,0 @@ -using Microsoft.Data.Sqlite; -using System; -using System.Collections.Generic; -using System.Text; - -namespace VRChatActivityToolsShared.Database -{ - /// - /// DBのマイグレーションを行うクラスです。 - /// - public static class DatabaseMigration - { - /// - /// データベースを新規作成します。 - /// - /// - public static void CreateDatabase() - { - using var db = new SqliteConnection($"Filename={DatabaseContext.DBFilePath}"); - - db.Open(); - - #region var sql = "..."; - var sql = -@" -CREATE TABLE ""Information"" ( - ""ID"" INTEGER NOT NULL CONSTRAINT ""PK_Information"" PRIMARY KEY AUTOINCREMENT, - ""Version"" INTEGER NOT NULL -); - -INSERT INTO ""Information"" ( - ""Version"" -) -VALUES ( - @Version -); - -CREATE TABLE ""ActivityLogs"" ( - ""ID"" INTEGER NOT NULL CONSTRAINT ""PK_ActivityLogs"" PRIMARY KEY AUTOINCREMENT, - ""ActivityType"" INTEGER NOT NULL, - ""Timestamp"" TEXT NULL, - ""NotificationID"" TEXT NULL, - ""UserID"" TEXT NULL, - ""UserName"" TEXT NULL, - ""WorldID"" TEXT NULL, - ""WorldName"" TEXT NULL, - ""Message"" TEXT NULL, - ""Url"" TEXT NULL -); -"; - #endregion - - using var command = new SqliteCommand(sql, db); - - command.Parameters.Add(new SqliteParameter("@Version", DatabaseContext.Version)); - - command.ExecuteNonQuery(); - } - - /// - /// 現在のデータベースがどのバージョンで作成されたかを取得します。 - /// - /// - public static int GetCurrentVersion() - { - using (var db = new SqliteConnection($"Filename={DatabaseContext.DBFilePath}")) - { - db.Open(); - - #region var existsTableSql = "..."; - var existsTableSql = -@" -SELECT - COUNT(*) -FROM - sqlite_master -WHERE - TYPE = 'table' AND - name = 'Information'; -"; - #endregion - - using (var command = new SqliteCommand(existsTableSql, db)) - { - using var reader = command.ExecuteReader(); - reader.Read(); - - var hasTable = 0 < reader.GetInt32(0); - - if (!hasTable) - { - return 1; - } - } - - #region var versionSql = "..."; - var versionSql = -@" -SELECT - Version -FROM - Information; -"; - #endregion - - using (var command = new SqliteCommand(versionSql, db)) - { - using var reader = command.ExecuteReader(); - reader.Read(); - - var version = reader.GetInt32(0); - - return version; - } - } - } - - /// - /// データベースを更新します。 - /// - public static void UpgradeDatabase() - { - var currentVersion = GetCurrentVersion(); - - if (currentVersion < 2) - { - UpgradeDatabaseVersion2(); - } - - if (currentVersion < 3) - { - UpgradeDatabaseVersion3(); - } - } - - /// - /// データベースをver2へ更新します。 - /// - private static void UpgradeDatabaseVersion2() - { - using var db = new SqliteConnection($"Filename={DatabaseContext.DBFilePath}"); - - db.Open(); - - #region var sql = "..."; - var sql = -@" -CREATE TABLE ""Information"" ( - ""ID"" INTEGER NOT NULL CONSTRAINT ""PK_Information"" PRIMARY KEY AUTOINCREMENT, - ""Version"" INTEGER NOT NULL -); - -INSERT INTO ""Information"" ( - ""Version"" -) -VALUES ( - @Version -); - -ALTER TABLE - ""ActivityLogs"" -ADD COLUMN - ""Message"" TEXT NULL; - -ALTER TABLE - ""ActivityLogs"" -ADD COLUMN - ""Url"" TEXT NULL; -"; - #endregion - - using var command = new SqliteCommand(sql, db); - - command.Parameters.Add(new SqliteParameter("@Version", 2)); - - command.ExecuteNonQuery(); - } - - /// - /// データベースをver3へ更新します。 - /// - private static void UpgradeDatabaseVersion3() - { - using var db = new SqliteConnection($"Filename={DatabaseContext.DBFilePath}"); - - db.Open(); - - #region var sql = "..."; - var sql = -@" -UPDATE - ""Information"" -SET - ""Version"" = @Version; -"; - #endregion - - using var command = new SqliteCommand(sql, db); - - command.Parameters.Add(new SqliteParameter("@Version", 3)); - - command.ExecuteNonQuery(); - } - } -} diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbConfig.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbConfig.cs new file mode 100644 index 0000000..bec1426 --- /dev/null +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbConfig.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; + +namespace VRChatActivityToolsShared.Database +{ + /// + /// DB接続設定クラス + /// + public class DbConfig + { + /// + /// 接続先DBの種類 + /// + public DbKind DbKind { get; } = DbKind.SQLite; + + /// + /// 接続文字列 + /// + /// + /// DB種類によって記入内容が異なるため注意 + /// + public string ConnectionString { get; } + + public DbConfig() { } + + public DbConfig(DbKind kind, string connectionString) : base() + { + DbKind = kind; + ConnectionString = connectionString; + } + + /// + /// configファイルから値を取り込む + /// + /// + public DbConfig(IConfiguration config) : base() + { + DbKind = Enum.Parse(config["DbKind"]); + ConnectionString = config.GetConnectionString("VRChatActivityLog"); + } + } +} diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbKind.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbKind.cs new file mode 100644 index 0000000..dc746f3 --- /dev/null +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbKind.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VRChatActivityToolsShared.Database +{ + public enum DbKind + { + SQLite, + MariaDB, + } +} diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationBase.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationBase.cs new file mode 100644 index 0000000..79f4e70 --- /dev/null +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationBase.cs @@ -0,0 +1,121 @@ +using Microsoft.Data.Sqlite; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; + +namespace VRChatActivityToolsShared.Database +{ + /// + /// DBのマイグレーションを行うクラスです。 + /// + public abstract class DbMigrationBase : IDbMigration + { + public string ConnectionString { get; protected set; } + + public DbMigrationBase(string connectionString) + { + ConnectionString = connectionString; + } + + /// + /// DBへの接続オブジェクトを求める + /// + /// + protected abstract DbConnection GetDbConnection(); + + /// + /// SQL応答を得るオブジェクトを求める + /// + /// + /// + /// + protected abstract DbCommand GetDbCommand(string sql, DbConnection db); + + public void CreateDbAndTables(ActivityContextBase context) + { + context.Database.EnsureCreated(); + context.Add(new Information { Version = ActivityContextBase.Version }); + context.SaveChanges(); + } + + public int GetCurrentVersion(ActivityContextBase context) + { + if (!context.HasTable("Information")) + { return 1; } + return context.Information.Select(a => a.Version).First(); + } + + /// + /// データベースを更新します。 + /// + public void UpgradeDatabase(ActivityContextBase context) + { + var currentVersion = GetCurrentVersion(context); + + if (currentVersion < 2) + { + UpgradeDatabaseVersion2(); + } + + if (currentVersion < 3) + { + UpgradeDatabaseVersion3(context); + } + } + + /// + /// データベースをver2へ更新します。 + /// + private void UpgradeDatabaseVersion2() + { + using var db = GetDbConnection(); + + db.Open(); + + #region var sql = "..."; + var sql = +@" +CREATE TABLE ""Information"" ( + ""ID"" INTEGER NOT NULL CONSTRAINT ""PK_Information"" PRIMARY KEY AUTOINCREMENT, + ""Version"" INTEGER NOT NULL +); + +INSERT INTO ""Information"" ( + ""Version"" +) +VALUES ( + @Version +); + +ALTER TABLE + ""ActivityLogs"" +ADD COLUMN + ""Message"" TEXT NULL; + +ALTER TABLE + ""ActivityLogs"" +ADD COLUMN + ""Url"" TEXT NULL; +"; + #endregion + + using var command = GetDbCommand(sql, db); + + command.Parameters.Add(new SqliteParameter("@Version", 2)); + + command.ExecuteNonQuery(); + } + + /// + /// データベースをver3へ更新します。 + /// + private void UpgradeDatabaseVersion3(ActivityContextBase context) + { + foreach (var inf in context.Information) + { inf.Version = 3; } + context.SaveChanges(); + } + } +} diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationMariaDb.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationMariaDb.cs new file mode 100644 index 0000000..6ba1da3 --- /dev/null +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationMariaDb.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Data.Sqlite; +using MySqlConnector; +using Pomelo.EntityFrameworkCore; +using MySql.Data.MySqlClient; + +namespace VRChatActivityToolsShared.Database +{ + public class DbMigrationMariaDb : DbMigrationBase + { + protected override DbCommand GetDbCommand(string sql, DbConnection db) => + new MySqlCommand(sql, (MySqlConnection)db); + + protected override DbConnection GetDbConnection() => + new MySqlConnection(ConnectionString); + + public DbMigrationMariaDb(string connectionString) : base(connectionString) { } + } +} diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationSQLite.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationSQLite.cs new file mode 100644 index 0000000..0e9b05d --- /dev/null +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbMigrationSQLite.cs @@ -0,0 +1,23 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.ValueGeneration.Internal; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Text; + +namespace VRChatActivityToolsShared.Database +{ + /// + /// DBのマイグレーションを行うクラスです。 + /// + public class DbMigrationSQLite : DbMigrationBase + { + public DbMigrationSQLite(string connectionString) : base(connectionString) { } + + protected override DbConnection GetDbConnection() => + new SqliteConnection(ConnectionString); + + protected override DbCommand GetDbCommand(string sql, DbConnection db) => + new SqliteCommand(sql, (SqliteConnection)db); + } +} diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbOperatorFactory.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbOperatorFactory.cs new file mode 100644 index 0000000..fd39f6f --- /dev/null +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DbOperatorFactory.cs @@ -0,0 +1,77 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using MySql.Data.MySqlClient; +using Pomelo.EntityFrameworkCore.MySql.Storage; +using Pomelo.EntityFrameworkCore.MySql.Infrastructure; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VRChatActivityToolsShared.Database +{ + public class DbOperatorFactory + { + /// + /// バージョン移行オブジェクト + /// + public IDbMigration DbMigration { get; private set; } + + /// + /// 接続設定オブジェクト + /// + private readonly DbConfig _config; + + public DbOperatorFactory(DbConfig config) + { + _config = config; + + switch (_config.DbKind) + { + case DbKind.SQLite: + var c = ActivityContextSQLite.ParseConnectionString(config.ConnectionString); + DbMigration = new DbMigrationSQLite(c); + break; + case DbKind.MariaDB: + DbMigration = new DbMigrationMariaDb(config.ConnectionString); + break; + default: + break; + } + } + + /// + /// コンテキストを返す + /// + /// + public ActivityContextBase GetDbContext() + { + ActivityContextBase context; + var connectionString = _config.ConnectionString; + var builder = new DbContextOptionsBuilder(); + switch (_config.DbKind) + { + case DbKind.SQLite: + builder.UseSqlite(connectionString); + context = new ActivityContextSQLite(builder.Options); + break; + case DbKind.MariaDB: + builder.UseMySql(connectionString, + options => + { + options.ServerVersion(ServerVersion.AutoDetect(connectionString)); + options.CharSetBehavior(CharSetBehavior.AppendToAllColumns); + options.CharSet(CharSet.Utf8Mb4); + }); + context = new ActivityContextMariaDb(builder.Options); + break; + default: + throw new NotSupportedException(); + } + + return context; + } + } +} diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/IDbMigration.cs b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/IDbMigration.cs new file mode 100644 index 0000000..d2063a7 --- /dev/null +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/IDbMigration.cs @@ -0,0 +1,32 @@ +using System.Data.Common; + +namespace VRChatActivityToolsShared.Database +{ + public interface IDbMigration + { + /// + /// 接続文字列 + /// + string ConnectionString { get; } + + /// + /// DB本体とテーブルを生成する + /// + /// + void CreateDbAndTables(ActivityContextBase context); + + /// + /// 現在のデータベースがどのバージョンで作成されたかを取得します。 + /// + /// + /// バージョン情報テーブルが無い場合は1を返します。 + /// バージョン情報テーブルがある場合はバージョンの数値を返します。 + /// + int GetCurrentVersion(ActivityContextBase context); + + /// + /// データベースを更新します。 + /// + void UpgradeDatabase(ActivityContextBase context); + } +} \ No newline at end of file diff --git a/VRChatActivityToolsShared/VRChatActivityToolsShared/VRChatActivityToolsShared.csproj b/VRChatActivityToolsShared/VRChatActivityToolsShared/VRChatActivityToolsShared.csproj index c6d7e1d..b446061 100644 --- a/VRChatActivityToolsShared/VRChatActivityToolsShared/VRChatActivityToolsShared.csproj +++ b/VRChatActivityToolsShared/VRChatActivityToolsShared/VRChatActivityToolsShared.csproj @@ -11,6 +11,7 @@ +