From a933e97f41cc8f1a9f8e655db3cf658a76704b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=B0=E9=98=9F=E7=9A=84=E5=81=B6=E5=83=8F-=E5=B2=9B?= =?UTF-8?q?=E9=A3=8E=E9=85=B1=EF=BC=81?= Date: Mon, 11 Jan 2021 19:43:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E4=B8=8B=E8=BD=BD=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=99=A8=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PixivFSUWP/Data/DownloadManager.cs | 231 +++++++++++++++++++++++++---- PixivFSUWP/DownloadManager.xaml | 9 +- PixivFSUWP/DownloadManager.xaml.cs | 29 +--- PixivFSUWP/DownloadedPage.xaml | 39 +++++ PixivFSUWP/DownloadedPage.xaml.cs | 37 +++++ PixivFSUWP/DownloadingPage.xaml | 15 +- PixivFSUWP/DownloadingPage.xaml.cs | 21 ++- PixivFSUWP/MainPage.xaml | 8 +- PixivFSUWP/MainPage.xaml.cs | 12 +- PixivFSUWP/PixivFSUWP.csproj | 7 + PixivFSUWP/WaterfallPage.xaml.cs | 35 ++--- 11 files changed, 359 insertions(+), 84 deletions(-) create mode 100644 PixivFSUWP/DownloadedPage.xaml create mode 100644 PixivFSUWP/DownloadedPage.xaml.cs diff --git a/PixivFSUWP/Data/DownloadManager.cs b/PixivFSUWP/Data/DownloadManager.cs index 970a619..a1d4ce0 100644 --- a/PixivFSUWP/Data/DownloadManager.cs +++ b/PixivFSUWP/Data/DownloadManager.cs @@ -1,4 +1,5 @@ using Lumia.Imaging.Compositing; + using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -8,30 +9,73 @@ using System.Text; using System.Threading; using System.Threading.Tasks; + using Windows.Storage; +using Windows.UI.Core; namespace PixivFSUWP.Data { //下载完成时的事件参数 public class DownloadCompletedEventArgs : EventArgs { + public DownloadCompletedEventArgs() + { + } + + public DownloadCompletedEventArgs(bool hasError) => HasError = hasError; + public bool HasError { get; set; } } - + public class DownloadJobStatusChangedEventArgs : EventArgs + { + public DownloadJobStatusChangedEventArgs(DownloadJobStatus status) => Status = status; + public DownloadJobStatus Status { get; } + } + public enum DownloadJobStatus + { + Readying, + Running, + Pausing, + Canceled, + Finished, + Faild, + } public class DownloadJob : INotifyPropertyChanged { public string Title { get; } public string Uri { get; } public string FilePath { get; } - + public bool IsPause + { + get => isPause; set + { + isPause = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsPause))); + } + } + public DownloadJobStatus Status + { + get => status; protected set + { + if (status == value) + return; + status = value; + StatusChanged?.Invoke(this, new DownloadJobStatusChangedEventArgs(status)); + } + } private int progress; + private DownloadJobStatus status = DownloadJobStatus.Readying; + private bool isPause = false; + public int Progress { get => progress; private set { progress = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Progress")); + _ = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + CoreDispatcherPriority.High, + () => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Progress)))); } } @@ -48,14 +92,16 @@ public DownloadJob(string Title, string Uri, string FilePath) public bool Downloading { get; private set; } //用于暂停的ManualResetEvent - ManualResetEvent pauseEvent = new ManualResetEvent(true); + readonly ManualResetEvent pauseEvent = new ManualResetEvent(true); //用于取消任务的CancellationTokenSource - CancellationTokenSource tokenSource = new CancellationTokenSource(); + readonly CancellationTokenSource tokenSource = new CancellationTokenSource(); //下载完成时的事件 public event Action DownloadCompleted; + public event Action StatusChanged; + //通知属性更改 public event PropertyChangedEventHandler PropertyChanged; @@ -66,65 +112,92 @@ public async Task Download() { Downloading = true; using (var memStream = await OverAll.DownloadImage(Uri, tokenSource.Token, pauseEvent, async (loaded, length) => - { - await Task.Run(() => { - Progress = (int)(loaded * 100 / length); - }); - })) + await Task.Run(() => + { + Status = DownloadJobStatus.Running; + Progress = (int)(loaded * 100 / length); + }); + })) { - if (tokenSource.IsCancellationRequested) return; - var file = await StorageFile.GetFileFromPathAsync(FilePath); - CachedFileManager.DeferUpdates(file); - using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite)) - { - await memStream.CopyToAsync(fileStream.AsStream()); - } - var result = await CachedFileManager.CompleteUpdatesAsync(file); - if (result == Windows.Storage.Provider.FileUpdateStatus.Complete) - { - DownloadCompleted?.Invoke(this, new DownloadCompletedEventArgs() { HasError = false }); - } - else - { - DownloadCompleted?.Invoke(this, new DownloadCompletedEventArgs() { HasError = true }); - } + if (tokenSource.IsCancellationRequested) + return; + + var result = await WriteToFile(memStream); + Status = DownloadJobStatus.Finished; + _ = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( + CoreDispatcherPriority.High, + () => DownloadCompleted?.Invoke(this, new DownloadCompletedEventArgs(result != Windows.Storage.Provider.FileUpdateStatus.Complete))); + } + Downloading = false; } } + // 文件的写入方法 + protected virtual async Task WriteToFile(Stream memStream) + { + var file = await StorageFile.GetFileFromPathAsync(FilePath); + CachedFileManager.DeferUpdates(file); + using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite)) + await memStream.CopyToAsync(fileStream.AsStream()); + return await CachedFileManager.CompleteUpdatesAsync(file); + } //暂停下载 public void Pause() { + IsPause = true; pauseEvent.Reset(); + Status = DownloadJobStatus.Pausing; } //恢复下载 public void Resume() { + IsPause = false; pauseEvent.Set(); + Status = DownloadJobStatus.Readying; } //取消下载 public void Cancel() { tokenSource.Cancel(); + Status = DownloadJobStatus.Canceled; } } + public class DownloadJobPlus : DownloadJob + { + protected readonly StorageFile File; + + public DownloadJobPlus(string Title, string Uri, StorageFile File) : base(Title, Uri, File.Path) => this.File = File; + protected override async Task WriteToFile(Stream memStream) + { + CachedFileManager.DeferUpdates(File); + using (var fileStream = await File.OpenAsync(FileAccessMode.ReadWrite)) + await memStream.CopyToAsync(fileStream.AsStream()); + return await CachedFileManager.CompleteUpdatesAsync(File); + } + } //静态的下载管理器。应用程序不会有多个下载管理器实例。 public static class DownloadManager { //下载任务列表 public static ObservableCollection DownloadJobs = new ObservableCollection(); + public static ObservableCollection FinishedJobs = new ObservableCollection(); //添加下载任务 public static void NewJob(string Title, string Uri, string FilePath) + => JobBackgroundWork(new DownloadJob(Title, Uri, FilePath)); + public static void NewJob(string Title, string Uri, StorageFile File) + => JobBackgroundWork(new DownloadJobPlus(Title, Uri, File)); + + private static void JobBackgroundWork(DownloadJob job) { - var job = new DownloadJob(Title, Uri, FilePath); job.DownloadCompleted += Job_DownloadCompleted; DownloadJobs.Add(job); - _ = job.Download(); + DownloadProcessManager.Enqueue(job); } //有任务下载完成时的事件 @@ -133,8 +206,11 @@ public static void NewJob(string Title, string Uri, string FilePath) //下载完成时 private static void Job_DownloadCompleted(DownloadJob source, DownloadCompletedEventArgs args) { - DownloadJobs.Remove(source); + System.Diagnostics.Debug.WriteLine("[Job_DownloadCompleted]: " + source.Title); + source.DownloadCompleted -= Job_DownloadCompleted; DownloadCompleted?.Invoke(source.Title, args.HasError); + FinishedJobs.Add(source); + DownloadJobs.Remove(source); } //移除下载任务 @@ -153,5 +229,102 @@ public static void RemoveJob(DownloadJob Job) Job.Cancel(); DownloadJobs.Remove(Job); } + + private static class DownloadProcessManager + { + private static readonly Mutex download_lock = new Mutex();// 同步锁 + private static readonly List downloadingJobs = new List();// 正在下载任务列表 + private static readonly Queue downloadJobs = new Queue();// 下载队列 + private static readonly List waitingJobs = new List();// 暂停任务列表 + private static Thread DownloadProcessThread = null; + + /// + /// 同时下载最大进程数 + /// + public static int MaxJobs { get; set; } = 3; + + static DownloadProcessManager() => new Thread(DownloadProcessDaemon).Start(); + + public static void Enqueue(DownloadJob job) + { + job.StatusChanged += Job_StatusChanged; + downloadJobs.Enqueue(job); + } + + private static void DownloadProcess() + { + System.Diagnostics.Debug.WriteLine("[DownloadProcess]: 启动"); + if (!download_lock.WaitOne(200)) + { + System.Diagnostics.Debug.WriteLine("[DownloadProcess]: 程序正在运行"); + return; + } + try + { + System.Diagnostics.Debug.WriteLine("[DownloadProcess]: 运行中"); + while (true) + { + lock (downloadingJobs) + { + if (downloadingJobs.Count < MaxJobs) + { + if (downloadJobs.Count > 0) + { + var job = downloadJobs.Dequeue(); + downloadingJobs.Add(job); + System.Diagnostics.Debug.WriteLine("[DownloadProcess]: 开始下载: " + job.Title); + _ = job.Download(); + } + } + } + } + } + finally + { + System.Diagnostics.Debug.WriteLine("[DownloadProcess]: 意外结束"); + download_lock.ReleaseMutex(); + } + } + + private static void Job_StatusChanged(DownloadJob job, DownloadJobStatusChangedEventArgs e) + { + System.Diagnostics.Debug.WriteLine($"[Job_StatusChanged]: {job.Title} = {e.Status}"); + switch (e.Status) + { + case DownloadJobStatus.Readying: + waitingJobs.Remove(job); + downloadJobs.Enqueue(job); + break; + case DownloadJobStatus.Running: + break; + case DownloadJobStatus.Pausing: + lock (downloadingJobs) + downloadingJobs.Remove(job); + waitingJobs.Add(job); + break; + case DownloadJobStatus.Canceled: + case DownloadJobStatus.Finished: + job.StatusChanged -= Job_StatusChanged; + lock (downloadingJobs) + downloadingJobs.Remove(job); + break; + default: + break; + } + } + + private static void DownloadProcessDaemon() + { + System.Diagnostics.Debug.WriteLine("[DownloadProcessDaemon]: 启动"); + DownloadProcessThread = new Thread(DownloadProcess); + while (true) + { + if (!DownloadProcessThread.IsAlive) + (DownloadProcessThread = new Thread(DownloadProcess)).Start(); + else + Thread.Sleep(2000); + } + } + } } } diff --git a/PixivFSUWP/DownloadManager.xaml b/PixivFSUWP/DownloadManager.xaml index 544e02b..2218957 100644 --- a/PixivFSUWP/DownloadManager.xaml +++ b/PixivFSUWP/DownloadManager.xaml @@ -10,8 +10,8 @@ - - + @@ -23,5 +23,10 @@ + + + + + diff --git a/PixivFSUWP/DownloadManager.xaml.cs b/PixivFSUWP/DownloadManager.xaml.cs index 6c77ee0..59dc143 100644 --- a/PixivFSUWP/DownloadManager.xaml.cs +++ b/PixivFSUWP/DownloadManager.xaml.cs @@ -13,6 +13,7 @@ using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Animation; using Windows.UI.Xaml.Navigation; // https://go.microsoft.com/fwlink/?LinkId=234238 上介绍了“空白页”项模板 @@ -22,47 +23,25 @@ namespace PixivFSUWP /// /// 可用于自身或导航至 Frame 内部的空白页。 /// - public sealed partial class DownloadManager : Page, IGoBackFlag + public sealed partial class DownloadManager : Page { - bool _backflag = false; - public DownloadManager() { this.InitializeComponent(); - } - - public void SetBackFlag(bool value) - { - _backflag = value; - } - - protected override void OnNavigatedTo(NavigationEventArgs e) - { - base.OnNavigatedTo(e); - OverAll.TheMainPage.SelectNavPlaceholder(OverAll.GetResourceString("DownloadsPlain")); NavControl.SelectedItem = NavControl.MenuItems[1]; } - protected override void OnNavigatedFrom(NavigationEventArgs e) - { - if (!_backflag) - { - Backstack.Default.Push(typeof(DownloadManager), null); - ((Frame.Parent as Grid)?.Parent as MainPage)?.UpdateNavButtonState(); - } - base.OnNavigatedFrom(e); - } - private void NavControl_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) { if (args.SelectedItem == NavControl.MenuItems[1]) { //选择了“下载中” - ContentFrame.Navigate(typeof(DownloadingPage)); + ContentFrame.Navigate(typeof(DownloadingPage),null, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromLeft }); } else { //选择了“下载完毕” + ContentFrame.Navigate(typeof(DownloadedPage),null, new SlideNavigationTransitionInfo() { Effect = SlideNavigationTransitionEffect.FromRight }); } } } diff --git a/PixivFSUWP/DownloadedPage.xaml b/PixivFSUWP/DownloadedPage.xaml new file mode 100644 index 0000000..2042165 --- /dev/null +++ b/PixivFSUWP/DownloadedPage.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PixivFSUWP/DownloadedPage.xaml.cs b/PixivFSUWP/DownloadedPage.xaml.cs new file mode 100644 index 0000000..7bc6fd6 --- /dev/null +++ b/PixivFSUWP/DownloadedPage.xaml.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// https://go.microsoft.com/fwlink/?LinkId=234238 上介绍了“空白页”项模板 + +namespace PixivFSUWP +{ + /// + /// 可用于自身或导航至 Frame 内部的空白页。 + /// + public sealed partial class DownloadedPage : Page + { + public DownloadedPage() + { + this.InitializeComponent(); + //直接进行数据绑定 + lstDownloaded.ItemsSource = Data.DownloadManager.FinishedJobs; + } + + private void RemoveButton_Click(object sender, RoutedEventArgs e) + { + Data.DownloadManager.FinishedJobs.Remove((sender as Button).DataContext as Data.DownloadJob); + } + } +} diff --git a/PixivFSUWP/DownloadingPage.xaml b/PixivFSUWP/DownloadingPage.xaml index 506f24a..f2e6083 100644 --- a/PixivFSUWP/DownloadingPage.xaml +++ b/PixivFSUWP/DownloadingPage.xaml @@ -9,6 +9,10 @@ mc:Ignorable="d"> + + + +