public void Progress_manager_with_multiple_operations_aggregates_their_progress_according_to_their_weight() { // Arrange var manager = new ProgressManager(); var operationA = manager.CreateOperation(2); var operationB = manager.CreateOperation(8); // Assert manager.Progress.Should().Be(0); manager.IsActive.Should().BeTrue(); manager.Operations.Should().BeEquivalentTo(operationA, operationB); // Act operationA.Report(0.8); operationB.Report(0.4); // Assert manager.Progress.Should().BeApproximately(0.48, 10e-5); manager.IsActive.Should().BeTrue(); manager.Operations.Should().BeEquivalentTo(operationA, operationB); // Act operationA.Dispose(); operationB.Dispose(); // Assert manager.Progress.Should().Be(0); manager.IsActive.Should().BeFalse(); manager.Operations.Should().BeEmpty(); }
public void Progress_operation_correctly_implements_INotifyPropertyChanged() { // Arrange var manager = new ProgressManager(); var operation = manager.CreateOperation(); var eventValues = new List <double>(); operation.PropertyChanged += (_, args) => { if (args.PropertyName == nameof(manager.Progress)) { eventValues.Add(manager.Progress); } }; // Act operation.Report(0.1); operation.Report(0.3); operation.Dispose(); // Assert eventValues.Should().Equal(0.1, 0.3, 0); }
public async void PopulateGuildsAndChannels() { using var operation = ProgressManager.CreateOperation(); try { var tokenValue = TokenValue?.Trim('"', ' '); if (string.IsNullOrWhiteSpace(tokenValue)) { return; } var token = new AuthToken( IsBotToken ? AuthTokenType.Bot : AuthTokenType.User, tokenValue ); _settingsService.LastToken = token; var discord = new DiscordClient(token); var guildChannelMap = new Dictionary <Guild, IReadOnlyList <Channel> >(); await foreach (var guild in discord.GetUserGuildsAsync()) { guildChannelMap[guild] = await discord.GetGuildChannelsAsync(guild.Id); } GuildChannelMap = guildChannelMap; SelectedGuild = guildChannelMap.Keys.FirstOrDefault(); } catch (DiscordChatExporterException ex) when(!ex.IsCritical) { Notifications.Enqueue(ex.Message.TrimEnd('.')); } }
public void Start() { if (!CanStart) { return; } IsActive = true; IsSuccessful = false; IsCanceled = false; IsFailed = false; Task.Run(async() => { // Create cancellation token source _cancellationTokenSource = new CancellationTokenSource(); // Create progress operation ProgressOperation = ProgressManager.CreateOperation(); try { // If download option is not set - get the best download option if (DownloadOption == null) { DownloadOption = await _downloadService.GetBestDownloadOptionAsync(Video.Id, Format); } await _downloadService.DownloadVideoAsync(DownloadOption, FilePath, ProgressOperation, _cancellationTokenSource.Token); if (_settingsService.ShouldInjectTags) { await _taggingService.InjectTagsAsync(Video, Format, FilePath, _cancellationTokenSource.Token); } if (SubtitleOption != null && SubtitleOption.ClosedCaptionTrackInfos.Any()) { await _downloadService.DownloadSubtitleAsync(SubtitleOption, FilePath); } IsSuccessful = true; } catch (OperationCanceledException) { IsCanceled = true; } catch (Exception ex) { IsFailed = true; FailReason = ex.Message; } finally { IsActive = false; _cancellationTokenSource.Dispose(); ProgressOperation.Dispose(); } }); }
public void Progress_manager_with_a_single_operation_returns_its_progress() { // Arrange var manager = new ProgressManager(); var operation = manager.CreateOperation(); // Assert manager.Progress.Should().Be(0); manager.IsActive.Should().BeTrue(); manager.Operations.Should().BeEquivalentTo(operation); // Act operation.Report(0.44); // Assert manager.Progress.Should().BeApproximately(0.44, 10e-5); manager.IsActive.Should().BeTrue(); manager.Operations.Should().BeEquivalentTo(operation); // Act operation.Dispose(); // Assert manager.Progress.Should().Be(0); manager.IsActive.Should().BeFalse(); manager.Operations.Should().BeEmpty(); }
// This is async void on purpose because this is supposed to be always ran in background private async void EnqueueDownload(DownloadViewModel download) { // Cancel an existing download for this file path to prevent writing to the same file Downloads.FirstOrDefault(d => d.FilePath == download.FilePath)?.Cancel(); // Add to list Downloads.Add(download); // Create progress operation download.ProgressOperation = ProgressManager.CreateOperation(); // If download option is not set - get the best download option if (download.DownloadOption == null) { download.DownloadOption = await _downloadService.GetBestDownloadOptionAsync(download.Video.Id, download.Format); } // Download try { await _downloadService.DownloadVideoAsync(download.Video.Id, download.FilePath, download.DownloadOption, download.ProgressOperation, download.CancellationToken); } catch (OperationCanceledException) { // Ignore cancellations } // Mark download as completed download.MarkAsCompleted(); }
public void ProgressManager_SingleOperation_Test() { // Create manager var manager = new ProgressManager(); // Create operation var operation = manager.CreateOperation(); // Assert initial state after creating operation Assert.That(manager.Progress, Is.Zero); Assert.That(manager.IsActive, Is.True); Assert.That(manager.Operations.Count, Is.EqualTo(1)); // Report new progress const double newProgress = 0.5; operation.Report(newProgress); // Assert intermediate state Assert.That(manager.Progress, Is.EqualTo(newProgress)); Assert.That(manager.IsActive, Is.True); Assert.That(manager.Operations.Count, Is.EqualTo(1)); // Report completion operation.Dispose(); // Assert final state Assert.That(manager.Progress, Is.Zero); Assert.That(manager.IsActive, Is.False); Assert.That(manager.Operations.Count, Is.Zero); }
public void Start() { if (!CanStart) { return; } IsActive = true; IsSuccessful = false; IsCanceled = false; IsFailed = false; Task.Run(async() => { // Create cancellation token source _cancellationTokenSource = new CancellationTokenSource(); // Create progress operation ProgressOperation = ProgressManager.CreateOperation(); try { // daca nu sunt setate obtiuni de descarcare se foloseste setarea de baza // descarca video if (DownloadOption == null) { DownloadOption = await _downloadService.GetBestDownloadOptionAsync(Video.Id, Format); } await _downloadService.DownloadVideoAsync(DownloadOption, FilePath, ProgressOperation, _cancellationTokenSource.Token); if (_settingsService.ShouldInjectTags) { await _taggingService.InjectTagsAsync(Video, Format, FilePath, _cancellationTokenSource.Token); } IsSuccessful = true; } catch (OperationCanceledException) { IsCanceled = true; } catch (Exception ex) { IsFailed = true; FailReason = ex.Message; } finally { IsActive = false; _cancellationTokenSource.Dispose(); ProgressOperation.Dispose(); } }); }
public void Start() { if (!CanStart) { return; } Task.Run(async() => { // Create cancellation token source _cancellationTokenSource = new CancellationTokenSource(); // Create progress operation ProgressOperation = ProgressManager.CreateOperation(); try { IsSuccessful = false; IsCanceled = false; IsFailed = false; IsActive = true; // If download option is not set - get the best download option if (DownloadOption == null) { DownloadOption = await _downloadService.GetBestDownloadOptionAsync(Video.Id, Format); } // Download await _downloadService.DownloadVideoAsync(DownloadOption, FilePath, ProgressOperation, _cancellationTokenSource.Token); IsSuccessful = true; } catch (OperationCanceledException) { IsCanceled = true; } catch (Exception ex) { IsFailed = true; FailReason = ex.Message; } finally { IsActive = false; _cancellationTokenSource.Dispose(); ProgressOperation.Dispose(); } }); }
public void Progress_operation_cannot_report_progress_after_it_has_been_disposed() { // Arrange var manager = new ProgressManager(); var operation = manager.CreateOperation(); operation.Report(0.1); operation.Report(0.3); operation.Dispose(); // Act & Assert Assert.Throws <InvalidOperationException>(() => operation.Report(1)); }
public void StartOperation(double weight) { // Start a task that simulates some work and reports progress Task.Run(async() => { using var operation = ProgressManager.CreateOperation(weight); for (var i = 0; i < 100; i++) { // Delay execution to simulate activity await Task.Delay(TimeSpan.FromSeconds(0.1)); // Report new progress operation.Report((i + 1) / 100.0); } }); }
public void ProgressManager_NotifyPropertyChanged_Test() { // Create manager var manager = new ProgressManager(); // Subscribe to event var eventTriggerCount = 0; manager.PropertyChanged += (sender, args) => eventTriggerCount++; // Create operation var operation = manager.CreateOperation(); // Invoke changes operation.Report(0.1); operation.Report(0.3); operation.Dispose(); // Assert that the event was triggered accordingly Assert.That(eventTriggerCount, Is.GreaterThanOrEqualTo(3)); }
public async void PopulateRecommendations() { // Create progress operation var operation = ProgressManager.CreateOperation(); try { // Validate settings if (_settingsService.UserId.IsNullOrWhiteSpace() || _settingsService.ApiKey.IsNullOrWhiteSpace()) { Notifications.Enqueue("Not configured – set username and API key in settings", "OPEN", ShowSettings); return; } // Get recommendations Recommendations = await _recommendationService.GetRecommendationsAsync(operation); // Persist recommendations in cache _cacheService.Store("LastRecommendations", Recommendations); // Notify completion Notifications.Enqueue("Recommendations updated"); } catch (RecommendationsUnavailableException) { Notifications.Enqueue("Recommendations unavailable – no top plays set in selected game mode"); } catch (HttpErrorStatusCodeException ex) when(ex.StatusCode == HttpStatusCode.Unauthorized) { Notifications.Enqueue("Unauthorized – make sure API key is valid"); } finally { // Dispose progress operation operation.Dispose(); } }
public async void PopulateGuildsAndChannels() { // Create progress operation var operation = ProgressManager.CreateOperation(); try { // Sanitize token TokenValue = TokenValue.Trim('"'); // Create token var token = new AuthToken( IsBotToken ? AuthTokenType.Bot : AuthTokenType.User, TokenValue); // Save token _settingsService.LastToken = token; // Prepare available guild list var availableGuilds = new List <GuildViewModel>(); // Get direct messages { // Get fake guild var guild = Guild.DirectMessages; // Get channels var channels = await _dataService.GetDirectMessageChannelsAsync(token); // Create channel view models var channelViewModels = new List <ChannelViewModel>(); foreach (var channel in channels) { // Get fake category var category = channel.Type == ChannelType.DirectTextChat ? "Private" : "Group"; // Create channel view model var channelViewModel = _viewModelFactory.CreateChannelViewModel(channel, category); // Add to list channelViewModels.Add(channelViewModel); } // Create guild view model var guildViewModel = _viewModelFactory.CreateGuildViewModel(guild, channelViewModels.OrderBy(c => c.Category) .ThenBy(c => c.Model.Name) .ToArray()); // Add to list availableGuilds.Add(guildViewModel); } // Get guilds var guilds = await _dataService.GetUserGuildsAsync(token); foreach (var guild in guilds) { // Get channels var channels = await _dataService.GetGuildChannelsAsync(token, guild.Id); // Get category channels var categoryChannels = channels.Where(c => c.Type == ChannelType.Category).ToArray(); // Get text channels var textChannels = channels.Where(c => c.Type == ChannelType.GuildTextChat).ToArray(); // Create channel view models var channelViewModels = new List <ChannelViewModel>(); foreach (var channel in textChannels) { // Get category var category = categoryChannels.FirstOrDefault(c => c.Id == channel.ParentId)?.Name; // Create channel view model var channelViewModel = _viewModelFactory.CreateChannelViewModel(channel, category); // Add to list channelViewModels.Add(channelViewModel); } // Create guild view model var guildViewModel = _viewModelFactory.CreateGuildViewModel(guild, channelViewModels.OrderBy(c => c.Category) .ThenBy(c => c.Model.Name) .ToArray()); // Add to list availableGuilds.Add(guildViewModel); } // Update available guild list AvailableGuilds = availableGuilds; // Pre-select first guild SelectedGuild = AvailableGuilds.FirstOrDefault(); } catch (HttpErrorStatusCodeException ex) when(ex.StatusCode == HttpStatusCode.Unauthorized) { Notifications.Enqueue("Unauthorized – make sure the token is valid"); } catch (HttpErrorStatusCodeException ex) when(ex.StatusCode == HttpStatusCode.Forbidden) { Notifications.Enqueue("Forbidden – account may be locked by 2FA"); } finally { // Dispose progress operation operation.Dispose(); } }
public void Start() { if (!CanStart) { return; } IsActive = true; IsSuccessful = false; IsCanceled = false; IsFailed = false; Task.Run(async() => { _cancellationTokenSource = new CancellationTokenSource(); ProgressOperation = ProgressManager?.CreateOperation(); try { // If download option is not set - get the best download option VideoOption ??= await _downloadService.TryGetBestVideoDownloadOptionAsync( Video.Id, Format, QualityPreference ); // It's possible that video has no streams if (VideoOption == null) { throw new InvalidOperationException($"Video '{Video.Id}' contains no streams."); } await _downloadService.DownloadAsync( VideoOption, SubtitleOption, FilePath, ProgressOperation, _cancellationTokenSource.Token ); if (_settingsService.ShouldInjectTags) { await _taggingService.InjectTagsAsync( Video, Format, FilePath, _cancellationTokenSource.Token ); } IsSuccessful = true; } catch (OperationCanceledException) { IsCanceled = true; } catch (Exception ex) { IsFailed = true; // Short error message for expected errors, full for unexpected FailReason = ex is YoutubeExplodeException ? ex.Message : ex.ToString(); } finally { IsActive = false; _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; ProgressOperation?.Dispose(); } }); }
public async void ProcessQuery() { var operation = ProgressManager.CreateOperation(); try { // Split query into separate lines and parse them var parsedQueries = _queryService.ParseMultilineQuery(Query); // Execute separate queries var executedQueries = await _queryService.ExecuteQueriesAsync(parsedQueries, operation); // Extract videos and other details var videos = executedQueries.SelectMany(q => q.Videos).Distinct(v => v.Id).ToArray(); var dialogTitle = executedQueries.Count == 1 ? executedQueries.Single().Title : "Multiple queries"; // If no videos were found - tell the user if (videos.Length <= 0) { // Create dialog var dialog = _viewModelFactory.CreateMessageBoxViewModel("Nothing found", "Couldn't find any videos based on the query or URL you provided"); // Show dialog await _dialogManager.ShowDialogAsync(dialog); } // If only one video was found - show download setup for single video else if (videos.Length == 1) { // Get single video var video = videos.Single(); // Get download options var downloadOptions = await _downloadService.GetDownloadOptionsAsync(video.Id); // Create dialog var dialog = _viewModelFactory.CreateDownloadSingleSetupViewModel(dialogTitle, video, downloadOptions); // Show dialog and get download var download = await _dialogManager.ShowDialogAsync(dialog); // If canceled - return if (download == null) { return; } // Enqueue download EnqueueAndStartDownload(download); } // If multiple videos were found - show download setup for multiple videos else { // Create dialog var dialog = _viewModelFactory.CreateDownloadMultipleSetupViewModel(dialogTitle, videos); // Preselect all videos if none of the videos come from a search query if (executedQueries.All(q => q.Query.Type != QueryType.Search)) { dialog.SelectedVideos = dialog.AvailableVideos; } // Show dialog and get downloads var downloads = await _dialogManager.ShowDialogAsync(dialog); // If canceled - return if (downloads == null) { return; } // Enqueue downloads foreach (var download in downloads) { EnqueueAndStartDownload(download); } } } catch (Exception ex) { // Create dialog var dialog = _viewModelFactory.CreateMessageBoxViewModel("Error", ex.Message); // Show dialog await _dialogManager.ShowDialogAsync(dialog); } finally { // Dispose progress operation operation.Dispose(); } }
public async void ProcessQuery() { // Small operation weight to not offset any existing download operations var operation = ProgressManager.CreateOperation(0.01); IsBusy = true; try { var parsedQueries = _queryService.ParseMultilineQuery(Query !); var executedQueries = await _queryService.ExecuteQueriesAsync(parsedQueries, operation); var videos = executedQueries.SelectMany(q => q.Videos).Distinct(v => v.Id).ToArray(); var dialogTitle = executedQueries.Count == 1 ? executedQueries.Single().Title : "Multiple queries"; // No videos found if (videos.Length <= 0) { var dialog = _viewModelFactory.CreateMessageBoxViewModel( "Nothing found", "Couldn't find any videos based on the query or URL you provided" ); await _dialogManager.ShowDialogAsync(dialog); } // Single video else if (videos.Length == 1) { var video = videos.Single(); var downloadOptions = await _downloadService.GetVideoDownloadOptionsAsync(video.Id); var subtitleOptions = await _downloadService.GetSubtitleDownloadOptionsAsync(video.Id); var dialog = _viewModelFactory.CreateDownloadSingleSetupViewModel( dialogTitle, video, downloadOptions, subtitleOptions ); var download = await _dialogManager.ShowDialogAsync(dialog); if (download == null) // generics + NRTs issue { return; } EnqueueDownload(download); } // Multiple videos else { var dialog = _viewModelFactory.CreateDownloadMultipleSetupViewModel( dialogTitle, videos ); // Preselect all videos if none of the videos come from a search query if (executedQueries.All(q => q.Query.Kind != QueryKind.Search)) { dialog.SelectedVideos = dialog.AvailableVideos; } var downloads = await _dialogManager.ShowDialogAsync(dialog); if (downloads == null) // generics + NRTs issue { return; } foreach (var download in downloads) { EnqueueDownload(download); } } } catch (Exception ex) { var dialog = _viewModelFactory.CreateMessageBoxViewModel( "Error", // Short error message for expected errors, full for unexpected ex is YoutubeExplodeException ? ex.Message : ex.ToString() ); await _dialogManager.ShowDialogAsync(dialog); } finally { operation.Dispose(); IsBusy = false; } }
public async void ProcessQuery() { var operation = ProgressManager.CreateOperation(0.01); IsBusy = true; try { var parsedQueries = _queryService.ParseMultilineQuery(Query !); var executedQueries = await _queryService.ExecuteQueriesAsync(parsedQueries, operation); var videos = executedQueries.SelectMany(q => q.Videos).Distinct(v => v.Id).ToArray(); var dialogTitle = executedQueries.Count == 1 ? executedQueries.Single().Title : "Multiple queries"; if (videos.Length <= 0) { // Create dialog var dialog = _viewModelFactory.CreateMessageBoxViewModel("Nothing found", "Couldn't find any videos based on the query or URL you provided"); await _dialogManager.ShowDialogAsync(dialog); } else if (videos.Length == 1) { var video = videos.Single(); var downloadOptions = await _downloadService.GetDownloadOptionsAsync(video.Id); var dialog = _viewModelFactory.CreateDownloadSingleSetupViewModel(dialogTitle, video, downloadOptions); var download = await _dialogManager.ShowDialogAsync(dialog); if (download == null) { return; } EnqueueAndStartDownload(download); } else { var dialog = _viewModelFactory.CreateDownloadMultipleSetupViewModel(dialogTitle, videos); if (executedQueries.All(q => q.Query.Type != QueryType.Search)) { dialog.SelectedVideos = dialog.AvailableVideos; } var downloads = await _dialogManager.ShowDialogAsync(dialog); if (downloads == null) { return; } foreach (var download in downloads) { EnqueueAndStartDownload(download); } } } catch (Exception ex) { var dialog = _viewModelFactory.CreateMessageBoxViewModel("Error", ex.Message); await _dialogManager.ShowDialogAsync(dialog); } finally { operation.Dispose(); IsBusy = false; } }