From 31dfd6ffafc56dc54aeaa786853db37e94f53f9e Mon Sep 17 00:00:00 2001 From: yaobiao131 <28655758+yaobiao131@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:02:26 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=B9=B6=E4=BC=98=E5=8C=96=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=9B=B8=E5=85=B3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Packages.props | 5 +- DownKyi.Core/DownKyi.Core.csproj | 1 - DownKyi/App.axaml.cs | 155 +++++---- DownKyi/DownKyi.csproj | 10 +- DownKyi/Models/DownloadBase.cs | 53 +-- DownKyi/Models/Downloaded.cs | 21 +- DownKyi/Models/Downloading.cs | 35 +- .../Services/Download/AddToDownloadService.cs | 43 ++- .../Services/Download/AriaDownloadService.cs | 12 +- .../Download/BuiltinDownloadService.cs | 10 +- .../Download/CustomAriaDownloadService.cs | 12 +- DownKyi/Services/Download/DownloadService.cs | 144 ++++---- .../Download/DownloadStorageService.cs | 39 ++- .../Utils/DataAnnotations/JsonMapAttribute.cs | 10 + DownKyi/Utils/JsonMapCore.cs | 274 +++++++++++++++ .../NewVersionAvailableDialogViewModel.cs | 1 - .../Dialogs/ViewUpgradingDialogViewModel.cs | 129 +++---- .../DownloadManager/DownloadBaseItem.cs | 17 +- .../DownloadManager/DownloadedItem.cs | 241 ++++--------- .../DownloadManager/DownloadingItem.cs | 27 +- .../ViewDownloadFinishedViewModel.cs | 328 +++++++++++------- .../ViewDownloadingViewModel.cs | 62 ++-- .../ViewDownloadFinished.axaml | 10 +- .../DownloadManager/ViewDownloading.axaml | 4 +- 24 files changed, 912 insertions(+), 731 deletions(-) create mode 100644 DownKyi/Utils/DataAnnotations/JsonMapAttribute.cs create mode 100644 DownKyi/Utils/JsonMapCore.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index dcad14f..268acbf 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,8 +5,9 @@ - - + + + diff --git a/DownKyi.Core/DownKyi.Core.csproj b/DownKyi.Core/DownKyi.Core.csproj index 6da4cdd..c2bec4f 100644 --- a/DownKyi.Core/DownKyi.Core.csproj +++ b/DownKyi.Core/DownKyi.Core.csproj @@ -15,7 +15,6 @@ - diff --git a/DownKyi/App.axaml.cs b/DownKyi/App.axaml.cs index db92d17..e0a541f 100644 --- a/DownKyi/App.axaml.cs +++ b/DownKyi/App.axaml.cs @@ -29,9 +29,9 @@ using DownKyi.Views.Friends; using DownKyi.Views.Settings; using DownKyi.Views.Toolbox; using DownKyi.Views.UserSpace; +using FreeSql; using Prism.DryIoc; using Prism.Ioc; -using SqlSugar; using ViewSeasonsSeries = DownKyi.Views.ViewSeasonsSeries; using ViewSeasonsSeriesViewModel = DownKyi.ViewModels.ViewSeasonsSeriesViewModel; @@ -41,7 +41,7 @@ public partial class App : PrismApplication { public const string RepoOwner = "yaobiao131"; public const string RepoName = "downkyicore"; - + public static ObservableCollection DownloadingList { get; set; } = new(); public static ObservableCollection DownloadedList { get; set; } = new(); public new static App Current => (App)Application.Current!; @@ -49,18 +49,19 @@ public partial class App : PrismApplication public IClassicDesktopStyleApplicationLifetime? AppLife; private static Mutex _mutex; + // 下载服务 private IDownloadService? _downloadService; public override void Initialize() { - #if !DEBUG +#if !DEBUG _mutex = new Mutex(true, "Global\\DownKyi", out var createdNew); if (!createdNew) { Environment.Exit(0); } - #endif +#endif AvaloniaXamlLoader.Load(this); if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) @@ -74,37 +75,33 @@ public partial class App : PrismApplication protected override void RegisterTypes(IContainerRegistry containerRegistry) { - containerRegistry.RegisterSingleton(() => new SqlSugarScope(new ConnectionConfig - { - DbType = DbType.Sqlite, - ConnectionString = $"datasource={StorageManager.GetDbPath()}", - IsAutoCloseConnection = true, - }, - db => - { - db.Aop.OnLogExecuting = (sql, pars) => - { - if (pars != null && pars.Length > 0) - { - string fullSql = sql; - foreach (var param in pars) - { - // 处理参数值的格式 - var value = param.Value is string || param.Value is DateTime - ? $"'{param.Value}'" - : param.Value?.ToString() ?? "NULL"; - fullSql = fullSql.Replace(param.ParameterName, value); - } - - Console.WriteLine($"完整SQL: {fullSql}"); - } - else - { - Console.WriteLine($"SQL: {sql}"); - } - }; - }) - ); + containerRegistry.RegisterSingleton(() => + { + var freeSql = new FreeSqlBuilder() + .UseConnectionString(DataType.Sqlite, $"Data Source={StorageManager.GetDbPath()}") + .UseAdoConnectionPool(true) +#if DEBUG + .UseMonitorCommand(cmd => Console.WriteLine($"Sql:{cmd.CommandText}")) +#endif + .Build(); + freeSql.UseJsonMap(); + return freeSql; + }); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterScoped>(cp => + { + var freeSql = (IFreeSql)cp.Resolve(typeof(IFreeSql)); + var downloadingRepository = freeSql.GetRepository(); + downloadingRepository.DbContextOptions.EnableCascadeSave = true; + return downloadingRepository; + }); + containerRegistry.RegisterScoped>(cp => + { + var freeSql = (IFreeSql)cp.Resolve(typeof(IFreeSql)); + var downloadRepository = freeSql.GetRepository(); + downloadRepository.DbContextOptions.EnableCascadeSave = true; + return downloadRepository; + }); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); @@ -169,11 +166,11 @@ public partial class App : PrismApplication { if (!Design.IsDesignMode) { - Container.Resolve().CodeFirst.InitTables(typeof(DownloadBase), typeof(Downloaded), typeof(Downloading)); + Container.Resolve().CodeFirst.SyncStructure(typeof(DownloadBase), typeof(Downloaded), typeof(Downloading)); } // 下载数据存储服务 - var downloadStorageService = new DownloadStorageService(); + var downloadStorageService = Container.Resolve(); // 从数据库读取 var downloadingItems = downloadStorageService.GetDownloading(); @@ -217,37 +214,37 @@ public partial class App : PrismApplication }; // 下载完成列表发生变化时执行的任务 - DownloadedList.CollectionChanged += async (sender, e) => - { - await Task.Run(() => - { - if (e.Action == NotifyCollectionChangedAction.Add) - { - if (e.NewItems == null) return; - foreach (var item in e.NewItems) - { - if (item is DownloadedItem downloaded) - { - //Console.WriteLine("DownloadedList添加"); - downloadStorageService.AddDownloaded(downloaded); - } - } - } - - if (e.Action == NotifyCollectionChangedAction.Remove) - { - if (e.OldItems == null) return; - foreach (var item in e.OldItems) - { - if (item is DownloadedItem downloaded) - { - //Console.WriteLine("DownloadedList移除"); - downloadStorageService.RemoveDownloaded(downloaded); - } - } - } - }); - }; + // DownloadedList.CollectionChanged += async (sender, e) => + // { + // await Task.Run(() => + // { + // if (e.Action == NotifyCollectionChangedAction.Add) + // { + // if (e.NewItems == null) return; + // foreach (var item in e.NewItems) + // { + // if (item is DownloadedItem downloaded) + // { + // //Console.WriteLine("DownloadedList添加"); + // downloadStorageService.AddDownloaded(downloaded); + // } + // } + // } + // + // if (e.Action == NotifyCollectionChangedAction.Remove) + // { + // if (e.OldItems == null) return; + // foreach (var item in e.OldItems) + // { + // if (item is DownloadedItem downloaded) + // { + // //Console.WriteLine("DownloadedList移除"); + // downloadStorageService.RemoveDownloaded(downloaded); + // } + // } + // } + // }); + // }; // 启动下载服务 var download = SettingsManager.GetInstance().GetDownloader(); @@ -295,33 +292,43 @@ public partial class App : PrismApplication /// public static void SortDownloadedList(DownloadFinishedSort finishedSort) { - var list = DownloadedList?.ToList(); + var list = DownloadedList.ToList(); switch (finishedSort) { case DownloadFinishedSort.DownloadAsc: // 按下载先后排序 - list?.Sort((x, y) => x.Downloaded.FinishedTimestamp.CompareTo(y.Downloaded.FinishedTimestamp)); + list.Sort((x, y) => x.Downloaded.FinishedTimestamp.CompareTo(y.Downloaded.FinishedTimestamp)); break; case DownloadFinishedSort.DownloadDesc: // 按下载先后排序 - list?.Sort((x, y) => y.Downloaded.FinishedTimestamp.CompareTo(x.Downloaded.FinishedTimestamp)); + list.Sort((x, y) => y.Downloaded.FinishedTimestamp.CompareTo(x.Downloaded.FinishedTimestamp)); break; case DownloadFinishedSort.Number: // 按序号排序 - list?.Sort((x, y) => + list.Sort((x, y) => { var compare = string.Compare(x.MainTitle, y.MainTitle, StringComparison.Ordinal); return compare == 0 ? x.Order.CompareTo(y.Order) : compare; }); break; + case DownloadFinishedSort.NotSet: default: break; } // 更新下载完成列表 // 如果有更好的方法再重写 - DownloadedList?.Clear(); - list?.ForEach(item => DownloadedList?.Add(item)); + DownloadedList.Clear(); + list.ForEach(item => DownloadedList.Add(item)); + } + + public void RefreshDownloadedList() + { + // 重新获取下载完成列表 + var downloadStorageService = Container.Resolve(); + var downloadedItems = downloadStorageService.GetDownloaded(); + DownloadedList.Clear(); + DownloadedList.AddRange(downloadedItems); } private void OnExit(object sender, ControlledApplicationLifetimeExitEventArgs e) diff --git a/DownKyi/DownKyi.csproj b/DownKyi/DownKyi.csproj index 4266b92..c484b06 100644 --- a/DownKyi/DownKyi.csproj +++ b/DownKyi/DownKyi.csproj @@ -31,9 +31,11 @@ + + + - @@ -42,10 +44,4 @@ - - - - Always - - diff --git a/DownKyi/Models/DownloadBase.cs b/DownKyi/Models/DownloadBase.cs index 8924684..155454c 100644 --- a/DownKyi/Models/DownloadBase.cs +++ b/DownKyi/Models/DownloadBase.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using DownKyi.Core.BiliApi.BiliUtils; +using DownKyi.Utils.DataAnnotations; +using FreeSql.DataAnnotations; namespace DownKyi.Models; -[Serializable] -[SqlSugar.SugarTable("download_base", TableDescription = "下载项的基础信息")] +[Table(Name = "download_base")] +[Description("下载项的基础信息")] public class DownloadBase { public DownloadBase() @@ -25,75 +28,75 @@ public class DownloadBase } // 此条下载项的id - [SqlSugar.SugarColumn(IsPrimaryKey = true, ColumnName = "id")] + [Column(IsPrimary = true, Name = "id")] public string Id { get; set; } // 需要下载的内容 - [SqlSugar.SugarColumn(IsJson = true, ColumnName = "need_download_content")] + [Column(Name = "need_download_content"),] public Dictionary NeedDownloadContent { get; set; } // 视频的id - [SqlSugar.SugarColumn(ColumnName = "bvid")] - public string Bvid { get; set; } + [Column(Name = "bvid")] public string Bvid { get; set; } - [SqlSugar.SugarColumn(ColumnName = "avid")] - public long Avid { get; set; } + [Column(Name = "avid")] public long Avid { get; set; } - [SqlSugar.SugarColumn(ColumnName = "cid")] - public long Cid { get; set; } + [Column(Name = "cid")] public long Cid { get; set; } - [SqlSugar.SugarColumn(ColumnName = "episode_id")] - public long EpisodeId { get; set; } + [Column(Name = "episode_id")] public long EpisodeId { get; set; } // 视频封面的url - [SqlSugar.SugarColumn(ColumnName = "cover_url", ColumnDescription = "视频封面的url")] + [Column(Name = "cover_url"), Description("视频封面的url")] public string CoverUrl { get; set; } // 视频page的封面的url - [SqlSugar.SugarColumn(ColumnName = "page_cover_url", ColumnDescription = "视频page的封面的url")] + [Column(Name = "page_cover_url"), Description("视频page的封面的url")] public string PageCoverUrl { get; set; } // 分区id - [SqlSugar.SugarColumn(ColumnName = "zone_id", ColumnDescription = "分区id")] + [Column(Name = "zone_id"), Description("分区id")] public int ZoneId { get; set; } // 视频序号 - [SqlSugar.SugarColumn(ColumnName = "order", ColumnDescription = "视频序号")] + [Column(Name = "order"), Description("视频序号")] public int Order { get; set; } // 视频主标题 - [SqlSugar.SugarColumn(ColumnName = "main_title", ColumnDescription = "视频主标题")] + [Column(Name = "main_title"), Description("视频主标题")] public string MainTitle { get; set; } // 视频标题 - [SqlSugar.SugarColumn(ColumnName = "name", ColumnDescription = "视频标题")] + [Column(Name = "name"), Description("视频标题")] public string Name { get; set; } // 时长 - [SqlSugar.SugarColumn(ColumnName = "duration", ColumnDescription = "时长")] + [Column(Name = "duration"), Description("时长")] public string Duration { get; set; } // 视频编码名称,AVC、HEVC - [SqlSugar.SugarColumn(ColumnName = "video_codec_name", ColumnDescription = "视频编码名称,AVC、HEVC")] + [Column(Name = "video_codec_name")] + [Description("视频编码名称,AVC、HEVC")] public string VideoCodecName { get; set; } // 视频画质 - [SqlSugar.SugarColumn(ColumnName = "resolution", ColumnDescription = "视频画质", IsJson = true)] + [Column(Name = "resolution"), Description("视频画质"), JsonMap] public Quality Resolution { get; set; } // 音频编码 - [SqlSugar.SugarColumn(ColumnName = "audio_codec", ColumnDescription = "音频编码", IsJson = true,IsNullable = true)] + [Column(Name = "audio_codec", IsNullable = true), Description("音频编码"), JsonMap] public Quality AudioCodec { get; set; } // 文件路径,不包含扩展名,所有内容均以此路径下载 - [SqlSugar.SugarColumn(ColumnName = "file_path", ColumnDescription = "文件路径,不包含扩展名,所有内容均以此路径下载")] + [Column(Name = "file_path"), Description("文件路径,不包含扩展名,所有内容均以此路径下载")] public string FilePath { get; set; } // 文件大小 - [SqlSugar.SugarColumn(ColumnName = "file_size", ColumnDescription = "文件大小", IsNullable = true)] + [Column(Name = "file_size", IsNullable = true), Description("文件大小")] public string? FileSize { get; set; } // 视频分p(默认为1) - [SqlSugar.SugarColumn(ColumnName = "page", ColumnDescription = "视频分p(默认为1)")] + [Column(Name = "page"), Description("视频分p(默认为1)")] public int Page { get; set; } = 1; + + [Navigate(nameof(Id))] public Downloaded? Downloaded { get; set; } + [Navigate(nameof(Id))] public Downloading? Downloading { get; set; } } \ No newline at end of file diff --git a/DownKyi/Models/Downloaded.cs b/DownKyi/Models/Downloaded.cs index 9a479c6..822f404 100644 --- a/DownKyi/Models/Downloaded.cs +++ b/DownKyi/Models/Downloaded.cs @@ -1,21 +1,19 @@ using System; -using SqlSugar; +using FreeSql.DataAnnotations; namespace DownKyi.Models; -[Serializable] -[SugarTable(TableName = "downloaded")] +[Table(Name = "downloaded")] public class Downloaded { - [SugarColumn(IsPrimaryKey = true, ColumnName = "id")] + [Column(IsPrimary = true, Name = "id")] public string Id { get; set; } = null!; + // 下载速度 - [SugarColumn(ColumnName = "max_speed_display")] - public string? MaxSpeedDisplay { get; set; } + [Column(Name = "max_speed_display")] public string? MaxSpeedDisplay { get; set; } // 完成时间戳 - [SugarColumn(ColumnName = "finished_timestamp")] - public long FinishedTimestamp { get; set; } + [Column(Name = "finished_timestamp")] public long FinishedTimestamp { get; set; } public void SetFinishedTimestamp(long finishedTimestamp) { @@ -27,10 +25,7 @@ public class Downloaded } // 完成时间 - [SugarColumn(ColumnName = "finished_time")] - public string FinishedTime { get; set; } + [Column(Name = "finished_time")] public string FinishedTime { get; set; } - [Navigate(NavigateType.OneToOne, nameof(Id))] - [SugarColumn(IsIgnore = true)] - public DownloadBase DownloadBase { get; set; } + [Navigate(nameof(Id))] public DownloadBase? DownloadBase { get; set; } } \ No newline at end of file diff --git a/DownKyi/Models/Downloading.cs b/DownKyi/Models/Downloading.cs index 7d577e6..810e62a 100644 --- a/DownKyi/Models/Downloading.cs +++ b/DownKyi/Models/Downloading.cs @@ -1,61 +1,56 @@ using System; using System.Collections.Generic; using DownKyi.Core.BiliApi.VideoStream; -using Downloader; -using SqlSugar; +using DownKyi.Utils.DataAnnotations; +using FreeSql.DataAnnotations; namespace DownKyi.Models; [Serializable] public class Downloading { - [SugarColumn(IsPrimaryKey = true, ColumnName = "id")] + [Column(IsPrimary = true, Name = "id")] public string Id { get; set; } = null!; // Aria相关 - [SugarColumn(ColumnName = "gid", IsNullable = true)] + [Column(Name = "gid", IsNullable = true)] public string? Gid { get; set; } // 下载的文件 - [SugarColumn(ColumnName = "download_files", IsJson = true)] + [Column(Name = "download_files"), JsonMap] public Dictionary DownloadFiles { get; set; } = new(); // 已下载的文件 - [SugarColumn(ColumnName = "downloaded_files", IsJson = true)] + [Column(Name = "downloaded_files"), JsonMap] public List DownloadedFiles { get; set; } = new(); // 视频类别 - [SugarColumn(ColumnName = "play_stream_type")] - public PlayStreamType PlayStreamType { get; set; } + [Column(Name = "play_stream_type")] public PlayStreamType PlayStreamType { get; set; } // 下载状态 - [SugarColumn(ColumnName = "download_status")] - public DownloadStatus DownloadStatus { get; set; } + [Column(Name = "download_status")] public DownloadStatus DownloadStatus { get; set; } // 正在下载内容(音频、视频、弹幕、字幕、封面) - [SugarColumn(ColumnName = "download_content", IsNullable = true)] + [Column(Name = "download_content", IsNullable = true)] public string? DownloadContent { get; set; } // 下载状态显示 - [SugarColumn(ColumnName = "download_status_title", IsNullable = true)] + [Column(Name = "download_status_title", IsNullable = true)] public string? DownloadStatusTitle { get; set; } // 下载进度 - [SugarColumn(ColumnName = "progress")] public float Progress { get; set; } + [Column(Name = "progress")] public float Progress { get; set; } // 已下载大小/文件大小 - [SugarColumn(ColumnName = "downloading_file_size", IsNullable = true)] + [Column(Name = "downloading_file_size", IsNullable = true)] public string? DownloadingFileSize { get; set; } // 下载的最高速度 - [SugarColumn(ColumnName = "max_speed")] - public long MaxSpeed { get; set; } + [Column(Name = "max_speed")] public long MaxSpeed { get; set; } // 下载速度 - [SugarColumn(ColumnName = "speed_display", IsNullable = true)] + [Column(Name = "speed_display", IsNullable = true)] public string? SpeedDisplay { get; set; } - [Navigate(NavigateType.OneToOne, nameof(Id))] - [SugarColumn(IsIgnore = true)] - public DownloadBase DownloadBase { get; set; } + [Navigate(nameof(Id))] public DownloadBase? DownloadBase { get; set; } } \ No newline at end of file diff --git a/DownKyi/Services/Download/AddToDownloadService.cs b/DownKyi/Services/Download/AddToDownloadService.cs index 9da6cac..d1494e7 100644 --- a/DownKyi/Services/Download/AddToDownloadService.cs +++ b/DownKyi/Services/Download/AddToDownloadService.cs @@ -33,6 +33,7 @@ public class AddToDownloadService private IInfoService _videoInfoService; private VideoInfoView? _videoInfoView; private List? _videoSections; + private DownloadStorageService _downloadStorageService = (DownloadStorageService)App.Current.Container.Resolve(typeof(DownloadStorageService)); // 下载内容 private bool _downloadAudio = true; @@ -297,7 +298,7 @@ public class AddToDownloadService continue; } - bool f = item.DownloadBase.Cid == page.Cid && + bool f = item.DownloadBase.Cid == page.Cid && item.Resolution.Id == page.VideoQuality.Quality && item.VideoCodecName == page.VideoQuality.SelectedVideoCodec && ( @@ -328,7 +329,7 @@ public class AddToDownloadService continue; } - bool f = item.DownloadBase.Cid == page.Cid && + bool f = item.DownloadBase.Cid == page.Cid && item.Resolution.Id == page.VideoQuality.Quality && item.VideoCodecName == page.VideoQuality.SelectedVideoCodec && ( @@ -344,30 +345,34 @@ public class AddToDownloadService switch (repeatDownloadStrategy) { case RepeatDownloadStrategy.Ask: + { + var result = ButtonResult.Cancel; + await Dispatcher.UIThread.Invoke(async () => { - var result = ButtonResult.Cancel; - await Dispatcher.UIThread.Invoke(async () => - { - var param = new DialogParameters + var param = new DialogParameters { { "message", $"{item.Name}已下载,是否重新下载" }, }; - await dialogService.ShowDialogAsync(ViewAlreadyDownloadedDialogViewModel.Tag, param, buttonResult => { result = buttonResult.Result; }); + await dialogService.ShowDialogAsync(ViewAlreadyDownloadedDialogViewModel.Tag, param, buttonResult => { result = buttonResult.Result; }); + }); + + if (result == ButtonResult.OK) + { + App.PropertyChangeAsync(() => + { + App.DownloadedList.Remove(item); + _downloadStorageService.RemoveDownloaded(item); }); - - if (result == ButtonResult.OK) - { - App.PropertyChangeAsync(() => { App.DownloadedList.Remove(item); }); - isDownloaded = false; - } - else - { - isDownloaded = true; - } - - break; + isDownloaded = false; } + else + { + isDownloaded = true; + } + + break; + } case RepeatDownloadStrategy.ReDownload: isDownloaded = false; break; diff --git a/DownKyi/Services/Download/AriaDownloadService.cs b/DownKyi/Services/Download/AriaDownloadService.cs index 85f70fe..efc1bbb 100644 --- a/DownKyi/Services/Download/AriaDownloadService.cs +++ b/DownKyi/Services/Download/AriaDownloadService.cs @@ -176,7 +176,7 @@ public class AriaDownloadService : DownloadService, IDownloadService case DownloadResult.FAILED: case DownloadResult.ABORT: default: - return nullMark; + return NullMark; } } @@ -221,7 +221,7 @@ public class AriaDownloadService : DownloadService, IDownloadService /// public override string MixedFlow(DownloadingItem downloading, string? audioUid, string? videoUid) { - if (videoUid == nullMark) + if (videoUid == NullMark) { return null; } @@ -284,7 +284,7 @@ public class AriaDownloadService : DownloadService, IDownloadService /// protected override void Pause(DownloadingItem downloading) { - cancellationToken.ThrowIfCancellationRequested(); + CancellationToken?.ThrowIfCancellationRequested(); downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing"); if (downloading.Downloading.DownloadStatus == DownloadStatus.Pause) @@ -307,7 +307,7 @@ public class AriaDownloadService : DownloadService, IDownloadService /// private async Task IsExist(DownloadingItem downloading) { - var isExist = downloadingList.Contains(downloading); + var isExist = DownloadingList.Contains(downloading); if (isExist) { return true; @@ -479,7 +479,7 @@ public class AriaDownloadService : DownloadService, IDownloadService ariaManager.DownloadFinish += AriaDownloadFinish; return ariaManager.GetDownloadStatus(downloading.Downloading.Gid, new Action(() => { - cancellationToken.ThrowIfCancellationRequested(); + CancellationToken?.ThrowIfCancellationRequested(); switch (downloading.Downloading.DownloadStatus) { case DownloadStatus.Pause: @@ -499,7 +499,7 @@ public class AriaDownloadService : DownloadService, IDownloadService DownloadingItem? video = null; try { - video = downloadingList.FirstOrDefault(it => it.Downloading.Gid == gid); + video = DownloadingList.FirstOrDefault(it => it.Downloading.Gid == gid); } catch (InvalidOperationException e) { diff --git a/DownKyi/Services/Download/BuiltinDownloadService.cs b/DownKyi/Services/Download/BuiltinDownloadService.cs index 0421619..1fb92a0 100644 --- a/DownKyi/Services/Download/BuiltinDownloadService.cs +++ b/DownKyi/Services/Download/BuiltinDownloadService.cs @@ -177,7 +177,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService } else { - return nullMark; + return NullMark; } } catch (FileNotFoundException e) @@ -185,7 +185,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService Console.PrintLine("BuiltinDownloadService.DownloadVideo()发生异常: {0}", e); LogManager.Error("BuiltinDownloadService.DownloadVideo()", e); - return nullMark; + return NullMark; } } @@ -274,7 +274,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService /// protected override void Pause(DownloadingItem downloading) { - cancellationToken.ThrowIfCancellationRequested(); + CancellationToken?.ThrowIfCancellationRequested(); downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing"); if (downloading.Downloading.DownloadStatus == DownloadStatus.Pause) @@ -297,7 +297,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService /// private bool IsExist(DownloadingItem downloading) { - return downloadingList.Contains(downloading); + return DownloadingList.Contains(downloading); } #region 内建下载器 @@ -379,7 +379,7 @@ public class BuiltinDownloadService : DownloadService, IDownloadService // 阻塞当前任务,监听暂停事件 while (!isComplete) { - cancellationToken.ThrowIfCancellationRequested(); + CancellationToken?.ThrowIfCancellationRequested(); switch (downloading.Downloading.DownloadStatus) { case DownloadStatus.Pause: diff --git a/DownKyi/Services/Download/CustomAriaDownloadService.cs b/DownKyi/Services/Download/CustomAriaDownloadService.cs index a77a172..fe6120d 100644 --- a/DownKyi/Services/Download/CustomAriaDownloadService.cs +++ b/DownKyi/Services/Download/CustomAriaDownloadService.cs @@ -176,7 +176,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService case DownloadResult.FAILED: case DownloadResult.ABORT: default: - return nullMark; + return NullMark; } } @@ -218,7 +218,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService /// public override string MixedFlow(DownloadingItem downloading, string? audioUid, string? videoUid) { - if (videoUid == nullMark) + if (videoUid == NullMark) { return null; } @@ -278,7 +278,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService /// protected override void Pause(DownloadingItem downloading) { - cancellationToken.ThrowIfCancellationRequested(); + CancellationToken?.ThrowIfCancellationRequested(); downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing"); if (downloading.Downloading.DownloadStatus == DownloadStatus.Pause) @@ -301,7 +301,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService /// private async Task IsExist(DownloadingItem downloading) { - bool isExist = downloadingList.Contains(downloading); + bool isExist = DownloadingList.Contains(downloading); if (isExist) { return true; @@ -408,7 +408,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService ariaManager.DownloadFinish += AriaDownloadFinish; return ariaManager.GetDownloadStatus(downloading.Downloading.Gid, new Action(() => { - cancellationToken.ThrowIfCancellationRequested(); + CancellationToken?.ThrowIfCancellationRequested(); switch (downloading.Downloading.DownloadStatus) { case DownloadStatus.Pause: @@ -428,7 +428,7 @@ public class CustomAriaDownloadService : DownloadService, IDownloadService DownloadingItem video = null; try { - video = downloadingList.FirstOrDefault(it => it.Downloading.Gid == gid); + video = DownloadingList.FirstOrDefault(it => it.Downloading.Gid == gid); } catch (InvalidOperationException e) { diff --git a/DownKyi/Services/Download/DownloadService.cs b/DownKyi/Services/Download/DownloadService.cs index a0ef47c..8f1e312 100644 --- a/DownKyi/Services/Download/DownloadService.cs +++ b/DownKyi/Services/Download/DownloadService.cs @@ -19,7 +19,6 @@ using DownKyi.Models; using DownKyi.PrismExtension.Dialog; using DownKyi.Utils; using DownKyi.ViewModels.DownloadManager; -using ImTools; using Console = DownKyi.Core.Utils.Debugging.Console; namespace DownKyi.Services.Download; @@ -30,16 +29,18 @@ public abstract class DownloadService // protected TaskbarIcon _notifyIcon; protected readonly IDialogService? DialogService; - protected ObservableCollection downloadingList; - protected ObservableCollection downloadedList; + protected readonly ObservableCollection DownloadingList; + protected readonly ObservableCollection DownloadedList; - protected Task workTask; - protected CancellationTokenSource tokenSource; - protected CancellationToken cancellationToken; - protected List downloadingTasks = new(); + protected Task? WorkTask; + protected CancellationTokenSource? TokenSource; + protected CancellationToken? CancellationToken; + protected readonly List DownloadingTasks = new(); - protected readonly int retry = 5; - protected readonly string nullMark = ""; + protected const int Retry = 5; + protected const string NullMark = ""; + + protected readonly DownloadStorageService DownloadStorageService = (DownloadStorageService)App.Current.Container.Resolve(typeof(DownloadStorageService)); /// /// 初始化 @@ -50,8 +51,8 @@ public abstract class DownloadService /// public DownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList, IDialogService? dialogService) { - this.downloadingList = downloadingList; - this.downloadedList = downloadedList; + DownloadingList = downloadingList; + DownloadedList = downloadedList; DialogService = dialogService; } @@ -133,7 +134,7 @@ public abstract class DownloadService var codecs = Constant.GetCodecIds().FirstOrDefault(t => t.Id == video.CodecId); if (video.Id == downloading.Resolution.Id && codecs?.Name == downloading.VideoCodecName) { - return new() + return new VideoPlayUrlBasic { BackupUrl = video.BackupUrl, Codecs = video.Codecs, @@ -147,7 +148,7 @@ public abstract class DownloadService if (downloading?.PlayUrl?.Durl?.Count > 0) { var durl = downloading.PlayUrl.Durl.First(); - return new() + return new VideoPlayUrlBasic { BackupUrl = durl.BackupUrl, BaseUrl = durl.Url, @@ -199,7 +200,7 @@ public abstract class DownloadService downloading.SpeedDisplay = string.Empty; var title = $"{downloading.Name}"; - var assFile = $"{downloading.DownloadBase.FilePath}.ass"; + var assFile = $"{downloading.DownloadBase?.FilePath}.ass"; // 记录本次下载的文件 if (!downloading.Downloading.DownloadFiles.ContainsKey("danmaku")) @@ -243,8 +244,8 @@ public abstract class DownloadService return assFile; } - - + + protected List BaseDownloadSubtitle(DownloadingItem downloading) { // 更新状态显示 @@ -286,7 +287,7 @@ public abstract class DownloadService if (srtFiles.Count > 0) { var srtFile = $"{downloading.DownloadBase.FilePath}.srt"; - File.Copy(srtFiles[0], srtFile,true); + File.Copy(srtFiles[0], srtFile, true); srtFiles.Add(srtFile); } @@ -334,17 +335,17 @@ public abstract class DownloadService return finalFile; } - - - private string ConcatVideos(DownloadingItem downloading,List videoUids) + + + private string ConcatVideos(DownloadingItem downloading, List videoUids) { downloading.DownloadStatusTitle = DictionaryResource.GetString("ConcatVideos"); downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); downloading.DownloadingFileSize = string.Empty; downloading.SpeedDisplay = string.Empty; - + var finalFile = $"{downloading.DownloadBase.FilePath}.mp4"; - FFMpeg.Instance.ConcatVideos(videoUids,finalFile,(x)=>{}); + FFMpeg.Instance.ConcatVideos(videoUids, finalFile, (x) => { }); if (File.Exists(finalFile)) { var info = new FileInfo(finalFile); @@ -354,6 +355,7 @@ public abstract class DownloadService { downloading.FileSize = Format.FormatFileSize(0); } + return finalFile; } @@ -421,8 +423,8 @@ public abstract class DownloadService try { - downloadingTasks.RemoveAll((m) => m.IsCompleted); - foreach (var downloading in downloadingList) + DownloadingTasks.RemoveAll((m) => m.IsCompleted); + foreach (var downloading in DownloadingList) { if (downloading.Downloading.DownloadStatus == DownloadStatus.Downloading) { @@ -430,7 +432,7 @@ public abstract class DownloadService } } - foreach (var downloading in downloadingList) + foreach (var downloading in DownloadingList) { if (downloadingCount >= maxDownloading) { @@ -441,7 +443,7 @@ public abstract class DownloadService if (downloading.Downloading.DownloadStatus is not (DownloadStatus.NotStarted or DownloadStatus.WaitForDownload)) continue; //这里需要立刻设置状态,否则如果SingleDownload没有及时执行,会重复创建任务 downloading.Downloading.DownloadStatus = DownloadStatus.Downloading; - downloadingTasks.Add(SingleDownload(downloading)); + DownloadingTasks.Add(SingleDownload(downloading)); downloadingCount++; } } @@ -457,7 +459,7 @@ public abstract class DownloadService } // 判断是否该结束线程,若为true,跳出while循环 - if (cancellationToken.IsCancellationRequested) + if (CancellationToken?.IsCancellationRequested == true) { Console.PrintLine($"{Tag}.DoWork() 下载服务结束,跳出while循环"); LogManager.Debug($"{Tag}.DoWork()", "下载服务结束"); @@ -465,19 +467,19 @@ public abstract class DownloadService } // 判断下载列表中的视频是否全部下载完成 - if (lastDownloadingCount > 0 && downloadingList.Count == 0 && downloadedList.Count > 0) + if (lastDownloadingCount > 0 && DownloadingList.Count == 0 && DownloadedList.Count > 0) { AfterDownload(); } - lastDownloadingCount = downloadingList.Count; + lastDownloadingCount = DownloadingList.Count; // 降低CPU占用 await Task.Delay(500); } - await Task.WhenAny(Task.WhenAll(downloadingTasks), Task.Delay(30000)); - foreach (var tsk in downloadingTasks.FindAll((m) => !m.IsCompleted)) + await Task.WhenAny(Task.WhenAll(DownloadingTasks), Task.Delay(30000)); + foreach (var tsk in DownloadingTasks.FindAll((m) => !m.IsCompleted)) { Console.PrintLine($"{Tag}.DoWork() 任务结束超时"); LogManager.Debug($"{Tag}.DoWork()", "任务结束超时"); @@ -510,7 +512,7 @@ public abstract class DownloadService LogManager.Debug(Tag, e.Message); var alertService = new AlertService(DialogService); - var result = await alertService.ShowError($"{path}{DictionaryResource.GetString("DirectoryError")}"); + await alertService.ShowError($"{path}{DictionaryResource.GetString("DirectoryError")}"); return; } @@ -541,17 +543,17 @@ public abstract class DownloadService // 如果需要下载音频 if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"]) { - for (var i = 0; i < retry; i++) + for (var i = 0; i < Retry; i++) { audioUid = DownloadAudio(downloading); - if (audioUid != null && audioUid != nullMark) + if (audioUid != null && audioUid != NullMark) { break; } } } - if (audioUid == nullMark) + if (audioUid == NullMark) { DownloadFailed(downloading); return; @@ -559,22 +561,22 @@ public abstract class DownloadService Pause(downloading); - + // 如果需要下载视频 if (downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) { //videoUid = DownloadVideo(downloading); - for (var i = 0; i < retry; i++) + for (var i = 0; i < Retry; i++) { videoUid = DownloadVideo(downloading); - if (videoUid != null && videoUid != nullMark) + if (videoUid != null && videoUid != NullMark) { break; } } } - if (videoUid == nullMark) + if (videoUid == NullMark) { DownloadFailed(downloading); return; @@ -601,29 +603,29 @@ public abstract class DownloadService isMediaSuccess = File.Exists(outputMedia); } } - else if(downloading.PlayUrl.Durl != null) + else if (downloading.PlayUrl.Durl != null) { if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || - downloading.DownloadBase.NeedDownloadContent["downloadVideo"] ) + downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) { var durls = downloading.PlayUrl.Durl.ToList(); var downloadStatus = durls - .Select((durl, index) => new { Durl = durl, Index = index }) - .ToDictionary(x => x.Index, x => new { Durl = x.Durl, Result = string.Empty }); + .Select((durl, index) => new { Durl = durl, Index = index }) + .ToDictionary(x => x.Index, x => new { Durl = x.Durl, Result = string.Empty }); for (int i = 0; i < durls.Count; i++) { downloading.PlayUrl.Durl = new List { durls[i] }; var result = DownloadVideo(downloading); - downloadStatus[i] = new { Durl = durls[i], Result = result ?? nullMark }; + downloadStatus[i] = new { Durl = durls[i], Result = result ?? NullMark }; } - + int retryCount = 0; - while (retryCount < retry && downloadStatus.Values - .Any(x => x.Result == nullMark)) + while (retryCount < Retry && downloadStatus.Values + .Any(x => x.Result == NullMark)) { var toRetry = downloadStatus - .Where(x => retryCount == 0 || x.Value.Result == nullMark) + .Where(x => retryCount == 0 || x.Value.Result == NullMark) .ToList(); foreach (var item in toRetry) @@ -632,11 +634,12 @@ public abstract class DownloadService var result = DownloadVideo(downloading); downloadStatus[item.Key] = new { item.Value.Durl, Result = result }; } + retryCount++; await Task.Delay(1000); } - - if(downloadStatus.Values.Any(x => x.Result == nullMark)) + + if (downloadStatus.Values.Any(x => x.Result == NullMark)) { DownloadFailed(downloading); return; @@ -646,26 +649,27 @@ public abstract class DownloadService if (durls.Count > 1) { - var output = ConcatVideos(downloading,downloadStatus.Values + var output = ConcatVideos(downloading, downloadStatus.Values .Select(x => x.Result).ToList()); isMediaSuccess = File.Exists(output); } else { - var outputMedia = MixedFlow(downloading, null, downloadStatus.First().Value.Result); - isMediaSuccess = File.Exists(outputMedia); + var outputMedia = MixedFlow(downloading, null, downloadStatus.First().Value.Result); + isMediaSuccess = File.Exists(outputMedia); } } - if(downloading.DownloadBase.NeedDownloadContent["downloadAudio"] && - !downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) + if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] && + !downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) { //音频分离? } Pause(downloading); } + string? outputDanmaku = null; // 如果需要下载弹幕 if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"]) @@ -777,11 +781,12 @@ public abstract class DownloadService Downloaded = downloaded }; + DownloadStorageService.AddDownloaded(downloadedItem); App.PropertyChangeAsync(() => { // 加入到下载完成list中,并从下载中list去除 - downloadedList.Add(downloadedItem); - downloadingList.Remove(downloading); + DownloadedList.Add(downloadedItem); + DownloadingList.Remove(downloading); // 下载完成列表排序 var finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort(); @@ -868,25 +873,22 @@ public abstract class DownloadService protected async Task BaseEndTask() { // 结束任务 - tokenSource.Cancel(); + TokenSource?.Cancel(); - await workTask; + if (WorkTask != null) await WorkTask; //先简单等待一下 // 下载数据存储服务 - var downloadStorageService = new DownloadStorageService(); + var downloadStorageService = (DownloadStorageService)App.Current.Container.Resolve(typeof(DownloadStorageService)); // 保存数据 - foreach (var item in downloadingList) + foreach (var item in DownloadingList) { switch (item.Downloading.DownloadStatus) { case DownloadStatus.NotStarted: - break; case DownloadStatus.WaitForDownload: - break; case DownloadStatus.PauseStarted: - break; case DownloadStatus.Pause: break; case DownloadStatus.Downloading: @@ -896,7 +898,6 @@ public abstract class DownloadService break; case DownloadStatus.DownloadSucceed: case DownloadStatus.DownloadFailed: - break; default: break; } @@ -905,11 +906,6 @@ public abstract class DownloadService downloadStorageService.UpdateDownloading(item); } - - foreach (var item in downloadedList) - { - downloadStorageService.UpdateDownloaded(item); - } } /// @@ -917,14 +913,14 @@ public abstract class DownloadService /// protected void BaseStart() { - tokenSource = new CancellationTokenSource(); - cancellationToken = tokenSource.Token; + TokenSource = new CancellationTokenSource(); + CancellationToken = TokenSource.Token; // _notifyIcon = new TaskbarIcon(); // _notifyIcon.IconSource = new BitmapImage(new Uri("pack://application:,,,/Resources/favicon.ico")); - workTask = Task.Run(DoWork); + WorkTask = Task.Run(DoWork); } - + #region 抽象接口函数 public abstract void Parse(DownloadingItem downloading); diff --git a/DownKyi/Services/Download/DownloadStorageService.cs b/DownKyi/Services/Download/DownloadStorageService.cs index 9c8c5dd..130c0cd 100644 --- a/DownKyi/Services/Download/DownloadStorageService.cs +++ b/DownKyi/Services/Download/DownloadStorageService.cs @@ -2,13 +2,21 @@ using System.Linq; using DownKyi.Models; using DownKyi.ViewModels.DownloadManager; -using SqlSugar; +using FreeSql; namespace DownKyi.Services.Download; public class DownloadStorageService { - private readonly ISqlSugarClient _sqlSugarClient = (ISqlSugarClient)App.Current.Container.Resolve(typeof(ISqlSugarClient)); + private readonly IBaseRepository _downloadingRepository; + private readonly IBaseRepository _downloadedRepository; + + + public DownloadStorageService(IBaseRepository downloadingRepository, IBaseRepository downloadedRepository) + { + _downloadingRepository = downloadingRepository; + _downloadedRepository = downloadedRepository; + } #region 下载中数据 @@ -27,7 +35,7 @@ public class DownloadStorageService downloading.Id = downloadingItem.DownloadBase.Id; downloading.DownloadBase = downloadingItem.DownloadBase; - _sqlSugarClient.UpdateNav(downloading, new UpdateNavRootOptions { IsInsertRoot = true }).IncludesAllFirstLayer().ExecuteCommand(); + _downloadingRepository.InsertOrUpdate(downloading); } /// @@ -41,7 +49,7 @@ public class DownloadStorageService return; } - _sqlSugarClient.Deleteable(it => it.Id == downloadingItem.Downloading.Id).ExecuteCommand(); + _downloadingRepository.Delete(it => it.Id == downloadingItem.Downloading.Id); } /// @@ -50,7 +58,7 @@ public class DownloadStorageService /// public List GetDownloading() { - var downloadingList = _sqlSugarClient.Queryable().IncludesAllFirstLayer().ToList(); + var downloadingList = _downloadingRepository.Select.ToList(); return downloadingList.Select(downloading => new DownloadingItem { Downloading = downloading, DownloadBase = downloading.DownloadBase }).ToList(); } @@ -69,7 +77,7 @@ public class DownloadStorageService var downloading = downloadingItem.Downloading; downloading.DownloadBase = downloadingItem.DownloadBase; - _sqlSugarClient.UpdateNav(downloading).IncludesAllFirstLayer().ExecuteCommand(); + _downloadingRepository.Update(downloading); } #endregion @@ -89,9 +97,11 @@ public class DownloadStorageService var downloaded = downloadedItem.Downloaded; downloaded.Id = downloadedItem.DownloadBase.Id; - _sqlSugarClient.AsTenant().BeginTran(); - _sqlSugarClient.Storageable(downloaded).TranLock().ExecuteCommand(); - _sqlSugarClient.AsTenant().CommitTran(); + var exists = _downloadedRepository.Select.Any(download => download.Id == downloaded.Id); + if (!exists) + { + _downloadedRepository.Insert(downloaded); + } } /// @@ -105,7 +115,7 @@ public class DownloadStorageService return; } - _sqlSugarClient.DeleteNav(it => it.Id == downloadedItem.Downloaded.Id).Include(o1 => o1.DownloadBase).ExecuteCommand(); + _downloadedRepository.Delete(it => it.Id == downloadedItem.Downloaded.Id); } /// @@ -114,7 +124,7 @@ public class DownloadStorageService /// public List GetDownloaded() { - var downloadedList = _sqlSugarClient.Queryable().IncludesAllFirstLayer().ToList(); + var downloadedList = _downloadedRepository.Select.LeftJoin(downloaded => downloaded.DownloadBase.Id == downloaded.Id).ToList(); return downloadedList.Select(downloaded => new DownloadedItem { Downloaded = downloaded, DownloadBase = downloaded.DownloadBase }).ToList(); } @@ -132,7 +142,12 @@ public class DownloadStorageService var downloaded = downloadedItem.Downloaded; downloaded.DownloadBase = downloadedItem.DownloadBase; - _sqlSugarClient.UpdateNav(downloaded).IncludesAllFirstLayer().ExecuteCommand(); + _downloadedRepository.Update(downloaded); + } + + public void ClearDownloaded() + { + _downloadedRepository.DeleteCascadeByDatabase(item => true); } #endregion diff --git a/DownKyi/Utils/DataAnnotations/JsonMapAttribute.cs b/DownKyi/Utils/DataAnnotations/JsonMapAttribute.cs new file mode 100644 index 0000000..3c1c246 --- /dev/null +++ b/DownKyi/Utils/DataAnnotations/JsonMapAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace DownKyi.Utils.DataAnnotations; + +/// +/// When the entity class property is , map storage in JSON format.
+/// 当实体类属性为【对象】时,以 JSON 形式映射存储 +///
+[AttributeUsage(AttributeTargets.Property)] +public class JsonMapAttribute : Attribute { } \ No newline at end of file diff --git a/DownKyi/Utils/JsonMapCore.cs b/DownKyi/Utils/JsonMapCore.cs new file mode 100644 index 0000000..78ba665 --- /dev/null +++ b/DownKyi/Utils/JsonMapCore.cs @@ -0,0 +1,274 @@ +using System.Text.Json; +using DownKyi.Utils.DataAnnotations; +using FreeSql; +using FreeSql.DataAnnotations; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; + +namespace DownKyi.Utils; + +public static class SystemTextJsonHelper +{ + private static readonly MethodInfo _deserializeMethodInfo = + typeof(JsonSerializer).GetMethods(BindingFlags.Public | BindingFlags.Static) + .First(m => m.Name == "Deserialize" + && m.IsGenericMethod + && m.GetParameters().Length == 2 + && m.GetParameters()[0].ParameterType == typeof(string)); + + public static object? Deserialize(string json, Type type, JsonSerializerOptions options) + { + // 创建泛型方法 + var genericMethod = _deserializeMethodInfo.MakeGenericMethod(type); + return genericMethod.Invoke(null, new object[] { json, options }); + } + + public static string Serialize(object value, JsonSerializerOptions options) + { + return JsonSerializer.Serialize(value, options); + } +} + +public static class FreeSqlJsonMapCoreExtensions +{ + private static int _isAoped = 0; + private static readonly ConcurrentDictionary DicTypes = new(); + + private static readonly MethodInfo? MethodJsonConvertDeserializeObject = + typeof(SystemTextJsonHelper).GetMethod("Deserialize", new[] { typeof(string), typeof(Type), typeof(JsonSerializerOptions) }); + + private static readonly MethodInfo? MethodJsonConvertSerializeObject = + typeof(SystemTextJsonHelper).GetMethod("Serialize", new[] { typeof(object), typeof(JsonSerializerOptions) }); + + private static readonly ConcurrentDictionary> DicJsonMapFluentApi = new(); + + private static readonly object ConcurrentObj = new object(); + + public static ColumnFluent JsonMap(this ColumnFluent col) + { + DicJsonMapFluentApi.GetOrAdd(col._entityType, et => new ConcurrentDictionary()) + .GetOrAdd(col._property.Name, pn => true); + return col; + } + + /// + /// When the entity class property is and the attribute is marked as , map storage in JSON format.
+ /// 当实体类属性为【对象】时,并且标记特性 [JsonMap] 时,该属性将以JSON形式映射存储 + ///
+ /// + public static void UseJsonMap(this IFreeSql fsql) + { + UseJsonMap(fsql, JsonSerializerOptions.Default); + } + + public static void UseJsonMap(this IFreeSql fsql, JsonSerializerOptions options) + { + if (Interlocked.CompareExchange(ref _isAoped, 1, 0) == 0) + { + FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionSwitchTypeFullName.Add((LabelTarget returnTarget, Expression valueExp, Type type) => + { + if (DicTypes.ContainsKey(type)) + return Expression.IfThenElse( + Expression.TypeIs(valueExp, type), + Expression.Return(returnTarget, valueExp), + Expression.Return(returnTarget, + Expression.TypeAs( + Expression.Call(MethodJsonConvertDeserializeObject, Expression.Convert(valueExp, typeof(string)), Expression.Constant(type), + Expression.Constant(options)), type)) + ); + return null; + }); + } + + fsql.Aop.ConfigEntityProperty += (s, e) => + { + var isJsonMap = e.Property.GetCustomAttributes(typeof(JsonMapAttribute), false).Any() || + DicJsonMapFluentApi.TryGetValue(e.EntityType, out var tryjmfu) && tryjmfu.ContainsKey(e.Property.Name); + if (isJsonMap) + { + if (DicTypes.ContainsKey(e.Property.PropertyType) == false && + FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(e.Property.PropertyType)) + return; //基础类型使用 JsonMap 无效 + + if (e.ModifyResult.MapType == null) + { + e.ModifyResult.MapType = typeof(string); + e.ModifyResult.StringLength = -2; + } + + if (DicTypes.TryAdd(e.Property.PropertyType, true)) + { + lock (ConcurrentObj) + { + FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple[e.Property.PropertyType] = true; + FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToStringIfThenElse.Add((LabelTarget returnTarget, Expression valueExp, Expression elseExp, + Type type) => + { + return Expression.IfThenElse( + Expression.TypeIs(valueExp, e.Property.PropertyType), + Expression.Return(returnTarget, + Expression.Call(MethodJsonConvertSerializeObject, Expression.Convert(valueExp, typeof(object)), Expression.Constant(options)), typeof(object)), + elseExp); + }); + } + } + } + }; + switch (fsql.Ado.DataType) + { + case DataType.Sqlite: + case DataType.MySql: + case DataType.OdbcMySql: + case DataType.CustomMySql: + case DataType.SqlServer: + case DataType.OdbcSqlServer: + case DataType.CustomSqlServer: + case DataType.Oracle: + case DataType.OdbcOracle: + case DataType.CustomOracle: + case DataType.Dameng: + case DataType.DuckDB: + fsql.Aop.ParseExpression += (_, e) => + { + //if (e.Expression is MethodCallExpression callExp) + //{ + // var objExp = callExp.Object; + // var objType = objExp?.Type; + // if (objType?.FullName == "System.Byte[]") return; + + // if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable)) + // { + // objExp = callExp.Arguments.FirstOrDefault(); + // objType = objExp?.Type; + // } + // if (objType == null) objType = callExp.Method.DeclaringType; + // if (objType != null || objType.IsArrayOrList()) + // { + // string left = null; + // switch (callExp.Method.Name) + // { + // case "Any": + // left = objExp == null ? null : getExp(objExp); + // if (left.StartsWith("(") || left.EndsWith(")")) left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; + // return $"(case when {left} is null then 0 else array_length({left},1) end > 0)"; + // case "Contains": + // } + // } + //} + //解析 POCO Json a.Customer.Name + if (e.Expression is MemberExpression memExp) + { + if (e.Expression.IsParameter() == false) return; + var parentMemExps = new Stack(); + parentMemExps.Push(memExp); + while (true) + { + switch (memExp.Expression?.NodeType) + { + case ExpressionType.MemberAccess: + case ExpressionType.Parameter: break; + default: return; + } + + switch (memExp.Expression.NodeType) + { + case ExpressionType.MemberAccess: + memExp = memExp.Expression as MemberExpression; + if (memExp == null) return; + parentMemExps.Push(memExp); + break; + case ExpressionType.Parameter: + var tb = fsql.CodeFirst.GetTableByEntity(memExp.Expression.Type); + if (tb == null) return; + if (tb.ColumnsByCs.TryGetValue(parentMemExps.Pop().Member.Name, out var trycol) == false) return; + if (DicTypes.ContainsKey(trycol.CsType) == false) return; + var result = e.FreeParse(Expression.MakeMemberAccess(memExp.Expression, tb.Properties[trycol.CsName])); + if (parentMemExps.Any() == false) + { + e.Result = result; + return; + } + + var jsonPath = ""; + switch (fsql.Ado.DataType) + { + case DataType.Sqlite: + case DataType.MySql: + case DataType.OdbcMySql: + case DataType.CustomMySql: + StyleJsonExtract(); + return; + case DataType.SqlServer: + case DataType.OdbcSqlServer: + case DataType.CustomSqlServer: + case DataType.Oracle: + case DataType.OdbcOracle: + case DataType.CustomOracle: + case DataType.Dameng: + StyleJsonValue(); + return; + case DataType.DuckDB: + StyleDotAccess(); + return; + } + + StylePgJson(); + return; + + void StyleJsonExtract() + { + while (parentMemExps.Any()) + { + memExp = parentMemExps.Pop(); + jsonPath = $"{jsonPath}.{memExp.Member.Name}"; + } + + e.Result = $"json_extract({result},'${jsonPath}')"; + } + + void StyleJsonValue() + { + while (parentMemExps.Any()) + { + memExp = parentMemExps.Pop(); + jsonPath = $"{jsonPath}.{memExp.Member.Name}"; + } + + e.Result = $"json_value({result},'${jsonPath}')"; + } + + void StyleDotAccess() + { + while (parentMemExps.Any()) + { + memExp = parentMemExps.Pop(); + result = $"{result}['{memExp.Member.Name}']"; + } + + e.Result = result; + } + + void StylePgJson() + { + while (parentMemExps.Any()) + { + memExp = parentMemExps.Pop(); + var opt = parentMemExps.Any() ? "->" : $"->>{(memExp.Type.IsArrayOrList() ? "/*json array*/" : "")}"; + result = $"{result}{opt}'{memExp.Member.Name}'"; + } + + e.Result = result; + } + } + } + } + }; + break; + } + } +} \ No newline at end of file diff --git a/DownKyi/ViewModels/Dialogs/NewVersionAvailableDialogViewModel.cs b/DownKyi/ViewModels/Dialogs/NewVersionAvailableDialogViewModel.cs index 6c06d89..47d7879 100644 --- a/DownKyi/ViewModels/Dialogs/NewVersionAvailableDialogViewModel.cs +++ b/DownKyi/ViewModels/Dialogs/NewVersionAvailableDialogViewModel.cs @@ -3,7 +3,6 @@ using Avalonia.Controls.Documents; using DownKyi.Utils; using Prism.Commands; using Prism.Services.Dialogs; -using SqlSugar; using System.Linq; using DownKyi.Core.Settings; using DownKyi.Models; diff --git a/DownKyi/ViewModels/Dialogs/ViewUpgradingDialogViewModel.cs b/DownKyi/ViewModels/Dialogs/ViewUpgradingDialogViewModel.cs index 3be559a..42a0040 100644 --- a/DownKyi/ViewModels/Dialogs/ViewUpgradingDialogViewModel.cs +++ b/DownKyi/ViewModels/Dialogs/ViewUpgradingDialogViewModel.cs @@ -12,17 +12,16 @@ using DownKyi.Core.BiliApi.Login; using DownKyi.Core.Storage; using DownKyi.Core.Storage.Database; using DownKyi.Models; +using FreeSql; using Prism.Commands; using Prism.Services.Dialogs; -using SqlSugar; -using Exception = System.Exception; namespace DownKyi.ViewModels.Dialogs; public class ViewUpgradingDialogViewModel : BaseDialogViewModel { public const string Tag = "DialogLoading"; - private readonly ISqlSugarClient _sqlSugarClient; + private readonly IBaseRepository _downloadedRepository; #region 页面属性申明 @@ -70,9 +69,9 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel #endregion - public ViewUpgradingDialogViewModel(ISqlSugarClient sqlSugarClient) + public ViewUpgradingDialogViewModel(IBaseRepository downloadedRepository) { - _sqlSugarClient = sqlSugarClient; + _downloadedRepository = downloadedRepository; Message = "数据迁移中、请不要关闭软件"; } @@ -90,7 +89,6 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel { Task.Run(() => { Upgrade1_0_20To1_0_21(); }); } - private void Upgrade1_0_20To1_0_21() { @@ -137,18 +135,25 @@ 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 SqliteDatabase(oldDbPath); +#else var dbHelper = new SqliteDatabase(oldDbPath, "bdb8eb69-3698-4af9-b722-9312d0fba623"); +#endif 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 => + JOIN download_base db ON d.id = db.id", reader => { while (reader.Read()) { @@ -161,23 +166,24 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel // 反序列化 var record = NrbfDecoder.DecodeClassRecord(stream); if (!record.TypeNameMatches(typeof(Downloaded))) continue; - + var downloadedObj = new Downloaded { + Id = reader["id"].ToString() ?? Guid.NewGuid().ToString("N"), MaxSpeedDisplay = record.GetString($"<{nameof(Downloaded.MaxSpeedDisplay)}>k__BackingField"), FinishedTime = record.GetString($"<{nameof(Downloaded.FinishedTime)}>k__BackingField") ?? "", FinishedTimestamp = record.GetInt64($"<{nameof(Downloaded.FinishedTimestamp)}>k__BackingField") }; - - + + validRecords.Add( - (string)reader["id"], - new DownloadedWithData - { + (string)reader["id"], + new DownloadedWithData + { Downloaded = downloadedObj, DownloadBaseData = (byte[])reader["download_base_data"] }); - + totalCount++; } catch (Exception e) @@ -212,15 +218,13 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel return quality; }); - var i = 0; - + try { - int batchSize = 200; + const int batchSize = 200; var downloadedList = new List(); - int processedCount = 0; + var processedCount = 0; - _sqlSugarClient.Ado.BeginTran(); foreach (var item in validRecords) { try @@ -229,17 +233,16 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel var record = NrbfDecoder.DecodeClassRecord(stream); if (record.TypeNameMatches(typeof(DownloadBase))) { - var needDownloadContentRecord = - record.GetClassRecord($"<{nameof(DownloadBase.NeedDownloadContent)}>k__BackingField"); - var needDownloadContent = needDownloadContentRecord != null - ? readNeedDownloadContent(needDownloadContentRecord) - : new Dictionary(); + var needDownloadContentRecord = record.GetClassRecord($"<{nameof(DownloadBase.NeedDownloadContent)}>k__BackingField"); + var needDownloadContent = needDownloadContentRecord != null ? readNeedDownloadContent(needDownloadContentRecord) : new Dictionary(); var download = new Downloaded { + Id = item.Value.Downloaded.Id, MaxSpeedDisplay = item.Value.Downloaded.MaxSpeedDisplay, FinishedTime = item.Value.Downloaded.FinishedTime, FinishedTimestamp = item.Value.Downloaded.FinishedTimestamp, + DownloadBase = new DownloadBase { NeedDownloadContent = needDownloadContent, @@ -247,77 +250,56 @@ 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); processedCount++; - if (processedCount % batchSize == 0 || processedCount == totalCount) + if (processedCount % batchSize != 0 && processedCount != totalCount) continue; + _downloadedRepository.Insert(downloadedList); + downloadedList.Clear(); + + // 更新进度 + var percent = processedCount / (double)totalCount * 100; + Dispatcher.UIThread.Invoke(() => { - _sqlSugarClient.InsertNav(downloadedList) - .Include(o1 => o1.DownloadBase) - .ExecuteCommand(); - - - downloadedList.Clear(); - - // 更新进度 - var percent = processedCount / (double)totalCount * 100; - Dispatcher.UIThread.Invoke(() => - { - Percent = percent; - SetMessage($"正在迁移下载信息({processedCount}/{totalCount})"); - }); - } + Percent = percent; + SetMessage($"正在迁移下载信息({processedCount}/{totalCount})"); + }); } } catch (Exception ex) { - /*忽略*/ + Console.WriteLine(ex.ToString()); } } - _sqlSugarClient.Ado.CommitTran(); + dbHelper.Dispose(); File.Delete(oldDbPath); - + Dispatcher.UIThread.Invoke(() => { RestartVisible = true; SetMessage("下载信息迁移完成"); + App.Current.RefreshDownloadedList(); }); } catch (Exception e) { - _sqlSugarClient.Ado.RollbackTran(); SetMessage($"迁移失败: {e.Message}"); } - } else { @@ -329,11 +311,10 @@ public class ViewUpgradingDialogViewModel : BaseDialogViewModel Dispatcher.UIThread.Invoke(() => RaiseRequestClose(new DialogResult())); } } - + private class DownloadedWithData { - public Downloaded Downloaded { get; set; } - public byte[] DownloadBaseData { get; set; } + public Downloaded Downloaded { get; set; } = new(); + public byte[] DownloadBaseData { get; set; } = Array.Empty(); } - } \ No newline at end of file diff --git a/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs b/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs index 5be5d12..d471c22 100644 --- a/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs +++ b/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs @@ -2,7 +2,6 @@ using DownKyi.Core.BiliApi.BiliUtils; using DownKyi.Core.BiliApi.Zone; using DownKyi.Models; -using DownKyi.PrismExtension.Dialog; using DownKyi.Utils; using Prism.Mvvm; @@ -10,18 +9,6 @@ namespace DownKyi.ViewModels.DownloadManager { public class DownloadBaseItem : BindableBase { - public IDialogService? DialogService; - - public DownloadBaseItem() - { - DialogService = null; - } - - public DownloadBaseItem(IDialogService? dialogService) - { - DialogService = dialogService; - } - // model数据 private DownloadBase? _downloadBase; @@ -40,9 +27,9 @@ namespace DownKyi.ViewModels.DownloadManager } // 视频分区image - private DrawingImage _zoneImage; + private DrawingImage? _zoneImage; - public DrawingImage ZoneImage + public DrawingImage? ZoneImage { get => _zoneImage; set => SetProperty(ref _zoneImage, value); diff --git a/DownKyi/ViewModels/DownloadManager/DownloadedItem.cs b/DownKyi/ViewModels/DownloadManager/DownloadedItem.cs index 9747e82..f75ac54 100644 --- a/DownKyi/ViewModels/DownloadManager/DownloadedItem.cs +++ b/DownKyi/ViewModels/DownloadManager/DownloadedItem.cs @@ -1,183 +1,76 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using DownKyi.Images; +using DownKyi.Images; using DownKyi.Models; -using DownKyi.Services; using DownKyi.Utils; -using Prism.Commands; -using Prism.Services.Dialogs; -using IDialogService = DownKyi.PrismExtension.Dialog.IDialogService; -namespace DownKyi.ViewModels.DownloadManager +namespace DownKyi.ViewModels.DownloadManager; + +public class DownloadedItem : DownloadBaseItem { - public class DownloadedItem : DownloadBaseItem + public DownloadedItem() { - public DownloadedItem() : this(null) - { - } + // 打开文件夹按钮 + OpenFolder = ButtonIcon.Instance().Folder; + OpenFolder.Fill = DictionaryResource.GetColor("ColorPrimary"); - public DownloadedItem(IDialogService? dialogService) : base(dialogService) - { - // 打开文件夹按钮 - OpenFolder = ButtonIcon.Instance().Folder; - OpenFolder.Fill = DictionaryResource.GetColor("ColorPrimary"); + // 打开视频按钮 + OpenVideo = ButtonIcon.Instance().Start; + OpenVideo.Fill = DictionaryResource.GetColor("ColorPrimary"); - // 打开视频按钮 - OpenVideo = ButtonIcon.Instance().Start; - OpenVideo.Fill = DictionaryResource.GetColor("ColorPrimary"); - - // 删除按钮 - RemoveVideo = ButtonIcon.Instance().Trash; - RemoveVideo.Fill = DictionaryResource.GetColor("ColorWarning"); - } - - // model数据 - public Downloaded Downloaded { get; set; } - - // 下载速度 - public string? MaxSpeedDisplay - { - get => Downloaded.MaxSpeedDisplay; - set - { - Downloaded.MaxSpeedDisplay = value; - RaisePropertyChanged(); - } - } - - // 完成时间 - public string FinishedTime - { - get => Downloaded.FinishedTime; - set - { - Downloaded.FinishedTime = value; - RaisePropertyChanged("FinishedTime"); - } - } - - #region 控制按钮 - - private VectorImage _openFolder; - - public VectorImage OpenFolder - { - get => _openFolder; - set => SetProperty(ref _openFolder, value); - } - - private VectorImage _openVideo; - - public VectorImage OpenVideo - { - get => _openVideo; - set => SetProperty(ref _openVideo, value); - } - - private VectorImage _removeVideo; - - public VectorImage RemoveVideo - { - get => _removeVideo; - set => SetProperty(ref _removeVideo, value); - } - - #endregion - - #region 命令申明 - - // 打开文件夹事件 - private DelegateCommand? _openFolderCommand; - - public DelegateCommand OpenFolderCommand => _openFolderCommand ??= new DelegateCommand(ExecuteOpenFolderCommand); - - - private static readonly IReadOnlyDictionary FileSuffixMap = new Dictionary - { - { "downloadVideo", new[] { ".mp4", ".flv" } }, - { "downloadAudio", new[] { ".aac", ".mp3" } }, - { "downloadCover", new[] { ".jpg" } }, - { "downloadDanmaku", new[] { ".ass" } }, - { "downloadSubtitle", new[] { ".srt" } } - }; - - /// - /// 打开文件夹事件 - /// - private void ExecuteOpenFolderCommand() - { - if (DownloadBase == null) - { - return; - } - - var downLoadContents = DownloadBase.NeedDownloadContent.Where(e => e.Value == true).Select(e => e.Key); - var fileSuffixes = downLoadContents - .Where(content => FileSuffixMap.ContainsKey(content)) - .SelectMany(content => FileSuffixMap[content]) - .ToArray(); - if (fileSuffixes.Length <= 0) return; - foreach (var suffix in fileSuffixes) - { - var videoPath = $"{DownloadBase.FilePath}{suffix}"; - var fileInfo = new FileInfo(videoPath); - if (File.Exists(fileInfo.FullName) && fileInfo.DirectoryName != null) - { - PlatformHelper.OpenFolder(fileInfo.DirectoryName); - return; - } - } - // eventAggregator.GetEvent().Publish("没有找到视频文件,可能被删除或移动!"); - } - - // 打开视频事件 - private DelegateCommand? _openVideoCommand; - public DelegateCommand OpenVideoCommand => _openVideoCommand ??= new DelegateCommand(ExecuteOpenVideoCommand); - - /// - /// 打开视频事件 - /// - private void ExecuteOpenVideoCommand() - { - if (DownloadBase == null) - { - return; - } - - var videoPath = $"{DownloadBase.FilePath}.mp4"; - var fileInfo = new FileInfo(videoPath); - if (File.Exists(fileInfo.FullName)) - { - PlatformHelper.Open(fileInfo.FullName); - } - else - { - //eventAggregator.GetEvent().Publish(DictionaryResource.GetString("TipAddDownloadingZero")); - //eventAggregator.GetEvent().Publish("没有找到视频文件,可能被删除或移动!"); - } - } - - // 删除事件 - private DelegateCommand? _removeVideoCommand; - - public DelegateCommand RemoveVideoCommand => _removeVideoCommand ??= new DelegateCommand(ExecuteRemoveVideoCommand); - - /// - /// 删除事件 - /// - private async void ExecuteRemoveVideoCommand() - { - var alertService = new AlertService(DialogService); - var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete"), 2); - if (result != ButtonResult.OK) - { - return; - } - - App.DownloadedList?.Remove(this); - } - - #endregion + // 删除按钮 + RemoveVideo = ButtonIcon.Instance().Trash; + RemoveVideo.Fill = DictionaryResource.GetColor("ColorWarning"); } + + // model数据 + public Downloaded Downloaded { get; set; } + + // 下载速度 + public string? MaxSpeedDisplay + { + get => Downloaded.MaxSpeedDisplay; + set + { + Downloaded.MaxSpeedDisplay = value; + RaisePropertyChanged(); + } + } + + // 完成时间 + public string FinishedTime + { + get => Downloaded.FinishedTime; + set + { + Downloaded.FinishedTime = value; + RaisePropertyChanged(); + } + } + + #region 控制按钮 + + private VectorImage _openFolder; + + public VectorImage OpenFolder + { + get => _openFolder; + set => SetProperty(ref _openFolder, value); + } + + private VectorImage _openVideo; + + public VectorImage OpenVideo + { + get => _openVideo; + set => SetProperty(ref _openVideo, value); + } + + private VectorImage _removeVideo; + + public VectorImage RemoveVideo + { + get => _removeVideo; + set => SetProperty(ref _removeVideo, value); + } + + #endregion } \ No newline at end of file diff --git a/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs b/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs index 102bf9a..bae4e64 100644 --- a/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs +++ b/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs @@ -1,23 +1,17 @@ using DownKyi.Core.BiliApi.VideoStream.Models; using DownKyi.Images; using DownKyi.Models; -using DownKyi.Services; using DownKyi.Utils; using Downloader; using Prism.Commands; -using Prism.Services.Dialogs; using DownloadStatus = DownKyi.Models.DownloadStatus; -using IDialogService = DownKyi.PrismExtension.Dialog.IDialogService; namespace DownKyi.ViewModels.DownloadManager { public class DownloadingItem : DownloadBaseItem { - public DownloadingItem() : this(null) - { - } - public DownloadingItem(IDialogService? dialogService) : base(dialogService) + public DownloadingItem() { // 暂停继续按钮 StartOrPause = ButtonIcon.Instance().Pause; @@ -218,25 +212,6 @@ namespace DownKyi.ViewModels.DownloadManager } } - // 下载列表删除事件 - private DelegateCommand? _deleteCommand; - public DelegateCommand DeleteCommand => _deleteCommand ??= new DelegateCommand(ExecuteDeleteCommand); - - /// - /// 下载列表删除事件 - /// - private async void ExecuteDeleteCommand() - { - var alertService = new AlertService(DialogService); - var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete"), 2); - if (result != ButtonResult.OK) - { - return; - } - - App.DownloadingList?.Remove(this); - } - #endregion } } \ No newline at end of file diff --git a/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs b/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs index 94a2d18..349a7a0 100644 --- a/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs +++ b/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs @@ -1,115 +1,120 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Collections.Specialized; +using System.IO; using System.Linq; -using System.Threading.Tasks; -using Avalonia.Threading; -using DownKyi.Core.Logging; using DownKyi.Core.Settings; +using DownKyi.Events; using DownKyi.Services; +using DownKyi.Services.Download; using DownKyi.Utils; using Prism.Commands; using Prism.Events; -using Prism.Regions; using Prism.Services.Dialogs; -using Console = DownKyi.Core.Utils.Debugging.Console; using IDialogService = DownKyi.PrismExtension.Dialog.IDialogService; -namespace DownKyi.ViewModels.DownloadManager +namespace DownKyi.ViewModels.DownloadManager; + +public class ViewDownloadFinishedViewModel : ViewModelBase { - public class ViewDownloadFinishedViewModel : ViewModelBase + public const string Tag = "PageDownloadManagerDownloadFinished"; + + private DownloadStorageService _downloadStorageService; + + #region 页面属性申明 + + private ObservableCollection _downloadedList = new(); + + public ObservableCollection DownloadedList { - public const string Tag = "PageDownloadManagerDownloadFinished"; + get => _downloadedList; + set => SetProperty(ref _downloadedList, value); + } - #region 页面属性申明 + private int _finishedSortBy; - private ObservableCollection _downloadedList; - public ObservableCollection DownloadedList + public int FinishedSortBy + { + get => _finishedSortBy; + set => SetProperty(ref _finishedSortBy, value); + } + + #endregion + + public ViewDownloadFinishedViewModel( + IEventAggregator eventAggregator, + IDialogService dialogService, + DownloadStorageService downloadStorageService + ) : base(eventAggregator, + dialogService) + { + // 初始化DownloadedList + DownloadedList = App.DownloadedList; + _downloadStorageService = downloadStorageService; + + var finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort(); + FinishedSortBy = finishedSort switch { - get => _downloadedList; - set => SetProperty(ref _downloadedList, value); + DownloadFinishedSort.DownloadAsc => 0, + DownloadFinishedSort.DownloadDesc => 1, + DownloadFinishedSort.Number => 2, + _ => 0 + }; + App.SortDownloadedList(finishedSort); + } + + #region 命令申明 + + // 下载完成列表排序事件 + private DelegateCommand? _finishedSortCommand; + public DelegateCommand FinishedSortCommand => _finishedSortCommand ??= new DelegateCommand(ExecuteFinishedSortCommand); + + /// + /// 下载完成列表排序事件 + /// + /// + private void ExecuteFinishedSortCommand(object parameter) + { + if (parameter is not int index) + { + return; } - private int _finishedSortBy; - public int FinishedSortBy + switch (index) { - get => _finishedSortBy; - set => SetProperty(ref _finishedSortBy, value); + case 0: + App.SortDownloadedList(DownloadFinishedSort.DownloadAsc); + // 更新设置 + SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadAsc); + break; + case 1: + App.SortDownloadedList(DownloadFinishedSort.DownloadDesc); + // 更新设置 + SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadDesc); + break; + case 2: + App.SortDownloadedList(DownloadFinishedSort.Number); + // 更新设置 + SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.Number); + break; + default: + App.SortDownloadedList(DownloadFinishedSort.DownloadAsc); + // 更新设置 + SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadAsc); + break; } + } - #endregion + // 清空下载完成列表事件 + private DelegateCommand? _clearAllDownloadedCommand; + public DelegateCommand ClearAllDownloadedCommand => _clearAllDownloadedCommand ??= new DelegateCommand(ExecuteClearAllDownloadedCommand); - public ViewDownloadFinishedViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator, dialogService) - { - // 初始化DownloadedList - DownloadedList = App.DownloadedList; - DownloadedList.CollectionChanged += (sender, e) => - { - if (e.Action == NotifyCollectionChangedAction.Add) - { - SetDialogService(); - } - }; - SetDialogService(); - - var finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort(); - FinishedSortBy = finishedSort switch - { - DownloadFinishedSort.DownloadAsc => 0, - DownloadFinishedSort.DownloadDesc => 1, - DownloadFinishedSort.Number => 2, - _ => 0 - }; - App.SortDownloadedList(finishedSort); - } - - #region 命令申明 - - // 下载完成列表排序事件 - private DelegateCommand? _finishedSortCommand; - public DelegateCommand FinishedSortCommand => _finishedSortCommand ??= new DelegateCommand(ExecuteFinishedSortCommand); - - /// - /// 下载完成列表排序事件 - /// - /// - private void ExecuteFinishedSortCommand(object parameter) - { - if (parameter is not int index) { return; } - - switch (index) - { - case 0: - App.SortDownloadedList(DownloadFinishedSort.DownloadAsc); - // 更新设置 - SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadAsc); - break; - case 1: - App.SortDownloadedList(DownloadFinishedSort.DownloadDesc); - // 更新设置 - SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadDesc); - break; - case 2: - App.SortDownloadedList(DownloadFinishedSort.Number); - // 更新设置 - SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.Number); - break; - default: - App.SortDownloadedList(DownloadFinishedSort.DownloadAsc); - // 更新设置 - SettingsManager.GetInstance().SetDownloadFinishedSort(DownloadFinishedSort.DownloadAsc); - break; - } - } - - // 清空下载完成列表事件 - private DelegateCommand? _clearAllDownloadedCommand; - public DelegateCommand ClearAllDownloadedCommand => _clearAllDownloadedCommand ??= new DelegateCommand(ExecuteClearAllDownloadedCommand); - - /// - /// 清空下载完成列表事件 - /// - private async void ExecuteClearAllDownloadedCommand() + /// + /// 清空下载完成列表事件 + /// + private async void ExecuteClearAllDownloadedCommand() + { + try { var alertService = new AlertService(DialogService); var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete")); @@ -118,53 +123,110 @@ namespace DownKyi.ViewModels.DownloadManager return; } + // 使用Clear()不能触发NotifyCollectionChangedAction.Remove事件 // 因此遍历删除 // DownloadingList中元素被删除后不能继续遍历 - await Task.Run(() => - { - var list = DownloadedList.ToList(); - foreach (var item in list) - { - App.PropertyChangeAsync(() => - { - App.DownloadedList?.Remove(item); - }); - } - }); + _downloadStorageService.ClearDownloaded(); + App.PropertyChangeAsync(() => { App.DownloadedList.Clear(); }); } - - #endregion - - private async void SetDialogService() + catch (Exception e) { - try - { - await Task.Run(() => - { - var list = Dispatcher.UIThread.Invoke(()=>DownloadedList.ToList()); - foreach (var item in list) - { - if (item != null && item.DialogService == null) - { - item.DialogService = DialogService; - } - } - }); - } - catch (Exception e) - { - Console.PrintLine("SetDialogService()发生异常: {0}", e); - LogManager.Error($"{Tag}.SetDialogService()", e); - } + var alertService = new AlertService(DialogService); + await alertService.ShowError(e.Message); } - - public override void OnNavigatedFrom(NavigationContext navigationContext) - { - base.OnNavigatedFrom(navigationContext); - - SetDialogService(); - } - } -} + + // 打开视频事件 + private DelegateCommand? _openVideoCommand; + public DelegateCommand OpenVideoCommand => _openVideoCommand ??= new DelegateCommand(ExecuteOpenVideoCommand); + + /// + /// 打开视频事件 + /// + private void ExecuteOpenVideoCommand(DownloadedItem downloadedItem) + { + if (downloadedItem.DownloadBase == null) + { + return; + } + + var videoPath = $"{downloadedItem.DownloadBase.FilePath}.mp4"; + var fileInfo = new FileInfo(videoPath); + if (File.Exists(fileInfo.FullName)) + { + PlatformHelper.Open(fileInfo.FullName); + } + else + { + //eventAggregator.GetEvent().Publish(DictionaryResource.GetString("TipAddDownloadingZero")); + EventAggregator.GetEvent().Publish("没有找到视频文件,可能被删除或移动!"); + } + } + + // 打开文件夹事件 + private DelegateCommand? _openFolderCommand; + + public DelegateCommand OpenFolderCommand => _openFolderCommand ??= new DelegateCommand(ExecuteOpenFolderCommand); + + + private static readonly IReadOnlyDictionary FileSuffixMap = new Dictionary + { + { "downloadVideo", new[] { ".mp4", ".flv" } }, + { "downloadAudio", new[] { ".aac", ".mp3" } }, + { "downloadCover", new[] { ".jpg" } }, + { "downloadDanmaku", new[] { ".ass" } }, + { "downloadSubtitle", new[] { ".srt" } } + }; + + /// + /// 打开文件夹事件 + /// + private void ExecuteOpenFolderCommand(DownloadedItem downloadedItem) + { + if (downloadedItem.DownloadBase == null) + { + return; + } + + var downLoadContents = downloadedItem.DownloadBase.NeedDownloadContent.Where(e => e.Value == true).Select(e => e.Key); + var fileSuffixes = downLoadContents + .Where(content => FileSuffixMap.ContainsKey(content)) + .SelectMany(content => FileSuffixMap[content]) + .ToArray(); + if (fileSuffixes.Length <= 0) return; + foreach (var suffix in fileSuffixes) + { + var videoPath = $"{downloadedItem.DownloadBase.FilePath}{suffix}"; + var fileInfo = new FileInfo(videoPath); + if (!File.Exists(fileInfo.FullName) || fileInfo.DirectoryName == null) continue; + PlatformHelper.OpenFolder(fileInfo.DirectoryName); + return; + } + + EventAggregator.GetEvent().Publish("没有找到视频文件,可能被删除或移动!"); + } + + // 删除事件 + private DelegateCommand? _removeVideoCommand; + + public DelegateCommand RemoveVideoCommand => _removeVideoCommand ??= new DelegateCommand(ExecuteRemoveVideoCommand); + + /// + /// 删除事件 + /// + private async void ExecuteRemoveVideoCommand(DownloadedItem downloadedItem) + { + var alertService = new AlertService(DialogService); + var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete"), 2); + if (result != ButtonResult.OK) + { + return; + } + + App.DownloadedList.Remove(downloadedItem); + _downloadStorageService.RemoveDownloaded(downloadedItem); + } + + #endregion +} \ No newline at end of file diff --git a/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs b/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs index 1295836..a474997 100644 --- a/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs +++ b/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs @@ -23,7 +23,7 @@ namespace DownKyi.ViewModels.DownloadManager #region 页面属性申明 - private ObservableCollection _downloadingList; + private ObservableCollection _downloadingList = new(); public ObservableCollection DownloadingList { @@ -38,14 +38,6 @@ namespace DownKyi.ViewModels.DownloadManager { // 初始化DownloadingList DownloadingList = App.DownloadingList; - DownloadingList.CollectionChanged += (sender, e) => - { - if (e.Action == NotifyCollectionChangedAction.Add) - { - SetDialogService(); - } - }; - SetDialogService(); } #region 命令申明 @@ -163,41 +155,31 @@ namespace DownKyi.ViewModels.DownloadManager var list = DownloadingList.ToList(); foreach (var item in list) { - App.PropertyChangeAsync(() => { App.DownloadingList?.Remove(item); }); + App.PropertyChangeAsync(() => { App.DownloadingList.Remove(item); }); } }); } + + // 下载列表删除事件 + private DelegateCommand? _deleteCommand; + public DelegateCommand DeleteCommand => _deleteCommand ??= new DelegateCommand(ExecuteDeleteCommand); + + /// + /// 下载列表删除事件 + /// + private async void ExecuteDeleteCommand(DownloadingItem downloadingItem) + { + var alertService = new AlertService(DialogService); + var result = await alertService.ShowWarning(DictionaryResource.GetString("ConfirmDelete"), 2); + if (result != ButtonResult.OK) + { + return; + } + + App.DownloadingList.Remove(downloadingItem); + } + #endregion - - private async void SetDialogService() - { - try - { - await Task.Run(() => - { - var list = DownloadingList.ToList(); - foreach (var item in list) - { - if (item != null && item.DialogService == null) - { - item.DialogService = DialogService; - } - } - }); - } - catch (Exception e) - { - Console.PrintLine("SetDialogService()发生异常: {0}", e); - LogManager.Error($"{Tag}.SetDialogService()", e); - } - } - - public override void OnNavigatedFrom(NavigationContext navigationContext) - { - base.OnNavigatedFrom(navigationContext); - - SetDialogService(); - } } } \ No newline at end of file diff --git a/DownKyi/Views/DownloadManager/ViewDownloadFinished.axaml b/DownKyi/Views/DownloadManager/ViewDownloadFinished.axaml index 9922dc2..510e915 100644 --- a/DownKyi/Views/DownloadManager/ViewDownloadFinished.axaml +++ b/DownKyi/Views/DownloadManager/ViewDownloadFinished.axaml @@ -85,7 +85,8 @@ Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" - Command="{Binding OpenFolderCommand}" + Command="{Binding ElementName=DownloadedList,Path=((vmdm:ViewDownloadFinishedViewModel)DataContext).OpenFolderCommand}" + CommandParameter="{Binding }" Theme="{StaticResource ImageBtnStyle}" ToolTip.Tip="{DynamicResource OpenFolder}"> @@ -102,7 +103,8 @@ Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" - Command="{Binding OpenVideoCommand}" + Command="{Binding ElementName=DownloadedList,Path=((vmdm:ViewDownloadFinishedViewModel)DataContext).OpenVideoCommand}" + CommandParameter="{Binding }" Theme="{StaticResource ImageBtnStyle}" ToolTip.Tip="{DynamicResource OpenVideo}"> @@ -119,7 +121,8 @@ Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" - Command="{Binding RemoveVideoCommand}" + Command="{Binding ElementName=DownloadedList,Path=((vmdm:ViewDownloadFinishedViewModel)DataContext).RemoveVideoCommand}" + CommandParameter="{Binding }" Theme="{StaticResource ImageBtnStyle}" ToolTip.Tip="{DynamicResource DeleteDownload}"> @@ -144,6 +147,7 @@ @@ -189,6 +190,7 @@ RowDefinitions="*,1,50" IsVisible="{Binding DownloadingList.Count}">