From 85178d9b57dc433000081357d3e55f30520cf0d1 Mon Sep 17 00:00:00 2001 From: leiurayer <1432593898@qq.com> Date: Sat, 14 May 2022 22:32:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=86=85=E5=BB=BA=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E5=99=A8=EF=BC=8C=E6=96=B0=E5=A2=9E=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=99=A8=E5=88=87=E6=8D=A2=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DownKyi.Core/DownKyi.Core.csproj | 1 + .../Downloader/MultiThreadDownloader.cs | 26 +- src/DownKyi.Core/Settings/Downloader.cs | 9 + .../Settings/Models/NetworkSettings.cs | 14 +- .../Settings/SettingsManager.Network.cs | 206 ++++- src/DownKyi/App.xaml.cs | 19 +- src/DownKyi/DownKyi.csproj | 1 + src/DownKyi/Languages/Default.xaml | 12 +- .../Services/Download/AriaDownloadService.cs | 722 +--------------- .../Download/BuiltinDownloadService.cs | 363 +++++++++ .../Services/Download/DownloadService.cs | 770 +++++++++++++++++- .../Settings/ViewNetworkViewModel.cs | 281 ++++++- src/DownKyi/Views/Settings/ViewNetwork.xaml | 511 ++++++++---- 13 files changed, 2005 insertions(+), 930 deletions(-) create mode 100644 src/DownKyi.Core/Settings/Downloader.cs create mode 100644 src/DownKyi/Services/Download/BuiltinDownloadService.cs diff --git a/src/DownKyi.Core/DownKyi.Core.csproj b/src/DownKyi.Core/DownKyi.Core.csproj index 1411cfd..a9875b5 100644 --- a/src/DownKyi.Core/DownKyi.Core.csproj +++ b/src/DownKyi.Core/DownKyi.Core.csproj @@ -290,6 +290,7 @@ + diff --git a/src/DownKyi.Core/Downloader/MultiThreadDownloader.cs b/src/DownKyi.Core/Downloader/MultiThreadDownloader.cs index c4d8a0b..84490f7 100644 --- a/src/DownKyi.Core/Downloader/MultiThreadDownloader.cs +++ b/src/DownKyi.Core/Downloader/MultiThreadDownloader.cs @@ -385,10 +385,34 @@ namespace DownKyi.Core.Downloader /// /// 开始下载 /// - public void Start() + public void StartAsync() + { + //Task th = new Task(CreateFirstPartitions); + //th.Start(); + StartAsync(false); + } + + /// + /// 开始下载 + /// + /// + public void StartAsync(bool isWait) { Task th = new Task(CreateFirstPartitions); th.Start(); + + if (isWait) + { + th.Wait(); + } + } + + /// + /// 开始下载 + /// + public void Start() + { + CreateFirstPartitions(); } /// diff --git a/src/DownKyi.Core/Settings/Downloader.cs b/src/DownKyi.Core/Settings/Downloader.cs new file mode 100644 index 0000000..ff5cb8c --- /dev/null +++ b/src/DownKyi.Core/Settings/Downloader.cs @@ -0,0 +1,9 @@ +namespace DownKyi.Core.Settings +{ + public enum Downloader + { + NOT_SET = 0, + BUILT_IN, + ARIA, + } +} diff --git a/src/DownKyi.Core/Settings/Models/NetworkSettings.cs b/src/DownKyi.Core/Settings/Models/NetworkSettings.cs index 6b283f8..1e98ceb 100644 --- a/src/DownKyi.Core/Settings/Models/NetworkSettings.cs +++ b/src/DownKyi.Core/Settings/Models/NetworkSettings.cs @@ -8,9 +8,20 @@ namespace DownKyi.Core.Settings.Models public class NetworkSettings { public AllowStatus IsLiftingOfRegion { get; set; } = AllowStatus.NONE; + + public Downloader Downloader { get; set; } = Downloader.NOT_SET; + public int MaxCurrentDownloads { get; set; } = -1; + + #region built-in + public int Split { get; set; } = -1; + public AllowStatus IsHttpProxy { get; set; } = AllowStatus.NONE; + public string HttpProxy { get; set; } = null; + public int HttpProxyListenPort { get; set; } = -1; + #endregion + + #region Aria public int AriaListenPort { get; set; } = -1; public AriaConfigLogLevel AriaLogLevel { get; set; } = AriaConfigLogLevel.NOT_SET; - public int AriaMaxConcurrentDownloads { get; set; } = -1; public int AriaSplit { get; set; } = -1; public int AriaMaxOverallDownloadLimit { get; set; } = -1; public int AriaMaxDownloadLimit { get; set; } = -1; @@ -19,5 +30,6 @@ namespace DownKyi.Core.Settings.Models public AllowStatus IsAriaHttpProxy { get; set; } = AllowStatus.NONE; public string AriaHttpProxy { get; set; } = null; public int AriaHttpProxyListenPort { get; set; } = -1; + #endregion } } diff --git a/src/DownKyi.Core/Settings/SettingsManager.Network.cs b/src/DownKyi.Core/Settings/SettingsManager.Network.cs index f7786d2..302846d 100644 --- a/src/DownKyi.Core/Settings/SettingsManager.Network.cs +++ b/src/DownKyi.Core/Settings/SettingsManager.Network.cs @@ -7,15 +7,26 @@ namespace DownKyi.Core.Settings // 是否开启解除地区限制 private readonly AllowStatus isLiftingOfRegion = AllowStatus.YES; + // 下载器 + private readonly Downloader downloader = Downloader.ARIA; + + // 最大同时下载数(任务数) + private readonly int maxCurrentDownloads = 3; + + // 单文件最大线程数 + private readonly int split = 8; + + // HttpProxy代理 + private readonly AllowStatus isHttpProxy = AllowStatus.NO; + private readonly string httpProxy = ""; + private readonly int httpProxyListenPort = 0; + // Aria服务器端口号 private readonly int ariaListenPort = 6800; // Aria日志等级 private readonly AriaConfigLogLevel ariaLogLevel = AriaConfigLogLevel.INFO; - // Aria最大同时下载数(任务数) - private readonly int ariaMaxConcurrentDownloads = 3; - // Aria单文件最大线程数 private readonly int ariaSplit = 5; @@ -60,6 +71,168 @@ namespace DownKyi.Core.Settings return SetSettings(); } + /// + /// 获取下载器 + /// + /// + public Downloader GetDownloader() + { + appSettings = GetSettings(); + if (appSettings.Network.Downloader == Downloader.NOT_SET) + { + // 第一次获取,先设置默认值 + SetDownloader(downloader); + return downloader; + } + return appSettings.Network.Downloader; + } + + /// + /// 设置下载器 + /// + /// + /// + public bool SetDownloader(Downloader downloader) + { + appSettings.Network.Downloader = downloader; + return SetSettings(); + } + + /// + /// 获取最大同时下载数(任务数) + /// + /// + public int GetMaxCurrentDownloads() + { + appSettings = GetSettings(); + if (appSettings.Network.MaxCurrentDownloads == -1) + { + // 第一次获取,先设置默认值 + SetMaxCurrentDownloads(maxCurrentDownloads); + return maxCurrentDownloads; + } + return appSettings.Network.MaxCurrentDownloads; + } + + /// + /// 设置最大同时下载数(任务数) + /// + /// + /// + public bool SetMaxCurrentDownloads(int maxCurrentDownloads) + { + appSettings.Network.MaxCurrentDownloads = maxCurrentDownloads; + return SetSettings(); + } + + /// + /// 获取单文件最大线程数 + /// + /// + public int GetSplit() + { + appSettings = GetSettings(); + if (appSettings.Network.Split == -1) + { + // 第一次获取,先设置默认值 + SetSplit(split); + return split; + } + return appSettings.Network.Split; + } + + /// + /// 设置单文件最大线程数 + /// + /// + /// + public bool SetSplit(int split) + { + appSettings.Network.Split = split; + return SetSettings(); + } + + /// + /// 获取是否开启Http代理 + /// + /// + public AllowStatus IsHttpProxy() + { + appSettings = GetSettings(); + if (appSettings.Network.IsHttpProxy == AllowStatus.NONE) + { + // 第一次获取,先设置默认值 + IsHttpProxy(isHttpProxy); + return isHttpProxy; + } + return appSettings.Network.IsHttpProxy; + } + + /// + /// 设置是否开启Http代理 + /// + /// + /// + public bool IsHttpProxy(AllowStatus isHttpProxy) + { + appSettings.Network.IsHttpProxy = isHttpProxy; + return SetSettings(); + } + + /// + /// 获取Http代理的地址 + /// + /// + public string GetHttpProxy() + { + appSettings = GetSettings(); + if (appSettings.Network.HttpProxy == null) + { + // 第一次获取,先设置默认值 + SetHttpProxy(httpProxy); + return httpProxy; + } + return appSettings.Network.HttpProxy; + } + + /// + /// 设置Aria的http代理的地址 + /// + /// + /// + public bool SetHttpProxy(string httpProxy) + { + appSettings.Network.HttpProxy = httpProxy; + return SetSettings(); + } + + /// + /// 获取Http代理的端口 + /// + /// + public int GetHttpProxyListenPort() + { + appSettings = GetSettings(); + if (appSettings.Network.HttpProxyListenPort == -1) + { + // 第一次获取,先设置默认值 + SetHttpProxyListenPort(httpProxyListenPort); + return httpProxyListenPort; + } + return appSettings.Network.HttpProxyListenPort; + } + + /// + /// 设置Http代理的端口 + /// + /// + /// + public bool SetHttpProxyListenPort(int httpProxyListenPort) + { + appSettings.Network.HttpProxyListenPort = httpProxyListenPort; + return SetSettings(); + } + /// /// 获取Aria服务器的端口号 /// @@ -114,33 +287,6 @@ namespace DownKyi.Core.Settings return SetSettings(); } - /// - /// 获取Aria最大同时下载数(任务数) - /// - /// - public int GetAriaMaxConcurrentDownloads() - { - appSettings = GetSettings(); - if (appSettings.Network.AriaMaxConcurrentDownloads == -1) - { - // 第一次获取,先设置默认值 - SetAriaMaxConcurrentDownloads(ariaMaxConcurrentDownloads); - return ariaMaxConcurrentDownloads; - } - return appSettings.Network.AriaMaxConcurrentDownloads; - } - - /// - /// 设置Aria最大同时下载数(任务数) - /// - /// - /// - public bool SetAriaMaxConcurrentDownloads(int ariaMaxConcurrentDownloads) - { - appSettings.Network.AriaMaxConcurrentDownloads = ariaMaxConcurrentDownloads; - return SetSettings(); - } - /// /// 获取Aria单文件最大线程数 /// diff --git a/src/DownKyi/App.xaml.cs b/src/DownKyi/App.xaml.cs index b3723d8..cae6342 100644 --- a/src/DownKyi/App.xaml.cs +++ b/src/DownKyi/App.xaml.cs @@ -14,7 +14,6 @@ using DownKyi.Views.Settings; using DownKyi.Views.Toolbox; using DownKyi.Views.UserSpace; using Prism.Ioc; -using Prism.Services.Dialogs; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -122,8 +121,22 @@ namespace DownKyi }); // 启动下载服务 - downloadService = new AriaDownloadService(DownloadingList, DownloadedList); - downloadService.Start(); + var download = SettingsManager.GetInstance().GetDownloader(); + switch (download) + { + case Downloader.NOT_SET: + break; + case Downloader.BUILT_IN: + downloadService = new BuiltinDownloadService(DownloadingList, DownloadedList); + break; + case Downloader.ARIA: + downloadService = new AriaDownloadService(DownloadingList, DownloadedList); + break; + } + if (downloadService != null) + { + downloadService.Start(); + } return Container.Resolve(); } diff --git a/src/DownKyi/DownKyi.csproj b/src/DownKyi/DownKyi.csproj index 789105a..328074d 100644 --- a/src/DownKyi/DownKyi.csproj +++ b/src/DownKyi/DownKyi.csproj @@ -117,6 +117,7 @@ + diff --git a/src/DownKyi/Languages/Default.xaml b/src/DownKyi/Languages/Default.xaml index 0204679..f5527a3 100644 --- a/src/DownKyi/Languages/Default.xaml +++ b/src/DownKyi/Languages/Default.xaml @@ -183,6 +183,9 @@ 解析后自动下载已解析视频 网络 + 选择下载器(重启生效): + 内建下载器 + Aria2下载器 Aria服务器端口: Aria日志等级: Aria同时下载数: @@ -190,10 +193,12 @@ Aria下载速度限制(KB/s) 全局下载速度限制[0: 无限制] 单任务下载速度限制[0: 无限制] - 使用Http代理 - 代理地址: - 端口: Aria文件预分配: + 使用Http代理 + 代理地址: + 端口: + 同时下载数: + 最大线程数: 视频 优先下载的视频编码: @@ -300,6 +305,7 @@ 确定 取消 + 此项需重启生效,您确定要重新启动吗? 您确定要删除吗? 请选择文件夹 diff --git a/src/DownKyi/Services/Download/AriaDownloadService.cs b/src/DownKyi/Services/Download/AriaDownloadService.cs index 9981b2f..2ffe7df 100644 --- a/src/DownKyi/Services/Download/AriaDownloadService.cs +++ b/src/DownKyi/Services/Download/AriaDownloadService.cs @@ -3,15 +3,10 @@ using DownKyi.Core.Aria2cNet.Client; using DownKyi.Core.Aria2cNet.Client.Entity; using DownKyi.Core.Aria2cNet.Server; using DownKyi.Core.BiliApi.Login; -using DownKyi.Core.BiliApi.VideoStream; using DownKyi.Core.BiliApi.VideoStream.Models; -using DownKyi.Core.Danmaku2Ass; -using DownKyi.Core.FFmpeg; using DownKyi.Core.Logging; using DownKyi.Core.Settings; -using DownKyi.Core.Storage; using DownKyi.Core.Utils; -using DownKyi.Images; using DownKyi.Models; using DownKyi.Utils; using DownKyi.ViewModels.DownloadManager; @@ -20,7 +15,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace DownKyi.Services.Download @@ -30,14 +24,6 @@ namespace DownKyi.Services.Download /// public class AriaDownloadService : DownloadService, IDownloadService { - private Task workTask; - private CancellationTokenSource tokenSource; - private CancellationToken cancellationToken; - private List downloadingTasks = new List(); - - private readonly int retry = 5; - private readonly string nullMark = ""; - public AriaDownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList) : base(downloadingList, downloadedList) { Tag = "AriaDownloadService"; @@ -50,40 +36,9 @@ namespace DownKyi.Services.Download /// /// /// - public string DownloadAudio(DownloadingItem downloading) + public override string DownloadAudio(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingAudio"); - - // 如果没有Dash,返回null - if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } - - // 如果audio列表没有内容,则返回null - if (downloading.PlayUrl.Dash.Audio == null) { return null; } - else if (downloading.PlayUrl.Dash.Audio.Count == 0) { return null; } - - // 根据音频id匹配 - PlayUrlDashVideo downloadAudio = null; - foreach (PlayUrlDashVideo audio in downloading.PlayUrl.Dash.Audio) - { - if (audio.Id == downloading.AudioCodec.Id) - { - downloadAudio = audio; - break; - } - } - - // 避免Dolby==null及其它未知情况,直接使用异常捕获 - try - { - // Dolby Atmos - if (downloading.AudioCodec.Id == 30250) - { - downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0]; - } - } - catch (Exception) { } + PlayUrlDashVideo downloadAudio = BaseDownloadAudio(downloading); return DownloadVideo(downloading, downloadAudio); } @@ -93,29 +48,9 @@ namespace DownKyi.Services.Download /// /// /// - public string DownloadVideo(DownloadingItem downloading) + public override string DownloadVideo(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); - - // 如果没有Dash,返回null - if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } - - // 如果Video列表没有内容,则返回null - if (downloading.PlayUrl.Dash.Video == null) { return null; } - else if (downloading.PlayUrl.Dash.Video.Count == 0) { return null; } - - // 根据视频编码匹配 - PlayUrlDashVideo downloadVideo = null; - foreach (PlayUrlDashVideo video in downloading.PlayUrl.Dash.Video) - { - if (video.Id == downloading.Resolution.Id && Utils.GetVideoCodecName(video.Codecs) == downloading.VideoCodecName) - { - downloadVideo = video; - break; - } - } + PlayUrlDashVideo downloadVideo = BaseDownloadVideo(downloading); return DownloadVideo(downloading, downloadVideo); } @@ -199,150 +134,27 @@ namespace DownKyi.Services.Download /// 下载封面 /// /// - public string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName) + public override string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingCover"); - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - - // 查询、保存封面 - StorageCover storageCover = new StorageCover(); - string cover = storageCover.GetCover(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, coverUrl); - if (cover == null) - { - return null; - } - - // 复制图片到指定位置 - try - { - File.Copy(cover, fileName, true); - - // 记录本次下载的文件 - if (!downloading.Downloading.DownloadFiles.ContainsKey(coverUrl)) - { - downloading.Downloading.DownloadFiles.Add(coverUrl, fileName); - } - - return fileName; - } - catch (Exception e) - { - Core.Utils.Debugging.Console.PrintLine(e); - LogManager.Error(Tag, e); - } - - return null; + return BaseDownloadCover(downloading, coverUrl, fileName); } /// /// 下载弹幕 /// /// - public string DownloadDanmaku(DownloadingItem downloading) + public override string DownloadDanmaku(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingDanmaku"); - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - - string title = $"{downloading.Name}"; - string assFile = $"{downloading.DownloadBase.FilePath}.ass"; - - // 记录本次下载的文件 - if (!downloading.Downloading.DownloadFiles.ContainsKey("danmaku")) - { - downloading.Downloading.DownloadFiles.Add("danmaku", assFile); - } - - int screenWidth = SettingsManager.GetInstance().GetDanmakuScreenWidth(); - int screenHeight = SettingsManager.GetInstance().GetDanmakuScreenHeight(); - //if (SettingsManager.GetInstance().IsCustomDanmakuResolution() != AllowStatus.YES) - //{ - // if (downloadingEntity.Width > 0 && downloadingEntity.Height > 0) - // { - // screenWidth = downloadingEntity.Width; - // screenHeight = downloadingEntity.Height; - // } - //} - - // 字幕配置 - Config subtitleConfig = new Config - { - Title = title, - ScreenWidth = screenWidth, - ScreenHeight = screenHeight, - FontName = SettingsManager.GetInstance().GetDanmakuFontName(), - BaseFontSize = SettingsManager.GetInstance().GetDanmakuFontSize(), - LineCount = SettingsManager.GetInstance().GetDanmakuLineCount(), - LayoutAlgorithm = SettingsManager.GetInstance().GetDanmakuLayoutAlgorithm().ToString("G").ToLower(), // async/sync - TuneDuration = 0, - DropOffset = 0, - BottomMargin = 0, - CustomOffset = 0 - }; - - Core.Danmaku2Ass.Bilibili.GetInstance() - .SetTopFilter(SettingsManager.GetInstance().GetDanmakuTopFilter() == AllowStatus.YES) - .SetBottomFilter(SettingsManager.GetInstance().GetDanmakuBottomFilter() == AllowStatus.YES) - .SetScrollFilter(SettingsManager.GetInstance().GetDanmakuScrollFilter() == AllowStatus.YES) - .Create(downloading.DownloadBase.Avid, downloading.DownloadBase.Cid, subtitleConfig, assFile); - - return assFile; + return BaseDownloadDanmaku(downloading); } /// /// 下载字幕 /// /// - public List DownloadSubtitle(DownloadingItem downloading) + public override List DownloadSubtitle(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingSubtitle"); - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - - List srtFiles = new List(); - - var subRipTexts = VideoStream.GetSubtitle(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); - if (subRipTexts == null) - { - return null; - } - - foreach (var subRip in subRipTexts) - { - string srtFile = $"{downloading.DownloadBase.FilePath}_{subRip.LanDoc}.srt"; - try - { - File.WriteAllText(srtFile, subRip.SrtString); - - // 记录本次下载的文件 - if (!downloading.Downloading.DownloadFiles.ContainsKey("subtitle")) - { - downloading.Downloading.DownloadFiles.Add("subtitle", srtFile); - } - - srtFiles.Add(srtFile); - } - catch (Exception e) - { - Core.Utils.Debugging.Console.PrintLine("DownloadSubtitle()发生异常: {0}", e); - LogManager.Error("DownloadSubtitle()", e); - } - } - - return srtFiles; + return BaseDownloadSubtitle(downloading); } /// @@ -352,84 +164,22 @@ namespace DownKyi.Services.Download /// /// /// - public string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) + public override string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("MixedFlow"); - downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - if (videoUid == nullMark) { return null; } - - string finalFile = $"{downloading.DownloadBase.FilePath}.mp4"; - if (videoUid == null) - { - finalFile = $"{downloading.DownloadBase.FilePath}.aac"; - } - - // 合并音视频 - FFmpegHelper.MergeVideo(audioUid, videoUid, finalFile); - - // 获取文件大小 - if (File.Exists(finalFile)) - { - FileInfo info = new FileInfo(finalFile); - downloading.FileSize = Format.FormatFileSize(info.Length); - } - else - { - downloading.FileSize = Format.FormatFileSize(0); - } - - return finalFile; + return BaseMixedFlow(downloading, audioUid, videoUid); } /// /// 解析视频流的下载链接 /// /// - public void Parse(DownloadingItem downloading) + public override void Parse(DownloadingItem downloading) { - // 更新状态显示 - downloading.DownloadStatusTitle = DictionaryResource.GetString("Parsing"); - downloading.DownloadContent = string.Empty; - // 下载大小 - downloading.DownloadingFileSize = string.Empty; - // 下载速度 - downloading.SpeedDisplay = string.Empty; - - if (downloading.PlayUrl != null && downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED) - { - // 设置下载状态 - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; - - return; - } - - // 设置下载状态 - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; - - // 解析 - switch (downloading.Downloading.PlayStreamType) - { - case PlayStreamType.VIDEO: - downloading.PlayUrl = VideoStream.GetVideoPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); - break; - case PlayStreamType.BANGUMI: - downloading.PlayUrl = VideoStream.GetBangumiPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); - break; - case PlayStreamType.CHEESE: - downloading.PlayUrl = VideoStream.GetCheesePlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, downloading.DownloadBase.EpisodeId); - break; - default: - break; - } + BaseParse(downloading); } /// @@ -437,48 +187,8 @@ namespace DownKyi.Services.Download /// private async Task EndTask() { - // 结束任务 - tokenSource.Cancel(); - - await workTask; - - //先简单等待一下 - - // 下载数据存储服务 - DownloadStorageService downloadStorageService = new DownloadStorageService(); - // 保存数据 - foreach (DownloadingItem item in downloadingList) - { - switch (item.Downloading.DownloadStatus) - { - case DownloadStatus.NOT_STARTED: - break; - case DownloadStatus.WAIT_FOR_DOWNLOAD: - break; - case DownloadStatus.PAUSE_STARTED: - break; - case DownloadStatus.PAUSE: - break; - case DownloadStatus.DOWNLOADING: - // TODO 添加设置让用户选择重启后是否自动开始下载 - item.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; - //item.Downloading.DownloadStatus = DownloadStatus.PAUSE; - break; - case DownloadStatus.DOWNLOAD_SUCCEED: - case DownloadStatus.DOWNLOAD_FAILED: - break; - default: - break; - } - - item.Progress = 0; - - downloadStorageService.UpdateDownloading(item); - } - foreach (DownloadedItem item in downloadedList) - { - downloadStorageService.UpdateDownloaded(item); - } + // 停止基本任务 + await BaseEndTask(); // 关闭Aria服务器 await CloseAriaServer(); @@ -500,395 +210,16 @@ namespace DownKyi.Services.Download // 启动Aria服务器 StartAriaServer(); - tokenSource = new CancellationTokenSource(); - cancellationToken = tokenSource.Token; - workTask = Task.Run(DoWork); - } - - /// - /// 执行任务 - /// - private async Task DoWork() - { - // 上次循环时正在下载的数量 - int lastDownloadingCount = 0; - - while (true) - { - int maxDownloading = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(); - int downloadingCount = 0; - - try - { - downloadingTasks.RemoveAll((m) => m.IsCompleted); - foreach (DownloadingItem downloading in downloadingList) - { - if (downloading.Downloading.DownloadStatus == DownloadStatus.DOWNLOADING) - { - downloadingCount++; - } - } - - foreach (DownloadingItem downloading in downloadingList) - { - if (downloadingCount >= maxDownloading) - { - break; - } - - // 开始下载 - if (downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED || downloading.Downloading.DownloadStatus == DownloadStatus.WAIT_FOR_DOWNLOAD) - { - //这里需要立刻设置状态,否则如果SingleDownload没有及时执行,会重复创建任务 - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; - downloadingTasks.Add(SingleDownload(downloading)); - downloadingCount++; - } - } - } - catch (InvalidOperationException e) - { - Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生InvalidOperationException异常: {0}", e); - LogManager.Error("Start DoWork() InvalidOperationException", e); - } - catch (Exception e) - { - Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生异常: {0}", e); - LogManager.Error("Start DoWork()", e); - } - - // 判断是否该结束线程,若为true,跳出while循环 - if (cancellationToken.IsCancellationRequested) - { - Core.Utils.Debugging.Console.PrintLine("AriaDownloadService: 下载服务结束,跳出while循环"); - LogManager.Debug(Tag, "下载服务结束"); - break; - } - - // 判断下载列表中的视频是否全部下载完成 - if (lastDownloadingCount > 0 && downloadingList.Count == 0 && downloadedList.Count > 0) - { - AfterDownload(); - } - lastDownloadingCount = downloadingList.Count; - - // 降低CPU占用 - await Task.Delay(500); - } - - await Task.WhenAny(Task.WhenAll(downloadingTasks), Task.Delay(30000)); - foreach (Task tsk in downloadingTasks.FindAll((m) => !m.IsCompleted)) - { - Core.Utils.Debugging.Console.PrintLine("AriaDownloadService: 任务结束超时"); - LogManager.Debug(Tag, "任务结束超时"); - } - } - - /// - /// 下载一个视频 - /// - /// - /// - private async Task SingleDownload(DownloadingItem downloading) - { - // 路径 - downloading.DownloadBase.FilePath = downloading.DownloadBase.FilePath.Replace("\\", "/"); - string[] temp = downloading.DownloadBase.FilePath.Split('/'); - //string path = downloading.DownloadBase.FilePath.Replace(temp[temp.Length - 1], ""); - string path = downloading.DownloadBase.FilePath.TrimEnd(temp[temp.Length - 1].ToCharArray()); - - // 路径不存在则创建 - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - } - - try - { - await Task.Run(new Action(() => - { - // 初始化 - downloading.DownloadStatusTitle = string.Empty; - downloading.DownloadContent = string.Empty; - //downloading.Downloading.DownloadFiles.Clear(); - - // 解析并依次下载音频、视频、弹幕、字幕、封面等内容 - Parse(downloading); - - // 暂停 - Pause(downloading); - - string audioUid = null; - // 如果需要下载音频 - if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"]) - { - //audioUid = DownloadAudio(downloading); - for (int i = 0; i < retry; i++) - { - audioUid = DownloadAudio(downloading); - if (audioUid != null && audioUid != nullMark) - { - break; - } - } - } - if (audioUid == nullMark) - { - DownloadFailed(downloading); - return; - } - - // 暂停 - Pause(downloading); - - string videoUid = null; - // 如果需要下载视频 - if (downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) - { - //videoUid = DownloadVideo(downloading); - for (int i = 0; i < retry; i++) - { - videoUid = DownloadVideo(downloading); - if (videoUid != null && videoUid != nullMark) - { - break; - } - } - } - if (videoUid == nullMark) - { - DownloadFailed(downloading); - return; - } - - // 暂停 - Pause(downloading); - - string outputDanmaku = null; - // 如果需要下载弹幕 - if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"]) - { - outputDanmaku = DownloadDanmaku(downloading); - } - - // 暂停 - Pause(downloading); - - List outputSubtitles = null; - // 如果需要下载字幕 - if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"]) - { - outputSubtitles = DownloadSubtitle(downloading); - } - - // 暂停 - Pause(downloading); - - string outputCover = null; - string outputPageCover = null; - // 如果需要下载封面 - if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) - { - string fileName = $"{downloading.DownloadBase.FilePath}.{GetImageExtension(downloading.DownloadBase.PageCoverUrl)}"; - - // page的封面 - outputPageCover = DownloadCover(downloading, downloading.DownloadBase.PageCoverUrl, fileName); - // 封面 - outputCover = DownloadCover(downloading, downloading.DownloadBase.CoverUrl, $"{path}/Cover.{GetImageExtension(downloading.DownloadBase.CoverUrl)}"); - } - - // 暂停 - Pause(downloading); - - // 混流 - string outputMedia = string.Empty; - if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) - { - outputMedia = MixedFlow(downloading, audioUid, videoUid); - } - - // 这里本来只有IsExist,没有pause,不知道怎么处理 - // 是否存在 - //isExist = IsExist(downloading); - //if (!isExist.Result) - //{ - // return; - //} - - // 检测音频、视频是否下载成功 - bool isMediaSuccess = true; - if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) - { - // 只有下载音频不下载视频时才输出aac - // 只要下载视频就输出mp4 - if (File.Exists(outputMedia)) - { - // 成功 - isMediaSuccess = true; - } - else - { - isMediaSuccess = false; - } - } - - // 检测弹幕是否下载成功 - bool isDanmakuSuccess = true; - if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"]) - { - if (File.Exists(outputDanmaku)) - { - // 成功 - isDanmakuSuccess = true; - } - else - { - isDanmakuSuccess = false; - } - } - - // 检测字幕是否下载成功 - bool isSubtitleSuccess = true; - if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"]) - { - if (outputSubtitles == null) - { - // 为null时表示不存在字幕 - } - else - { - foreach (string subtitle in outputSubtitles) - { - if (!File.Exists(subtitle)) - { - // 如果有一个不存在则失败 - isSubtitleSuccess = false; - } - } - } - } - - // 检测封面是否下载成功 - bool isCover = true; - if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) - { - if (File.Exists(outputCover) || File.Exists(outputPageCover)) - { - // 成功 - isCover = true; - } - else - { - isCover = false; - } - } - - if (!isMediaSuccess || !isDanmakuSuccess || !isSubtitleSuccess || !isCover) - { - DownloadFailed(downloading); - return; - } - - // 下载完成后处理 - Downloaded downloaded = new Downloaded - { - MaxSpeedDisplay = Format.FormatSpeed(downloading.Downloading.MaxSpeed), - }; - // 设置完成时间 - downloaded.SetFinishedTimestamp(new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds()); - - DownloadedItem downloadedItem = new DownloadedItem - { - DownloadBase = downloading.DownloadBase, - Downloaded = downloaded - }; - - App.PropertyChangeAsync(new Action(() => - { - // 加入到下载完成list中,并从下载中list去除 - downloadedList.Add(downloadedItem); - downloadingList.Remove(downloading); - - // 下载完成列表排序 - DownloadFinishedSort finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort(); - App.SortDownloadedList(finishedSort); - })); - })); - } - catch (OperationCanceledException) - { - } - } - - /// - /// 下载失败后的处理 - /// - /// - private void DownloadFailed(DownloadingItem downloading) - { - downloading.DownloadStatusTitle = DictionaryResource.GetString("DownloadFailed"); - downloading.DownloadContent = string.Empty; - downloading.DownloadingFileSize = string.Empty; - downloading.SpeedDisplay = string.Empty; - - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOAD_FAILED; - downloading.StartOrPause = ButtonIcon.Instance().Retry; - downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); - } - - /// - /// 下载完成后的操作 - /// - private void AfterDownload() - { - AfterDownloadOperation operation = SettingsManager.GetInstance().GetAfterDownloadOperation(); - switch (operation) - { - case AfterDownloadOperation.NONE: - // 没有操作 - break; - case AfterDownloadOperation.OPEN_FOLDER: - // 打开文件夹 - break; - case AfterDownloadOperation.CLOSE_APP: - // 关闭程序 - App.PropertyChangeAsync(() => - { - System.Windows.Application.Current.Shutdown(); - }); - break; - case AfterDownloadOperation.CLOSE_SYSTEM: - // 关机 - System.Diagnostics.Process.Start("shutdown.exe", "-s"); - break; - default: - break; - } - } - - /// - /// 获取图片的扩展名 - /// - /// - /// - private string GetImageExtension(string coverUrl) - { - if (coverUrl == null) - { - return string.Empty; - } - - // 图片的扩展名 - string[] temp = coverUrl.Split('.'); - string fileExtension = temp[temp.Length - 1]; - return fileExtension; + // 启动基本服务 + BaseStart(); } /// /// 强制暂停 /// /// - private void Pause(DownloadingItem downloading) + /// + protected override void Pause(DownloadingItem downloading) { cancellationToken.ThrowIfCancellationRequested(); @@ -951,8 +282,8 @@ namespace DownKyi.Services.Download ListenPort = SettingsManager.GetInstance().GetAriaListenPort(), Token = "downkyi", LogLevel = SettingsManager.GetInstance().GetAriaLogLevel(), - MaxConcurrentDownloads = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(), - MaxConnectionPerServer = 16, // 最大取16 + MaxConcurrentDownloads = SettingsManager.GetInstance().GetMaxCurrentDownloads(), + MaxConnectionPerServer = 8, // 最大取16 Split = SettingsManager.GetInstance().GetAriaSplit(), //MaxTries = 5, MinSplitSize = 10, // 10MB @@ -983,11 +314,15 @@ namespace DownKyi.Services.Download { // 暂停所有下载 var ariaPause = await AriaClient.PauseAllAsync(); +#if DEBUG Core.Utils.Debugging.Console.PrintLine(ariaPause.ToString()); +#endif // 关闭服务器 bool close = AriaServer.CloseServer(); +#if DEBUG Core.Utils.Debugging.Console.PrintLine(close); +#endif } /// @@ -1025,8 +360,8 @@ namespace DownKyi.Services.Download //HttpProxy = $"http://{Settings.GetAriaHttpProxy()}:{Settings.GetAriaHttpProxyListenPort()}", Dir = path, Out = localFileName, - Header = $"cookie: {LoginHelper.GetLoginInfoCookiesString()}\nreferer: https://www.bilibili.com", - UseHead = "true", + //Header = $"cookie: {LoginHelper.GetLoginInfoCookiesString()}\nreferer: https://www.bilibili.com", + //UseHead = "true", UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36", }; @@ -1088,6 +423,7 @@ namespace DownKyi.Services.Download if (video == null) { return; } + // 下载进度百分比 float percent = 0; if (totalLength != 0) { diff --git a/src/DownKyi/Services/Download/BuiltinDownloadService.cs b/src/DownKyi/Services/Download/BuiltinDownloadService.cs new file mode 100644 index 0000000..7c20f1b --- /dev/null +++ b/src/DownKyi/Services/Download/BuiltinDownloadService.cs @@ -0,0 +1,363 @@ +using DownKyi.Core.BiliApi.Login; +using DownKyi.Core.BiliApi.VideoStream.Models; +using DownKyi.Core.Downloader; +using DownKyi.Core.Logging; +using DownKyi.Core.Settings; +using DownKyi.Core.Utils; +using DownKyi.Models; +using DownKyi.Utils; +using DownKyi.ViewModels.DownloadManager; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace DownKyi.Services.Download +{ + public class BuiltinDownloadService : DownloadService, IDownloadService + { + public BuiltinDownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList) : base(downloadingList, downloadedList) + { + Tag = "BuiltinDownloadService"; + } + + #region 音视频 + + /// + /// 下载音频,返回下载文件路径 + /// + /// + /// + public override string DownloadAudio(DownloadingItem downloading) + { + PlayUrlDashVideo downloadAudio = BaseDownloadAudio(downloading); + + return DownloadVideo(downloading, downloadAudio); + } + + /// + /// 下载视频,返回下载文件路径 + /// + /// + /// + public override string DownloadVideo(DownloadingItem downloading) + { + PlayUrlDashVideo downloadVideo = BaseDownloadVideo(downloading); + + return DownloadVideo(downloading, downloadVideo); + } + + /// + /// 将下载音频和视频的函数中相同代码抽象出来 + /// + /// + /// + /// + private string DownloadVideo(DownloadingItem downloading, PlayUrlDashVideo downloadVideo) + { + // 如果为空,说明没有匹配到可下载的音频视频 + if (downloadVideo == null) { return null; } + + // 下载链接 + List urls = new List(); + if (downloadVideo.BaseUrl != null) { urls.Add(downloadVideo.BaseUrl); } + if (downloadVideo.BackupUrl != null) { urls.AddRange(downloadVideo.BackupUrl); } + + // 路径 + downloading.DownloadBase.FilePath = downloading.DownloadBase.FilePath.Replace("\\", "/"); + string[] temp = downloading.DownloadBase.FilePath.Split('/'); + //string path = downloading.DownloadBase.FilePath.Replace(temp[temp.Length - 1], ""); + string path = downloading.DownloadBase.FilePath.TrimEnd(temp[temp.Length - 1].ToCharArray()); + + // 下载文件名 + string fileName = Guid.NewGuid().ToString("N"); + string key = $"{downloadVideo.Id}_{downloadVideo.Codecs}"; + + // 老版本数据库没有这一项,会变成null + if (downloading.Downloading.DownloadedFiles == null) + { + downloading.Downloading.DownloadedFiles = new List(); + } + + if (downloading.Downloading.DownloadFiles.ContainsKey(key)) + { + // 如果存在,表示下载过, + // 则继续使用上次下载的文件名 + fileName = downloading.Downloading.DownloadFiles[key]; + + // 还要检查一下文件有没有被人删掉,删掉的话重新下载 + // 如果下载视频之后音频文件被人删了。此时gid还是视频的,会下错文件 + if (downloading.Downloading.DownloadedFiles.Contains(key) && File.Exists(Path.Combine(path, fileName))) + { + return Path.Combine(path, fileName); + } + } + else + { + // 记录本次下载的文件 + try + { + downloading.Downloading.DownloadFiles.Add(key, fileName); + } + catch (ArgumentException) { } + // Gid最好能是每个文件单独存储,现在复用有可能会混 + // 不过好消息是下载是按固定顺序的,而且下载了两个音频会混流不过 + downloading.Downloading.Gid = null; + } + + // 开始下载 + var downloadStatus = DownloadByBuiltin(downloading, urls, path, fileName); + if (downloadStatus) + { + downloading.Downloading.DownloadedFiles.Add(key); + downloading.Downloading.Gid = null; + return Path.Combine(path, fileName); + } + else + { + return nullMark; + } + } + + #endregion + + /// + /// 下载封面 + /// + /// + /// + /// + /// + public override string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName) + { + return BaseDownloadCover(downloading, coverUrl, fileName); + } + + /// + /// 下载弹幕 + /// + /// + /// + public override string DownloadDanmaku(DownloadingItem downloading) + { + return BaseDownloadDanmaku(downloading); + } + + /// + /// 下载字幕 + /// + /// + /// + public override List DownloadSubtitle(DownloadingItem downloading) + { + return BaseDownloadSubtitle(downloading); + } + + /// + /// 混流音频和视频 + /// + /// + /// + /// + /// + public override string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) + { + return BaseMixedFlow(downloading, audioUid, videoUid); + } + + /// + /// 解析视频流的下载链接 + /// + /// + public override void Parse(DownloadingItem downloading) + { + BaseParse(downloading); + } + + /// + /// 停止下载服务(转换await和Task.Wait两种调用形式) + /// + private async Task EndTask() + { + // 停止基本任务 + await BaseEndTask(); + } + + /// + /// 停止下载服务 + /// + public void End() + { + Task.Run(EndTask).Wait(); + } + + public void Start() + { + // 启动基本服务 + BaseStart(); + } + + /// + /// 强制暂停 + /// + /// + /// + protected override void Pause(DownloadingItem downloading) + { + cancellationToken.ThrowIfCancellationRequested(); + + downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing"); + if (downloading.Downloading.DownloadStatus == DownloadStatus.PAUSE) + { + throw new OperationCanceledException("Stop thread by pause"); + } + // 是否存在 + var isExist = IsExist(downloading); + if (!isExist) + { + throw new OperationCanceledException("Task is deleted"); + } + } + + /// + /// 是否存在于下载列表中 + /// + /// + /// + private bool IsExist(DownloadingItem downloading) + { + bool isExist = downloadingList.Contains(downloading); + if (isExist) + { + return true; + } + else + { + return false; + } + } + + #region 内建下载器 + + /// + /// 下载文件 + /// + /// + /// + /// + /// + /// + private bool DownloadByBuiltin(DownloadingItem downloading, List urls, string path, string localFileName) + { + // path已斜杠结尾,去掉斜杠 + path = path.TrimEnd('/').TrimEnd('\\'); + + foreach (var url in urls) + { + // 创建下载器 + var mtd = new MultiThreadDownloader(url, + Environment.GetEnvironmentVariable("temp"), + Path.Combine(path, localFileName), + SettingsManager.GetInstance().GetSplit()); + // 配置网络请求 + mtd.Configure(req => + { + req.CookieContainer = LoginHelper.GetLoginInfoCookies(); + req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36"; + req.Referer = "https://www.bilibili.com"; + req.Headers.Add("Origin", "https://www.bilibili.com"); + + if (SettingsManager.GetInstance().IsHttpProxy() == AllowStatus.YES) + { + req.Proxy = new WebProxy(SettingsManager.GetInstance().GetHttpProxy(), + SettingsManager.GetInstance().GetHttpProxyListenPort()); + } + }); + + // 下载进度回调 + mtd.TotalProgressChanged += (sender, e) => + { + try + { + // 状态更新 + var downloader = sender as MultiThreadDownloader; + + // 下载进度百分比 + float percent = downloader.TotalProgress; + + // 根据进度判断本次是否需要更新UI + if (Math.Abs(percent - downloading.Progress) < 0.01) { return; } + if (Math.Abs(percent - downloading.Progress) > 5) { return; } + + // 下载进度 + downloading.Progress = percent; + + // 下载大小 + downloading.DownloadingFileSize = Format.FormatFileSize(downloader.TotalBytesReceived) + "/" + Format.FormatFileSize(downloader.Size); + + // 下载速度 + long speed = (long)downloader.TotalSpeedInBytes; + + // 下载速度显示 + downloading.SpeedDisplay = Format.FormatSpeed(speed); + + // 最大下载速度 + if (downloading.Downloading.MaxSpeed < speed) + { + downloading.Downloading.MaxSpeed = speed; + } + } + catch (InvalidOperationException ex) + { + Core.Utils.Debugging.Console.PrintLine($"{Tag}.DownloadByBuiltin()发生InvalidOperationException异常: {0}", ex); + LogManager.Error($"{Tag}.DownloadByBuiltin()", ex); + } + catch (Exception ex) + { + Core.Utils.Debugging.Console.PrintLine($"{Tag}.DownloadByBuiltin()发生异常: {0}", ex); + LogManager.Error($"{Tag}.DownloadByBuiltin()", ex); + } + }; + + // 文件合并完成回调 + bool isComplete = false; + mtd.FileMergedComplete += (sender, e) => + { + // 跳出循环 + if (File.Exists(Path.Combine(path, localFileName))) { isComplete = true; } + }; + + // 开始下载 + mtd.StartAsync(); + + // 阻塞当前任务,监听暂停事件 + while (!isComplete) + { + cancellationToken.ThrowIfCancellationRequested(); + switch (downloading.Downloading.DownloadStatus) + { + case DownloadStatus.PAUSE: + // 暂停下载 + mtd.Pause(); + // 通知UI,并阻塞当前线程 + Pause(downloading); + break; + case DownloadStatus.DOWNLOADING: + break; + } + + Thread.Sleep(100); + } + return isComplete; + } + + return false; + } + + #endregion + + } +} diff --git a/src/DownKyi/Services/Download/DownloadService.cs b/src/DownKyi/Services/Download/DownloadService.cs index f8612a8..9123866 100644 --- a/src/DownKyi/Services/Download/DownloadService.cs +++ b/src/DownKyi/Services/Download/DownloadService.cs @@ -1,15 +1,39 @@ -using DownKyi.ViewModels.DownloadManager; +using DownKyi.Core.BiliApi.VideoStream; +using DownKyi.Core.BiliApi.VideoStream.Models; +using DownKyi.Core.Danmaku2Ass; +using DownKyi.Core.FFmpeg; +using DownKyi.Core.Logging; +using DownKyi.Core.Settings; +using DownKyi.Core.Storage; +using DownKyi.Core.Utils; +using DownKyi.Images; +using DownKyi.Models; +using DownKyi.Utils; +using DownKyi.ViewModels.DownloadManager; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace DownKyi.Services.Download { - public class DownloadService + public abstract class DownloadService { protected string Tag = "DownloadService"; protected ObservableCollection downloadingList; protected ObservableCollection downloadedList; + protected Task workTask; + protected CancellationTokenSource tokenSource; + protected CancellationToken cancellationToken; + protected List downloadingTasks = new List(); + + protected readonly int retry = 5; + protected readonly string nullMark = ""; + /// /// 初始化 /// @@ -21,5 +45,747 @@ namespace DownKyi.Services.Download this.downloadedList = downloadedList; } + protected PlayUrlDashVideo BaseDownloadAudio(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingAudio"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + downloading.Progress = 0; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + // 如果没有Dash,返回null + if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } + + // 如果audio列表没有内容,则返回null + if (downloading.PlayUrl.Dash.Audio == null) { return null; } + else if (downloading.PlayUrl.Dash.Audio.Count == 0) { return null; } + + // 根据音频id匹配 + PlayUrlDashVideo downloadAudio = null; + foreach (PlayUrlDashVideo audio in downloading.PlayUrl.Dash.Audio) + { + if (audio.Id == downloading.AudioCodec.Id) + { + downloadAudio = audio; + break; + } + } + + // 避免Dolby==null及其它未知情况,直接使用异常捕获 + try + { + // Dolby Atmos + if (downloading.AudioCodec.Id == 30250) + { + downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0]; + } + } + catch (Exception) { } + + return downloadAudio; + } + + protected PlayUrlDashVideo BaseDownloadVideo(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + downloading.Progress = 0; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + // 如果没有Dash,返回null + if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } + + // 如果Video列表没有内容,则返回null + if (downloading.PlayUrl.Dash.Video == null) { return null; } + else if (downloading.PlayUrl.Dash.Video.Count == 0) { return null; } + + // 根据视频编码匹配 + PlayUrlDashVideo downloadVideo = null; + foreach (PlayUrlDashVideo video in downloading.PlayUrl.Dash.Video) + { + if (video.Id == downloading.Resolution.Id && Utils.GetVideoCodecName(video.Codecs) == downloading.VideoCodecName) + { + downloadVideo = video; + break; + } + } + + return downloadVideo; + } + + protected string BaseDownloadCover(DownloadingItem downloading, string coverUrl, string fileName) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingCover"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + // 查询、保存封面 + StorageCover storageCover = new StorageCover(); + string cover = storageCover.GetCover(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, coverUrl); + if (cover == null) + { + return null; + } + + // 复制图片到指定位置 + try + { + File.Copy(cover, fileName, true); + + // 记录本次下载的文件 + if (!downloading.Downloading.DownloadFiles.ContainsKey(coverUrl)) + { + downloading.Downloading.DownloadFiles.Add(coverUrl, fileName); + } + + return fileName; + } + catch (Exception e) + { + Core.Utils.Debugging.Console.PrintLine($"{Tag}.DownloadCover()发生异常: {0}", e); + LogManager.Error($"{Tag}.DownloadCover()", e); + } + + return null; + } + + protected string BaseDownloadDanmaku(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingDanmaku"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + string title = $"{downloading.Name}"; + string assFile = $"{downloading.DownloadBase.FilePath}.ass"; + + // 记录本次下载的文件 + if (!downloading.Downloading.DownloadFiles.ContainsKey("danmaku")) + { + downloading.Downloading.DownloadFiles.Add("danmaku", assFile); + } + + int screenWidth = SettingsManager.GetInstance().GetDanmakuScreenWidth(); + int screenHeight = SettingsManager.GetInstance().GetDanmakuScreenHeight(); + //if (SettingsManager.GetInstance().IsCustomDanmakuResolution() != AllowStatus.YES) + //{ + // if (downloadingEntity.Width > 0 && downloadingEntity.Height > 0) + // { + // screenWidth = downloadingEntity.Width; + // screenHeight = downloadingEntity.Height; + // } + //} + + // 字幕配置 + Config subtitleConfig = new Config + { + Title = title, + ScreenWidth = screenWidth, + ScreenHeight = screenHeight, + FontName = SettingsManager.GetInstance().GetDanmakuFontName(), + BaseFontSize = SettingsManager.GetInstance().GetDanmakuFontSize(), + LineCount = SettingsManager.GetInstance().GetDanmakuLineCount(), + LayoutAlgorithm = SettingsManager.GetInstance().GetDanmakuLayoutAlgorithm().ToString("G").ToLower(), // async/sync + TuneDuration = 0, + DropOffset = 0, + BottomMargin = 0, + CustomOffset = 0 + }; + + Core.Danmaku2Ass.Bilibili.GetInstance() + .SetTopFilter(SettingsManager.GetInstance().GetDanmakuTopFilter() == AllowStatus.YES) + .SetBottomFilter(SettingsManager.GetInstance().GetDanmakuBottomFilter() == AllowStatus.YES) + .SetScrollFilter(SettingsManager.GetInstance().GetDanmakuScrollFilter() == AllowStatus.YES) + .Create(downloading.DownloadBase.Avid, downloading.DownloadBase.Cid, subtitleConfig, assFile); + + return assFile; + } + + protected List BaseDownloadSubtitle(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingSubtitle"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + List srtFiles = new List(); + + var subRipTexts = VideoStream.GetSubtitle(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); + if (subRipTexts == null) + { + return null; + } + + foreach (var subRip in subRipTexts) + { + string srtFile = $"{downloading.DownloadBase.FilePath}_{subRip.LanDoc}.srt"; + try + { + File.WriteAllText(srtFile, subRip.SrtString); + + // 记录本次下载的文件 + if (!downloading.Downloading.DownloadFiles.ContainsKey("subtitle")) + { + downloading.Downloading.DownloadFiles.Add("subtitle", srtFile); + } + + srtFiles.Add(srtFile); + } + catch (Exception e) + { + Core.Utils.Debugging.Console.PrintLine($"{Tag}.DownloadSubtitle()发生异常: {0}", e); + LogManager.Error($"{Tag}.DownloadSubtitle()", e); + } + } + + return srtFiles; + } + + protected string BaseMixedFlow(DownloadingItem downloading, string audioUid, string videoUid) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("MixedFlow"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + //if (videoUid == nullMark) + //{ + // return null; + //} + + string finalFile = $"{downloading.DownloadBase.FilePath}.mp4"; + if (videoUid == null) + { + finalFile = $"{downloading.DownloadBase.FilePath}.aac"; + } + + // 合并音视频 + FFmpegHelper.MergeVideo(audioUid, videoUid, finalFile); + + // 获取文件大小 + if (File.Exists(finalFile)) + { + FileInfo info = new FileInfo(finalFile); + downloading.FileSize = Format.FormatFileSize(info.Length); + } + else + { + downloading.FileSize = Format.FormatFileSize(0); + } + + return finalFile; + } + + protected void BaseParse(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("Parsing"); + downloading.DownloadContent = string.Empty; + // 下载大小 + downloading.DownloadingFileSize = string.Empty; + downloading.Progress = 0; + // 下载速度 + downloading.SpeedDisplay = string.Empty; + + if (downloading.PlayUrl != null && downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED) + { + // 设置下载状态 + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + + return; + } + + // 设置下载状态 + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + + // 解析 + switch (downloading.Downloading.PlayStreamType) + { + case PlayStreamType.VIDEO: + downloading.PlayUrl = VideoStream.GetVideoPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); + break; + case PlayStreamType.BANGUMI: + downloading.PlayUrl = VideoStream.GetBangumiPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid); + break; + case PlayStreamType.CHEESE: + downloading.PlayUrl = VideoStream.GetCheesePlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, downloading.DownloadBase.EpisodeId); + break; + default: + break; + } + } + + /// + /// 执行任务 + /// + protected async Task DoWork() + { + // 上次循环时正在下载的数量 + int lastDownloadingCount = 0; + + while (true) + { + int maxDownloading = SettingsManager.GetInstance().GetMaxCurrentDownloads(); + int downloadingCount = 0; + + try + { + downloadingTasks.RemoveAll((m) => m.IsCompleted); + foreach (DownloadingItem downloading in downloadingList) + { + if (downloading.Downloading.DownloadStatus == DownloadStatus.DOWNLOADING) + { + downloadingCount++; + } + } + + foreach (DownloadingItem downloading in downloadingList) + { + if (downloadingCount >= maxDownloading) + { + break; + } + + // 开始下载 + if (downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED || downloading.Downloading.DownloadStatus == DownloadStatus.WAIT_FOR_DOWNLOAD) + { + //这里需要立刻设置状态,否则如果SingleDownload没有及时执行,会重复创建任务 + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + downloadingTasks.Add(SingleDownload(downloading)); + downloadingCount++; + } + } + } + catch (InvalidOperationException e) + { + Core.Utils.Debugging.Console.PrintLine($"{Tag}.DoWork()发生InvalidOperationException异常: {0}", e); + LogManager.Error($"{Tag}.DoWork() InvalidOperationException", e); + } + catch (Exception e) + { + Core.Utils.Debugging.Console.PrintLine($"{Tag}.DoWork()发生异常: {0}", e); + LogManager.Error($"{Tag}.DoWork()", e); + } + + // 判断是否该结束线程,若为true,跳出while循环 + if (cancellationToken.IsCancellationRequested) + { + Core.Utils.Debugging.Console.PrintLine($"{Tag}.DoWork() 下载服务结束,跳出while循环"); + LogManager.Debug($"{Tag}.DoWork()", "下载服务结束"); + break; + } + + // 判断下载列表中的视频是否全部下载完成 + if (lastDownloadingCount > 0 && downloadingList.Count == 0 && downloadedList.Count > 0) + { + AfterDownload(); + } + lastDownloadingCount = downloadingList.Count; + + // 降低CPU占用 + await Task.Delay(500); + } + + await Task.WhenAny(Task.WhenAll(downloadingTasks), Task.Delay(30000)); + foreach (Task tsk in downloadingTasks.FindAll((m) => !m.IsCompleted)) + { + Core.Utils.Debugging.Console.PrintLine($"{Tag}.DoWork() 任务结束超时"); + LogManager.Debug($"{Tag}.DoWork()", "任务结束超时"); + } + } + + /// + /// 下载一个视频 + /// + /// + /// + private async Task SingleDownload(DownloadingItem downloading) + { + // 路径 + downloading.DownloadBase.FilePath = downloading.DownloadBase.FilePath.Replace("\\", "/"); + string[] temp = downloading.DownloadBase.FilePath.Split('/'); + //string path = downloading.DownloadBase.FilePath.Replace(temp[temp.Length - 1], ""); + string path = downloading.DownloadBase.FilePath.TrimEnd(temp[temp.Length - 1].ToCharArray()); + + // 路径不存在则创建 + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + try + { + await Task.Run(new Action(() => + { + // 初始化 + downloading.DownloadStatusTitle = string.Empty; + downloading.DownloadContent = string.Empty; + //downloading.Downloading.DownloadFiles.Clear(); + + // 解析并依次下载音频、视频、弹幕、字幕、封面等内容 + Parse(downloading); + + // 暂停 + Pause(downloading); + + string audioUid = null; + // 如果需要下载音频 + if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"]) + { + //audioUid = DownloadAudio(downloading); + for (int i = 0; i < retry; i++) + { + audioUid = DownloadAudio(downloading); + if (audioUid != null && audioUid != nullMark) + { + break; + } + } + } + if (audioUid == nullMark) + { + DownloadFailed(downloading); + return; + } + + // 暂停 + Pause(downloading); + + string videoUid = null; + // 如果需要下载视频 + if (downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) + { + //videoUid = DownloadVideo(downloading); + for (int i = 0; i < retry; i++) + { + videoUid = DownloadVideo(downloading); + if (videoUid != null && videoUid != nullMark) + { + break; + } + } + } + if (videoUid == nullMark) + { + DownloadFailed(downloading); + return; + } + + // 暂停 + Pause(downloading); + + string outputDanmaku = null; + // 如果需要下载弹幕 + if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"]) + { + outputDanmaku = DownloadDanmaku(downloading); + } + + // 暂停 + Pause(downloading); + + List outputSubtitles = null; + // 如果需要下载字幕 + if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"]) + { + outputSubtitles = DownloadSubtitle(downloading); + } + + // 暂停 + Pause(downloading); + + string outputCover = null; + string outputPageCover = null; + // 如果需要下载封面 + if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) + { + string fileName = $"{downloading.DownloadBase.FilePath}.{GetImageExtension(downloading.DownloadBase.PageCoverUrl)}"; + + // page的封面 + outputPageCover = DownloadCover(downloading, downloading.DownloadBase.PageCoverUrl, fileName); + // 封面 + outputCover = DownloadCover(downloading, downloading.DownloadBase.CoverUrl, $"{path}/Cover.{GetImageExtension(downloading.DownloadBase.CoverUrl)}"); + } + + // 暂停 + Pause(downloading); + + // 混流 + string outputMedia = string.Empty; + if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) + { + outputMedia = MixedFlow(downloading, audioUid, videoUid); + } + + // 这里本来只有IsExist,没有pause,不知道怎么处理 + // 是否存在 + //isExist = IsExist(downloading); + //if (!isExist.Result) + //{ + // return; + //} + + // 检测音频、视频是否下载成功 + bool isMediaSuccess = true; + if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) + { + // 只有下载音频不下载视频时才输出aac + // 只要下载视频就输出mp4 + if (File.Exists(outputMedia)) + { + // 成功 + isMediaSuccess = true; + } + else + { + isMediaSuccess = false; + } + } + + // 检测弹幕是否下载成功 + bool isDanmakuSuccess = true; + if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"]) + { + if (File.Exists(outputDanmaku)) + { + // 成功 + isDanmakuSuccess = true; + } + else + { + isDanmakuSuccess = false; + } + } + + // 检测字幕是否下载成功 + bool isSubtitleSuccess = true; + if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"]) + { + if (outputSubtitles == null) + { + // 为null时表示不存在字幕 + } + else + { + foreach (string subtitle in outputSubtitles) + { + if (!File.Exists(subtitle)) + { + // 如果有一个不存在则失败 + isSubtitleSuccess = false; + } + } + } + } + + // 检测封面是否下载成功 + bool isCover = true; + if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) + { + if (File.Exists(outputCover) || File.Exists(outputPageCover)) + { + // 成功 + isCover = true; + } + else + { + isCover = false; + } + } + + if (!isMediaSuccess || !isDanmakuSuccess || !isSubtitleSuccess || !isCover) + { + DownloadFailed(downloading); + return; + } + + // 下载完成后处理 + Downloaded downloaded = new Downloaded + { + MaxSpeedDisplay = Format.FormatSpeed(downloading.Downloading.MaxSpeed), + }; + // 设置完成时间 + downloaded.SetFinishedTimestamp(new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds()); + + DownloadedItem downloadedItem = new DownloadedItem + { + DownloadBase = downloading.DownloadBase, + Downloaded = downloaded + }; + + App.PropertyChangeAsync(new Action(() => + { + // 加入到下载完成list中,并从下载中list去除 + downloadedList.Add(downloadedItem); + downloadingList.Remove(downloading); + + // 下载完成列表排序 + DownloadFinishedSort finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort(); + App.SortDownloadedList(finishedSort); + })); + })); + } + catch (OperationCanceledException e) + { + Core.Utils.Debugging.Console.PrintLine(Tag, e.ToString()); + LogManager.Debug(Tag, e.Message); + } + } + + /// + /// 下载失败后的处理 + /// + /// + protected void DownloadFailed(DownloadingItem downloading) + { + downloading.DownloadStatusTitle = DictionaryResource.GetString("DownloadFailed"); + downloading.DownloadContent = string.Empty; + downloading.DownloadingFileSize = string.Empty; + downloading.SpeedDisplay = string.Empty; + downloading.Progress = 0; + + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOAD_FAILED; + downloading.StartOrPause = ButtonIcon.Instance().Retry; + downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + } + + /// + /// 获取图片的扩展名 + /// + /// + /// + protected string GetImageExtension(string coverUrl) + { + if (coverUrl == null) + { + return string.Empty; + } + + // 图片的扩展名 + string[] temp = coverUrl.Split('.'); + string fileExtension = temp[temp.Length - 1]; + return fileExtension; + } + + /// + /// 下载完成后的操作 + /// + protected void AfterDownload() + { + AfterDownloadOperation operation = SettingsManager.GetInstance().GetAfterDownloadOperation(); + switch (operation) + { + case AfterDownloadOperation.NONE: + // 没有操作 + break; + case AfterDownloadOperation.OPEN_FOLDER: + // 打开文件夹 + break; + case AfterDownloadOperation.CLOSE_APP: + // 关闭程序 + App.PropertyChangeAsync(() => + { + System.Windows.Application.Current.Shutdown(); + }); + break; + case AfterDownloadOperation.CLOSE_SYSTEM: + // 关机 + System.Diagnostics.Process.Start("shutdown.exe", "-s"); + break; + default: + break; + } + } + + /// + /// 停止基本下载服务(转换await和Task.Wait两种调用形式) + /// + protected async Task BaseEndTask() + { + // 结束任务 + tokenSource.Cancel(); + + await workTask; + + //先简单等待一下 + + // 下载数据存储服务 + DownloadStorageService downloadStorageService = new DownloadStorageService(); + // 保存数据 + foreach (DownloadingItem item in downloadingList) + { + switch (item.Downloading.DownloadStatus) + { + case DownloadStatus.NOT_STARTED: + break; + case DownloadStatus.WAIT_FOR_DOWNLOAD: + break; + case DownloadStatus.PAUSE_STARTED: + break; + case DownloadStatus.PAUSE: + break; + case DownloadStatus.DOWNLOADING: + // TODO 添加设置让用户选择重启后是否自动开始下载 + item.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + //item.Downloading.DownloadStatus = DownloadStatus.PAUSE; + break; + case DownloadStatus.DOWNLOAD_SUCCEED: + case DownloadStatus.DOWNLOAD_FAILED: + break; + default: + break; + } + + item.Progress = 0; + + downloadStorageService.UpdateDownloading(item); + } + foreach (DownloadedItem item in downloadedList) + { + downloadStorageService.UpdateDownloaded(item); + } + } + + /// + /// 启动基本下载服务 + /// + protected void BaseStart() + { + tokenSource = new CancellationTokenSource(); + cancellationToken = tokenSource.Token; + workTask = Task.Run(DoWork); + } + + #region 抽象接口函数 + public abstract void Parse(DownloadingItem downloading); + public abstract string DownloadAudio(DownloadingItem downloading); + public abstract string DownloadVideo(DownloadingItem downloading); + public abstract string DownloadDanmaku(DownloadingItem downloading); + public abstract List DownloadSubtitle(DownloadingItem downloading); + public abstract string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName); + public abstract string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid); + + protected abstract void Pause(DownloadingItem downloading); + #endregion } } diff --git a/src/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs b/src/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs index fedbd87..0aa3468 100644 --- a/src/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs +++ b/src/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs @@ -2,10 +2,12 @@ using DownKyi.Core.Settings; using DownKyi.Core.Utils.Validator; using DownKyi.Events; +using DownKyi.Services; using DownKyi.Utils; using Prism.Commands; using Prism.Events; using Prism.Regions; +using Prism.Services.Dialogs; using System.Collections.Generic; namespace DownKyi.ViewModels.Settings @@ -18,111 +20,182 @@ namespace DownKyi.ViewModels.Settings #region 页面属性申明 + private bool builtin; + public bool Builtin + { + get => builtin; + set => SetProperty(ref builtin, value); + } + + private bool aria2c; + public bool Aria2c + { + get => aria2c; + set => SetProperty(ref aria2c, value); + } + + private List maxCurrentDownloads; + public List MaxCurrentDownloads + { + get => maxCurrentDownloads; + set => SetProperty(ref maxCurrentDownloads, value); + } + + private int selectedMaxCurrentDownload; + public int SelectedMaxCurrentDownload + { + get => selectedMaxCurrentDownload; + set => SetProperty(ref selectedMaxCurrentDownload, value); + } + + private List splits; + public List Splits + { + get => splits; + set => SetProperty(ref splits, value); + } + + private int selectedSplit; + public int SelectedSplit + { + get => selectedSplit; + set => SetProperty(ref selectedSplit, value); + } + + private bool isHttpProxy; + public bool IsHttpProxy + { + get => isHttpProxy; + set => SetProperty(ref isHttpProxy, value); + } + + private string httpProxy; + public string HttpProxy + { + get => httpProxy; + set => SetProperty(ref httpProxy, value); + } + + private int httpProxyPort; + public int HttpProxyPort + { + get => httpProxyPort; + set => SetProperty(ref httpProxyPort, value); + } + private int ariaListenPort; public int AriaListenPort { - get { return ariaListenPort; } - set { SetProperty(ref ariaListenPort, value); } + get => ariaListenPort; + set => SetProperty(ref ariaListenPort, value); } private List ariaLogLevels; public List AriaLogLevels { - get { return ariaLogLevels; } - set { SetProperty(ref ariaLogLevels, value); } + get => ariaLogLevels; + set => SetProperty(ref ariaLogLevels, value); } private string selectedAriaLogLevel; public string SelectedAriaLogLevel { - get { return selectedAriaLogLevel; } - set { SetProperty(ref selectedAriaLogLevel, value); } + get => selectedAriaLogLevel; + set => SetProperty(ref selectedAriaLogLevel, value); } private List ariaMaxConcurrentDownloads; public List AriaMaxConcurrentDownloads { - get { return ariaMaxConcurrentDownloads; } - set { SetProperty(ref ariaMaxConcurrentDownloads, value); } + get => ariaMaxConcurrentDownloads; + set => SetProperty(ref ariaMaxConcurrentDownloads, value); } private int selectedAriaMaxConcurrentDownload; public int SelectedAriaMaxConcurrentDownload { - get { return selectedAriaMaxConcurrentDownload; } - set { SetProperty(ref selectedAriaMaxConcurrentDownload, value); } + get => selectedAriaMaxConcurrentDownload; + set => SetProperty(ref selectedAriaMaxConcurrentDownload, value); } private List ariaSplits; public List AriaSplits { - get { return ariaSplits; } - set { SetProperty(ref ariaSplits, value); } + get => ariaSplits; + set => SetProperty(ref ariaSplits, value); } private int selectedAriaSplit; public int SelectedAriaSplit { - get { return selectedAriaSplit; } - set { SetProperty(ref selectedAriaSplit, value); } + get => selectedAriaSplit; + set => SetProperty(ref selectedAriaSplit, value); } private int ariaMaxOverallDownloadLimit; public int AriaMaxOverallDownloadLimit { - get { return ariaMaxOverallDownloadLimit; } - set { SetProperty(ref ariaMaxOverallDownloadLimit, value); } + get => ariaMaxOverallDownloadLimit; + set => SetProperty(ref ariaMaxOverallDownloadLimit, value); } private int ariaMaxDownloadLimit; public int AriaMaxDownloadLimit { - get { return ariaMaxDownloadLimit; } - set { SetProperty(ref ariaMaxDownloadLimit, value); } + get => ariaMaxDownloadLimit; + set => SetProperty(ref ariaMaxDownloadLimit, value); } private bool isAriaHttpProxy; public bool IsAriaHttpProxy { - get { return isAriaHttpProxy; } - set { SetProperty(ref isAriaHttpProxy, value); } + get => isAriaHttpProxy; + set => SetProperty(ref isAriaHttpProxy, value); } private string ariaHttpProxy; public string AriaHttpProxy { - get { return ariaHttpProxy; } - set { SetProperty(ref ariaHttpProxy, value); } + get => ariaHttpProxy; + set => SetProperty(ref ariaHttpProxy, value); } private int ariaHttpProxyPort; public int AriaHttpProxyPort { - get { return ariaHttpProxyPort; } - set { SetProperty(ref ariaHttpProxyPort, value); } + get => ariaHttpProxyPort; + set => SetProperty(ref ariaHttpProxyPort, value); } private List ariaFileAllocations; public List AriaFileAllocations { - get { return ariaFileAllocations; } - set { SetProperty(ref ariaFileAllocations, value); } + get => ariaFileAllocations; + set => SetProperty(ref ariaFileAllocations, value); } private string selectedAriaFileAllocation; public string SelectedAriaFileAllocation { - get { return selectedAriaFileAllocation; } - set { SetProperty(ref selectedAriaFileAllocation, value); } + get => selectedAriaFileAllocation; + set => SetProperty(ref selectedAriaFileAllocation, value); } #endregion - public ViewNetworkViewModel(IEventAggregator eventAggregator) : base(eventAggregator) + public ViewNetworkViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator, dialogService) { #region 属性初始化 + // builtin同时下载数 + MaxCurrentDownloads = new List(); + for (int i = 1; i <= 10; i++) { MaxCurrentDownloads.Add(i); } + + // builtin最大线程数 + Splits = new List(); + for (int i = 1; i <= 10; i++) { Splits.Add(i); } + // Aria的日志等级 AriaLogLevels = new List { @@ -154,7 +227,7 @@ namespace DownKyi.ViewModels.Settings } /// - /// 导航到VideoDetail页面时执行 + /// 导航到页面时执行 /// /// public override void OnNavigatedTo(NavigationContext navigationContext) @@ -163,6 +236,36 @@ namespace DownKyi.ViewModels.Settings isOnNavigatedTo = true; + // 选择下载器 + var downloader = SettingsManager.GetInstance().GetDownloader(); + switch (downloader) + { + case Downloader.NOT_SET: + break; + case Downloader.BUILT_IN: + Builtin = true; + break; + case Downloader.ARIA: + Aria2c = true; + break; + } + + // builtin同时下载数 + SelectedMaxCurrentDownload = SettingsManager.GetInstance().GetMaxCurrentDownloads(); + + // builtin最大线程数 + SelectedSplit = SettingsManager.GetInstance().GetSplit(); + + // 是否开启builtin http代理 + AllowStatus isHttpProxy = SettingsManager.GetInstance().IsHttpProxy(); + IsHttpProxy = isHttpProxy == AllowStatus.YES; + + // builtin的http代理的地址 + HttpProxy = SettingsManager.GetInstance().GetHttpProxy(); + + // builtin的http代理的端口 + HttpProxyPort = SettingsManager.GetInstance().GetHttpProxyListenPort(); + // Aria服务器端口 AriaListenPort = SettingsManager.GetInstance().GetAriaListenPort(); @@ -171,7 +274,7 @@ namespace DownKyi.ViewModels.Settings SelectedAriaLogLevel = ariaLogLevel.ToString("G"); // Aria同时下载数 - SelectedAriaMaxConcurrentDownload = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(); + SelectedAriaMaxConcurrentDownload = SettingsManager.GetInstance().GetMaxCurrentDownloads(); // Aria最大线程数 SelectedAriaSplit = SettingsManager.GetInstance().GetAriaSplit(); @@ -201,6 +304,120 @@ namespace DownKyi.ViewModels.Settings #region 命令申明 + // 下载器选择事件 + private DelegateCommand selectDownloaderCommand; + public DelegateCommand SelectDownloaderCommand => selectDownloaderCommand ?? (selectDownloaderCommand = new DelegateCommand(ExecuteSelectDownloaderCommand)); + + /// + /// 下载器选择事件 + /// + /// + private void ExecuteSelectDownloaderCommand(string parameter) + { + Downloader downloader; + switch (parameter) + { + case "Builtin": + downloader = Downloader.BUILT_IN; + break; + case "Aria2c": + downloader = Downloader.ARIA; + break; + default: + downloader = SettingsManager.GetInstance().GetDownloader(); + break; + } + + bool isSucceed = SettingsManager.GetInstance().SetDownloader(downloader); + PublishTip(isSucceed); + + AlertService alertService = new AlertService(dialogService); + ButtonResult result = alertService.ShowInfo(DictionaryResource.GetString("ConfirmReboot")); + if (result == ButtonResult.OK) + { + System.Windows.Application.Current.Shutdown(); + System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().Location); + } + } + + // builtin同时下载数事件 + private DelegateCommand maxCurrentDownloadsCommand; + public DelegateCommand MaxCurrentDownloadsCommand => maxCurrentDownloadsCommand ?? (maxCurrentDownloadsCommand = new DelegateCommand(ExecuteMaxCurrentDownloadsCommand)); + + /// + /// builtin同时下载数事件 + /// + /// + private void ExecuteMaxCurrentDownloadsCommand(object parameter) + { + SelectedMaxCurrentDownload = (int)parameter; + + bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedMaxCurrentDownload); + PublishTip(isSucceed); + } + + // builtin最大线程数事件 + private DelegateCommand splitsCommand; + public DelegateCommand SplitsCommand => splitsCommand ?? (splitsCommand = new DelegateCommand(ExecuteSplitsCommand)); + + /// + /// builtin最大线程数事件 + /// + /// + private void ExecuteSplitsCommand(object parameter) + { + SelectedSplit = (int)parameter; + + bool isSucceed = SettingsManager.GetInstance().SetSplit(SelectedSplit); + PublishTip(isSucceed); + } + + // 是否开启builtin http代理事件 + private DelegateCommand isHttpProxyCommand; + public DelegateCommand IsHttpProxyCommand => isHttpProxyCommand ?? (isHttpProxyCommand = new DelegateCommand(ExecuteIsHttpProxyCommand)); + + /// + /// 是否开启builtin http代理事件 + /// + private void ExecuteIsHttpProxyCommand() + { + AllowStatus isHttpProxy = IsHttpProxy ? AllowStatus.YES : AllowStatus.NO; + + bool isSucceed = SettingsManager.GetInstance().IsHttpProxy(isHttpProxy); + PublishTip(isSucceed); + } + + // builtin的http代理的地址事件 + private DelegateCommand httpProxyCommand; + public DelegateCommand HttpProxyCommand => httpProxyCommand ?? (httpProxyCommand = new DelegateCommand(ExecuteHttpProxyCommand)); + + /// + /// builtin的http代理的地址事件 + /// + /// + private void ExecuteHttpProxyCommand(string parameter) + { + bool isSucceed = SettingsManager.GetInstance().SetHttpProxy(parameter); + PublishTip(isSucceed); + } + + // builtin的http代理的端口事件 + private DelegateCommand httpProxyPortCommand; + public DelegateCommand HttpProxyPortCommand => httpProxyPortCommand ?? (httpProxyPortCommand = new DelegateCommand(ExecuteHttpProxyPortCommand)); + + /// + /// builtin的http代理的端口事件 + /// + /// + private void ExecuteHttpProxyPortCommand(string parameter) + { + int httpProxyPort = (int)Number.GetInt(parameter); + HttpProxyPort = httpProxyPort; + + bool isSucceed = SettingsManager.GetInstance().SetHttpProxyListenPort(HttpProxyPort); + PublishTip(isSucceed); + } + // Aria服务器端口事件 private DelegateCommand ariaListenPortCommand; public DelegateCommand AriaListenPortCommand => ariaListenPortCommand ?? (ariaListenPortCommand = new DelegateCommand(ExecuteAriaListenPortCommand)); @@ -267,7 +484,7 @@ namespace DownKyi.ViewModels.Settings { SelectedAriaMaxConcurrentDownload = (int)parameter; - bool isSucceed = SettingsManager.GetInstance().SetAriaMaxConcurrentDownloads(SelectedAriaMaxConcurrentDownload); + bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedAriaMaxConcurrentDownload); PublishTip(isSucceed); } diff --git a/src/DownKyi/Views/Settings/ViewNetwork.xaml b/src/DownKyi/Views/Settings/ViewNetwork.xaml index fc64dff..4430c61 100644 --- a/src/DownKyi/Views/Settings/ViewNetwork.xaml +++ b/src/DownKyi/Views/Settings/ViewNetwork.xaml @@ -16,244 +16,425 @@ Text="{DynamicResource Network}" /> - + - - - - - - + Text="{DynamicResource SelectDownloader}" /> - - - - - - - - - + + + + + + - - - - - - - - - - + - - - - - - - - - - + + + + - - + - + Text="{DynamicResource MaxCurrentDownloads}" /> + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + Text="{DynamicResource HttpProxy}" /> + Text="{Binding HttpProxy}"> + Command="{Binding HttpProxyCommand}" + CommandParameter="{Binding ElementName=nameHttpProxy, Path=Text}" /> - + + Text="{DynamicResource HttpProxyPort}" /> + Text="{Binding HttpProxyPort}"> + Command="{Binding HttpProxyPortCommand}" + CommandParameter="{Binding ElementName=nameHttpProxyPort, Path=Text}" /> - - - + - + - + + Text="{DynamicResource AriaServerPort}" /> + Text="{Binding AriaListenPort}"> + Command="{Binding AriaListenPortCommand}" + CommandParameter="{Binding ElementName=nameAriaListenPort, Path=Text}" /> - + + - + - - - - + ItemsSource="{Binding AriaLogLevels}" + SelectedValue="{Binding SelectedAriaLogLevel}"> + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + IsChecked="{Binding IsAriaHttpProxy, Mode=TwoWay}" + Style="{StaticResource CheckBoxStyle}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +