From 7b021940c355d05ddedafc748625593869c6f675 Mon Sep 17 00:00:00 2001 From: Elwador <75888166+Elwador@users.noreply.github.com> Date: Tue, 11 Jun 2024 23:58:44 +0200 Subject: [PATCH] Add - Added setting to use sonarr numbering instead of crunchyroll numbering Chg - History "Refresh All" now shows in more detail what is being updated Chg - Calendar design changes - highlight "Premiere" episodes Fix - Crash caused by using sonarr Fix - Memory leak caused by progress bar Fix - Sometimes it downloaded Hardsub because it didn't know the language --- CRD/App.axaml | 10 +- CRD/Downloader/CrEpisode.cs | 4 +- CRD/Downloader/Crunchyroll.cs | 55 ++-- CRD/Downloader/History.cs | 146 ++++++---- CRD/Utils/Files/CfgManager.cs | 16 +- CRD/Utils/HLS/HLSDownloader.cs | 10 +- CRD/Utils/Helpers.cs | 44 ++- CRD/Utils/Sonarr/SonarrClient.cs | 37 ++- CRD/Utils/Structs/CalendarStructs.cs | 5 +- CRD/Utils/Structs/CrunchyStreamData.cs | 2 +- CRD/Utils/Structs/EpisodeStructs.cs | 271 +++++++++++------- .../UI/UiValueConverterCalendarBackground.cs | 21 ++ CRD/ViewModels/HistoryPageViewModel.cs | 34 ++- CRD/ViewModels/SettingsPageViewModel.cs | 21 +- CRD/Views/CalendarPageView.axaml | 122 ++++---- CRD/Views/HistoryPageView.axaml | 32 +-- CRD/Views/HistoryPageView.axaml.cs | 6 +- CRD/Views/SettingsPageView.axaml | 10 +- CRD/Views/SettingsPageView.axaml.cs | 9 + 19 files changed, 546 insertions(+), 309 deletions(-) create mode 100644 CRD/Utils/UI/UiValueConverterCalendarBackground.cs diff --git a/CRD/App.axaml b/CRD/App.axaml index c6ae445..00654fb 100644 --- a/CRD/App.axaml +++ b/CRD/App.axaml @@ -4,16 +4,14 @@ xmlns:sty="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia" x:Class="CRD.App" RequestedThemeVariant="Dark"> - + - + - - - + - + diff --git a/CRD/Downloader/CrEpisode.cs b/CRD/Downloader/CrEpisode.cs index b37c315..9306e0b 100644 --- a/CRD/Downloader/CrEpisode.cs +++ b/CRD/Downloader/CrEpisode.cs @@ -47,7 +47,7 @@ public class CrEpisode(){ } - public CrunchySeriesList EpisodeData(CrunchyEpisodeList dlEpisodes){ + public async Task EpisodeData(CrunchyEpisodeList dlEpisodes){ bool serieshasversions = true; Dictionary episodes = new Dictionary(); @@ -56,7 +56,7 @@ public class CrEpisode(){ foreach (var episode in dlEpisodes.Data){ if (crunInstance.CrunOptions.History){ - crunInstance.CrHistory.UpdateWithEpisode(episode); + await crunInstance.CrHistory.UpdateWithEpisode(episode); } // Prepare the episode array diff --git a/CRD/Downloader/Crunchyroll.cs b/CRD/Downloader/Crunchyroll.cs index 6a0d6d7..67d5a51 100644 --- a/CRD/Downloader/Crunchyroll.cs +++ b/CRD/Downloader/Crunchyroll.cs @@ -164,7 +164,6 @@ public class Crunchyroll{ RefreshSonarr(); } - calendarLanguage = new(){ { "en-us", "https://www.crunchyroll.com/simulcastcalendar" }, { "es", "https://www.crunchyroll.com/es/simulcastcalendar" }, @@ -181,8 +180,8 @@ public class Crunchyroll{ } public async void RefreshSonarr(){ - if (CrunOptions.SonarrProperties != null && !string.IsNullOrEmpty(CrunOptions.SonarrProperties.ApiKey)){ - SonarrClient.Instance.SetApiUrl(); + await SonarrClient.Instance.CheckSonarrSettings(); + if (CrunOptions.SonarrProperties is{ SonarrEnabled: true }){ SonarrSeries = await SonarrClient.Instance.GetSeries(); CrHistory.MatchHistorySeriesWithSonarr(true); } @@ -221,8 +220,6 @@ public class Crunchyroll{ var dayName = day.SelectSingleNode(".//h1[@class='day-name']/time")?.InnerText.Trim(); - // Console.WriteLine($"Day: {dayName}, Date: {date}"); - CalendarDay calDay = new CalendarDay(); calDay.CalendarEpisodes = new List(); @@ -242,14 +239,10 @@ public class Crunchyroll{ var episodeLink = episode.SelectSingleNode(".//a[contains(@class, 'available-episode-link')]")?.GetAttributeValue("href", "No link"); var thumbnailUrl = episode.SelectSingleNode(".//img[contains(@class, 'thumbnail')]")?.GetAttributeValue("src", "No image"); var isPremiumOnly = episode.SelectSingleNode(".//svg[contains(@class, 'premium-flag')]") != null; + var isPremiere = episode.SelectSingleNode(".//div[contains(@class, 'premiere-flag')]") != null; var seasonName = episode.SelectSingleNode(".//a[contains(@class, 'js-season-name-link')]")?.SelectSingleNode(".//cite[@itemprop='name']")?.InnerText.Trim(); var episodeNumber = episode.SelectSingleNode(".//meta[contains(@itemprop, 'episodeNumber')]")?.GetAttributeValue("content", "?"); - // Console.WriteLine($" Time: {episodeTime} (Has Passed: {hasPassed}), Episode: {episodeName}"); - // Console.WriteLine($" Season Link: {seasonLink}"); - // Console.WriteLine($" Episode Link: {episodeLink}"); - // Console.WriteLine($" Thumbnail URL: {thumbnailUrl}"); - CalendarEpisode calEpisode = new CalendarEpisode(); calEpisode.DateTime = episodeTime; @@ -259,6 +252,7 @@ public class Crunchyroll{ calEpisode.EpisodeUrl = episodeLink; calEpisode.ThumbnailUrl = thumbnailUrl; calEpisode.IsPremiumOnly = isPremiumOnly; + calEpisode.IsPremiere = isPremiere; calEpisode.SeasonName = seasonName; calEpisode.EpisodeNumber = episodeNumber; @@ -267,7 +261,6 @@ public class Crunchyroll{ } week.CalendarDays.Add(calDay); - // Console.WriteLine(); } } else{ Console.WriteLine("No days found in the HTML document."); @@ -291,10 +284,19 @@ public class Crunchyroll{ return; } - var sList = CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL); + var sList = await CrEpisode.EpisodeData((CrunchyEpisodeList)episodeL); var selected = CrEpisode.EpisodeMeta(sList.Data, dubLang); var metas = selected.Values.ToList(); + foreach (var crunchyEpMeta in metas){ + if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){ + var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId); + if (historyEpisode != null){ + crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber; + crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber; + } + } + Queue.Add(crunchyEpMeta); } @@ -310,6 +312,14 @@ public class Crunchyroll{ foreach (var crunchyEpMeta in selected.Values.ToList()){ if (crunchyEpMeta.Data?.First().Playback != null){ + if (CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){ + var historyEpisode = CrHistory.GetHistoryEpisode(crunchyEpMeta.ShowId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId); + if (historyEpisode != null){ + crunchyEpMeta.EpisodeNumber = historyEpisode.SonarrEpisodeNumber; + crunchyEpMeta.Season = historyEpisode.SonarrSeasonNumber; + } + } + Queue.Add(crunchyEpMeta); } else{ failed = true; @@ -362,7 +372,7 @@ public class Crunchyroll{ }; Queue.Refresh(); - + await MuxStreams(res.Data, new CrunchyMuxOptions{ FfmpegOptions = options.FfmpegOptions, @@ -434,7 +444,7 @@ public class Crunchyroll{ subt.Fonts = downloadedMedia.Fonts; subsList.Add(subt); } - + if (File.Exists($"{filename}.{(muxToMp3 ? "mp3" : options.Mp4 ? "mp4" : "mkv")}") && !string.IsNullOrEmpty(filename)){ string newFilePath = filename; int counter = 1; @@ -446,7 +456,7 @@ public class Crunchyroll{ filename = newFilePath; } - + var merger = new Merger(new MergerOptions{ OnlyVid = hasAudioStreams ? data.Where(a => a.Type == DownloadMediaType.Video).Select(a => new MergerInput{ Language = a.Lang, Path = a.Path ?? string.Empty }).ToList() : new List(), @@ -559,10 +569,11 @@ public class Crunchyroll{ }; } + bool dlFailed = false; bool dlVideoOnce = false; - if (data.Data != null) + if (data.Data != null){ foreach (CrunchyEpMetaData epMeta in data.Data){ Console.WriteLine($"Requesting: [{epMeta.MediaId}] {mediaName}"); @@ -1065,6 +1076,7 @@ public class Crunchyroll{ if (File.Exists($"{tsFile}.video.m4s")){ File.Delete($"{tsFile}.video.m4s"); } + File.Move($"{tempTsFile}.video.m4s", $"{tsFile}.video.m4s"); } catch (IOException ex){ Console.WriteLine($"An error occurred: {ex.Message}"); @@ -1111,6 +1123,7 @@ public class Crunchyroll{ if (File.Exists($"{tsFile}.audio.m4s")){ File.Delete($"{tsFile}.audio.m4s"); } + File.Move($"{tempTsFile}.audio.m4s", $"{tsFile}.audio.m4s"); } catch (IOException ex){ Console.WriteLine($"An error occurred: {ex.Message}"); @@ -1226,7 +1239,7 @@ public class Crunchyroll{ await Task.Delay(options.Waittime); } - + } // variables.Add(new Variable("height", quality == 0 ? plQuality.Last().RESOLUTION.Height : plQuality[quality - 1].RESOLUTION.Height, false)); // variables.Add(new Variable("width", quality == 0 ? plQuality.Last().RESOLUTION.Width : plQuality[quality - 1].RESOLUTION.Width, false)); @@ -1476,7 +1489,7 @@ public class Crunchyroll{ var stream = hardsub.Value; derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{ Url = stream.Url, - HardsubLocale = Helpers.ConvertStringToLocale(stream.Hlang) + HardsubLocale = stream.Hlang }; } @@ -1552,7 +1565,7 @@ public class Crunchyroll{ playbackRequestResponse = await HttpClientReq.Instance.SendHttpRequest(playbackRequest); if (playbackRequestResponse.IsOk){ - // temppbData = Helpers.Deserialize(playbackRequestResponse22.ResponseContent, SettingsJsonSerializerSettings) ?? + // var temppbData2 = Helpers.Deserialize(playbackRequestResponse22.ResponseContent, SettingsJsonSerializerSettings) ?? // new PlaybackData{ Total = 0, Data = new List>>() }; temppbData = new PlaybackData{ Total = 0, Data = new List>>() }; @@ -1569,7 +1582,7 @@ public class Crunchyroll{ var stream = hardsub.Value; derivedPlayCrunchyStreams[hardsub.Key] = new StreamDetails{ Url = stream.Url, - HardsubLocale = Helpers.ConvertStringToLocale(stream.Hlang) + HardsubLocale = stream.Hlang }; } @@ -1578,7 +1591,7 @@ public class Crunchyroll{ HardsubLocale = Locale.DefaulT }; - if (temppbData.Data != null) temppbData.Data[0]["drm_adaptive_dash"] = derivedPlayCrunchyStreams; + if (temppbData.Data != null) temppbData.Data[0]["drm_adaptive_switch_dash"] = derivedPlayCrunchyStreams; temppbData.Meta = new PlaybackMeta(){ AudioLocale = playStream.AudioLocale, Versions = playStream.Versions, Bifs = new List{ playStream.Bifs }, MediaId = mediaId }; temppbData.Meta.Subtitles = new Subtitles(); diff --git a/CRD/Downloader/History.cs b/CRD/Downloader/History.cs index 8bad1f6..886f44a 100644 --- a/CRD/Downloader/History.cs +++ b/CRD/Downloader/History.cs @@ -22,7 +22,7 @@ public class History(){ public async Task UpdateSeries(string seriesId, string? seasonId){ await crunInstance.CrAuth.RefreshToken(true); - CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja"); + CrSeriesSearch? parsedSeries = await crunInstance.CrSeries.ParseSeriesById(seriesId, "ja"); if (parsedSeries == null){ Console.WriteLine("Parse Data Invalid"); @@ -79,11 +79,30 @@ public class History(){ MessageBus.Current.SendMessage(new ToastMessage($"Couldn't update download History", ToastType.Warning, 1)); } + + public HistoryEpisode? GetHistoryEpisode(string? seriesId, string? seasonId, string episodeId){ + var historySeries = crunInstance.HistoryList.FirstOrDefault(series => series.SeriesId == seriesId); + + if (historySeries != null){ + var historySeason = historySeries.Seasons.Find(s => s.SeasonId == seasonId); + + if (historySeason != null){ + var historyEpisode = historySeason.EpisodesList.Find(e => e.EpisodeId == episodeId); + + if (historyEpisode != null){ + + return historyEpisode; + } + } + } + + return null; + } - public async void UpdateWithEpisode(CrunchyEpisode episodeParam){ + public async Task UpdateWithEpisode(CrunchyEpisode episodeParam){ var episode = episodeParam; - + if (episode.Versions != null){ var version = episode.Versions.Find(a => a.Original); if (version.AudioLocale != episode.AudioLocale){ @@ -137,23 +156,23 @@ public class History(){ historySeries.UpdateNewEpisodes(); } else{ - var newHistorySeries = new HistorySeries{ + historySeries = new HistorySeries{ SeriesTitle = episode.SeriesTitle, SeriesId = episode.SeriesId, Seasons =[], }; - crunInstance.HistoryList.Add(newHistorySeries); + crunInstance.HistoryList.Add(historySeries); var newSeason = NewHistorySeason(episode); var series = await crunInstance.CrSeries.SeriesById(seriesId); if (series?.Data != null){ - newHistorySeries.SeriesDescription = series.Data.First().Description; - newHistorySeries.ThumbnailImageUrl = GetSeriesThumbnail(series); - newHistorySeries.SeriesTitle = series.Data.First().Title; + historySeries.SeriesDescription = series.Data.First().Description; + historySeries.ThumbnailImageUrl = GetSeriesThumbnail(series); + historySeries.SeriesTitle = series.Data.First().Title; } - newHistorySeries.Seasons.Add(newSeason); - newHistorySeries.UpdateNewEpisodes(); + historySeries.Seasons.Add(newSeason); + historySeries.UpdateNewEpisodes(); } var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList(); @@ -163,10 +182,11 @@ public class History(){ } MatchHistorySeriesWithSonarr(false); + await MatchHistoryEpisodesWithSonarr(false,historySeries); UpdateHistoryFile(); } - public async void UpdateWithSeasonData(CrunchyEpisodeList seasonData){ + public async Task UpdateWithSeasonData(CrunchyEpisodeList seasonData){ if (seasonData.Data != null){ var firstEpisode = seasonData.Data.First(); var seriesId = firstEpisode.SeriesId; @@ -203,7 +223,6 @@ public class History(){ historyEpisode.EpisodeId = crunchyEpisode.Id; historyEpisode.Episode = crunchyEpisode.Episode; historyEpisode.EpisodeSeasonNum = Helpers.ExtractNumberAfterS(crunchyEpisode.Identifier) ?? crunchyEpisode.SeasonNumber + ""; - } } @@ -220,12 +239,12 @@ public class History(){ historySeries.UpdateNewEpisodes(); } else{ - var newHistorySeries = new HistorySeries{ + historySeries = new HistorySeries{ SeriesTitle = firstEpisode.SeriesTitle, SeriesId = firstEpisode.SeriesId, Seasons =[], }; - crunInstance.HistoryList.Add(newHistorySeries); + crunInstance.HistoryList.Add(historySeries); var newSeason = NewHistorySeason(seasonData, firstEpisode); @@ -233,26 +252,26 @@ public class History(){ var series = await crunInstance.CrSeries.SeriesById(seriesId); if (series?.Data != null){ - newHistorySeries.SeriesDescription = series.Data.First().Description; - newHistorySeries.ThumbnailImageUrl = GetSeriesThumbnail(series); - newHistorySeries.SeriesTitle = series.Data.First().Title; + historySeries.SeriesDescription = series.Data.First().Description; + historySeries.ThumbnailImageUrl = GetSeriesThumbnail(series); + historySeries.SeriesTitle = series.Data.First().Title; } - newHistorySeries.Seasons.Add(newSeason); + historySeries.Seasons.Add(newSeason); - newHistorySeries.UpdateNewEpisodes(); + historySeries.UpdateNewEpisodes(); + } + var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList(); + crunInstance.HistoryList.Clear(); + foreach (var item in sortedList){ + crunInstance.HistoryList.Add(item); } - } - var sortedList = crunInstance.HistoryList.OrderBy(item => item.SeriesTitle).ToList(); - crunInstance.HistoryList.Clear(); - foreach (var item in sortedList){ - crunInstance.HistoryList.Add(item); + MatchHistorySeriesWithSonarr(false); + await MatchHistoryEpisodesWithSonarr(false,historySeries); + UpdateHistoryFile(); } - - MatchHistorySeriesWithSonarr(false); - UpdateHistoryFile(); } private string GetSeriesThumbnail(CrSeriesBase series){ @@ -322,6 +341,11 @@ public class History(){ } public void MatchHistorySeriesWithSonarr(bool updateAll){ + + if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){ + return; + } + foreach (var historySeries in crunInstance.HistoryList){ if (updateAll || string.IsNullOrEmpty(historySeries.SonarrSeriesId)){ var sonarrSeries = FindClosestMatch(historySeries.SeriesTitle); @@ -334,7 +358,11 @@ public class History(){ } } - public async void MatchHistoryEpisodesWithSonarr(bool updateAll, HistorySeries historySeries){ + public async Task MatchHistoryEpisodesWithSonarr(bool updateAll, HistorySeries historySeries){ + if (crunInstance.CrunOptions.SonarrProperties is{ SonarrEnabled: false }){ + return; + } + if (!string.IsNullOrEmpty(historySeries.SonarrSeriesId)){ var episodes = await SonarrClient.Instance.GetEpisodes(int.Parse(historySeries.SonarrSeriesId)); @@ -345,7 +373,7 @@ public class History(){ } List failedEpisodes =[]; - + foreach (var historyEpisode in allHistoryEpisodes){ if (updateAll || string.IsNullOrEmpty(historyEpisode.SonarrEpisodeId)){ var episode = FindClosestMatchEpisodes(episodes, historyEpisode.EpisodeTitle); @@ -372,8 +400,9 @@ public class History(){ historyEpisode.SonarrSeasonNumber = episode.SeasonNumber + ""; episodes.Remove(episode); } else{ - var episode1 = episodes.Find(ele => !string.IsNullOrEmpty(historyEpisode.EpisodeDescription) && !string.IsNullOrEmpty(ele.Overview) && Helpers.CalculateCosineSimilarity(ele.Overview, historyEpisode.EpisodeDescription) > 0.8); - + var episode1 = episodes.Find(ele => + !string.IsNullOrEmpty(historyEpisode.EpisodeDescription) && !string.IsNullOrEmpty(ele.Overview) && Helpers.CalculateCosineSimilarity(ele.Overview, historyEpisode.EpisodeDescription) > 0.8); + if (episode1 != null){ historyEpisode.SonarrEpisodeId = episode1.Id + ""; historyEpisode.SonarrEpisodeNumber = episode1.EpisodeNumber + ""; @@ -395,11 +424,7 @@ public class History(){ } } } - } - - - } } @@ -442,7 +467,8 @@ public class History(){ return 1.0 - (double)distance / Math.Max(source.Length, target.Length); } - private int LevenshteinDistance(string source, string target){ + + public int LevenshteinDistance(string source, string target){ if (string.IsNullOrEmpty(source)){ return string.IsNullOrEmpty(target) ? 0 : target.Length; } @@ -454,35 +480,32 @@ public class History(){ int n = source.Length; int m = target.Length; - // Create two work arrays of integer distances. - int[] previousDistances = new int[m + 1]; - int[] currentDistances = new int[m + 1]; + // Use a single array for distances. + int[] distances = new int[m + 1]; - // Initialize the previous distance array. + // Initialize the distance array. for (int j = 0; j <= m; j++){ - previousDistances[j] = j; + distances[j] = j; } for (int i = 1; i <= n; i++){ - // Initialize the current distance array. - currentDistances[0] = i; + int previousDiagonal = distances[0]; + distances[0] = i; for (int j = 1; j <= m; j++){ + int previousDistance = distances[j]; int cost = (target[j - 1] == source[i - 1]) ? 0 : 1; - currentDistances[j] = Math.Min( - Math.Min(currentDistances[j - 1] + 1, previousDistances[j] + 1), - previousDistances[j - 1] + cost); - } + distances[j] = Math.Min( + Math.Min(distances[j - 1] + 1, distances[j] + 1), + previousDiagonal + cost); - // Swap the arrays for the next iteration. - var temp = previousDistances; - previousDistances = currentDistances; - currentDistances = temp; + previousDiagonal = previousDistance; + } } - // The final distance is in the previous distance array. - return previousDistances[m]; + // The final distance is in the last cell. + return distances[m]; } } @@ -530,6 +553,9 @@ public class HistorySeries : INotifyPropertyChanged{ public event PropertyChangedEventHandler? PropertyChanged; + [JsonIgnore] + public bool FetchingData{ get; set; } + public async Task LoadImage(){ try{ using (var client = new HttpClient()){ @@ -574,6 +600,11 @@ public class HistorySeries : INotifyPropertyChanged{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewEpisodes))); } + public void SetFetchingData(){ + FetchingData = true; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData))); + } + public async Task AddNewMissingToDownloads(){ bool foundWatched = false; @@ -600,7 +631,12 @@ public class HistorySeries : INotifyPropertyChanged{ } public async Task FetchData(string? seasonId){ + FetchingData = true; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData))); await Crunchyroll.Instance.CrHistory.UpdateSeries(SeriesId, seasonId); + FetchingData = false; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FetchingData))); + Crunchyroll.Instance.CrHistory.MatchHistoryEpisodesWithSonarr(false,this); } } @@ -654,10 +690,10 @@ public partial class HistoryEpisode : INotifyPropertyChanged{ [JsonProperty("episode_cr_episode_number")] public string? Episode{ get; set; } - + [JsonProperty("episode_cr_episode_description")] public string? EpisodeDescription{ get; set; } - + [JsonProperty("episode_cr_season_number")] public string? EpisodeSeasonNum{ get; set; } diff --git a/CRD/Utils/Files/CfgManager.cs b/CRD/Utils/Files/CfgManager.cs index 1c25349..5bc0fde 100644 --- a/CRD/Utils/Files/CfgManager.cs +++ b/CRD/Utils/Files/CfgManager.cs @@ -146,12 +146,9 @@ public class CfgManager{ } private static object fileLock = new object(); - + public static void WriteJsonToFile(string pathToFile, object obj){ try{ - // Serialize the object to a JSON string. - var jsonString = JsonConvert.SerializeObject(obj, Formatting.Indented); - // Check if the directory exists; if not, create it. string directoryPath = Path.GetDirectoryName(pathToFile); if (!Directory.Exists(directoryPath)){ @@ -159,14 +156,19 @@ public class CfgManager{ } lock (fileLock){ - // Write the JSON string to file. Creates the file if it does not exist. - File.WriteAllText(pathToFile, jsonString); + // Write the JSON string to file using a streaming approach. + using (var fileStream = new FileStream(pathToFile, FileMode.Create, FileAccess.Write)) + using (var streamWriter = new StreamWriter(fileStream)) + using (var jsonWriter = new JsonTextWriter(streamWriter){ Formatting = Formatting.Indented }){ + var serializer = new JsonSerializer(); + serializer.Serialize(jsonWriter, obj); + } } } catch (Exception ex){ Console.WriteLine($"An error occurred: {ex.Message}"); } } - + public static bool CheckIfFileExists(string filePath){ string dirPath = Path.GetDirectoryName(filePath) ?? string.Empty; diff --git a/CRD/Utils/HLS/HLSDownloader.cs b/CRD/Utils/HLS/HLSDownloader.cs index 07b65da..7f0d66e 100644 --- a/CRD/Utils/HLS/HLSDownloader.cs +++ b/CRD/Utils/HLS/HLSDownloader.cs @@ -421,7 +421,7 @@ public class HlsDownloader{ try{ response = await HttpClientReq.Instance.GetHttpClient().SendAsync(request, HttpCompletionOption.ResponseContentRead); response.EnsureSuccessStatusCode(); - return await response.Content.ReadAsByteArrayAsync(); + return await ReadContentAsByteArrayAsync(response.Content); } catch (HttpRequestException ex){ // Log retry attempts string partType = isKey ? "Key" : "Part"; @@ -437,6 +437,14 @@ public class HlsDownloader{ return null; // Should not reach here } + private async Task ReadContentAsByteArrayAsync(HttpContent content){ + using (var memoryStream = new MemoryStream()) + using (var contentStream = await content.ReadAsStreamAsync()){ + await contentStream.CopyToAsync(memoryStream, 81920); + return memoryStream.ToArray(); + } + } + private HttpRequestMessage CloneHttpRequestMessage(HttpRequestMessage originalRequest){ var clone = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri){ Content = originalRequest.Content?.Clone(), diff --git a/CRD/Utils/Helpers.cs b/CRD/Utils/Helpers.cs index b8f8a52..312c226 100644 --- a/CRD/Utils/Helpers.cs +++ b/CRD/Utils/Helpers.cs @@ -38,7 +38,11 @@ public class Helpers{ } } - return Locale.DefaulT; // Return default if not found + if (string.IsNullOrEmpty(value)){ + return Locale.DefaulT; + } + + return Locale.Unknown; // Return default if not found } public static string GenerateSessionId(){ @@ -88,22 +92,44 @@ public class Helpers{ return CosineSimilarity(vector1, vector2); } - private static Dictionary ComputeWordFrequency(string text){ - var wordFrequency = new Dictionary(); - var words = text.Split(new[]{ ' ', ',', '.', ';', ':', '-', '_', '\'' }, StringSplitOptions.RemoveEmptyEntries); + private static readonly char[] Delimiters ={ ' ', ',', '.', ';', ':', '-', '_', '\'' }; + + public static Dictionary ComputeWordFrequency(string text){ + var wordFrequency = new Dictionary(StringComparer.OrdinalIgnoreCase); + var words = SplitText(text); foreach (var word in words){ - var lowerWord = word.ToLower(); - if (!wordFrequency.ContainsKey(lowerWord)){ - wordFrequency[lowerWord] = 0; + if (wordFrequency.TryGetValue(word, out double count)){ + wordFrequency[word] = count + 1; + } else{ + wordFrequency[word] = 1; } - - wordFrequency[lowerWord]++; } return wordFrequency; } + private static List SplitText(string text){ + var words = new List(); + int start = 0; + for (int i = 0; i < text.Length; i++){ + if (Array.IndexOf(Delimiters, text[i]) >= 0){ + if (i > start){ + words.Add(text.Substring(start, i - start)); + } + + start = i + 1; + } + } + + if (start < text.Length){ + words.Add(text.Substring(start)); + } + + return words; + } + + private static double CosineSimilarity(Dictionary vector1, Dictionary vector2){ var intersection = vector1.Keys.Intersect(vector2.Keys); diff --git a/CRD/Utils/Sonarr/SonarrClient.cs b/CRD/Utils/Sonarr/SonarrClient.cs index 3e8496b..8a578cb 100644 --- a/CRD/Utils/Sonarr/SonarrClient.cs +++ b/CRD/Utils/Sonarr/SonarrClient.cs @@ -16,7 +16,7 @@ using Newtonsoft.Json; namespace CRD.Utils.Sonarr; public class SonarrClient{ - private string apiUrl; + private string? apiUrl; private HttpClient httpClient; @@ -50,11 +50,39 @@ public class SonarrClient{ public void SetApiUrl(){ if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) properties = Crunchyroll.Instance.CrunOptions.SonarrProperties; - if (properties != null){ - apiUrl = $"http{(properties.UseSsl ? "s" : "")}://{properties.Host}:{properties.Port}{(properties.UrlBase ?? "")}/api"; + if (properties != null ){ + apiUrl = $"http{(properties.UseSsl ? "s" : "")}://{(!string.IsNullOrEmpty(properties.Host) ? properties.Host : "localhost")}:{properties.Port}{(properties.UrlBase ?? "")}/api"; } } + public async Task CheckSonarrSettings(){ + + SetApiUrl(); + + if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null){ + Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled = false; + } else{ + Crunchyroll.Instance.CrunOptions.SonarrProperties = new SonarrProperties(){SonarrEnabled = false}; + return; + } + + Debug.WriteLine($"[DEBUG] [SonarrClient.CheckSonarrSettings] Endpoint URL: '{apiUrl}'"); + + var request = CreateRequestMessage($"{apiUrl}", HttpMethod.Get); + HttpResponseMessage response; + + try{ + response = await httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled = true; + } catch (Exception ex){ + Debug.WriteLine($"[ERROR] [SonarrClient.GetJson] Endpoint URL: '{apiUrl}', {ex}"); + if (Crunchyroll.Instance.CrunOptions.SonarrProperties != null) Crunchyroll.Instance.CrunOptions.SonarrProperties.SonarrEnabled = false; + } + + + } + public async Task> GetSeries(){ var json = await GetJson($"/v3/series{(true ? $"?includeSeasonImages={true}" : "")}"); @@ -151,4 +179,7 @@ public class SonarrProperties(){ public bool UseSsl{ get; set; } public string? UrlBase{ get; set; } + + public bool UseSonarrNumbering{ get; set; } + public bool SonarrEnabled{ get; set; } } \ No newline at end of file diff --git a/CRD/Utils/Structs/CalendarStructs.cs b/CRD/Utils/Structs/CalendarStructs.cs index 6b520ed..a6178bc 100644 --- a/CRD/Utils/Structs/CalendarStructs.cs +++ b/CRD/Utils/Structs/CalendarStructs.cs @@ -7,8 +7,6 @@ using System.Threading.Tasks; using Avalonia.Media.Imaging; using CommunityToolkit.Mvvm.Input; using CRD.Downloader; -using CRD.Views; -using ReactiveUI; namespace CRD.Utils.Structs; @@ -36,6 +34,7 @@ public partial class CalendarEpisode : INotifyPropertyChanged{ public string? EpisodeNumber{ get; set; } public bool IsPremiumOnly{ get; set; } + public bool IsPremiere{ get; set; } public string? SeasonName{ get; set; } @@ -51,7 +50,7 @@ public partial class CalendarEpisode : INotifyPropertyChanged{ Crunchyroll.Instance.AddEpisodeToQue(id, locale, Crunchyroll.Instance.CrunOptions.DubLang); } } - + public async Task LoadImage(){ try{ using (var client = new HttpClient()){ diff --git a/CRD/Utils/Structs/CrunchyStreamData.cs b/CRD/Utils/Structs/CrunchyStreamData.cs index 335faa6..74cd577 100644 --- a/CRD/Utils/Structs/CrunchyStreamData.cs +++ b/CRD/Utils/Structs/CrunchyStreamData.cs @@ -25,7 +25,7 @@ public class Caption{ } public class HardSub{ - public string? Hlang{ get; set; } + public Locale? Hlang{ get; set; } public string? Url{ get; set; } public string? Quality{ get; set; } } diff --git a/CRD/Utils/Structs/EpisodeStructs.cs b/CRD/Utils/Structs/EpisodeStructs.cs index 49c170d..c1cacb3 100644 --- a/CRD/Utils/Structs/EpisodeStructs.cs +++ b/CRD/Utils/Structs/EpisodeStructs.cs @@ -11,24 +11,55 @@ public struct CrunchyEpisodeList{ } public struct CrunchyEpisode{ - [JsonProperty("next_episode_id")] public string NextEpisodeId{ get; set; } - [JsonProperty("series_id")] public string SeriesId{ get; set; } - [JsonProperty("season_number")] public int SeasonNumber{ get; set; } - [JsonProperty("next_episode_title")] public string NextEpisodeTitle{ get; set; } - [JsonProperty("availability_notes")] public string AvailabilityNotes{ get; set; } - [JsonProperty("duration_ms")] public int DurationMs{ get; set; } - [JsonProperty("series_slug_title")] public string SeriesSlugTitle{ get; set; } - [JsonProperty("series_title")] public string SeriesTitle{ get; set; } - [JsonProperty("is_dubbed")] public bool IsDubbed{ get; set; } - public List? Versions{ get; set; } // Assume Version is defined elsewhere. + [JsonProperty("next_episode_id")] + public string NextEpisodeId{ get; set; } + + [JsonProperty("series_id")] + public string SeriesId{ get; set; } + + [JsonProperty("season_number")] + public int SeasonNumber{ get; set; } + + [JsonProperty("next_episode_title")] + public string NextEpisodeTitle{ get; set; } + + [JsonProperty("availability_notes")] + public string AvailabilityNotes{ get; set; } + + [JsonProperty("duration_ms")] + public int DurationMs{ get; set; } + + [JsonProperty("series_slug_title")] + public string SeriesSlugTitle{ get; set; } + + [JsonProperty("series_title")] + public string SeriesTitle{ get; set; } + + [JsonProperty("is_dubbed")] + public bool IsDubbed{ get; set; } + + public List? Versions{ get; set; } public string Identifier{ get; set; } - [JsonProperty("sequence_number")] public float SequenceNumber{ get; set; } - [JsonProperty("eligible_region")] public string EligibleRegion{ get; set; } - [JsonProperty("availability_starts")] public DateTime? AvailabilityStarts{ get; set; } - public Images? Images{ get; set; } // Assume Images is a struct or class you've defined elsewhere. - [JsonProperty("season_id")] public string SeasonId{ get; set; } - [JsonProperty("seo_title")] public string SeoTitle{ get; set; } - [JsonProperty("is_premium_only")] public bool IsPremiumOnly{ get; set; } + + [JsonProperty("sequence_number")] + public float SequenceNumber{ get; set; } + + [JsonProperty("eligible_region")] + public string EligibleRegion{ get; set; } + + [JsonProperty("availability_starts")] + public DateTime? AvailabilityStarts{ get; set; } + + public Images? Images{ get; set; } + + [JsonProperty("season_id")] + public string SeasonId{ get; set; } + + [JsonProperty("seo_title")] + public string SeoTitle{ get; set; } + + [JsonProperty("is_premium_only")] + public bool IsPremiumOnly{ get; set; } [JsonProperty("extended_maturity_rating")] public Dictionary ExtendedMaturityRating{ get; set; } @@ -41,104 +72,119 @@ public struct CrunchyEpisode{ [JsonProperty("premium_available_date")] public DateTime? PremiumAvailableDate{ get; set; } - [JsonProperty("season_title")] public string SeasonTitle{ get; set; } - [JsonProperty("seo_description")] public string SeoDescription{ get; set; } + [JsonProperty("season_title")] + public string SeasonTitle{ get; set; } + + [JsonProperty("seo_description")] + public string SeoDescription{ get; set; } + + [JsonProperty("audio_locale")] + public string AudioLocale{ get; set; } - [JsonProperty("audio_locale")] public string AudioLocale{ get; set; } public string Id{ get; set; } - [JsonProperty("media_type")] public MediaType? MediaType{ get; set; } // MediaType should be an enum you define based on possible values. - [JsonProperty("availability_ends")] public DateTime? AvailabilityEnds{ get; set; } - [JsonProperty("free_available_date")] public DateTime? FreeAvailableDate{ get; set; } + + [JsonProperty("media_type")] + public MediaType? MediaType{ get; set; } + + [JsonProperty("availability_ends")] + public DateTime? AvailabilityEnds{ get; set; } + + [JsonProperty("free_available_date")] + public DateTime? FreeAvailableDate{ get; set; } + public string Playback{ get; set; } - [JsonProperty("channel_id")] public ChannelId? ChannelId{ get; set; } // ChannelID should be an enum or struct. + + [JsonProperty("channel_id")] + public ChannelId? ChannelId{ get; set; } + public string? Episode{ get; set; } - [JsonProperty("is_mature")] public bool IsMature{ get; set; } - [JsonProperty("listing_id")] public string ListingId{ get; set; } - [JsonProperty("episode_air_date")] public DateTime? EpisodeAirDate{ get; set; } + + [JsonProperty("is_mature")] + public bool IsMature{ get; set; } + + [JsonProperty("listing_id")] + public string ListingId{ get; set; } + + [JsonProperty("episode_air_date")] + public DateTime? EpisodeAirDate{ get; set; } + public string Slug{ get; set; } - [JsonProperty("available_date")] public DateTime? AvailableDate{ get; set; } - [JsonProperty("subtitle_locales")] public List SubtitleLocales{ get; set; } - [JsonProperty("slug_title")] public string SlugTitle{ get; set; } - [JsonProperty("available_offline")] public bool AvailableOffline{ get; set; } + + [JsonProperty("available_date")] + public DateTime? AvailableDate{ get; set; } + + [JsonProperty("subtitle_locales")] + public List SubtitleLocales{ get; set; } + + [JsonProperty("slug_title")] + public string SlugTitle{ get; set; } + + [JsonProperty("available_offline")] + public bool AvailableOffline{ get; set; } + public string Description{ get; set; } - [JsonProperty("is_subbed")] public bool IsSubbed{ get; set; } - [JsonProperty("premium_date")] public DateTime? PremiumDate{ get; set; } - [JsonProperty("upload_date")] public DateTime? UploadDate{ get; set; } - [JsonProperty("season_slug_title")] public string SeasonSlugTitle{ get; set; } + + [JsonProperty("is_subbed")] + public bool IsSubbed{ get; set; } + + [JsonProperty("premium_date")] + public DateTime? PremiumDate{ get; set; } + + [JsonProperty("upload_date")] + public DateTime? UploadDate{ get; set; } + + [JsonProperty("season_slug_title")] + public string SeasonSlugTitle{ get; set; } [JsonProperty("closed_captions_available")] public bool ClosedCaptionsAvailable{ get; set; } - [JsonProperty("episode_number")] public int? EpisodeNumber{ get; set; } - [JsonProperty("season_tags")] public List SeasonTags{ get; set; } // More specific type could be used if known. - [JsonProperty("maturity_ratings")] public List MaturityRatings{ get; set; } // MaturityRating should be defined based on possible values. - [JsonProperty("streams_link")] public string? StreamsLink{ get; set; } - [JsonProperty("mature_blocked")] public bool? MatureBlocked{ get; set; } - [JsonProperty("is_clip")] public bool IsClip{ get; set; } - [JsonProperty("hd_flag")] public bool HdFlag{ get; set; } - [JsonProperty("hide_season_title")] public bool? HideSeasonTitle{ get; set; } - [JsonProperty("hide_season_number")] public bool? HideSeasonNumber{ get; set; } + [JsonProperty("episode_number")] + public int? EpisodeNumber{ get; set; } + + [JsonProperty("season_tags")] + public List SeasonTags{ get; set; } + + [JsonProperty("maturity_ratings")] + public List MaturityRatings{ get; set; } + + [JsonProperty("streams_link")] + public string? StreamsLink{ get; set; } + + [JsonProperty("mature_blocked")] + public bool? MatureBlocked{ get; set; } + + [JsonProperty("is_clip")] + public bool IsClip{ get; set; } + + [JsonProperty("hd_flag")] + public bool HdFlag{ get; set; } + + [JsonProperty("hide_season_title")] + public bool? HideSeasonTitle{ get; set; } + + [JsonProperty("hide_season_number")] + public bool? HideSeasonNumber{ get; set; } + public bool? IsSelected{ get; set; } - [JsonProperty("seq_id")] public string SeqId{ get; set; } - [JsonProperty("__links__")] public Links? Links{ get; set; } + + [JsonProperty("seq_id")] + public string SeqId{ get; set; } + + [JsonProperty("__links__")] + public Links? Links{ get; set; } } -// public struct CrunchyEpisode{ -// -// public string channel_id{ get; set; } -// public bool is_mature{ get; set; } -// public string upload_date{ get; set; } -// public string free_available_date{ get; set; } -// public List content_descriptors{ get; set; } -// public Dictionary images{ get; set; } // Consider specifying actual key and value types if known -// public int season_sequence_number{ get; set; } -// public string audio_locale{ get; set; } -// public string title{ get; set; } -// public Dictionary -// extended_maturity_rating{ get; set; } // Consider specifying actual key and value types if known -// public bool available_offline{ get; set; } -// public string identifier{ get; set; } -// public string listing_id{ get; set; } -// public List season_tags{ get; set; } -// public string next_episode_id{ get; set; } -// public string next_episode_title{ get; set; } -// public bool is_subbed{ get; set; } -// public string slug{ get; set; } -// public List versions{ get; set; } -// public int season_number{ get; set; } -// public string availability_ends{ get; set; } -// public string eligible_region{ get; set; } -// public bool is_clip{ get; set; } -// public string description{ get; set; } -// public string seo_description{ get; set; } -// public bool is_premium_only{ get; set; } -// public string streams_link{ get; set; } -// public int episode_number{ get; set; } -// public bool closed_captions_available{ get; set; } -// -// public bool is_dubbed{ get; set; } -// public string seo_title{ get; set; } -// public long duration_ms{ get; set; } -// public string id{ get; set; } -// public string series_id{ get; set; } -// public string series_slug_title{ get; set; } -// public string episode_air_date{ get; set; } -// public bool hd_flag{ get; set; } -// public bool mature_blocked{ get; set; } -// -// public string availability_notes{ get; set; } -// -// public List maturity_ratings{ get; set; } -// public string episode{ get; set; } -// public int sequence_number{ get; set; } -// public List subtitle_locales{ get; set; } -// -// } - public struct Images{ - [JsonProperty("poster_tall")] public List>? PosterTall{ get; set; } - [JsonProperty("poster_wide")] public List>? PosterWide{ get; set; } - [JsonProperty("promo_image")] public List>? PromoImage{ get; set; } + [JsonProperty("poster_tall")] + public List>? PosterTall{ get; set; } + + [JsonProperty("poster_wide")] + public List>? PosterWide{ get; set; } + + [JsonProperty("promo_image")] + public List>? PromoImage{ get; set; } + public List> Thumbnail{ get; set; } } @@ -150,12 +196,22 @@ public struct Image{ } public struct EpisodeVersion{ - [JsonProperty("audio_locale")] public string AudioLocale{ get; set; } + [JsonProperty("audio_locale")] + public string AudioLocale{ get; set; } + public string Guid{ get; set; } - [JsonProperty("is_premium_only")] public bool IsPremiumOnly{ get; set; } - [JsonProperty("media_guid")] public string? MediaGuid{ get; set; } + + [JsonProperty("is_premium_only")] + public bool IsPremiumOnly{ get; set; } + + [JsonProperty("media_guid")] + public string? MediaGuid{ get; set; } + public bool Original{ get; set; } - [JsonProperty("season_guid")] public string SeasonGuid{ get; set; } + + [JsonProperty("season_guid")] + public string SeasonGuid{ get; set; } + public string Variant{ get; set; } } @@ -187,14 +243,13 @@ public class CrunchyEpMeta{ public string? Image{ get; set; } public bool Paused{ get; set; } public DownloadProgress? DownloadProgress{ get; set; } - + public List? SelectedDubs{ get; set; } - + public List? AvailableSubs{ get; set; } } public class DownloadProgress{ - public bool IsDownloading = false; public bool Done = false; public bool Error = false; diff --git a/CRD/Utils/UI/UiValueConverterCalendarBackground.cs b/CRD/Utils/UI/UiValueConverterCalendarBackground.cs new file mode 100644 index 0000000..0e75721 --- /dev/null +++ b/CRD/Utils/UI/UiValueConverterCalendarBackground.cs @@ -0,0 +1,21 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; +using Avalonia.Media; +using FluentAvalonia.UI.Controls; + +namespace CRD.Utils.UI; + +public class UiValueConverterCalendarBackground : IValueConverter{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture){ + if (value is bool boolValue){ + return boolValue ? new SolidColorBrush(Color.Parse("#10f5d800")) : new SolidColorBrush(Color.Parse("#10FFFFFF")); + } + + return new SolidColorBrush(Color.Parse("#10FFFFFF")); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture){ + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/CRD/ViewModels/HistoryPageViewModel.cs b/CRD/ViewModels/HistoryPageViewModel.cs index b79a9f7..62714fc 100644 --- a/CRD/ViewModels/HistoryPageViewModel.cs +++ b/CRD/ViewModels/HistoryPageViewModel.cs @@ -14,7 +14,7 @@ public partial class HistoryPageViewModel : ViewModelBase{ public ObservableCollection Items{ get; } [ObservableProperty] - private bool? _showLoading = false; + private static bool _fetchingData; [ObservableProperty] public HistorySeries _selectedSeries; @@ -35,38 +35,52 @@ public partial class HistoryPageViewModel : ViewModelBase{ } + + partial void OnSelectedSeriesChanged(HistorySeries value){ Crunchyroll.Instance.SelectedSeries = value; - MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, false)); + NavToSeries(); _selectedSeries = null; } - + [RelayCommand] public void RemoveSeries(string? seriesId){ - HistorySeries? objectToRemove = Crunchyroll.Instance.HistoryList.ToList().Find(se => se.SeriesId == seriesId) ?? null; - if (objectToRemove != null) { + if (objectToRemove != null){ Crunchyroll.Instance.HistoryList.Remove(objectToRemove); Items.Remove(objectToRemove); } - CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList); + + CfgManager.WriteJsonToFile(CfgManager.PathCrHistory, Crunchyroll.Instance.HistoryList); } - + [RelayCommand] public void NavToSeries(){ + if (FetchingData){ + return; + } + MessageBus.Current.SendMessage(new NavigationMessage(typeof(SeriesPageViewModel), false, false)); } [RelayCommand] public async void RefreshAll(){ + FetchingData = true; + RaisePropertyChanged(nameof(FetchingData)); for (int i = 0; i < Items.Count; i++){ - ShowLoading = true; + Items[i].SetFetchingData(); + } + + for (int i = 0; i < Items.Count; i++){ + FetchingData = true; + RaisePropertyChanged(nameof(FetchingData)); await Items[i].FetchData(""); Items[i].UpdateNewEpisodes(); } - ShowLoading = false; + FetchingData = false; + RaisePropertyChanged(nameof(FetchingData)); } [RelayCommand] @@ -74,7 +88,5 @@ public partial class HistoryPageViewModel : ViewModelBase{ for (int i = 0; i < Items.Count; i++){ await Items[i].AddNewMissingToDownloads(); } - - ShowLoading = false; } } \ No newline at end of file diff --git a/CRD/ViewModels/SettingsPageViewModel.cs b/CRD/ViewModels/SettingsPageViewModel.cs index 2f721ec..e24be2e 100644 --- a/CRD/ViewModels/SettingsPageViewModel.cs +++ b/CRD/ViewModels/SettingsPageViewModel.cs @@ -10,6 +10,7 @@ using Avalonia.Controls; using Avalonia.Media; using Avalonia.Styling; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using CRD.Downloader; using CRD.Utils; using CRD.Utils.Sonarr; @@ -100,6 +101,9 @@ public partial class SettingsPageViewModel : ViewModelBase{ [ObservableProperty] private bool _sonarrUseSsl = false; + [ObservableProperty] + private bool _sonarrUseSonarrNumbering = false; + public ObservableCollection PredefinedColors{ get; } = new(){ Color.FromRgb(255, 185, 0), Color.FromRgb(255, 140, 0), @@ -230,6 +234,7 @@ public partial class SettingsPageViewModel : ViewModelBase{ if (props != null){ SonarrUseSsl = props.UseSsl; + SonarrUseSonarrNumbering = props.UseSonarrNumbering; SonarrHost = props.Host + ""; SonarrPort = props.Port + ""; SonarrApiKey = props.ApiKey + ""; @@ -316,13 +321,21 @@ public partial class SettingsPageViewModel : ViewModelBase{ var props = new SonarrProperties(); props.UseSsl = SonarrUseSsl; + props.UseSonarrNumbering = SonarrUseSonarrNumbering; props.Host = SonarrHost; - props.Port = Convert.ToInt32(SonarrPort); + + if (int.TryParse(SonarrPort, out var portNumber)){ + props.Port = portNumber; + } else{ + props.Port = 8989; + } + props.ApiKey = SonarrApiKey; + Crunchyroll.Instance.CrunOptions.SonarrProperties = props; - Crunchyroll.Instance.RefreshSonarr(); + //TODO - Mux Options @@ -469,4 +482,8 @@ public partial class SettingsPageViewModel : ViewModelBase{ partial void OnSonarrUseSslChanged(bool value){ UpdateSettings(); } + + partial void OnSonarrUseSonarrNumberingChanged(bool value){ + UpdateSettings(); + } } \ No newline at end of file diff --git a/CRD/Views/CalendarPageView.axaml b/CRD/Views/CalendarPageView.axaml index 36b4e1f..4c2e918 100644 --- a/CRD/Views/CalendarPageView.axaml +++ b/CRD/Views/CalendarPageView.axaml @@ -4,11 +4,16 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:vm="clr-namespace:CRD.ViewModels" + xmlns:ui="clr-namespace:CRD.Utils.UI" x:DataType="vm:CalendarPageViewModel" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="CRD.Views.CalendarPageView"> + + + + @@ -51,12 +56,12 @@ - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - Edit + + + Edit - - - - - - - + @@ -59,8 +48,8 @@ Padding="0,5,0,0"/> - + @@ -70,6 +59,7 @@ VerticalAlignment="Center" Command="{Binding $parent[UserControl].((vm:HistoryPageViewModel)DataContext).RemoveSeries}" CommandParameter="{Binding SeriesId}" + IsEnabled="{Binding !$parent[UserControl].((vm:HistoryPageViewModel)DataContext).FetchingData}" > @@ -80,6 +70,14 @@ + + + + + + + diff --git a/CRD/Views/HistoryPageView.axaml.cs b/CRD/Views/HistoryPageView.axaml.cs index 98a6f5f..1859b0a 100644 --- a/CRD/Views/HistoryPageView.axaml.cs +++ b/CRD/Views/HistoryPageView.axaml.cs @@ -1,11 +1,11 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; +using Avalonia.Controls; namespace CRD.Views; public partial class HistoryPageView : UserControl{ + public HistoryPageView(){ InitializeComponent(); } + } \ No newline at end of file diff --git a/CRD/Views/SettingsPageView.axaml b/CRD/Views/SettingsPageView.axaml index e5afd5b..bbb7cf7 100644 --- a/CRD/Views/SettingsPageView.axaml +++ b/CRD/Views/SettingsPageView.axaml @@ -6,7 +6,8 @@ xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:DataType="vm:SettingsPageViewModel" - x:Class="CRD.Views.SettingsPageView"> + x:Class="CRD.Views.SettingsPageView" + Unloaded="OnUnloaded"> @@ -240,6 +241,7 @@ @@ -269,6 +271,12 @@ + + + + + +