From 25041f9d43c8e778ae82d894cbfd921413fcf250 Mon Sep 17 00:00:00 2001 From: Mateusz Skoczek Date: Wed, 2 Mar 2022 01:36:26 +0100 Subject: [PATCH] 1.0-dev9 (Videos list improvements and queue bugs removed) --- README.md | 4 +- VDownload.Core/Services/Sources/Twitch/Vod.cs | 1 - VDownload.Core/Services/TaskId.cs | 38 ++++ VDownload.Core/VDownload.Core.csproj | 1 + VDownload/Assets/Icons/Error.svg | 2 +- VDownload/VDownload.csproj | 2 +- VDownload/Views/Home/HomeMain.xaml | 4 +- VDownload/Views/Home/HomeMain.xaml.cs | 51 ++++- .../Views/Home/HomeVideoAddingPanel.xaml.cs | 1 + VDownload/Views/Home/HomeVideoPanel.xaml | 3 +- VDownload/Views/Home/HomeVideoPanel.xaml.cs | 182 ++++++++++-------- VDownload/Views/Home/HomeVideosList.cs | 48 +---- 12 files changed, 191 insertions(+), 146 deletions(-) create mode 100644 VDownload.Core/Services/TaskId.cs diff --git a/README.md b/README.md index 8a0f015..c9b3a05 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,8 @@ At the moment, the application is in the **early stage of development**. Only so #### App development plans -- **1.0-prerelease1** - with Twitch support only, all video and playlist download options and playlist subscription (Q1/Q2 2022) -- **1.0-prerelease2** - Youtube support and issues from 1.0-prelease1 closed *[Q2 2022]* +- **1.0-prerelease1** - with Twitch support only, all video and playlist download options and playlist subscription *[Q2 2022]* +- **1.0-prerelease2** - Youtube support and issues from 1.0-prelease1 closed *[Q3 2022]* - **1.0** - issues from 1.0-prelease2 closed and support for Polish (and maybe German) language *[Q3 2022]* - **1.?** - new video services and language support and closing issues from previous versions. *[?]* - **2.0** - switch from Universal Windows Platform to Windows App SDK *[when Windows App SDK will be more developed and will offer elements which I use in UWP (e.g. Mica)]* diff --git a/VDownload.Core/Services/Sources/Twitch/Vod.cs b/VDownload.Core/Services/Sources/Twitch/Vod.cs index 260d017..a9ffcaf 100644 --- a/VDownload.Core/Services/Sources/Twitch/Vod.cs +++ b/VDownload.Core/Services/Sources/Twitch/Vod.cs @@ -231,7 +231,6 @@ namespace VDownload.Core.Services.Sources.Twitch // Get playlist string response = await client.DownloadStringTaskAsync(streamUrl); - Debug.WriteLine(response); // Create dictionary List<(Uri ChunkUrl, TimeSpan ChunkDuration)> chunks = new List<(Uri ChunkUrl, TimeSpan ChunkDuration)>(); diff --git a/VDownload.Core/Services/TaskId.cs b/VDownload.Core/Services/TaskId.cs new file mode 100644 index 0000000..91161f8 --- /dev/null +++ b/VDownload.Core/Services/TaskId.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VDownload.Core.Services +{ + public class TaskId + { + // VARIABLES + private static readonly Random Random = new Random(); + private static readonly char[] CharsID = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray(); + private static readonly int LengthID = 10; + private static readonly List UsedIDs = new List(); + + // METHOD + public static string Get() + { + string id; + do + { + id = ""; + while (id.Length < LengthID) + { + id += CharsID[Random.Next(0, CharsID.Length)]; + } + } while (UsedIDs.Contains(id)); + UsedIDs.Add(id); + return id; + } + + public static void Dispose(string id) + { + UsedIDs.Remove(id); + } + } +} diff --git a/VDownload.Core/VDownload.Core.csproj b/VDownload.Core/VDownload.Core.csproj index c7edad2..2c3a4cc 100644 --- a/VDownload.Core/VDownload.Core.csproj +++ b/VDownload.Core/VDownload.Core.csproj @@ -145,6 +145,7 @@ + diff --git a/VDownload/Assets/Icons/Error.svg b/VDownload/Assets/Icons/Error.svg index ea7da51..a3bbfd7 100644 --- a/VDownload/Assets/Icons/Error.svg +++ b/VDownload/Assets/Icons/Error.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/VDownload/VDownload.csproj b/VDownload/VDownload.csproj index 85059bb..15535cb 100644 --- a/VDownload/VDownload.csproj +++ b/VDownload/VDownload.csproj @@ -140,10 +140,10 @@ HomeVideoAddingPanel.xaml - HomeVideoPanel.xaml + MainPage.xaml diff --git a/VDownload/Views/Home/HomeMain.xaml b/VDownload/Views/Home/HomeMain.xaml index b584224..b4233a8 100644 --- a/VDownload/Views/Home/HomeMain.xaml +++ b/VDownload/Views/Home/HomeMain.xaml @@ -59,7 +59,9 @@ - + + + diff --git a/VDownload/Views/Home/HomeMain.xaml.cs b/VDownload/Views/Home/HomeMain.xaml.cs index eb64992..c44ac7b 100644 --- a/VDownload/Views/Home/HomeMain.xaml.cs +++ b/VDownload/Views/Home/HomeMain.xaml.cs @@ -1,17 +1,21 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Threading; +using System.Threading.Tasks; using VDownload.Core.Enums; using VDownload.Core.EventArgsObjects; using VDownload.Core.Exceptions; using VDownload.Core.Interfaces; using VDownload.Core.Services; using Windows.ApplicationModel.Resources; +using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media.Imaging; +using Windows.UI.Xaml.Navigation; namespace VDownload.Views.Home { @@ -34,13 +38,26 @@ namespace VDownload.Views.Home private readonly Microsoft.UI.Xaml.Controls.ProgressRing HomeOptionsBarSearchingStatusProgressRing = new Microsoft.UI.Xaml.Controls.ProgressRing { Width = 15, Height = 15, Margin = new Thickness(5), IsActive = true }; private readonly Image HomeOptionsBarSearchingStatusErrorImage = new Image { Width = 15, Height = 15, Margin = new Thickness(5), Source = (SvgImageSource)new ResourceDictionary { Source = new Uri("ms-appx:///Resources/Icons.xaml") }["ErrorIcon"] }; - CancellationTokenSource SearchingCancellationToken = new CancellationTokenSource(); + // CANCELLATON TOKEN + private CancellationTokenSource SearchingCancellationToken = new CancellationTokenSource(); + + // HOME VIDEOS LIST + private static StackPanel HomeVideosListOldPanel = null; + public static List VideoPanelsList = new List(); #endregion - #region BUTTONS EVENTS + #region EVENT HANDLERS VOIDS + + // ON NAVIGATED TO + protected override async void OnNavigatedTo(NavigationEventArgs e) + { + if (HomeVideosListOldPanel != null) HomeVideosListOldPanel.Children.Clear(); + HomeVideosListOldPanel = HomeVideosList; + foreach (HomeVideoPanel homeVideoPanel in VideoPanelsList) HomeVideosList.Children.Add(homeVideoPanel); + } // ADD VIDEO BUTTON CHECKED private void HomeOptionsBarAddVideoButton_Checked(object sender, RoutedEventArgs e) @@ -159,13 +176,13 @@ namespace VDownload.Views.Home videoPanel.VideoRemovingRequested += (s, a) => { // Remove video panel/task from videos list + VideoPanelsList.Remove(videoPanel); HomeVideosList.Children.Remove(videoPanel); - Home.HomeVideosList.VideoList.Remove(videoPanel); }; // Add video panel/task to videos list HomeVideosList.Children.Add(videoPanel); - Home.HomeVideosList.VideoList.Add(videoPanel); + VideoPanelsList.Add(videoPanel); } @@ -277,12 +294,32 @@ namespace VDownload.Views.Home HomeOptionsBarSearchingStatusControl.Content = null; } + // DOWNLOAD ALL BUTTON CLICKED private async void HomeOptionsBarDownloadAllButton_Click(object sender, RoutedEventArgs e) { - foreach (HomeVideoPanel videoPanel in Home.HomeVideosList.VideoList.Where(video => video.VideoStatus == VideoStatus.Idle)) await videoPanel.Start(); + foreach (HomeVideoPanel videoPanel in HomeVideosList.Children.Where(video => ((HomeVideoPanel)video).VideoStatus == VideoStatus.Idle)) + { + await Task.Delay(50); + videoPanel.Start(); + } } - #endregion -} + #endregion + + + + #region METHODS + + // WAIT IN QUEUE + public static async Task WaitInQueue(CancellationToken token) + { + while (VideoPanelsList.Where(video => video.VideoStatus == VideoStatus.InProgress).Count() >= (int)Config.GetValue("max_active_video_task") && !token.IsCancellationRequested) + { + await Task.Delay(50); + } + } + + #endregion + } } diff --git a/VDownload/Views/Home/HomeVideoAddingPanel.xaml.cs b/VDownload/Views/Home/HomeVideoAddingPanel.xaml.cs index 1a7a374..59e400c 100644 --- a/VDownload/Views/Home/HomeVideoAddingPanel.xaml.cs +++ b/VDownload/Views/Home/HomeVideoAddingPanel.xaml.cs @@ -86,6 +86,7 @@ namespace VDownload.Views.Home { new Regex(@".*)>"), VideoService.Duration }, }; foreach (KeyValuePair template in filenameFormatTemplates) foreach (Match templateMatch in template.Key.Matches(temporaryFilename)) temporaryFilename = temporaryFilename.Replace(templateMatch.Value, template.Value.ToString(templateMatch.Groups["format"].Value, null)); + foreach (char c in System.IO.Path.GetInvalidFileNameChars()) temporaryFilename = temporaryFilename.Replace(c, ' '); HomeVideoAddingFilenameTextBox.Text = temporaryFilename; Filename = temporaryFilename; diff --git a/VDownload/Views/Home/HomeVideoPanel.xaml b/VDownload/Views/Home/HomeVideoPanel.xaml index 95e3872..dd1a4f0 100644 --- a/VDownload/Views/Home/HomeVideoPanel.xaml +++ b/VDownload/Views/Home/HomeVideoPanel.xaml @@ -6,6 +6,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:muxc="using:Microsoft.UI.Xaml.Controls" mc:Ignorable="d" + CornerRadius="{ThemeResource ControlCornerRadius}" d:DesignHeight="150" d:DesignWidth="800"> @@ -20,7 +21,7 @@ - + diff --git a/VDownload/Views/Home/HomeVideoPanel.xaml.cs b/VDownload/Views/Home/HomeVideoPanel.xaml.cs index b463fdc..507a549 100644 --- a/VDownload/Views/Home/HomeVideoPanel.xaml.cs +++ b/VDownload/Views/Home/HomeVideoPanel.xaml.cs @@ -129,109 +129,121 @@ namespace VDownload.Views.Home HomeVideoPanelStateProgressBar.IsIndeterminate = true; // Wait in queue - await HomeVideosList.WaitInQueue(CancellationTokenSource.Token); - - // Get task unique ID - string uniqueID = HomeVideosList.GetUniqueID(); - - // Get temporary folder - StorageFolder tempFolder; - if ((bool)Config.GetValue("custom_temp_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_temp_location")) - tempFolder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("custom_temp_location"); - else - tempFolder = ApplicationData.Current.TemporaryFolder; - tempFolder = await tempFolder.CreateFolderAsync(uniqueID); - - try + await HomeMain.WaitInQueue(CancellationTokenSource.Token); + if (!CancellationTokenSource.IsCancellationRequested) { - // Set cancellation token to throw exception on request - CancellationTokenSource.Token.ThrowIfCancellationRequested(); - // Set video status VideoStatus = VideoStatus.InProgress; - // Start stopwatch - Stopwatch taskStopwatch = Stopwatch.StartNew(); + // Get task unique ID + string uniqueID = TaskId.Get(); - // Set progress event handlers - VideoService.DownloadingStarted += (s, a) => + // Get temporary folder + StorageFolder tempFolder; + if ((bool)Config.GetValue("custom_temp_location") && StorageApplicationPermissions.FutureAccessList.ContainsItem("custom_temp_location")) + tempFolder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("custom_temp_location"); + else + tempFolder = ApplicationData.Current.TemporaryFolder; + tempFolder = await tempFolder.CreateFolderAsync(uniqueID); + + try { - HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"]; - HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextDownloading")} (0%)"; - HomeVideoPanelStateProgressBar.IsIndeterminate = false; - HomeVideoPanelStateProgressBar.Value = 0; - }; - VideoService.DownloadingProgressChanged += (s, a) => + // Set cancellation token to throw exception on request + CancellationTokenSource.Token.ThrowIfCancellationRequested(); + + // Start stopwatch + Stopwatch taskStopwatch = Stopwatch.StartNew(); + + // Set progress event handlers + VideoService.DownloadingStarted += (s, a) => + { + HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"]; + HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextDownloading")} (0%)"; + HomeVideoPanelStateProgressBar.IsIndeterminate = false; + HomeVideoPanelStateProgressBar.Value = 0; + }; + VideoService.DownloadingProgressChanged += (s, a) => + { + HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"]; + HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextDownloading")} ({a.ProgressPercentage}%)"; + HomeVideoPanelStateProgressBar.Value = a.ProgressPercentage; + }; + VideoService.ProcessingStarted += (s, a) => + { + HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"]; + HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextProcessing")} (0%)"; + HomeVideoPanelStateProgressBar.Value = 0; + }; + VideoService.ProcessingProgressChanged += (s, a) => + { + HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"]; + HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextProcessing")} ({a.ProgressPercentage}%)"; + HomeVideoPanelStateProgressBar.Value = a.ProgressPercentage; + }; + + // Request extended session + ExtendedExecutionSession session = new ExtendedExecutionSession { Reason = ExtendedExecutionReason.Unspecified }; + await session.RequestExtensionAsync(); + + // Start task + StorageFile tempOutputFile = await VideoService.DownloadAndTranscodeAsync(tempFolder, Stream, Extension, MediaType, CancellationTokenSource.Token); + + // Dispose session + session.Dispose(); + + // Cancel if requested + CancellationTokenSource.Token.ThrowIfCancellationRequested(); + + // Set state controls + HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateFinalizingIcon"]; + HomeVideoPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextFinalizing"); + HomeVideoPanelStateProgressBar.IsIndeterminate = true; + + // Move to output location + StorageFile outputFile; + Debug.WriteLine($"{Filename}.{Extension.ToString().ToLower()}"); + if (Location != null) outputFile = await Location.CreateFileAsync($"{Filename}.{Extension.ToString().ToLower()}", (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName); + else outputFile = await DownloadsFolder.CreateFileAsync($"{Filename}.{Extension.ToString().ToLower()}", (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName); + await tempOutputFile.MoveAndReplaceAsync(outputFile); + + // Stop stopwatch + taskStopwatch.Stop(); + + // Set state controls + HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDoneIcon"]; + HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextDone")} ({(Math.Floor(taskStopwatch.Elapsed.TotalHours) > 0 ? $"{ Math.Floor(taskStopwatch.Elapsed.TotalHours):0}:" : "")}{taskStopwatch.Elapsed.Minutes:00}:{taskStopwatch.Elapsed.Seconds:00})"; + HomeVideoPanelStateProgressBar.Visibility = Visibility.Collapsed; + + } + catch (OperationCanceledException) { - HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDownloadingIcon"]; - HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextDownloading")} ({a.ProgressPercentage}%)"; - HomeVideoPanelStateProgressBar.Value = a.ProgressPercentage; - }; - VideoService.ProcessingStarted += (s, a) => + // Set state controls + HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateCancelledIcon"]; + HomeVideoPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextCancelled"); + HomeVideoPanelStateProgressBar.Visibility = Visibility.Collapsed; + } + finally { - HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"]; - HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextProcessing")} (0%)"; - HomeVideoPanelStateProgressBar.Value = 0; - }; - VideoService.ProcessingProgressChanged += (s, a) => - { - HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateProcessingIcon"]; - HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextProcessing")} ({a.ProgressPercentage}%)"; - HomeVideoPanelStateProgressBar.Value = a.ProgressPercentage; - }; + // Change icon + HomeVideoPanelStartStopButton.Icon = new SymbolIcon(Symbol.Download); - // Request extended session - ExtendedExecutionSession session = new ExtendedExecutionSession { Reason = ExtendedExecutionReason.Unspecified }; - await session.RequestExtensionAsync(); + // Set video status + VideoStatus = VideoStatus.Idle; - // Start task - StorageFile tempOutputFile = await VideoService.DownloadAndTranscodeAsync(tempFolder, Stream, Extension, MediaType, CancellationTokenSource.Token); - - // Dispose session - session.Dispose(); - - // Cancel if requested - CancellationTokenSource.Token.ThrowIfCancellationRequested(); - - // Set state controls - HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateFinalizingIcon"]; - HomeVideoPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextFinalizing"); - HomeVideoPanelStateProgressBar.IsIndeterminate = true; - - // Move to output location - StorageFile outputFile; - if (Location != null) outputFile = await Location.CreateFileAsync($"{Filename}.{Extension.ToString().ToLower()}", (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName); - else outputFile = await DownloadsFolder.CreateFileAsync($"{Filename}.{Extension.ToString().ToLower()}", (bool)Config.GetValue("replace_output_file_if_exists") ? CreationCollisionOption.ReplaceExisting : CreationCollisionOption.GenerateUniqueName); - await tempOutputFile.MoveAndReplaceAsync(outputFile); - - // Stop stopwatch - taskStopwatch.Stop(); - - // Set state controls - HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateDoneIcon"]; - HomeVideoPanelStateText.Text = $"{ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextDone")} ({(Math.Floor(taskStopwatch.Elapsed.TotalHours) > 0 ? $"{ Math.Floor(taskStopwatch.Elapsed.TotalHours):0}:" : "")}{taskStopwatch.Elapsed.Minutes:00}:{taskStopwatch.Elapsed.Seconds:00})"; - HomeVideoPanelStateProgressBar.Visibility = Visibility.Collapsed; + // Delete temporary files + await tempFolder.DeleteAsync(); + // Dispose unique id + TaskId.Dispose(uniqueID); + } } - catch (OperationCanceledException) + else { // Set state controls HomeVideoPanelStateIcon.Source = (SvgImageSource)IconsRes["StateCancelledIcon"]; HomeVideoPanelStateText.Text = ResourceLoader.GetForCurrentView().GetString("HomeVideoPanelStateTextCancelled"); HomeVideoPanelStateProgressBar.Visibility = Visibility.Collapsed; } - - // Change icon - HomeVideoPanelStartStopButton.Icon = new SymbolIcon(Symbol.Download); - - // Set video status - VideoStatus = VideoStatus.Idle; - - // Delete temporary files - await tempFolder.DeleteAsync(); - - // Dispose unique id - HomeVideosList.DisposeUniqueID(uniqueID); } #endregion diff --git a/VDownload/Views/Home/HomeVideosList.cs b/VDownload/Views/Home/HomeVideosList.cs index 7909c55..0f9a9ef 100644 --- a/VDownload/Views/Home/HomeVideosList.cs +++ b/VDownload/Views/Home/HomeVideosList.cs @@ -1,58 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; +using System.Text; using System.Threading.Tasks; -using VDownload.Core.Services; namespace VDownload.Views.Home { public class HomeVideosList { - // VIDEO OBJECTS LIST - public static List VideoList = new List(); - - // WAIT IN QUEUE - public static async Task WaitInQueue(CancellationToken token) - { - await Task.Run(async () => - { - while (!(VideoList.Where(video => video.VideoStatus == Core.Enums.VideoStatus.InProgress).Count() < (int)Config.GetValue("max_active_video_task")) && !token.IsCancellationRequested) - { - await Task.Delay(50); - } - }); - } - - #region GET UNIQUE ID - - // VARIABLES - private static readonly Random Random = new Random(); - private static readonly char[] CharsID = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray(); - private static readonly int LengthID = 10; - private static readonly List UsedIDs = new List(); - - // METHOD - public static string GetUniqueID() - { - string id; - do - { - id = ""; - while (id.Length < LengthID) - { - id += CharsID[Random.Next(0, CharsID.Length)]; - } - } while (UsedIDs.Contains(id)); - UsedIDs.Add(id); - return id; - } - - public static void DisposeUniqueID(string id) - { - UsedIDs.Remove(id); - } - - #endregion } }