public DownloaderViewModel(DisplayMessageDelegate displayMessage, ShowUrlsDelegate showUrls) { _client = new HttpClient(); DownloadItemsList = new ObservableCollection <DownloaderObjectModel>(); CategoriesList = new ObservableCollection <Category>(); QueueProcessor = new QueueProcessor(Settings.Default.MaxParallelDownloads, QueueProcessor_PropertyChanged); _requestThrottler = new RequestThrottler(AppConstants.RequestThrottlerInterval); CollectionView = CollectionViewSource.GetDefaultView(DownloadItemsList); CollectionView.CurrentChanged += CollectionView_CurrentChanged; _clipboardService = new ClipboardObserver(); _semaphoreMeasuringSpeed = new SemaphoreSlim(1); _semaphoreUpdatingList = new SemaphoreSlim(1); _semaphoreRefreshingView = new SemaphoreSlim(1); _ctsUpdatingList = null; _ctsRefreshView = null; _displayMessage = displayMessage; _lockDownloadItemsList = DownloadItemsList; _lockBytesDownloaded = this.BytesDownloaded; _lockBytesTransferredOverLifetime = Settings.Default.BytesTransferredOverLifetime; _showUrls = showUrls; this.Count = 0; this.DownloadingCount = 0; this.ErroredCount = 0; this.FinishedCount = 0; this.PausedCount = 0; this.QueuedCount = 0; this.ReadyCount = 0; this.VerifyingCount = 0; this.BytesDownloaded = 0; this.Status = "Ready"; this.ProgressReporter = new Progress <long>(value => { Monitor.Enter(_lockBytesDownloaded); try { this.BytesDownloaded += value; } finally { Monitor.Exit(_lockBytesDownloaded); } }); AddCommand = new RelayCommand <object>(Add); StartCommand = new RelayCommand <object>(Start); RemoveFromListCommand = new RelayCommand <object>(RemoveFromList); CancelCommand = new RelayCommand <object>(Cancel); PauseCommand = new RelayCommand <object>(Pause); OpenCommand = new RelayCommand <object>(Open); OpenContainingFolderCommand = new RelayCommand <object>(OpenContainingFolder); StartQueueCommand = new RelayCommand <object>(StartQueue); StopQueueCommand = new RelayCommand <object>(StopQueue); CloseAppCommand = new RelayCommand <object>(CloseApp); CategoryChangedCommand = new RelayCommand <object>(CategoryChanged); OptionsCommand = new RelayCommand <object>(ShowOptions); EnqueueCommand = new RelayCommand <object>(Enqueue); DequeueCommand = new RelayCommand <object>(Dequeue); DeleteFileCommand = new RelayCommand <object>(DeleteFile); RecheckCommand = new RelayCommand <object>(Recheck); RedownloadCommand = new RelayCommand <object>(Redownload); CopyLinkToClipboardCommand = new RelayCommand <object>(CopyLinkToClipboard); ClearFinishedDownloadsCommand = new RelayCommand <object>(ClearFinishedDownloads); CancelBackgroundTaskCommand = new RelayCommand <object>( CancelBackgroundTask, CancelBackgroundTask_CanExecute); CheckForUpdatesCommand = new RelayCommand <object>(CheckForUpdates); ShowHelpTopicsCommand = new RelayCommand <object>(ShowHelpTopics); foreach (Category cat in (Category[])Enum.GetValues(typeof(Category))) { CategoriesList.Add(cat); } // Load last selected category if (string.IsNullOrEmpty(Settings.Default.LastSelectedCatagory)) { SwitchCategory(Category.All); } else { SwitchCategory((Category)Enum.Parse(typeof(Category), Settings.Default.LastSelectedCatagory)); } // Check for updates if (Settings.Default.AutoCheckForUpdates) { Task.Run(async() => await TriggerUpdateCheckAsync(true)); } // Populate history Task.Run(async() => { await _semaphoreUpdatingList.WaitAsync(); _ctsUpdatingList = new CancellationTokenSource(); var ct = _ctsUpdatingList.Token; RaisePropertyChanged(nameof(this.IsBackgroundWorking)); try { if (Directory.Exists(AppPaths.LocalAppData)) { this.Status = "Restoring data..."; RaisePropertyChanged(nameof(this.Status)); } else { return; } SerializableDownloaderObjectModelList source; var xmlReader = new XmlSerializer(typeof(SerializableDownloaderObjectModelList)); using (var streamReader = new StreamReader(AppPaths.DownloadsHistoryFile)) { source = (SerializableDownloaderObjectModelList)xmlReader.Deserialize(streamReader); } var sourceObjects = source.Objects.ToArray(); var finalObjects = new DownloaderObjectModel[sourceObjects.Count()]; var total = sourceObjects.Count(); for (int i = 0; i < sourceObjects.Count(); i++) { if (ct.IsCancellationRequested) { break; } if (sourceObjects[i] == null) { continue; } int progress = (int)((double)(i + 1) / total * 100); this.Progress = progress; this.Status = "Restoring " + (i + 1) + " of " + total + ": " + sourceObjects[i].Url; RaisePropertyChanged(nameof(this.Progress)); RaisePropertyChanged(nameof(this.Status)); var item = new DownloaderObjectModel( ref _client, sourceObjects[i].Url, sourceObjects[i].Destination, sourceObjects[i].IsQueued, sourceObjects[i].TotalBytesToDownload, sourceObjects[i].StatusCode, Download_Created, Download_Verifying, Download_Verified, Download_Started, Download_Stopped, Download_Enqueued, Download_Dequeued, Download_Finished, Download_PropertyChanged, ProgressReporter, ref _requestThrottler); item.SetCreationTime(sourceObjects[i].DateCreated); finalObjects[i] = item; } this.Status = "Listing..."; RaisePropertyChanged(nameof(this.Status)); AddObjects(finalObjects); } catch { return; } finally { _ctsUpdatingList = null; this.Progress = 0; this.Status = "Ready"; RaisePropertyChanged(nameof(this.Progress)); RaisePropertyChanged(nameof(this.Status)); RaisePropertyChanged(nameof(this.IsBackgroundWorking)); _semaphoreUpdatingList.Release(); RefreshCollection(); } }); }
private async Task AddItemsAsync(string destination, bool enqueue, bool start = false, params string[] urls) { await _semaphoreUpdatingList.WaitAsync(); _ctsUpdatingList = new CancellationTokenSource(); var ct = _ctsUpdatingList.Token; RaisePropertyChanged(nameof(this.IsBackgroundWorking)); int total = urls.Count(); int maxParallelDownloads = Settings.Default.MaxParallelDownloads; var items = new DownloaderObjectModel[urls.Count()]; var tasks = new List <Task>(); var forceEnqueue = false; var existingUrls = (from di in DownloadItemsList select di.Url).ToArray(); var existingDestinations = (from di in DownloadItemsList select di.Destination).ToArray(); List <string> skipping = new List <string>(); var wasCanceled = false; if (start && !enqueue && urls.Count() > Settings.Default.MaxParallelDownloads) { forceEnqueue = true; } for (int i = 0; i < total; i++) { int progress = (int)((double)(i + 1) / total * 100); this.Progress = progress; this.Status = "Creating download " + (i + 1) + " of " + total + ": " + urls[i]; RaisePropertyChanged(nameof(this.Status)); RaisePropertyChanged(nameof(this.Progress)); if (existingUrls.Contains(urls[i])) { skipping.Add(urls[i]); continue; } var fileName = CommonFunctions.GetFreshFilename(destination + Path.GetFileName(urls[i])); if (existingDestinations.Contains(fileName)) { skipping.Add(urls[i]); continue; } DownloaderObjectModel item; if (forceEnqueue || enqueue) { item = new DownloaderObjectModel( ref _client, urls[i], fileName, enqueue: true, Download_Created, Download_Verifying, Download_Verified, Download_Started, Download_Stopped, Download_Enqueued, Download_Dequeued, Download_Finished, Download_PropertyChanged, ProgressReporter, ref _requestThrottler); } else { item = new DownloaderObjectModel( ref _client, urls[i], fileName, enqueue: false, Download_Created, Download_Verifying, Download_Verified, Download_Started, Download_Stopped, Download_Enqueued, Download_Dequeued, Download_Finished, Download_PropertyChanged, ProgressReporter, ref _requestThrottler); if (start) { tasks.Add(item.StartAsync()); } } items[i] = item; if (ct.IsCancellationRequested) { wasCanceled = true; break; } } if (!wasCanceled) { this.Status = "Listing..."; RaisePropertyChanged(nameof(this.Status)); AddObjects(items); } _ctsUpdatingList = null; this.Progress = 0; this.Status = "Ready"; RaisePropertyChanged(nameof(this.Status)); RaisePropertyChanged(nameof(this.Progress)); RaisePropertyChanged(nameof(this.IsBackgroundWorking)); _semaphoreUpdatingList.Release(); RefreshCollection(); if (!wasCanceled) { if (skipping.Count > 0) { _showUrls( skipping, "Duplicate Entries", "The following URLs were not added because they are already in the list:"); } if ((enqueue && start) || forceEnqueue) { await QueueProcessor.StartAsync(); } else { await Task.WhenAll(tasks); } } }