diff --git a/DownKyi.Core/Storage/Database/DbHelper.cs b/DownKyi.Core/Storage/Database/DbHelper.cs deleted file mode 100644 index bddd7fb..0000000 --- a/DownKyi.Core/Storage/Database/DbHelper.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System.Data; -using DownKyi.Core.Logging; -using Microsoft.Data.Sqlite; -using Console = DownKyi.Core.Utils.Debugging.Console; - -namespace DownKyi.Core.Storage.Database; - -public class DbHelper -{ - private readonly string _connStr; - private readonly SqliteConnection? _conn; - - private static readonly Dictionary Database = new(); - - /// - /// 创建一个数据库 - /// - /// - public DbHelper(string dbPath) - { - _connStr = new SqliteConnectionStringBuilder - { - Mode = SqliteOpenMode.ReadWriteCreate, - DataSource = dbPath, - Pooling = false - }.ToString(); - if (Database.TryGetValue(_connStr, out var value)) - { - _conn = value; - - if (_conn != null) - { - return; - } - } - - _conn = new SqliteConnection(_connStr); - Database.Add(_connStr, _conn); - } - - /// - /// 创建一个带密码的数据库 - /// - /// - /// - public DbHelper(string dbPath, string secretKey) - { - _connStr = new SqliteConnectionStringBuilder - { - Mode = SqliteOpenMode.ReadWriteCreate, - Password = secretKey, - DataSource = dbPath - }.ToString(); - if (Database.TryGetValue(_connStr, out var value)) - { - _conn = value; - - if (_conn != null) - { - return; - } - } - - _conn = new SqliteConnection(_connStr); - // conn.SetPassword(secretKey); - Database.Add(_connStr, _conn); - } - - /// - /// 连接是否开启 - /// - /// - public bool IsOpen() - { - return _conn?.State == ConnectionState.Open; - } - - /// - /// 开启连接 - /// - public void Open() - { - if (_conn == null) - { - return; - } - - if (!IsOpen()) - { - _conn.Open(); - } - } - - /// - /// 关闭数据库 - /// - public void Close() - { - if (_conn == null) - { - return; - } - - if (!IsOpen()) return; - _conn.Close(); - _conn.Dispose(); - - Database.Remove(_connStr); - } - - /// - /// 执行一条SQL语句 - /// - /// - /// - public void ExecuteNonQuery(string sql, Action? action = null) - { - if (_conn == null) - { - return; - } - - try - { - lock (_conn) - { - Open(); - using var tr = _conn.BeginTransaction(); - using (var command = _conn.CreateCommand()) - { - command.CommandText = sql; - // 添加参数 - action?.Invoke(command.Parameters); - command.ExecuteNonQuery(); - } - - tr.Commit(); - } - } - catch (SqliteException e) - { - Console.PrintLine("DbHelper ExecuteNonQuery()发生异常: {0}", e); - LogManager.Error("DbHelper ExecuteNonQuery()", e); - } - } - - /// - /// 执行一条SQL语句,并执行提供的操作,一般用于查询 - /// - /// - /// - public void ExecuteQuery(string sql, Action action) - { - if (_conn == null) - { - return; - } - - try - { - lock (_conn) - { - Open(); - using var command = _conn.CreateCommand(); - command.CommandText = sql; - var reader = command.ExecuteReader(); - action(reader); - } - } - catch (SqliteException e) - { - Console.PrintLine("DbHelper ExecuteQuery()发生异常: {0}", e); - LogManager.Error("DbHelper ExecuteQuery()", e); - } - } -} \ No newline at end of file diff --git a/DownKyi.Core/Storage/Database/SqliteDatabase.cs b/DownKyi.Core/Storage/Database/SqliteDatabase.cs new file mode 100644 index 0000000..ed725a7 --- /dev/null +++ b/DownKyi.Core/Storage/Database/SqliteDatabase.cs @@ -0,0 +1,126 @@ +using System.Data; +using DownKyi.Core.Logging; +using Microsoft.Data.Sqlite; +using Console = DownKyi.Core.Utils.Debugging.Console; + +namespace DownKyi.Core.Storage.Database; + +/// +/// SQLite 数据库操作助手,支持加密数据库操作 +/// +public sealed class SqliteDatabase : IDisposable +{ + private readonly string _connectionString; + private SqliteConnection? _connection; + + /// + /// 创建或连接一个SQLite数据库 + /// + /// 数据库文件路径 + public SqliteDatabase(string dbPath) + { + _connectionString = new SqliteConnectionStringBuilder + { + Mode = SqliteOpenMode.ReadWriteCreate, + DataSource = dbPath, + Pooling = false + }.ToString(); + } + + /// + /// 创建或连接一个加密的SQLite数据库 + /// + /// 数据库文件路径 + /// 加密密钥 + public SqliteDatabase(string dbPath, string secretKey) + { + _connectionString = new SqliteConnectionStringBuilder + { + Mode = SqliteOpenMode.ReadWriteCreate, + Password = secretKey, + DataSource = dbPath + }.ToString(); + } + + + /// + /// 执行非查询SQL语句 + /// + /// SQL语句 + /// 参数设置委托 + public void ExecuteNonQuery(string sql, Action? parametersAction = null) + { + try + { + using var connection = CreateConnection(); + using var transaction = connection.BeginTransaction(); + using var command = connection.CreateCommand(); + + command.CommandText = sql; + command.Transaction = transaction; + parametersAction?.Invoke(command.Parameters); + + command.ExecuteNonQuery(); + transaction.Commit(); + } + catch (SqliteException ex) + { + Console.PrintLine("ExecuteNonQuery() 发生异常: {0}", ex); + LogManager.Error("SqliteDatabase.ExecuteNonQuery()", ex); + throw; + } + } + + /// + /// 执行查询SQL语句 + /// + /// SQL语句 + /// 读取数据的委托 + public void ExecuteQuery(string sql, Action readAction) + { + try + { + using var connection = CreateConnection(); + using var command = connection.CreateCommand(); + + command.CommandText = sql; + using var reader = command.ExecuteReader(); + + readAction(reader); + } + catch (SqliteException ex) + { + Console.PrintLine("ExecuteQuery() 发生异常: {0}", ex); + LogManager.Error("SqliteDatabase.ExecuteQuery()", ex); + throw; + } + } + + /// + /// 创建并打开一个新的数据库连接 + /// + private SqliteConnection CreateConnection() + { + _connection = new SqliteConnection(_connectionString); + _connection.Open(); + return _connection; + } + + /// + /// 释放资源 + /// + public void Dispose() + { + _connection?.Close(); + _connection?.Dispose(); + _connection = null; + + GC.Collect(); + GC.WaitForPendingFinalizers(); + try + { + SqliteConnection.ClearAllPools(); + } + catch { } + } +} \ No newline at end of file diff --git a/DownKyi/App.axaml.cs b/DownKyi/App.axaml.cs index 59833f1..210833a 100644 --- a/DownKyi/App.axaml.cs +++ b/DownKyi/App.axaml.cs @@ -45,19 +45,19 @@ public partial class App : PrismApplication public new MainWindow MainWindow => Container.Resolve(); public IClassicDesktopStyleApplicationLifetime? AppLife; + private static Mutex _mutex; // 下载服务 private IDownloadService? _downloadService; public override void Initialize() { - if (!Design.IsDesignMode) - { - var mutex = new Mutex(true, "Global\\DownKyi", out var createdNew); - if (!createdNew) - { - Environment.Exit(0); - } - } + #if !DEBUG + _mutex = new Mutex(true, "Global\\DownKyi", out var createdNew); + if (!createdNew) + { + Environment.Exit(0); + } + #endif AvaloniaXamlLoader.Load(this); if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) diff --git a/DownKyi/ViewModels/Dialogs/ViewUpgradingDialogViewModel.cs b/DownKyi/ViewModels/Dialogs/ViewUpgradingDialogViewModel.cs index 60063d1..3be559a 100644 --- a/DownKyi/ViewModels/Dialogs/ViewUpgradingDialogViewModel.cs +++ b/DownKyi/ViewModels/Dialogs/ViewUpgradingDialogViewModel.cs @@ -15,6 +15,7 @@ using DownKyi.Models; using Prism.Commands; using Prism.Services.Dialogs; using SqlSugar; +using Exception = System.Exception; namespace DownKyi.ViewModels.Dialogs; @@ -89,6 +90,7 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel { Task.Run(() => { Upgrade1_0_20To1_0_21(); }); } + private void Upgrade1_0_20To1_0_21() { @@ -135,43 +137,48 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel { noMigrate = true; } - - -#if DEBUG - var oldDbPath = StorageManager.GetDownload().Replace(".db", "_debug.db"); -#else + var oldDbPath = StorageManager.GetDownload(); -#endif - if (File.Exists(oldDbPath)) { SetMessage("正在迁移下载信息"); -#if DEBUG - var dbHelper = new DbHelper(oldDbPath); -#else - var dbHelper = new DbHelper(oldDbPath, "bdb8eb69-3698-4af9-b722-9312d0fba623"); -#endif - var objects = new Dictionary(); - dbHelper.ExecuteQuery("select * from downloaded", reader => + + var dbHelper = new SqliteDatabase(oldDbPath, "bdb8eb69-3698-4af9-b722-9312d0fba623"); + var validRecords = new Dictionary(); + var totalCount = 0; + dbHelper.ExecuteQuery(@"SELECT d.id, d.data as downloaded_data, db.data as download_base_data + FROM downloaded d + JOIN download_base db ON d.id = db.id",reader => { while (reader.Read()) { try { // 读取字节数组 - var array = (byte[])reader["data"]; + var array = (byte[])reader["downloaded_data"]; // 定义一个流 using var stream = new MemoryStream(array); // 反序列化 var record = NrbfDecoder.DecodeClassRecord(stream); if (!record.TypeNameMatches(typeof(Downloaded))) continue; - var obj = new Downloaded + + var downloadedObj = new Downloaded { MaxSpeedDisplay = record.GetString($"<{nameof(Downloaded.MaxSpeedDisplay)}>k__BackingField"), FinishedTime = record.GetString($"<{nameof(Downloaded.FinishedTime)}>k__BackingField") ?? "", FinishedTimestamp = record.GetInt64($"<{nameof(Downloaded.FinishedTimestamp)}>k__BackingField") }; - objects.Add((string)reader["id"], obj); + + + validRecords.Add( + (string)reader["id"], + new DownloadedWithData + { + Downloaded = downloadedObj, + DownloadBaseData = (byte[])reader["download_base_data"] + }); + + totalCount++; } catch (Exception e) { @@ -206,32 +213,33 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel return quality; }); var i = 0; - var downloadedList = new List(); - foreach (var item in objects) + + try { - if (item.Value is Downloaded downloaded) + int batchSize = 200; + var downloadedList = new List(); + int processedCount = 0; + + _sqlSugarClient.Ado.BeginTran(); + foreach (var item in validRecords) { - dbHelper.ExecuteQuery($"select * from download_base where id = '{item.Key}'", reader => + try { - while (reader.Read()) + using var stream = new MemoryStream(item.Value.DownloadBaseData); + var record = NrbfDecoder.DecodeClassRecord(stream); + if (record.TypeNameMatches(typeof(DownloadBase))) { - var array = (byte[])reader["data"]; - // 定义一个流 - using var stream = new MemoryStream(array); - var record = NrbfDecoder.DecodeClassRecord(stream); - if (!record.TypeNameMatches(typeof(DownloadBase))) continue; - var needDownloadContentRecord = record.GetClassRecord($"<{nameof(DownloadBase.NeedDownloadContent)}>k__BackingField"); - var needDownloadContent = new Dictionary(); - if (needDownloadContentRecord != null) - { - needDownloadContent = readNeedDownloadContent(needDownloadContentRecord); - } + var needDownloadContentRecord = + record.GetClassRecord($"<{nameof(DownloadBase.NeedDownloadContent)}>k__BackingField"); + var needDownloadContent = needDownloadContentRecord != null + ? readNeedDownloadContent(needDownloadContentRecord) + : new Dictionary(); var download = new Downloaded { - MaxSpeedDisplay = downloaded.MaxSpeedDisplay, - FinishedTime = downloaded.FinishedTime, - FinishedTimestamp = downloaded.FinishedTimestamp, + MaxSpeedDisplay = item.Value.Downloaded.MaxSpeedDisplay, + FinishedTime = item.Value.Downloaded.FinishedTime, + FinishedTimestamp = item.Value.Downloaded.FinishedTimestamp, DownloadBase = new DownloadBase { NeedDownloadContent = needDownloadContent, @@ -239,48 +247,77 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel Avid = record.GetInt64($"<{nameof(DownloadBase.Avid)}>k__BackingField"), Cid = record.GetInt64($"<{nameof(DownloadBase.Cid)}>k__BackingField"), EpisodeId = record.GetInt64($"<{nameof(DownloadBase.EpisodeId)}>k__BackingField"), - CoverUrl = record.GetString($"<{nameof(DownloadBase.CoverUrl)}>k__BackingField") ?? "", - PageCoverUrl = record.GetString($"<{nameof(DownloadBase.PageCoverUrl)}>k__BackingField") ?? "", + CoverUrl = record.GetString($"<{nameof(DownloadBase.CoverUrl)}>k__BackingField") ?? + "", + PageCoverUrl = + record.GetString($"<{nameof(DownloadBase.PageCoverUrl)}>k__BackingField") ?? "", ZoneId = record.GetInt32($"<{nameof(DownloadBase.ZoneId)}>k__BackingField"), Order = record.GetInt32($"<{nameof(DownloadBase.Order)}>k__BackingField"), - MainTitle = record.GetString($"<{nameof(DownloadBase.MainTitle)}>k__BackingField") ?? "", + MainTitle = + record.GetString($"<{nameof(DownloadBase.MainTitle)}>k__BackingField") ?? "", Name = record.GetString($"<{nameof(DownloadBase.Name)}>k__BackingField") ?? "", - Duration = record.GetString($"<{nameof(DownloadBase.Duration)}>k__BackingField") ?? "", - VideoCodecName = record.GetString($"<{nameof(DownloadBase.VideoCodecName)}>k__BackingField") ?? "", - Resolution = readQuality(record.GetClassRecord($"<{nameof(DownloadBase.Resolution)}>k__BackingField")), - AudioCodec = readQuality(record.GetClassRecord($"<{nameof(DownloadBase.AudioCodec)}>k__BackingField")), - FilePath = record.GetString($"<{nameof(DownloadBase.FilePath)}>k__BackingField") ?? "", - FileSize = record.GetString($"<{nameof(DownloadBase.FileSize)}>k__BackingField") ?? "", + Duration = record.GetString($"<{nameof(DownloadBase.Duration)}>k__BackingField") ?? + "", + VideoCodecName = + record.GetString($"<{nameof(DownloadBase.VideoCodecName)}>k__BackingField") ?? + "", + Resolution = + readQuality( + record.GetClassRecord( + $"<{nameof(DownloadBase.Resolution)}>k__BackingField")), + AudioCodec = + readQuality( + record.GetClassRecord( + $"<{nameof(DownloadBase.AudioCodec)}>k__BackingField")), + FilePath = record.GetString($"<{nameof(DownloadBase.FilePath)}>k__BackingField") ?? + "", + FileSize = record.GetString($"<{nameof(DownloadBase.FileSize)}>k__BackingField") ?? + "", Page = record.GetInt32($"<{nameof(DownloadBase.Page)}>k__BackingField") } }; downloadedList.Add(download); - i++; - // 分批插入数据库,避免一次性插入过多数据导致性能问题,最后一批可能小于100条 - if (i % 200 == 0 || i == objects.Count) + processedCount++; + if (processedCount % batchSize == 0 || processedCount == totalCount) { - _sqlSugarClient.InsertNav(downloadedList).Include(o1 => o1.DownloadBase).ExecuteCommand(); + _sqlSugarClient.InsertNav(downloadedList) + .Include(o1 => o1.DownloadBase) + .ExecuteCommand(); + + downloadedList.Clear(); - var percent = i / (double)objects.Count * 100; - var percentStr = $"{i}/{objects.Count}"; + + // 更新进度 + var percent = processedCount / (double)totalCount * 100; Dispatcher.UIThread.Invoke(() => { Percent = percent; - SetMessage($"正在迁移下载信息({percentStr})"); + SetMessage($"正在迁移下载信息({processedCount}/{totalCount})"); }); } } - }); + } + catch (Exception ex) + { + /*忽略*/ + } } + _sqlSugarClient.Ado.CommitTran(); + dbHelper.Dispose(); + File.Delete(oldDbPath); + + Dispatcher.UIThread.Invoke(() => + { + RestartVisible = true; + SetMessage("下载信息迁移完成"); + }); } - - dbHelper.Close(); - File.Delete(oldDbPath); - Dispatcher.UIThread.Invoke(() => + catch (Exception e) { - RestartVisible = true; - SetMessage("下载信息迁移完成"); - }); + _sqlSugarClient.Ado.RollbackTran(); + SetMessage($"迁移失败: {e.Message}"); + } + } else { @@ -292,4 +329,11 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel Dispatcher.UIThread.Invoke(() => RaiseRequestClose(new DialogResult())); } } + + private class DownloadedWithData + { + public Downloaded Downloaded { get; set; } + public byte[] DownloadBaseData { get; set; } + } + } \ No newline at end of file