diff --git a/CRD/Downloader/Crunchyroll.cs b/CRD/Downloader/Crunchyroll.cs index a96b7a5..a4003f8 100644 --- a/CRD/Downloader/Crunchyroll.cs +++ b/CRD/Downloader/Crunchyroll.cs @@ -699,7 +699,7 @@ public class Crunchyroll{ StreamDetailsPop? curStream = null; if (!dlFailed){ - // Validate or adjust options.kstream + options.Kstream = options.Kstream >= 1 && options.Kstream <= streams.Count ? options.Kstream : 1; @@ -741,7 +741,7 @@ public class Crunchyroll{ MPDParsed streamPlaylists = MPDParser.Parse(streamPlaylistsReqResponse.ResponseContent, Languages.FindLang(crLocal), matchedUrl); List streamServers = new List(streamPlaylists.Data.Keys); - options.X = options.X > streamServers.Count ? 1 : options.X; + options.StreamServer = options.StreamServer > streamServers.Count ? 1 : options.StreamServer; if (streamServers.Count == 0){ return new DownloadResponse{ @@ -751,11 +751,11 @@ public class Crunchyroll{ }; } - if (options.X == 0){ - options.X = 1; + if (options.StreamServer == 0){ + options.StreamServer = 1; } - string selectedServer = streamServers[options.X - 1]; + string selectedServer = streamServers[options.StreamServer - 1]; ServerData selectedList = streamPlaylists.Data[selectedServer]; var videos = selectedList.video.Select(item => new VideoItem{ diff --git a/CRD/Utils/Updater/Updater.cs b/CRD/Utils/Updater/Updater.cs new file mode 100644 index 0000000..1fdcf4c --- /dev/null +++ b/CRD/Utils/Updater/Updater.cs @@ -0,0 +1,95 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Net.Http; +using System.Reflection; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace CRD.Utils.Updater; + +public class Updater{ + #region Singelton + + private static Updater? _instance; + private static readonly object Padlock = new(); + + public static Updater Instance{ + get{ + if (_instance == null){ + lock (Padlock){ + if (_instance == null){ + _instance = new Updater(); + } + } + } + + return _instance; + } + } + + #endregion + + private string downloadUrl = ""; + private readonly string tempPath = Path.Combine(Path.GetTempPath(), "Update.zip"); + private readonly string extractPath = Path.Combine(Path.GetTempPath(), "ExtractedUpdate"); + + private readonly string apiEndpoint = "https://api.github.com/repos/Crunchy-DL/Crunchy-Downloader/releases/latest"; + + public async Task CheckForUpdatesAsync(){ + try{ + using (var client = new HttpClient()){ + client.DefaultRequestHeaders.Add("User-Agent", "C# App"); // GitHub API requires a user agent + var response = await client.GetStringAsync(apiEndpoint); + var releaseInfo = JsonConvert.DeserializeObject(response); + + var latestVersion = releaseInfo.tag_name; + downloadUrl = releaseInfo.assets[0].browser_download_url; + + var version = Assembly.GetExecutingAssembly().GetName().Version; + var currentVersion = $"v{version?.Major}.{version?.Minor}.{version?.Build}"; + + + if (latestVersion != currentVersion){ + Console.WriteLine("Update available: " + latestVersion + " - Current Version: " + currentVersion); + return true; + } else{ + Console.WriteLine("No updates available."); + return false; + } + } + } catch (Exception e){ + Console.WriteLine("Failed to get Update information"); + return false; + } + } + + public async Task DownloadAndUpdateAsync(){ + try{ + using (var client = new HttpClient()){ + // Download the zip file + var response = await client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead); + using (var stream = await response.Content.ReadAsStreamAsync()) + using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None)){ + await stream.CopyToAsync(fileStream); + } + + ZipFile.ExtractToDirectory(tempPath, extractPath, true); + + ApplyUpdate(extractPath); + } + } catch (Exception e){ + Console.WriteLine("Failed to get Update"); + } + } + + private void ApplyUpdate(string updateFolder){ + var currentPath = AppDomain.CurrentDomain.BaseDirectory; + var updaterPath = Path.Combine(currentPath, "Updater.exe"); + var arguments = $"\"{currentPath.Substring(0, currentPath.Length - 1)}\" \"{updateFolder}\""; + + System.Diagnostics.Process.Start(updaterPath, arguments); + Environment.Exit(0); + } +} \ No newline at end of file diff --git a/CRD/ViewModels/MainWindowViewModel.cs b/CRD/ViewModels/MainWindowViewModel.cs index d574454..e00a45b 100644 --- a/CRD/ViewModels/MainWindowViewModel.cs +++ b/CRD/ViewModels/MainWindowViewModel.cs @@ -1,29 +1,42 @@ -using Avalonia; +using System; +using System.Net.Http; +using System.Reflection; +using System.Threading.Tasks; +using Avalonia; using Avalonia.Media; using Avalonia.Styling; +using CommunityToolkit.Mvvm.ComponentModel; using CRD.Downloader; +using CRD.Utils.Updater; using FluentAvalonia.Styling; +using Newtonsoft.Json; namespace CRD.ViewModels; public partial class MainWindowViewModel : ViewModelBase{ private readonly FluentAvaloniaTheme _faTheme; + [ObservableProperty] + private bool _updateAvailable = true; + public MainWindowViewModel(){ - _faTheme = App.Current.Styles[0] as FluentAvaloniaTheme; - + Init(); - } + + + public async void Init(){ + UpdateAvailable = await Updater.Instance.CheckForUpdatesAsync(); + await Crunchyroll.Instance.Init(); - + if (Crunchyroll.Instance.CrunOptions.AccentColor != null){ _faTheme.CustomAccentColor = Color.Parse(Crunchyroll.Instance.CrunOptions.AccentColor); } - + if (Crunchyroll.Instance.CrunOptions.Theme == "System"){ _faTheme.PreferSystemTheme = true; } else if (Crunchyroll.Instance.CrunOptions.Theme == "Dark"){ @@ -34,5 +47,4 @@ public partial class MainWindowViewModel : ViewModelBase{ Application.Current.RequestedThemeVariant = ThemeVariant.Light; } } - } \ No newline at end of file diff --git a/CRD/Views/MainWindow.axaml b/CRD/Views/MainWindow.axaml index 75ee72e..cdca202 100644 --- a/CRD/Views/MainWindow.axaml +++ b/CRD/Views/MainWindow.axaml @@ -38,9 +38,8 @@ + Text="{Binding Title, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}" + VerticalAlignment="Center"> @@ -65,6 +64,8 @@ IconSource="Library" /> + navigationStack = new Stack(); + + private object selectedNavVieItem; + public MainWindow(){ InitializeComponent(); - + TitleBar.ExtendsContentIntoTitleBar = true; TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex; @@ -27,7 +37,8 @@ public partial class MainWindow : AppWindow{ //select first element as default var nv = this.FindControl("NavView"); nv.SelectedItem = nv.MenuItems.ElementAt(0); - + selectedNavVieItem = nv.SelectedItem; + MessageBus.Current.Listen() .Subscribe(message => { if (message.Refresh){ @@ -48,11 +59,9 @@ public partial class MainWindow : AppWindow{ MessageBus.Current.Listen() .Subscribe(message => ShowToast(message.Message, message.Type, message.Seconds)); - - - } + public static void ShowError(string message){ var window = new ErrorWindow(); window.SetErrorMessage(message); @@ -63,39 +72,51 @@ public partial class MainWindow : AppWindow{ this.FindControl("Toast").Show(message, type, durationInSeconds); } - + private void NavView_SelectionChanged(object? sender, NavigationViewSelectionChangedEventArgs e){ if (sender is NavigationView navView){ var selectedItem = navView.SelectedItem as NavigationViewItem; if (selectedItem != null){ + switch (selectedItem.Tag){ case "DownloadQueue": - (sender as NavigationView).Content = Activator.CreateInstance(typeof(DownloadsPageViewModel)); + navView.Content = Activator.CreateInstance(typeof(DownloadsPageViewModel)); + selectedNavVieItem = selectedItem; break; case "AddDownload": - (sender as NavigationView).Content = Activator.CreateInstance(typeof(AddDownloadPageViewModel)); + navView.Content = Activator.CreateInstance(typeof(AddDownloadPageViewModel)); + selectedNavVieItem = selectedItem; break; case "Calendar": - (sender as NavigationView).Content = Activator.CreateInstance(typeof(CalendarPageViewModel)); + navView.Content = Activator.CreateInstance(typeof(CalendarPageViewModel)); + selectedNavVieItem = selectedItem; break; case "History": - (sender as NavigationView).Content = Activator.CreateInstance(typeof(HistoryPageViewModel)); + navView.Content = Activator.CreateInstance(typeof(HistoryPageViewModel)); navigationStack.Clear(); - navigationStack.Push((sender as NavigationView).Content); + navigationStack.Push(navView.Content); + selectedNavVieItem = selectedItem; break; case "Account": - (sender as NavigationView).Content = Activator.CreateInstance(typeof(AccountPageViewModel)); + navView.Content = Activator.CreateInstance(typeof(AccountPageViewModel)); + selectedNavVieItem = selectedItem; break; case "Settings": - (sender as NavigationView).Content = Activator.CreateInstance(typeof(SettingsPageViewModel)); + navView.Content = Activator.CreateInstance(typeof(SettingsPageViewModel)); + selectedNavVieItem = selectedItem; + break; + case "UpdateAvailable": + Updater.Instance.DownloadAndUpdateAsync(); break; default: - (sender as NavigationView).Content = Activator.CreateInstance(typeof(DownloadsPageViewModel)); + // (sender as NavigationView).Content = Activator.CreateInstance(typeof(DownloadsPageViewModel)); break; } } } } + + } public class ToastMessage(string message, ToastType type, int i){