private static QueueSlotsSlot CreateQueueSlotsSlot([NotNull] XElement slot, string simpleShowName, ItemMissing action) { string filename = slot.Attribute("filename")?.Value; if (string.IsNullOrWhiteSpace(filename)) { return(null); } FileInfo file = new FileInfo(filename); if (!FileHelper.SimplifyAndCheckFilename(file.FullName, simpleShowName, true, false)) { return(null); } if (!FinderHelper.FindSeasEp(file, out int seasF, out int epF, out int _, action.Episode.Show) || (seasF != action.Episode.AppropriateSeasonNumber) || (epF != action.Episode.AppropriateEpNum)) { return(null); } return(new QueueSlotsSlot { Filename = filename, Mb = slot.Attribute("mb")?.Value, Sizeleft = slot.Attribute("sizeleft")?.Value, Status = slot.Attribute("status")?.Value, Timeleft = slot.Attribute("timeleft")?.Value }); }
private List <ProcessedEpisode>?MatchEpisodes(FileInfo droppedFile) { ShowConfiguration?bestShow = FinderHelper.FindBestMatchingShow(droppedFile, MDoc.TvLibrary.Shows); if (bestShow is null) { return(null); } if (!FinderHelper.FindSeasEp(droppedFile, out int seasonNum, out int episodeNum, out int _, bestShow, out TVSettings.FilenameProcessorRE _)) { return(null); } try { ProcessedEpisode episode = bestShow.GetEpisode(seasonNum, episodeNum); return(new List <ProcessedEpisode> { episode }); } catch (ShowConfiguration.EpisodeNotFoundException) { return(null); } }
private void FillExamples() { if (eps is null) { return; } lvTest.Items.Clear(); foreach (ProcessedEpisode pe in eps) { ListViewItem lvi = new ListViewItem(); string fn = TVSettings.Instance.FilenameFriendly(cn.NameFor(pe)); lvi.Text = fn; bool ok = FinderHelper.FindSeasEp(new FileInfo(fn + ".avi"), out int seas, out int ep, out int maxEp, pe.Show); bool ok1 = ok && seas == pe.AppropriateSeasonNumber; bool ok2 = ok && ep == pe.AppropriateEpNum; string pre1 = ok1 ? "" : "* "; string pre2 = ok2 ? "" : "* "; lvi.SubItems.Add(pre1 + (seas != -1 ? seas.ToString() : "")); lvi.SubItems.Add(pre2 + (ep != -1 ? ep.ToString() : "") + (maxEp != -1 ? "-" + maxEp : "")); lvi.Tag = pe; if (!ok || !ok1 || !ok2) { lvi.BackColor = Helpers.WarningColor(); } lvTest.Items.Add(lvi); } }
private static (bool identifysuccess, int seasF, int epF, int maxEp) IdentifyFile([NotNull] ItemMissing me, [NotNull] FileInfo dce) { int season = me.Episode.AppropriateSeasonNumber; int epnum = me.Episode.AppropriateEpNum; bool regularMatch = FinderHelper.FindSeasEp(dce, out int seasF, out int epF, out int maxEp, me.Episode.Show) && seasF == season && epF == epnum; if (regularMatch) { return(true, seasF, epF, maxEp); } if (me.Episode.Show.UseSequentialMatch) { bool sequentialMatch = TVDoc.MatchesSequentialNumber(dce.Name, me.Episode); if (sequentialMatch) { return(true, season, epnum, me.Episode.EpNum2); } } return(false, 0, 0, 0); }
private static IEnumerable <Item> ReviewFileInDownloadDirectory(bool unattended, DirFilesCache dfc, ICollection <FileInfo> filesThatMayBeNeeded, FileInfo fi, [NotNull] List <ShowItem> matchingShows) { bool fileCanBeDeleted = TVSettings.Instance.RemoveDownloadDirectoriesFiles; List <Item> returnActions = new List <Item>(); ProcessedEpisode firstMatchingPep = null; foreach (ShowItem si in matchingShows) { FinderHelper.FindSeasEp(fi, out int seasF, out int epF, out int _, si, out TVSettings.FilenameProcessorRE re); SeriesInfo s = si.TheSeries(); if (s is null) { continue; } (firstMatchingPep, fileCanBeDeleted) = FirstMatchingPep(unattended, dfc, fi, matchingShows, s, seasF, epF, si, firstMatchingPep, returnActions, re, fileCanBeDeleted); } if (fileCanBeDeleted) { LOGGER.Info( $"Removing {fi.FullName} as it matches {string.Join(", ", matchingShows.Select(s => s.ShowName))} and no episodes are needed"); returnActions.Add(new ActionDeleteFile(fi, firstMatchingPep, TVSettings.Instance.Tidyup)); } else { filesThatMayBeNeeded.Add(fi); } return(returnActions); }
private void ExtractShowDetails(StringBuilder txt) { foreach (ShowConfiguration si in mDoc.TvLibrary.GetSortedShowItems()) { foreach (KeyValuePair <int, List <ProcessedEpisode> > kvp in si.ActiveSeasons) { int snum = kvp.Key; if (snum == 0 && !si.CountSpecials) { continue; // skip specials } if (!si.AllExistngFolderLocations().ContainsKey(snum)) { continue; // skip non seen seasons } if (snum == 0 && TVSettings.Instance.IgnoreAllSpecials) { continue; } foreach (string folder in si.AllExistngFolderLocations()[snum]) { txt.AppendLine(si.TvdbCode + " : " + si.ShowName + " : S" + snum); txt.AppendLine("Folder: " + folder); DirCache files = new DirCache(); if (Directory.Exists(folder)) { files.AddFolder(null, 0, 0, folder, true); } foreach (DirCacheEntry fi in files) { bool r = FinderHelper.FindSeasEp(fi.TheFile, out int seas, out int ep, out int maxEp, si); bool useful = fi.TheFile.IsMovieFile(); txt.AppendLine(fi.TheFile.FullName + " (" + (r ? "OK" : "No") + " " + seas + "," + ep + "," + maxEp + " " + (useful ? fi.TheFile.Extension : "-") + ")"); } txt.AppendLine(); } } txt.AppendLine(); } }
private static (bool identifysuccess, int foundSeason, int foundEpisode, int maxEp) IdentifyFile([NotNull] ShowItemMissing me, [NotNull] FileInfo dce) { int season = me.MissingEpisode.AppropriateSeasonNumber; int epnum = me.MissingEpisode.AppropriateEpNum; bool regularMatch = FinderHelper.FindSeasEp(dce, out int foundSeason, out int foundEpisode, out int maxEp, me.MissingEpisode.Show) && foundSeason == season && foundEpisode == epnum; if (regularMatch) { return(true, foundSeason, foundEpisode, maxEp); } if (me.MissingEpisode.Show.UseSequentialMatch) { if (FinderHelper.MatchesSequentialNumber(dce.RemoveExtension(false), me.MissingEpisode)) { return(true, season, epnum, me.MissingEpisode.EpNum2); } } if (me.MissingEpisode.Show.UseAirDateMatch) { if (FinderHelper.FindSeasEpDateCheck(dce.Name, out foundSeason, out foundEpisode, me.MissingEpisode.Show)) { if (foundEpisode == epnum && foundSeason == season) { return(true, foundSeason, foundEpisode, -1); } } } if (me.MissingEpisode.Show.UseEpNameMatch) { if (FinderHelper.FindSeasEpNameCheck(dce, me.MissingEpisode.Show, out foundSeason, out foundEpisode)) { if (foundEpisode == epnum && foundSeason == season) { return(true, foundSeason, foundEpisode, -1); } } } return(false, 0, 0, 0); }
private static void ExtractDownloadFolders(StringBuilder txt) { DirCache dirC = new DirCache(); foreach (string efi in TVSettings.Instance.DownloadFolders) { dirC.AddFolder(null, 0, 0, efi, true); } foreach (DirCacheEntry fi in dirC) { bool r = FinderHelper.FindSeasEp(fi.TheFile, out int seas, out int ep, out int maxEp, null); bool useful = fi.TheFile.IsMovieFile(); txt.AppendLine(fi.TheFile.FullName + " (" + (r ? "OK" : "No") + " " + seas + "," + ep + "," + maxEp + " " + (useful ? fi.TheFile.Extension : "-") + ")"); } }
protected void SearchForAppropriateDownloads(List <TorrentEntry> downloading, DownloadApp tApp, TVDoc.ScanSettings settings) { ItemList newList = new ItemList(); ItemList toRemove = new ItemList(); int c = ActionList.MissingItems().Count() + 2; int n = 1; UpdateStatus(n, c, "Searching torrent queue..."); foreach (ItemMissing action in ActionList.MissingItems().ToList()) { if (settings.Token.IsCancellationRequested) { return; } UpdateStatus(n++, c, action.Filename); foreach (TorrentEntry te in downloading) { FileInfo file = new FileInfo(te.DownloadingTo); if (!file.IsMovieFile()) // not a usefile file extension { continue; } //do any of the possible names for the series match the filename? bool matched = action.Episode.Show.NameMatch(file, true); if (!matched) { continue; } if (FinderHelper.FindSeasEp(file, out int seasF, out int epF, out int _, action.Episode.Show) && (seasF == action.Episode.AppropriateSeasonNumber) && (epF == action.Episode.AppropriateEpNum)) { toRemove.Add(action); newList.Add(new ItemDownloading(te, action.Episode, action.TheFileNoExt, tApp)); break; } } } ActionList.Replace(toRemove, newList); }
private static Action SetupDirectoryRemoval([NotNull] DirectoryInfo di, [NotNull] IReadOnlyList <ShowItem> matchingShows) { ShowItem si = matchingShows[0]; //Choose the first series FinderHelper.FindSeasEp(di, out int seasF, out int epF, si, out TVSettings.FilenameProcessorRE _); SeriesInfo s = si.TheSeries(); if (s is null) { throw new ArgumentNullException(nameof(s)); } ProcessedEpisode pep = si.GetEpisode(seasF, epF); LOGGER.Info( $"Removing {di.FullName} as it matches {matchingShows[0].ShowName} and no episodes are needed"); return(new ActionDeleteDirectory(di, pep, TVSettings.Instance.Tidyup)); }
private void UpdatePreview([NotNull] List <TVSettings.FilenameProcessorRE> rel) { lvPreview.BeginUpdate(); DirectoryInfo d = new DirectoryInfo(txtFolder.Text); foreach (FileInfo fi in d.GetFiles()) { if (!TVSettings.Instance.FileHasUsefulExtension(fi, true)) { continue; // move on } ShowItem si = cbShowList.SelectedIndex >= 0 ? shows[cbShowList.SelectedIndex] : null; bool r = FinderHelper.FindSeasEp(fi, out int seas, out int ep, out int maxEp, si, rel, false, out TVSettings.FilenameProcessorRE matchRex); IEnumerable <ShowItem> matchingShows = FinderHelper.FindMatchingShows(fi, shows); string bestShowName = FinderHelper.FindBestMatchingShow(fi, shows)?.ShowName; string otherShowNames = string.Join(", ", matchingShows.Select(item => item.ShowName).Where(s => s != bestShowName)); string showDisplayString = otherShowNames.Any() ? bestShowName + " - (" + otherShowNames + ")" : bestShowName; ListViewItem lvi = new ListViewItem { Text = fi.Name }; lvi.SubItems.Add(showDisplayString); lvi.SubItems.Add(seas == -1 ? "-" : seas.ToString()); lvi.SubItems.Add(ep == -1 ? "-" : ep + (maxEp != -1 ? "-" + maxEp : "")); lvi.SubItems.Add(matchRex is null ? "-" : matchRex.Notes); if (!r) { lvi.BackColor = Helpers.WarningColor(); } lvPreview.Items.Add(lvi); } lvPreview.EndUpdate(); }
private void UpdatePreview([NotNull] List <TVSettings.FilenameProcessorRE> rel) { lvPreview.BeginUpdate(); if (rdoFileSystem.Checked) { foreach (FileInfo file in new DirectoryInfo(txtFolder.Text).GetFiles()) { if (!TVSettings.Instance.FileHasUsefulExtension(file, true)) { continue; // move on } ShowConfiguration si = cbShowList.SelectedIndex >= 0 ? shows[cbShowList.SelectedIndex] : null; bool r = FinderHelper.FindSeasEp(file, out int seas, out int ep, out int maxEp, si, rel, out TVSettings.FilenameProcessorRE matchRex); AddItemToListView(file.Name, seas, ep, maxEp, matchRex, r); } } else { foreach (string filename in GetTorrentDownloads().Select(entry => entry.DownloadingTo)) { if (!TVSettings.Instance.FileHasUsefulExtension(filename, true)) { continue; // move on } ShowConfiguration si = cbShowList.SelectedIndex >= 0 ? shows[cbShowList.SelectedIndex] : null; bool r = FinderHelper.FindSeasEp(filename, out int seas, out int ep, out int maxEp, si, rel, out TVSettings.FilenameProcessorRE matchRex); AddItemToListView(filename, seas, ep, maxEp, matchRex, r); } } lvPreview.EndUpdate(); }
// ReSharper disable once FunctionComplexityOverflow protected bool ReviewFile(ItemMissing me, ItemList addTo, FileInfo dce, TVDoc.ScanSettings settings, bool addMergeRules, bool preventMove, bool doExtraFiles, bool useFullPath) { if (settings.Token.IsCancellationRequested) { return(false); } int season = me.Episode.AppropriateSeasonNumber; int epnum = me.Episode.AppropriateEpNum; bool matched = false; try { if (dce.IgnoreFile()) { return(false); } //do any of the possible names for the series match the filename? matched = me.Episode.Show.NameMatch(dce, useFullPath); if (!matched) { return(false); } bool regularMatch = FinderHelper.FindSeasEp(dce, out int seasF, out int epF, out int maxEp, me.Episode.Show) && seasF == season && epF == epnum; bool sequentialMatch = me.Episode.Show.UseSequentialMatch && TVDoc.MatchesSequentialNumber(dce.Name, ref seasF, ref epF, me.Episode) && seasF == season && epF == epnum; if (!regularMatch && !sequentialMatch) { return(false); } if (maxEp != -1 && addMergeRules) { me = UpdateMissingItem(me, dce, epF, maxEp, seasF); } FileInfo fi = new FileInfo(me.TheFileNoExt + dce.Extension); if (preventMove) { //We do not want to move the file, just rename it fi = new FileInfo(dce.DirectoryName + Path.DirectorySeparatorChar + me.Filename + dce.Extension); } if ((dce.FullName != fi.FullName) && (!FindExistingActionFor(addTo, dce))) { // don't remove the base search folders bool doTidyup = !TVSettings.Instance.DownloadFolders.Any(folder => folder.SameDirectoryLocation(fi.Directory.FullName)); addTo.Add(new ActionCopyMoveRename(ActionCopyMoveRename.Op.copy, dce, fi, me.Episode, doTidyup, me)); } if (doExtraFiles) { DownloadIdentifiersController di = new DownloadIdentifiersController(); // if we're copying/moving a file across, we might also want to make a thumbnail or NFO for it addTo.Add(di.ProcessEpisode(me.Episode, fi)); } return(true); } catch (PathTooLongException e) { WarnPathTooLong(me, dce, e, matched, season, epnum); } return(false); }
protected override void Check(ShowItem si, DirFilesCache dfc, TVDoc.ScanSettings settings) { if (settings.Token.IsCancellationRequested) { throw new TVRenameOperationInterruptedException(); } if (!TVSettings.Instance.AutoMergeLibraryEpisodes) { return; } Dictionary <int, List <string> > allFolders = si.AllExistngFolderLocations(); if (allFolders.Count == 0) // no folders defined for this show { return; // so, nothing to do. } int[] numbers = new int[si.SeasonEpisodes.Keys.Count]; si.SeasonEpisodes.Keys.CopyTo(numbers, 0); // process each folder for each season... foreach (int snum in numbers) { if (settings.Token.IsCancellationRequested) { throw new TVRenameOperationInterruptedException(); } if ((si.IgnoreSeasons.Contains(snum)) || (!allFolders.ContainsKey(snum))) { continue; // ignore/skip this season } if ((snum == 0) && (si.CountSpecials)) { continue; // don't process the specials season, as they're merged into the seasons themselves } if ((snum == 0) && TVSettings.Instance.IgnoreAllSpecials) { continue; } // all the folders for this particular season List <string> folders = allFolders[snum]; List <ProcessedEpisode> eps = si.SeasonEpisodes[snum]; List <ShowRule> rulesToAdd = new List <ShowRule>(); foreach (string folder in folders) { if (settings.Token.IsCancellationRequested) { throw new TVRenameOperationInterruptedException(); } FileInfo[] files = dfc.GetFiles(folder); if (files is null) { continue; } foreach (FileInfo fi in files) { if (settings.Token.IsCancellationRequested) { throw new TVRenameOperationInterruptedException(); } if (!fi.IsMovieFile()) { continue; //not a video file, so ignore } if (!FinderHelper.FindSeasEp(fi, out int seasNum, out int epNum, out int maxEp, si, out TVSettings.FilenameProcessorRE _)) { continue; // can't find season & episode, so this file is of no interest to us } if (seasNum == -1) { seasNum = snum; } int epIdx = eps.FindIndex(x => ((x.AppropriateEpNum == epNum) && (x.AppropriateSeasonNumber == seasNum))); if (epIdx == -1) { continue; // season+episode number don't correspond to any episode we know of from thetvdb } ProcessedEpisode ep = eps[epIdx]; if (ep.Type != ProcessedEpisode.ProcessedEpisodeType.merged && maxEp != -1) { LOGGER.Info( $"Looking at {ep.Show.ShowName} and have identified that episode {epNum} and {maxEp} of season {seasNum} should be merged into one file {fi.FullName}"); ShowRule sr = new ShowRule { DoWhatNow = RuleAction.kMerge, First = epNum, Second = maxEp }; rulesToAdd.Add(sr); } } // foreach file in folder } // for each folder for this season of this show foreach (ShowRule sr in rulesToAdd) { si.AddSeasonRule(snum, sr); LOGGER.Info($"Added new rule automatically for {sr}"); //Regenerate the episodes with the new rule added ShowLibrary.GenerateEpisodeDict(si); } } // for each season of this show }
private void ReviewFileInDownloadDirectory(bool unattended, FileInfo fi, [NotNull] List <ShowConfiguration> matchingShows, [NotNull] List <MovieConfiguration> matchingMovies, IDialogParent owner) { if (!matchingMovies.Any() && !matchingShows.Any()) { // Some sort of random file - ignore return; } bool fileCanBeDeleted = (TVSettings.Instance.RemoveDownloadDirectoriesFiles && matchingShows.Any()) || (TVSettings.Instance.RemoveDownloadDirectoriesFilesMatchMovies && matchingMovies.Any()); ProcessedEpisode firstMatchingPep = null; foreach (ShowConfiguration si in matchingShows) { FinderHelper.FindSeasEp(fi, out int seasF, out int epF, out int _, si, out TVSettings.FilenameProcessorRE re); if (!si.SeasonEpisodes.ContainsKey(seasF)) { if (seasF == -1) { LOGGER.Info( $"Can't find the right season for {fi.FullName} coming out as S{seasF}E{epF} using rule '{re?.Notes}' for show {si.ShowName}:{si.Code}"); } else { LOGGER.Warn( $"Can't find the right season for {fi.FullName} coming out as S{seasF}E{epF} using rule '{re?.Notes}' for show {si.ShowName}:{si.Code}"); } fileCanBeDeleted = false; continue; } ProcessedEpisode?pep = si.SeasonEpisodes[seasF].FirstOrDefault(ep => ep.AppropriateEpNum == epF); if (pep == null) { if (seasF == -1) { LOGGER.Info( $"Can't find the right episode for {fi.FullName} coming out as S{seasF}E{epF} using rule '{re?.Notes}' for show {si.ShowName}:{si.Code}"); } else { LOGGER.Warn( $"Can't find the right episode for {fi.FullName} coming out as S{seasF}E{epF} using rule '{re?.Notes}' for show {si.ShowName}:{si.Code}"); } fileCanBeDeleted = false; continue; } firstMatchingPep = pep; List <FileInfo> encumbants = dfc.FindEpOnDisk(pep, false); if (encumbants.Count == 0) { //File is needed as there are no files for that cachedSeries/episode fileCanBeDeleted = false; CopyFutureDatedFile(fi, pep, MDoc); } else { foreach (FileInfo existingFile in encumbants) { if (existingFile.FullName.Equals(fi.FullName, StringComparison.InvariantCultureIgnoreCase)) { //the user has put the search folder and the download folder in the same place - DO NOT DELETE fileCanBeDeleted = false; continue; } bool?deleteFile = ReviewFile(unattended, fi, matchingShows, existingFile, pep, owner); if (deleteFile.HasValue && deleteFile.Value == false) { fileCanBeDeleted = false; } } } } List <MovieConfiguration> neededMatchingMovie = matchingMovies.Where(si => FinderHelper.FileNeeded(fi, si, dfc)).ToList(); if (neededMatchingMovie.Any()) { LOGGER.Info($"Not removing {fi.FullName} as it may be needed for {neededMatchingMovie.Select(x => x.ShowName).ToCsv()}"); fileCanBeDeleted = false; } else { if (TVSettings.Instance.RemoveDownloadDirectoriesFilesMatchMoviesLengthCheck && (matchingMovies.Max(c => c.ShowName.Length) <= TVSettings.Instance.RemoveDownloadDirectoriesFilesMatchMoviesLengthCheckLength)) { LOGGER.Info($"Not removing {fi.FullName} as it may be needed for {matchingMovies.Select(x => x.ShowName).ToCsv()} and they are all too short"); fileCanBeDeleted = false; } if (TVSettings.Instance.ReplaceMoviesWithBetterQuality) { foreach (var testMovie in matchingMovies) { List <FileInfo> encumbants = dfc.FindMovieOnDisk(testMovie).ToList(); foreach (FileInfo existingFile in encumbants) { if (existingFile.FullName.Equals(fi.FullName, StringComparison.InvariantCultureIgnoreCase)) { //the user has put the search folder and the download folder in the same place - DO NOT DELETE fileCanBeDeleted = false; continue; } bool?deleteFile = ReviewFile(unattended, fi, matchingMovies, existingFile, testMovie, owner); if (deleteFile.HasValue && deleteFile.Value == false) { fileCanBeDeleted = false; } } } } } if (fileCanBeDeleted) { if (matchingMovies.Any()) { LOGGER.Info( $"Removing {fi.FullName} as it matches {matchingMovies.Select(s => s.ShowName).ToCsv()} and no files are needed for those movies"); returnActions.Add(new ActionDeleteFile(fi, matchingMovies.LongestShowName(), TVSettings.Instance.Tidyup)); } if (matchingShows.Any()) { LOGGER.Info( $"Removing {fi.FullName} as it matches {matchingShows.Select(s => s.ShowName).ToCsv()} and no episodes are needed"); if (!matchingShows.Any()) { returnActions.Add(new ActionDeleteFile(fi, firstMatchingPep, TVSettings.Instance.Tidyup)); } } } else { filesThatMayBeNeeded.Add(fi); } }
private static void MergeShowEpisodes([NotNull] ShowItem si, DirFilesCache dfc, CancellationToken token, int snum, IEnumerable <string> folders) { if (snum == 0 && si.CountSpecials) { return; } if (snum == 0 && TVSettings.Instance.IgnoreAllSpecials) { return; } List <ProcessedEpisode> eps = si.SeasonEpisodes[snum]; List <ShowRule> rulesToAdd = new List <ShowRule>(); foreach (string folder in folders) { if (token.IsCancellationRequested) { throw new TVRenameOperationInterruptedException(); } FileInfo[] files = dfc.GetFiles(folder); if (files is null) { continue; } foreach (FileInfo fi in files) { if (token.IsCancellationRequested) { throw new TVRenameOperationInterruptedException(); } if (!fi.IsMovieFile()) { continue; //not a video file, so ignore } if (!FinderHelper.FindSeasEp(fi, out int seasNum, out int epNum, out int maxEp, si, out TVSettings.FilenameProcessorRE _)) { continue; // can't find season & episode, so this file is of no interest to us } if (seasNum == -1) { seasNum = snum; } int epIdx = eps.FindIndex(x => x.AppropriateEpNum == epNum && x.AppropriateSeasonNumber == seasNum); if (epIdx == -1) { continue; // season+episode number don't correspond to any episode we know of from thetvdb } ProcessedEpisode ep = eps[epIdx]; if (ep.Type == ProcessedEpisode.ProcessedEpisodeType.merged || maxEp == -1) { continue; } LOGGER.Info( $"Looking at {ep.Show.ShowName} and have identified that episode {epNum} and {maxEp} of season {seasNum} should be merged into one file {fi.FullName}"); ShowRule sr = new ShowRule { DoWhatNow = RuleAction.kMerge, First = epNum, Second = maxEp }; rulesToAdd.Add(sr); } // foreach file in folder } // for each folder for this season of this show foreach (ShowRule sr in rulesToAdd) { si.AddSeasonRule(snum, sr); LOGGER.Info($"Added new rule automatically for {sr}"); } if (rulesToAdd.Any()) { //Regenerate the episodes with the new rule added ShowLibrary.GenerateEpisodeDict(si); } }
private bool ReadItem([NotNull] XElement itemElement) { string title = itemElement.ExtractString("title"); string link = itemElement.ExtractString("link"); string description = itemElement.ExtractString("description"); string enclosureLink = itemElement.Descendants("enclosure").FirstOrDefault(enclosure => enclosure.Attribute("type")?.Value == "application/x-bittorrent")?.Attribute("url")?.Value; if (TVSettings.Instance.DetailedRSSJSONLogging) { Logger.Info("Processing RSS Item"); Logger.Info(itemElement.ToString); Logger.Info("Extracted"); Logger.Info($"Title: {title}"); Logger.Info($"Link: {link}"); Logger.Info($"Description: {description}"); Logger.Info($"encLink: {enclosureLink}"); } link = (string.IsNullOrWhiteSpace(enclosureLink))?link:enclosureLink; if ((string.IsNullOrEmpty(title)) || (string.IsNullOrEmpty(link))) { return(false); } string showName = ""; FinderHelper.FindSeasEp("", title, out int season, out int episode, out int _, null, regxps); if (TVSettings.Instance.DetailedRSSJSONLogging) { Logger.Info($"Season: {season}"); Logger.Info($"Episode: {episode}"); } try { Match m = Regex.Match(description, "Show Name: (.*?)[;|$]", RegexOptions.IgnoreCase); if (m.Success) { showName = m.Groups[1].ToString(); } m = Regex.Match(description, "Season: ([0-9]+)", RegexOptions.IgnoreCase); if (m.Success) { season = int.Parse(m.Groups[1].ToString()); } m = Regex.Match(description, "Episode: ([0-9]+)", RegexOptions.IgnoreCase); if (m.Success) { episode = int.Parse(m.Groups[1].ToString()); } } catch { // ignored } if (TVSettings.Instance.DetailedRSSJSONLogging) { Logger.Info($"Show Name: {showName}"); Logger.Info($"Season: {season}"); Logger.Info($"Episode: {episode}"); } if ((season != -1) && (episode != -1)) { Add(new RSSItem(link, title, season, episode, showName)); } return(true); }
private static void FindMissingEpisode([NotNull] ItemMissing action, ItemList toRemove, ItemList newItems, UrlCache cache) { ProcessedEpisode pe = action.Episode; string imdbId = pe.TheSeries.GetImdbNumber(); if (string.IsNullOrWhiteSpace(imdbId)) { return; } string simpleShowName = Helpers.SimplifyName(pe.Show.ShowName); string simpleSeriesName = Helpers.SimplifyName(pe.TheSeries.Name); ItemList newItemsForThisMissingEpisode = new ItemList(); string response = cache.GetUrl($"{TVSettings.Instance.SearchJSONURL}{imdbId}", TVSettings.Instance.SearchJSONUseCloudflare); if (string.IsNullOrWhiteSpace(response)) { LOGGER.Warn( $"Got no response from {TVSettings.Instance.SearchJSONURL}{imdbId} for {action.Episode.TheSeries.Name}"); return; } JObject jsonResponse = JObject.Parse(response); if (jsonResponse.ContainsKey(TVSettings.Instance.SearchJSONRootNode)) { foreach (JToken item in jsonResponse[TVSettings.Instance.SearchJSONRootNode]) { if (item is null || !(item is JObject episodeResponse)) { continue; } if (episodeResponse.ContainsKey(TVSettings.Instance.SearchJSONFilenameToken) && episodeResponse.ContainsKey(TVSettings.Instance.SearchJSONURLToken)) { string itemName = (string)item[TVSettings.Instance.SearchJSONFilenameToken]; string itemUrl = (string)item[TVSettings.Instance.SearchJSONURLToken]; long itemSizeBytes = CalculateItemSizeBytes(item); if (TVSettings.Instance.DetailedRSSJSONLogging) { LOGGER.Info("Processing JSON Item"); LOGGER.Info(episodeResponse.ToString); LOGGER.Info("Extracted"); LOGGER.Info($"Name: {itemName}"); LOGGER.Info($"URL: {itemUrl}"); LOGGER.Info($"Size: {itemSizeBytes}"); } if (!FileHelper.SimplifyAndCheckFilename(itemName, simpleShowName, true, false) && !FileHelper.SimplifyAndCheckFilename(itemName, simpleSeriesName, true, false)) { continue; } if (!FinderHelper.FindSeasEp(itemName, out int seas, out int ep, out int _, pe.Show)) { continue; } if (TVSettings.Instance.DetailedRSSJSONLogging) { LOGGER.Info($"Season: {seas}"); LOGGER.Info($"Episode: {ep}"); } if (seas != pe.AppropriateSeasonNumber) { continue; } if (ep != pe.AppropriateEpNum) { continue; } LOGGER.Info( $"Adding {itemUrl} from JSON page as it appears to be match for {pe.Show.ShowName} S{pe.AppropriateSeasonNumber}E{pe.AppropriateEpNum}"); newItemsForThisMissingEpisode.Add(new ActionTDownload(itemName, itemSizeBytes, itemUrl, action.TheFileNoExt, pe, action)); toRemove.Add(action); } else { LOGGER.Info( $"{TVSettings.Instance.SearchJSONFilenameToken} or {TVSettings.Instance.SearchJSONURLToken} not found in {TVSettings.Instance.SearchJSONURL}{imdbId} for {action.Episode.TheSeries.Name}"); } } } else { LOGGER.Info( $"{TVSettings.Instance.SearchJSONRootNode} not found in {TVSettings.Instance.SearchJSONURL}{imdbId} for {action.Episode.TheSeries.Name}"); } RemoveDuplicates(newItemsForThisMissingEpisode); newItems.AddNullableRange(newItemsForThisMissingEpisode); }
private void CheckSeasonFolder(ShowConfiguration si, DirFilesCache dfc, TVDoc.ScanSettings settings, int snum, bool timeForBannerUpdate, string folder) { if (settings.Token.IsCancellationRequested) { return; } if (TVSettings.Instance.NeedToDownloadBannerFile() && timeForBannerUpdate) { //Image cachedSeries checks here Doc.TheActionList.Add( downloadIdentifiers.ForceUpdateSeason(DownloadIdentifier.DownloadType.downloadImage, si, folder, snum)); } //Image cachedSeries checks here Doc.TheActionList.Add(downloadIdentifiers.ProcessSeason(si, folder, snum)); FileInfo[] files = dfc.GetFiles(folder); bool renCheck = TVSettings.Instance.RenameCheck && si.DoRename && Directory.Exists(folder); // renaming check needs the folder to exist bool missCheck = TVSettings.Instance.MissingCheck && si.DoMissingCheck; if (!renCheck && !missCheck) { return; } Dictionary <int, FileInfo> localEps = new Dictionary <int, FileInfo>(); int maxEpNumFound = 0; if (!si.SeasonEpisodes.ContainsKey(snum)) { return; } List <ProcessedEpisode> eps = si.SeasonEpisodes[snum]; foreach (FileInfo fi in files) { if (settings.Token.IsCancellationRequested) { return; } if (!FinderHelper.FindSeasEp(fi, out int seasNum, out int epNum, out int _, si, out TVSettings.FilenameProcessorRE _)) { continue; // can't find season & episode, so this file is of no interest to us } if (seasNum == -1) { seasNum = snum; } ProcessedEpisode ep = eps.Find(x => x.AppropriateEpNum == epNum && x.AppropriateSeasonNumber == seasNum); if (ep is null) { continue; // season+episode number don't correspond to any episode we know of from thetvdb } FileInfo actualFile = fi; // == RENAMING CHECK == if (renCheck && TVSettings.Instance.FileHasUsefulExtension(fi, true)) { // Note that the extension of the file may not be fi.extension as users can put ".mkv.t" for example as an extension string otherExtension = TVSettings.Instance.FileHasUsefulExtensionDetails(fi, true); string newName = TVSettings.Instance.FilenameFriendly( TVSettings.Instance.NamingStyle.NameFor(ep, otherExtension, folder.Length)); FileInfo fileWorthAdding = CheckFile(folder, fi, actualFile, newName, ep, files); if (fileWorthAdding != null) { localEps[epNum] = fileWorthAdding; } } // == MISSING CHECK part 1/2 == if (missCheck && fi.IsMovieFile()) { // first pass of missing check is to tally up the episodes we do have if (!localEps.ContainsKey(epNum)) { localEps[epNum] = actualFile; } if (epNum > maxEpNumFound) { maxEpNumFound = epNum; } } } // foreach file in folder // == MISSING CHECK part 2/2 (includes NFO and Thumbnails) == // look at the official list of episodes, and look to see if we have any gaps DateTime today = DateTime.Now; foreach (ProcessedEpisode episode in eps) { if (!localEps.ContainsKey(episode.AppropriateEpNum)) // not here locally { // second part of missing check is to see what is missing! if (missCheck) { DateTime?dt = episode.GetAirDateDt(true); bool dtOk = dt != null; bool notFuture = dtOk && dt.Value.CompareTo(today) < 0; // isn't an episode yet to be aired // only add to the missing list if, either: // - force check is on // - there are no aired dates at all, for up to and including this season // - there is an aired date, and it isn't in the future bool noAirdatesUntilNow = si.NoAirdatesUntilNow(snum); bool siForceCheckFuture = (si.ForceCheckFuture || notFuture) && dtOk; bool siForceCheckNoAirdate = si.ForceCheckNoAirdate && !dtOk; if (noAirdatesUntilNow || siForceCheckFuture || siForceCheckNoAirdate) { // then add it as officially missing Doc.TheActionList.Add(new ShowItemMissing(episode, folder)); } }// if doing missing check } else { if (settings.Type == TVSettings.ScanType.Full) { Doc.CurrentStats.NsNumberOfEpisodes++; } // do NFO and thumbnail checks if required FileInfo filo = localEps[episode.AppropriateEpNum]; // filename (or future filename) of the file Doc.TheActionList.Add(downloadIdentifiers.ProcessEpisode(episode, filo)); } } // up to date check, for each episode in thetvdb }
public FileInfo MatchMissing(string torrentFile, int torrentFileNum, string nameInTorrent) { // returns true if we found a match (if actSetPrio is on, true also means we have set a priority for this file) string simplifiedfname = Helpers.SimplifyName(nameInTorrent); foreach (Item action1 in MissingList) { if ((!(action1 is ItemMissing)) && (!(action1 is ItemDownloading))) { continue; } ProcessedEpisode m = action1.Episode; string name = null; switch (action1) { case ItemMissing action: name = action.TheFileNoExt; break; case ItemDownloading actionIp: name = actionIp.DesiredLocationNoExt; break; default: throw new ArgumentOutOfRangeException(); } if ((m is null) || string.IsNullOrEmpty(name)) { continue; } // see if the show name matches... if (FileHelper.SimplifyAndCheckFilename(simplifiedfname, m.TheCachedSeries.Name, false, true)) { // see if season and episode match bool findFile = FinderHelper.FindSeasEp("", simplifiedfname, out int seasF, out int epF, out int maxEp, m.Show, Rexps, out TVSettings.FilenameProcessorRE rex); if (findFile && (seasF == m.AppropriateSeasonNumber) && (epF == m.AppropriateEpNum)) { // match! // get extension from nameInTorrent int p = nameInTorrent.LastIndexOf(".", StringComparison.Ordinal); string ext = (p == -1) ? "" : nameInTorrent.Substring(p); AlterResume(torrentFile, torrentFileNum, name + ext); if (SetPrios) { SetResumePrio(torrentFile, torrentFileNum, BTPrio.Normal); } return(new FileInfo(name + ext)); } } } return(null); }
private bool ReadItem([NotNull] XElement itemElement, string sourceUrl, string sourcePrefix) { string title = itemElement.ExtractString("title"); string link = itemElement.ExtractString("link"); string description = itemElement.ExtractString("description"); string enclosureLink = itemElement.Descendants("enclosure").FirstOrDefault(enclosure => enclosure.Attribute("type")?.Value == "application/x-bittorrent")?.Attribute("url")?.Value; int seeders = GetSeeders(itemElement); long size = itemElement.ExtractLong("size", 0); string source = itemElement.ExtractString("jackettindexer", sourceUrl); if (TVSettings.Instance.DetailedRSSJSONLogging) { Logger.Info("Processing RSS Item"); Logger.Info(itemElement.ToString); Logger.Info("Extracted"); Logger.Info($"Title: {title}"); Logger.Info($"Link: {link}"); Logger.Info($"Description: {description}"); Logger.Info($"encLink: {enclosureLink}"); Logger.Info($"Seeders: {seeders}"); Logger.Info($"Size: {size}"); Logger.Info($"Source: {source}"); } link = string.IsNullOrWhiteSpace(enclosureLink)?link:enclosureLink; if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(link)) { return(false); } string showName = string.Empty; FinderHelper.FindSeasEp(title, out int season, out int episode, out int _, null); if (TVSettings.Instance.DetailedRSSJSONLogging) { Logger.Info($"Season: {season}"); Logger.Info($"Episode: {episode}"); } try { Match m = Regex.Match(description, "Show Name: (.*?)[;|$]", RegexOptions.IgnoreCase); if (m.Success) { showName = m.Groups[1].ToString(); } m = Regex.Match(description, "Season: ([0-9]+)", RegexOptions.IgnoreCase); if (m.Success) { season = int.Parse(m.Groups[1].ToString()); } m = Regex.Match(description, "Episode: ([0-9]+)", RegexOptions.IgnoreCase); if (m.Success) { episode = int.Parse(m.Groups[1].ToString()); } } catch { // ignored } if (TVSettings.Instance.DetailedRSSJSONLogging) { Logger.Info($"Show Name: {showName}"); Logger.Info($"Season: {season}"); Logger.Info($"Episode: {episode}"); } if (season != -1 && episode != -1) { Add(new RSSItem(link, title, season, episode, showName, seeders, size, $"{sourcePrefix}: {source}")); } return(true); }
private void ReviewFileInDownloadDirectory(bool unattended, FileInfo fi, [NotNull] List <ShowConfiguration> matchingShows, IDialogParent owner) { bool fileCanBeDeleted = TVSettings.Instance.RemoveDownloadDirectoriesFiles; ProcessedEpisode firstMatchingPep = null; foreach (ShowConfiguration si in matchingShows) { FinderHelper.FindSeasEp(fi, out int seasF, out int epF, out int _, si, out TVSettings.FilenameProcessorRE re); if (!si.SeasonEpisodes.ContainsKey(seasF)) { LOGGER.Info($"Can't find the right season for {fi.FullName} coming out as S{seasF}E{epF} using rule '{re?.Notes}'"); fileCanBeDeleted = false; continue; } ProcessedEpisode?pep = si.SeasonEpisodes[seasF].FirstOrDefault(ep => ep.AppropriateEpNum == epF); if (pep == null) { LOGGER.Info($"Can't find the right episode for {fi.FullName} coming out as S{seasF}E{epF} using rule '{re?.Notes}'"); fileCanBeDeleted = false; continue; } firstMatchingPep = pep; List <FileInfo> encumbants = dfc.FindEpOnDisk(pep, false); if (encumbants.Count == 0) { //File is needed as there are no files for that cachedSeries/episode fileCanBeDeleted = false; CopyFutureDatedFile(fi, pep, MDoc); } else { foreach (FileInfo existingFile in encumbants) { if (existingFile.FullName.Equals(fi.FullName, StringComparison.InvariantCultureIgnoreCase)) { //the user has put the search folder and the download folder in the same place - DO NOT DELETE fileCanBeDeleted = false; continue; } bool?deleteFile = ReviewFile(unattended, fi, matchingShows, existingFile, pep, owner); if (deleteFile.HasValue && deleteFile.Value == false) { fileCanBeDeleted = false; } } } } if (fileCanBeDeleted) { LOGGER.Info( $"Removing {fi.FullName} as it matches { matchingShows.Select(s => s.ShowName).ToCsv()} and no episodes are needed"); returnActions.Add(new ActionDeleteFile(fi, firstMatchingPep, TVSettings.Instance.Tidyup)); } else { filesThatMayBeNeeded.Add(fi); } }
protected void SearchForAppropriateDownloads(List <TorrentEntry> downloading, DownloadApp tApp, TVDoc.ScanSettings settings) { ItemList newList = new ItemList(); ItemList toRemove = new ItemList(); int c = ActionList.Missing.Count + 2; int n = 1; UpdateStatus(n, c, "Searching torrent queue..."); foreach (ItemMissing?action in ActionList.Missing.ToList()) { if (settings.Token.IsCancellationRequested) { return; } UpdateStatus(n++, c, action.Filename); foreach (TorrentEntry te in downloading) { FileInfo file = new FileInfo(te.DownloadingTo); if (!file.IsMovieFile() && file.Extension != ".rar") // not a usefile file extension { continue; } if (action is ShowItemMissing showMissingAction) { //do any of the possible names for the cachedSeries match the filename? ProcessedEpisode episode = showMissingAction.MissingEpisode; bool matched = episode.Show.NameMatch(file, true); if (!matched) { continue; } if (FinderHelper.FindSeasEp(file, out int seasF, out int epF, out int _, episode.Show) && seasF == episode.AppropriateSeasonNumber && epF == episode.AppropriateEpNum) { toRemove.Add(action); newList.Add(new ItemDownloading(te, episode, action.TheFileNoExt, tApp, action)); break; } } if (action is MovieItemMissing movieMissingAction) { //do any of the possible names for the cachedSeries match the filename? MovieConfiguration movie = movieMissingAction.MovieConfig; bool matched = movie.NameMatch(file, true); if (!matched) { continue; } toRemove.Add(action); newList.Add(new ItemDownloading(te, movie, action.TheFileNoExt, tApp, action)); break; } } } ActionList.Replace(toRemove, newList); }