Crunchy-Downloader/CRD/Downloader/Crunchyroll/CrQueue.cs
Elwador d9813191ad - Added **Global Pause button** for the download queue [#418](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/418)
- Added **fallback for sync failures** [#407](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/407)
- Added **history setting** to remove non-existent series/episodes on refresh [#420](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/420)
- Added **movies to history**
- Added **queue persistence**
- Changed **download item state handling**
- Changed **download item removal processing**
- Made small changes to **font detection**
- Changed **rate limit error handling**
- Fixed issue where **files were not always cleaned up** for removed downloads
- Fixed **crash when the queue list was modified**
- Fixed **changelog parsing** not handling versions like `vX.X.X.X`, which caused changes to be re-added on every restart
2026-04-20 15:40:58 +02:00

439 lines
No EOL
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CRD.Utils;
using CRD.Utils.Structs;
using CRD.Utils.Structs.History;
using CRD.Views;
using ReactiveUI;
namespace CRD.Downloader.Crunchyroll;
public class CrQueue{
public async Task CrAddEpisodeToQueue(string epId, string crLocale, List<string> dubLang, bool updateHistory = false, EpisodeDownloadMode episodeDownloadMode = EpisodeDownloadMode.Default){
if (string.IsNullOrEmpty(epId)){
return;
}
await CrunchyrollManager.Instance.CrAuthEndpoint1.RefreshToken(true);
var episodeL = await CrunchyrollManager.Instance.CrEpisode.ParseEpisodeById(epId, crLocale);
if (episodeL != null){
if (episodeL.IsPremiumOnly && !CrunchyrollManager.Instance.CrAuthEndpoint1.Profile.HasPremium){
MessageBus.Current.SendMessage(new ToastMessage($"Episode is a premium episode make sure that you are signed in with an account that has an active premium subscription", ToastType.Error, 3));
return;
}
var sList = await CrunchyrollManager.Instance.CrEpisode.EpisodeData(episodeL, updateHistory);
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", "");
if (CrunchyrollManager.Instance.CrunOptions.History){
var variant = sList.EpisodeAndLanguages.Variants.First();
historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(variant.Item.SeriesId, variant.Item.SeasonId, variant.Item.Id);
if (historyEpisode.dublist.Count > 0){
dubLang = historyEpisode.dublist;
}
}
var selected = CrunchyrollManager.Instance.CrEpisode.EpisodeMeta(sList, dubLang);
if (CrunchyrollManager.Instance.CrunOptions.IncludeVideoDescription){
if (selected.Data is{ Count: > 0 }){
var episode = await CrunchyrollManager.Instance.CrEpisode.ParseEpisodeById(selected.Data.First().MediaId,
string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.DescriptionLang) ? CrunchyrollManager.Instance.DefaultLocale : CrunchyrollManager.Instance.CrunOptions.DescriptionLang, true);
selected.Description = episode?.Description ?? selected.Description;
}
}
if (selected.Data is{ Count: > 0 }){
if (CrunchyrollManager.Instance.CrunOptions.History){
// var historyEpisode = CrHistory.GetHistoryEpisodeWithDownloadDir(selected.ShowId, selected.SeasonId, selected.Data.First().MediaId);
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
if (historyEpisode.historyEpisode != null){
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrEpisodeNumber)){
selected.EpisodeNumber = historyEpisode.historyEpisode.SonarrEpisodeNumber;
}
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrSeasonNumber)){
selected.Season = historyEpisode.historyEpisode.SonarrSeasonNumber;
}
}
}
if (!string.IsNullOrEmpty(historyEpisode.downloadDirPath)){
selected.DownloadPath = historyEpisode.downloadDirPath;
}
}
selected.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
selected.DownloadSubs = historyEpisode.sublist.Count > 0 ? historyEpisode.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs;
selected.OnlySubs = episodeDownloadMode == EpisodeDownloadMode.OnlySubs;
if (CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub && selected.Data.Count > 1){
var sortedMetaData = selected.Data
.OrderBy(metaData => {
var locale = metaData.Lang?.CrLocale ?? string.Empty;
var index = dubLang.IndexOf(locale);
return index != -1 ? index : int.MaxValue;
})
.ToList();
if (sortedMetaData.Count != 0){
var first = sortedMetaData.First();
selected.Data = [first];
selected.SelectedDubs = [first.Lang?.CrLocale ?? string.Empty];
}
}
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
if (newOptions == null){
Console.Error.WriteLine("Failed to create a copy of your current settings");
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue, check the logs", ToastType.Error, 2));
return;
}
switch (episodeDownloadMode){
case EpisodeDownloadMode.OnlyVideo:
newOptions.Novids = false;
newOptions.Noaudio = true;
selected.DownloadSubs = ["none"];
break;
case EpisodeDownloadMode.OnlyAudio:
newOptions.Novids = true;
newOptions.Noaudio = false;
selected.DownloadSubs = ["none"];
break;
case EpisodeDownloadMode.OnlySubs:
newOptions.Novids = true;
newOptions.Noaudio = true;
break;
case EpisodeDownloadMode.Default:
default:
break;
}
if (!selected.DownloadSubs.Contains("none") && selected.DownloadSubs.All(item => (selected.AvailableSubs ?? []).Contains(item))){
if (!(selected.Data.Count < dubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub)){
selected.HighlightAllAvailable = true;
}
}
if (newOptions.DownloadOnlyWithAllSelectedDubSub){
if (!selected.DownloadSubs.Contains("none") && !selected.DownloadSubs.Contains("all") && !selected.DownloadSubs.All(item => (selected.AvailableSubs ?? []).Contains(item))){
//missing subs
Console.Error.WriteLine($"Episode not added because of missing subs - {selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle}");
return;
}
if (selected.Data.Count < dubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub){
//missing dubs
Console.Error.WriteLine($"Episode not added because of missing dubs - {selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle}");
return;
}
}
newOptions.DubLang = dubLang;
selected.DownloadSettings = newOptions;
QueueManager.Instance.AddToQueue(selected);
if (selected.Data.Count < dubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub){
Console.WriteLine("Added Episode to Queue but couldn't find all selected dubs");
Console.Error.WriteLine("Added Episode to Queue but couldn't find all selected dubs - Available dubs/subs: ");
var languages = sList.EpisodeAndLanguages.Variants
.Select(v => $"{(v.Item.IsPremiumOnly ? "+ " : "")}{v.Lang.CrLocale}")
.ToArray();
Console.Error.WriteLine(
$"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ?? [])}]");
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue but couldn't find all selected dubs", ToastType.Warning, 2));
} else{
Console.WriteLine("Added Episode to Queue");
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1));
}
} else{
Console.WriteLine("Episode couldn't be added to Queue");
Console.Error.WriteLine("Episode couldn't be added to Queue - Available dubs/subs: ");
var languages = sList.EpisodeAndLanguages.Variants
.Select(v => $"{(v.Item.IsPremiumOnly ? "+ " : "")}{v.Lang.CrLocale}")
.ToArray();
Console.Error.WriteLine($"{selected.SeasonTitle} - Season {selected.Season} - {selected.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", selected.AvailableSubs ?? [])}]");
if (!CrunchyrollManager.Instance.CrunOptions.DownloadOnlyWithAllSelectedDubSub){
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue with current dub settings", ToastType.Error, 2));
}
}
return;
}
Console.WriteLine("Couldn't find episode trying to find movie with id");
var movie = await CrunchyrollManager.Instance.CrMovies.ParseMovieById(epId, crLocale);
if (movie != null){
var movieMeta = CrunchyrollManager.Instance.CrMovies.EpisodeMeta(movie, dubLang);
if (movieMeta != null){
movieMeta.DownloadSubs = CrunchyrollManager.Instance.CrunOptions.DlSubs;
movieMeta.OnlySubs = episodeDownloadMode == EpisodeDownloadMode.OnlySubs;
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
if (newOptions == null){
Console.Error.WriteLine("Failed to create a copy of your current settings");
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue, check the logs", ToastType.Error, 2));
return;
}
switch (episodeDownloadMode){
case EpisodeDownloadMode.OnlyVideo:
newOptions.Novids = false;
newOptions.Noaudio = true;
movieMeta.DownloadSubs = ["none"];
break;
case EpisodeDownloadMode.OnlyAudio:
newOptions.Novids = true;
newOptions.Noaudio = false;
movieMeta.DownloadSubs = ["none"];
break;
case EpisodeDownloadMode.OnlySubs:
newOptions.Novids = true;
newOptions.Noaudio = true;
break;
case EpisodeDownloadMode.Default:
default:
break;
}
newOptions.DubLang = dubLang;
movieMeta.DownloadSettings = newOptions;
movieMeta.VideoQuality = CrunchyrollManager.Instance.CrunOptions.QualityVideo;
if (newOptions.DownloadOnlyWithAllSelectedDubSub){
if (!movieMeta.DownloadSubs.Contains("none") && !movieMeta.DownloadSubs.Contains("all") && !movieMeta.DownloadSubs.All(item => (movieMeta.AvailableSubs ?? []).Contains(item))){
//missing subs
Console.Error.WriteLine($"Episode not added because of missing subs - {movieMeta.SeasonTitle} - Season {movieMeta.Season} - {movieMeta.EpisodeTitle}");
return;
}
if (movieMeta.Data.Count < dubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub){
//missing dubs
Console.Error.WriteLine($"Episode not added because of missing dubs - {movieMeta.SeasonTitle} - Season {movieMeta.Season} - {movieMeta.EpisodeTitle}");
return;
}
}
QueueManager.Instance.AddToQueue(movieMeta);
Console.WriteLine("Added Movie to Queue");
MessageBus.Current.SendMessage(new ToastMessage($"Added Movie to Queue", ToastType.Information, 1));
return;
}
}
Console.Error.WriteLine($"No episode or movie found with the id: {epId}");
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue - No episode or movie found with the id: {epId}", ToastType.Error, 3));
}
public void CrAddMusicMetaToQueue(CrunchyEpMeta epMeta){
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
epMeta.DownloadSettings = newOptions;
QueueManager.Instance.AddToQueue(epMeta);
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue", ToastType.Information, 1));
}
public async Task CrAddMusicVideoToQueue(string epId, string overrideDownloadPath = ""){
await CrunchyrollManager.Instance.CrAuthEndpoint1.RefreshToken(true);
var musicVideo = await CrunchyrollManager.Instance.CrMusic.ParseMusicVideoByIdAsync(epId, "");
if (musicVideo != null){
var musicVideoMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(musicVideo);
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", "");
if (CrunchyrollManager.Instance.CrunOptions.History){
historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(musicVideoMeta.SeriesId, musicVideoMeta.SeasonId, musicVideoMeta.Data.First().MediaId);
}
musicVideoMeta.DownloadPath = !string.IsNullOrEmpty(overrideDownloadPath) ? overrideDownloadPath : (!string.IsNullOrEmpty(historyEpisode.downloadDirPath) ? historyEpisode.downloadDirPath : "");
musicVideoMeta.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
musicVideoMeta.DownloadSettings = newOptions;
QueueManager.Instance.AddToQueue(musicVideoMeta);
MessageBus.Current.SendMessage(new ToastMessage($"Added music video to the queue", ToastType.Information, 1));
}
}
public async Task CrAddConcertToQueue(string epId, string overrideDownloadPath = ""){
await CrunchyrollManager.Instance.CrAuthEndpoint1.RefreshToken(true);
var concert = await CrunchyrollManager.Instance.CrMusic.ParseConcertByIdAsync(epId, "");
if (concert != null){
var concertMeta = CrunchyrollManager.Instance.CrMusic.EpisodeMeta(concert);
(HistoryEpisode? historyEpisode, List<string> dublist, List<string> sublist, string downloadDirPath, string videoQuality) historyEpisode = (null, [], [], "", "");
if (CrunchyrollManager.Instance.CrunOptions.History){
historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDubListAndDownloadDir(concertMeta.SeriesId, concertMeta.SeasonId, concertMeta.Data.First().MediaId);
}
concertMeta.DownloadPath = !string.IsNullOrEmpty(overrideDownloadPath) ? overrideDownloadPath : (!string.IsNullOrEmpty(historyEpisode.downloadDirPath) ? historyEpisode.downloadDirPath : "");
concertMeta.VideoQuality = !string.IsNullOrEmpty(historyEpisode.videoQuality) ? historyEpisode.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
concertMeta.DownloadSettings = newOptions;
QueueManager.Instance.AddToQueue(concertMeta);
MessageBus.Current.SendMessage(new ToastMessage($"Added concert to the queue", ToastType.Information, 1));
}
}
public async Task CrAddSeriesToQueue(CrunchySeriesList list, CrunchyMultiDownload data){
var selected = CrunchyrollManager.Instance.CrSeries.ItemSelectMultiDub(list.Data, data.DubLang, data.AllEpisodes, data.E);
var failed = false;
var partialAdd = false;
foreach (var crunchyEpMeta in selected.Values.ToList()){
if (crunchyEpMeta.Data.FirstOrDefault() != null){
if (CrunchyrollManager.Instance.CrunOptions.History){
var historyEpisode = CrunchyrollManager.Instance.History.GetHistoryEpisodeWithDownloadDir(crunchyEpMeta.SeriesId, crunchyEpMeta.SeasonId, crunchyEpMeta.Data.First().MediaId);
if (CrunchyrollManager.Instance.CrunOptions.SonarrProperties is{ SonarrEnabled: true, UseSonarrNumbering: true }){
if (historyEpisode.historyEpisode != null){
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrEpisodeNumber)){
crunchyEpMeta.EpisodeNumber = historyEpisode.historyEpisode.SonarrEpisodeNumber;
}
if (!string.IsNullOrEmpty(historyEpisode.historyEpisode.SonarrSeasonNumber)){
crunchyEpMeta.Season = historyEpisode.historyEpisode.SonarrSeasonNumber;
}
}
}
if (!string.IsNullOrEmpty(historyEpisode.downloadDirPath)){
crunchyEpMeta.DownloadPath = historyEpisode.downloadDirPath;
}
}
if (CrunchyrollManager.Instance.CrunOptions.IncludeVideoDescription){
if (crunchyEpMeta.Data is{ Count: > 0 }){
var episode = await CrunchyrollManager.Instance.CrEpisode.ParseEpisodeById(crunchyEpMeta.Data.First().MediaId,
string.IsNullOrEmpty(CrunchyrollManager.Instance.CrunOptions.DescriptionLang) ? CrunchyrollManager.Instance.DefaultLocale : CrunchyrollManager.Instance.CrunOptions.DescriptionLang, true);
crunchyEpMeta.Description = episode?.Description ?? crunchyEpMeta.Description;
}
}
var subLangList = CrunchyrollManager.Instance.History.GetSubList(crunchyEpMeta.SeriesId, crunchyEpMeta.SeasonId);
crunchyEpMeta.VideoQuality = !string.IsNullOrEmpty(subLangList.videoQuality) ? subLangList.videoQuality : CrunchyrollManager.Instance.CrunOptions.QualityVideo;
crunchyEpMeta.DownloadSubs = subLangList.sublist.Count > 0 ? subLangList.sublist : CrunchyrollManager.Instance.CrunOptions.DlSubs;
if (CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub && crunchyEpMeta.Data.Count > 1){
var sortedMetaData = crunchyEpMeta.Data
.OrderBy(metaData => {
var locale = metaData.Lang?.CrLocale ?? string.Empty;
var index = data.DubLang.IndexOf(locale);
return index != -1 ? index : int.MaxValue;
})
.ToList();
if (sortedMetaData.Count != 0){
var first = sortedMetaData.First();
crunchyEpMeta.Data = [first];
crunchyEpMeta.SelectedDubs = [first.Lang?.CrLocale ?? string.Empty];
}
}
var newOptions = Helpers.DeepCopy(CrunchyrollManager.Instance.CrunOptions);
if (newOptions == null){
Console.Error.WriteLine("Failed to create a copy of your current settings");
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode to the queue, check the logs", ToastType.Error, 2));
return;
}
if (crunchyEpMeta.OnlySubs){
newOptions.Novids = true;
newOptions.Noaudio = true;
}
newOptions.DubLang = data.DubLang;
crunchyEpMeta.DownloadSettings = newOptions;
if (!crunchyEpMeta.DownloadSubs.Contains("none") && crunchyEpMeta.DownloadSubs.All(item => (crunchyEpMeta.AvailableSubs ?? []).Contains(item))){
if (!(crunchyEpMeta.Data.Count < data.DubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub)){
crunchyEpMeta.HighlightAllAvailable = true;
}
}
if (newOptions.DownloadOnlyWithAllSelectedDubSub){
if (!crunchyEpMeta.DownloadSubs.Contains("none") && !crunchyEpMeta.DownloadSubs.Contains("all") && !crunchyEpMeta.DownloadSubs.All(item => (crunchyEpMeta.AvailableSubs ?? []).Contains(item))){
//missing subs
Console.Error.WriteLine($"Episode not added because of missing subs - {crunchyEpMeta.SeasonTitle} - Season {crunchyEpMeta.Season} - {crunchyEpMeta.EpisodeTitle}");
continue;
}
if (crunchyEpMeta.Data.Count < data.DubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub){
//missing dubs
Console.Error.WriteLine($"Episode not added because of missing dubs - {crunchyEpMeta.SeasonTitle} - Season {crunchyEpMeta.Season} - {crunchyEpMeta.EpisodeTitle}");
continue;
}
}
QueueManager.Instance.AddToQueue(crunchyEpMeta);
if (crunchyEpMeta.Data.Count < data.DubLang.Count && !CrunchyrollManager.Instance.CrunOptions.DownloadFirstAvailableDub){
Console.WriteLine("Added Episode to Queue but couldn't find all selected dubs");
Console.Error.WriteLine("Added Episode to Queue but couldn't find all selected dubs - Available dubs/subs: ");
partialAdd = true;
var languages = (crunchyEpMeta.Data.First().Versions ?? []).Select(version => $"{(version.IsPremiumOnly ? "+ " : "")}{version.AudioLocale}").ToArray();
Console.Error.WriteLine(
$"{crunchyEpMeta.SeasonTitle} - Season {crunchyEpMeta.Season} - {crunchyEpMeta.EpisodeTitle} dubs - [{string.Join(", ", languages)}] subs - [{string.Join(", ", crunchyEpMeta.AvailableSubs ?? [])}]");
MessageBus.Current.SendMessage(new ToastMessage($"Added episode to the queue but couldn't find all selected dubs", ToastType.Warning, 2));
}
} else{
failed = true;
}
}
if (failed && !partialAdd){
MainWindow.Instance.ShowError("Not all episodes could be added make sure that you are signed in with an account that has an active premium subscription?");
} else if (selected.Values.Count > 0 && !partialAdd){
MessageBus.Current.SendMessage(new ToastMessage($"Added episodes to the queue", ToastType.Information, 1));
} else if (!partialAdd){
MessageBus.Current.SendMessage(new ToastMessage($"Couldn't add episode(s) to the queue with current dub settings", ToastType.Error, 2));
}
}
}