From 7a57fb65f34af4c746507d05b128713c2f0e9366 Mon Sep 17 00:00:00 2001 From: Mateusz Skoczek Date: Thu, 5 May 2022 15:14:26 +0200 Subject: [PATCH] 1.0-dev17 (Subscription page created) --- VDownload.Core/Enums/SubscriptionStatus.cs | 15 ++ .../Exceptions/SubscriptionExistsException.cs | 15 ++ VDownload.Core/Interfaces/IPlaylist.cs | 34 +++ VDownload.Core/Interfaces/IVideo.cs | 48 ++++ VDownload.Core/Services/Sources/Source.cs | 86 +++++++ VDownload.Core/Services/Subscription.cs | 67 ++++++ .../SubscriptionsCollectionManagement.cs | 79 +++++++ VDownload/Strings/en-US/DialogResources.resw | 141 ++++++++++++ VDownload/Views/Sources/MainPage.xaml | 34 +++ VDownload/Views/Sources/MainPage.xaml.cs | 212 ++++++++++++++++++ .../Controls/SubscriptionPanel.xaml | 36 +++ .../Controls/SubscriptionPanel.xaml.cs | 84 +++++++ VDownload/Views/Subscriptions/MainPage.xaml | 44 ++++ .../Views/Subscriptions/MainPage.xaml.cs | 148 ++++++++++++ 14 files changed, 1043 insertions(+) create mode 100644 VDownload.Core/Enums/SubscriptionStatus.cs create mode 100644 VDownload.Core/Exceptions/SubscriptionExistsException.cs create mode 100644 VDownload.Core/Interfaces/IPlaylist.cs create mode 100644 VDownload.Core/Interfaces/IVideo.cs create mode 100644 VDownload.Core/Services/Sources/Source.cs create mode 100644 VDownload.Core/Services/Subscription.cs create mode 100644 VDownload.Core/Services/SubscriptionsCollectionManagement.cs create mode 100644 VDownload/Strings/en-US/DialogResources.resw create mode 100644 VDownload/Views/Sources/MainPage.xaml create mode 100644 VDownload/Views/Sources/MainPage.xaml.cs create mode 100644 VDownload/Views/Subscriptions/Controls/SubscriptionPanel.xaml create mode 100644 VDownload/Views/Subscriptions/Controls/SubscriptionPanel.xaml.cs create mode 100644 VDownload/Views/Subscriptions/MainPage.xaml create mode 100644 VDownload/Views/Subscriptions/MainPage.xaml.cs diff --git a/VDownload.Core/Enums/SubscriptionStatus.cs b/VDownload.Core/Enums/SubscriptionStatus.cs new file mode 100644 index 0000000..41745b0 --- /dev/null +++ b/VDownload.Core/Enums/SubscriptionStatus.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VDownload.Core.Enums +{ + public enum SubscriptionStatus + { + Added, + Loaded, + Ready + } +} diff --git a/VDownload.Core/Exceptions/SubscriptionExistsException.cs b/VDownload.Core/Exceptions/SubscriptionExistsException.cs new file mode 100644 index 0000000..def8743 --- /dev/null +++ b/VDownload.Core/Exceptions/SubscriptionExistsException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VDownload.Core.Exceptions +{ + public class SubscriptionExistsException : Exception + { + public SubscriptionExistsException() { } + public SubscriptionExistsException(string message) : base(message) { } + public SubscriptionExistsException(string message, Exception inner) : base(message, inner) { } + } +} diff --git a/VDownload.Core/Interfaces/IPlaylist.cs b/VDownload.Core/Interfaces/IPlaylist.cs new file mode 100644 index 0000000..46e7c19 --- /dev/null +++ b/VDownload.Core/Interfaces/IPlaylist.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using VDownload.Core.Enums; + +namespace VDownload.Core.Interfaces +{ + public interface IPlaylist + { + #region PROPERTIES + + // PLAYLIST PROPERTIES + string ID { get; } + PlaylistSource Source { get; } + Uri Url { get; } + string Name { get; } + IVideo[] Videos { get; } + + #endregion + + + + #region METHODS + + // GET PLAYLIST METADATA + Task GetMetadataAsync(CancellationToken cancellationToken = default); + + // GET VIDEOS FROM PLAYLIST + Task GetVideosAsync(CancellationToken cancellationToken = default); + Task GetVideosAsync(int numberOfVideos, CancellationToken cancellationToken = default); + + #endregion + } +} diff --git a/VDownload.Core/Interfaces/IVideo.cs b/VDownload.Core/Interfaces/IVideo.cs new file mode 100644 index 0000000..f97b2dd --- /dev/null +++ b/VDownload.Core/Interfaces/IVideo.cs @@ -0,0 +1,48 @@ +using System; +using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; +using VDownload.Core.Enums; +using VDownload.Core.Structs; +using Windows.Storage; + +namespace VDownload.Core.Interfaces +{ + public interface IVideo + { + #region PROPERTIES + + // VIDEO PROPERTIES + VideoSource Source { get; } + string ID { get; } + Uri Url { get; } + Metadata Metadata { get; } + BaseStream[] BaseStreams { get; } + + #endregion + + + + #region METHODS + + // GET VIDEO METADATA + Task GetMetadataAsync(CancellationToken cancellationToken = default); + + // GET VIDEO STREAMS + Task GetStreamsAsync(CancellationToken cancellationToken = default); + + // DOWNLOAD VIDEO + Task DownloadAndTranscodeAsync(StorageFolder downloadingFolder, BaseStream baseStream, MediaFileExtension extension, MediaType mediaType, TimeSpan trimStart, TimeSpan trimEnd, CancellationToken cancellationToken = default); + + #endregion + + + + #region EVENT HANDLERS + + event EventHandler DownloadingProgressChanged; + event EventHandler ProcessingProgressChanged; + + #endregion + } +} diff --git a/VDownload.Core/Services/Sources/Source.cs b/VDownload.Core/Services/Sources/Source.cs new file mode 100644 index 0000000..3712430 --- /dev/null +++ b/VDownload.Core/Services/Sources/Source.cs @@ -0,0 +1,86 @@ +using System.Text.RegularExpressions; +using VDownload.Core.Enums; +using VDownload.Core.Interfaces; + +namespace VDownload.Core.Services.Sources +{ + public static class Source + { + #region CONSTANTS + + // VIDEO SOURCES REGULAR EXPRESSIONS + private static readonly (Regex Regex, VideoSource Type)[] VideoSources = new (Regex Regex, VideoSource Type)[] + { + (new Regex(@"^https://www.twitch.tv/videos/(?\d+)"), VideoSource.TwitchVod), + (new Regex(@"^https://www.twitch.tv/\S+/clip/(?[^?]+)"), VideoSource.TwitchClip), + (new Regex(@"^https://clips.twitch.tv/(?[^?]+)"), VideoSource.TwitchClip), + }; + + // PLAYLIST SOURCES REGULAR EXPRESSIONS + private static readonly (Regex Regex, PlaylistSource Type)[] PlaylistSources = new (Regex Regex, PlaylistSource Type)[] + { + (new Regex(@"^https://www.twitch.tv/(?[^?/]+)"), PlaylistSource.TwitchChannel), + }; + + #endregion + + + + #region METHODS + + // GET VIDEO SOURCE + public static IVideo GetVideo(string url) + { + VideoSource source = VideoSource.Null; + string id = string.Empty; + foreach ((Regex Regex, VideoSource Type) Source in VideoSources) + { + Match sourceMatch = Source.Regex.Match(url); + if (sourceMatch.Success) + { + source = Source.Type; + id = sourceMatch.Groups["id"].Value; + } + } + return GetVideo(source, id); + } + public static IVideo GetVideo(VideoSource source, string id) + { + IVideo videoService = null; + switch (source) + { + case VideoSource.TwitchVod: videoService = new Twitch.Vod(id); break; + case VideoSource.TwitchClip: videoService = new Twitch.Clip(id); break; + } + return videoService; + } + + // GET PLAYLIST SOURCE + public static IPlaylist GetPlaylist(string url) + { + PlaylistSource source = PlaylistSource.Null; + string id = string.Empty; + foreach ((Regex Regex, PlaylistSource Type) Source in PlaylistSources) + { + Match sourceMatch = Source.Regex.Match(url); + if (sourceMatch.Success) + { + source = Source.Type; + id = sourceMatch.Groups["id"].Value; + } + } + return GetPlaylist(source, id); + } + public static IPlaylist GetPlaylist(PlaylistSource source, string id) + { + IPlaylist playlistService = null; + switch (source) + { + case PlaylistSource.TwitchChannel: playlistService = new Twitch.Channel(id); break; + } + return playlistService; + } + + #endregion + } +} diff --git a/VDownload.Core/Services/Subscription.cs b/VDownload.Core/Services/Subscription.cs new file mode 100644 index 0000000..0b65bc3 --- /dev/null +++ b/VDownload.Core/Services/Subscription.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using VDownload.Core.Interfaces; + +namespace VDownload.Core.Services +{ + [Serializable] + public class Subscription + { + #region CONSTRUCTORS + + public Subscription(IPlaylist playlist) + { + Playlist = playlist; + SavedVideos = Playlist.Videos; + } + + #endregion + + + + #region PROPERTIES + + public IPlaylist Playlist { get; private set; } + public IVideo[] SavedVideos { get; private set; } + + #endregion + + + + #region PUBLIC METHODS + + public async Task GetNewVideosAsync() + { + await Playlist.GetVideosAsync(); + return GetUnsavedVideos(); + } + + public async Task GetNewVideosAndUpdateAsync() + { + await Playlist.GetVideosAsync(); + IVideo[] newVideos = GetUnsavedVideos(); + SavedVideos = Playlist.Videos; + return newVideos; + } + + #endregion + + + + #region PRIVATE METHODS + + private IVideo[] GetUnsavedVideos() + { + List newVideos = Playlist.Videos.ToList(); + foreach (IVideo savedVideo in SavedVideos) + { + newVideos.RemoveAll((v) => v.Source == savedVideo.Source && v.ID == savedVideo.ID); + } + return newVideos.ToArray(); + } + + #endregion + } +} diff --git a/VDownload.Core/Services/SubscriptionsCollectionManagement.cs b/VDownload.Core/Services/SubscriptionsCollectionManagement.cs new file mode 100644 index 0000000..8a9f54f --- /dev/null +++ b/VDownload.Core/Services/SubscriptionsCollectionManagement.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading.Tasks; +using VDownload.Core.Exceptions; +using Windows.Storage; + +namespace VDownload.Core.Services +{ + public static class SubscriptionsCollectionManagement + { + #region CONSTANTS + + private static readonly StorageFolder SubscriptionFolderLocation = ApplicationData.Current.LocalFolder; + private static readonly string SubscriptionsFolderName = "Subscriptions"; + private static readonly string SubscriptionFileExtension = "vsub"; + + #endregion + + + + #region PUBLIC METHODS + + public static async Task<(Subscription Subscription, StorageFile SubscriptionFile)[]> GetSubscriptionsAsync() + { + List<(Subscription Subscription, StorageFile SubscriptionFile)> subscriptions = new List<(Subscription Subscription,StorageFile SubscriptionFile)> (); + StorageFolder subscriptionsFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists); + BinaryFormatter formatter = new BinaryFormatter(); + foreach (StorageFile file in await subscriptionsFolder.GetFilesAsync()) + { + if (file.Name.EndsWith(SubscriptionFileExtension)) + { + Stream fileStream = await file.OpenStreamForReadAsync(); + Subscription subscription = (Subscription)formatter.Deserialize(fileStream); + subscriptions.Add((subscription, file)); + } + } + return subscriptions.ToArray(); + } + + public static async Task CreateSubscriptionFileAsync(Subscription subscription) + { + StorageFolder subscriptionsFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(SubscriptionsFolderName, CreationCollisionOption.OpenIfExists); + try + { + StorageFile subscriptionFile = await subscriptionsFolder.CreateFileAsync($"{(int)subscription.Playlist.Source}-{subscription.Playlist.ID}.{SubscriptionFileExtension}", CreationCollisionOption.FailIfExists); + BinaryFormatter formatter = new BinaryFormatter(); + Stream subscriptionFileStream = await subscriptionFile.OpenStreamForWriteAsync(); + formatter.Serialize(subscriptionFileStream, subscription); + return subscriptionFile; + } + catch (Exception ex) + { + if ((uint)ex.HResult == 0x800700B7) + { + throw new SubscriptionExistsException($"Subscription with id \"{(int)subscription.Playlist.Source}-{subscription.Playlist.ID}\" already exists"); + } + else + { + throw; + } + } + } + + public static async Task UpdateSubscriptionFileAsync(Subscription subscription, StorageFile subscriptionFile) + { + BinaryFormatter formatter = new BinaryFormatter(); + Stream subscriptionFileStream = await subscriptionFile.OpenStreamForWriteAsync(); + formatter.Serialize(subscriptionFileStream, subscription); + } + + public static async Task DeleteSubscriptionFileAsync(StorageFile subscriptionFile) => await subscriptionFile.DeleteAsync(StorageDeleteOption.PermanentDelete); + + #endregion + } +} diff --git a/VDownload/Strings/en-US/DialogResources.resw b/VDownload/Strings/en-US/DialogResources.resw new file mode 100644 index 0000000..2cd07aa --- /dev/null +++ b/VDownload/Strings/en-US/DialogResources.resw @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + OK + + + Playlist adding error + + + Unable to connect to servers. Check your internet connection. + + + Playlist not found. Check the URL. + + + This playlist has been already subscribed + + + To be able to subscribe Twitch playlists (Channels), you have to link your Twitch account with VDownload. Go to Sources page to sign in. + + + There is a problem with linked Twitch account. Check Twitch login status in Sources page. + + \ No newline at end of file diff --git a/VDownload/Views/Sources/MainPage.xaml b/VDownload/Views/Sources/MainPage.xaml new file mode 100644 index 0000000..b72952c --- /dev/null +++ b/VDownload/Views/Sources/MainPage.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + +