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);
            }
        }
Beispiel #2
0
        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);
            }
        }
Beispiel #3
0
        public static void GuessShowItem([NotNull] PossibleNewTvShow ai, [NotNull] ShowLibrary library, bool showErrorMsgBox)
        {
            string languageCode = TVSettings.Instance.DefaultProvider == TVDoc.ProviderType.TMDB
                ? TVSettings.Instance.TMDBLanguage
                : TVSettings.Instance.PreferredLanguageCode;

            string showName = GuessShowName(ai, library);
            //todo - (BulkAdd Manager needs to work for new providers)
            int tvdbId = FindTVDBShowCode(ai);

            if (string.IsNullOrEmpty(showName) && tvdbId == -1)
            {
                return;
            }

            if (tvdbId != -1)
            {
                try
                {
                    CachedSeriesInfo cachedSeries = TheTVDB.LocalCache.Instance.GetSeriesAndDownload(tvdbId, showErrorMsgBox);
                    if (cachedSeries != null)
                    {
                        ai.SetId(tvdbId, TVDoc.ProviderType.TheTVDB);
                        return;
                    }
                }
                catch (MediaNotFoundException)
                {
                    //continue to try the next method
                }
            }

            CachedSeriesInfo ser = TheTVDB.LocalCache.Instance.GetSeries(showName, showErrorMsgBox, languageCode);

            if (ser != null)
            {
                ai.SetId(tvdbId, TVDoc.ProviderType.TheTVDB);
                return;
            }

            //Try removing any year
            string showNameNoYear =
                Regex.Replace(showName, @"\(\d{4}\)", "").Trim();

            //Remove anything we can from hint to make it cleaner and hence more likely to match
            string refinedHint = FinderHelper.RemoveSeriesEpisodeIndicators(showNameNoYear, library.SeasonWords());

            if (string.IsNullOrWhiteSpace(refinedHint))
            {
                Logger.Info($"Ignoring {showName} as it refines to nothing.");
            }

            ser = TheTVDB.LocalCache.Instance.GetSeries(refinedHint, showErrorMsgBox, languageCode);

            ai.RefinedHint = refinedHint;
            if (ser != null)
            {
                ai.SetId(tvdbId, TVDoc.ProviderType.TheTVDB);
            }
        }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        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 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 ReviewDirInDownloadDirectory(string subDirPath)
        {
            //we are not checking for any file updates, so can return
            if (!TVSettings.Instance.RemoveDownloadDirectoriesFiles && !TVSettings.Instance.RemoveDownloadDirectoriesFilesMatchMovies)
            {
                return;
            }

            if (!Directory.Exists(subDirPath))
            {
                return;
            }

            DirectoryInfo di = new DirectoryInfo(subDirPath);

            FileInfo?neededFile = filesThatMayBeNeeded.FirstOrDefault(info => info.DirectoryName.Contains(di.FullName));

            if (neededFile != null)
            {
                LOGGER.Info($"Not removing {di.FullName} as it may be needed for {neededFile.FullName}");
                return;
            }

            List <MovieConfiguration> matchingMovies = movieList.Where(mi => mi.NameMatch(di, TVSettings.Instance.UseFullPathNameToMatchSearchFolders)).ToList();

            List <ShowConfiguration> matchingShows = showList.Where(si => si.NameMatch(di, TVSettings.Instance.UseFullPathNameToMatchSearchFolders)).ToList();

            if (!matchingShows.Any() && !matchingMovies.Any())
            {
                return; // Some sort of random file - ignore
            }

            var neededMatchingShows = matchingShows.Where(si => FinderHelper.FileNeeded(di, si, dfc)).ToList();

            if (neededMatchingShows.Any())
            {
                LOGGER.Info($"Not removing {di.FullName} as it may be needed for {neededMatchingShows.Select(x=>x.ShowName).ToCsv()}");
                return;
            }

            var neededMatchingMovie = matchingMovies.Where(si => FinderHelper.FileNeeded(di, si, dfc)).ToList();

            if (neededMatchingMovie.Any())
            {
                LOGGER.Info($"Not removing {di.FullName} as it may be needed for {neededMatchingMovie.Select(x=>x.ShowName).ToCsv()}");
                return;
            }

            if (matchingShows.Count > 0 && TVSettings.Instance.RemoveDownloadDirectoriesFiles)
            {
                returnActions.Add(SetupDirectoryRemoval(di, matchingShows));
            }
            if (matchingMovies.Count > 0 && TVSettings.Instance.RemoveDownloadDirectoriesFilesMatchMovies)
            {
                returnActions.Add(SetupDirectoryRemoval(di, matchingMovies));
            }
        }
Beispiel #8
0
        public static void GuessShowItem([NotNull] FoundFolder ai, [NotNull] ShowLibrary library, bool showErrorMsgBox)
        {
            string showName = GuessShowName(ai, library);

            int tvdbId = FindShowCode(ai);

            if (string.IsNullOrEmpty(showName) && tvdbId == -1)
            {
                return;
            }

            if (tvdbId != -1)
            {
                try
                {
                    SeriesInfo series = TheTVDB.Instance.GetSeriesAndDownload(tvdbId);
                    if (series != null)
                    {
                        ai.TVDBCode = tvdbId;
                        return;
                    }
                }
                catch (ShowNotFoundException)
                {
                    //continue to try the next method
                }
            }

            SeriesInfo ser = TheTVDB.Instance.GetSeries(showName, showErrorMsgBox);

            if (ser != null)
            {
                ai.TVDBCode = ser.TvdbCode;
                return;
            }

            //Try removing any year
            string showNameNoYear =
                Regex.Replace(showName, @"\(\d{4}\)", "").Trim();

            //Remove anything we can from hint to make it cleaner and hence more likely to match
            string refinedHint = FinderHelper.RemoveSeriesEpisodeIndicators(showNameNoYear, library.SeasonWords());

            if (string.IsNullOrWhiteSpace(refinedHint))
            {
                Logger.Info($"Ignoring {showName} as it refines to nothing.");
            }

            ser = TheTVDB.Instance.GetSeries(refinedHint, showErrorMsgBox);

            ai.RefinedHint = refinedHint;
            if (ser != null)
            {
                ai.TVDBCode = ser.TvdbCode;
            }
        }
        protected override void DoCheck(SetProgressDelegate prog,
                                        TVDoc.ScanSettings settings)
        {
            //for each directory in settings directory
            //for each file in directory
            //for each saved show (order by recent)
            //does show match selected file?
            //if so add cachedSeries to list of cachedSeries scanned
            if (!Active())
            {
                LOGGER.Info("Not looking for new media as 'Auto-Add' is turned off");
                return;
            }

            //Don't support unattended mode
            if (settings.Unattended || settings.Hidden)
            {
                LOGGER.Info("Not looking for new media as app is unattended");
                return;
            }

            IEnumerable <FileInfo>    possibleShowNames = GetPossibleShowNameStrings();
            List <MediaConfiguration> addedShows        = FinderHelper.FindMedia(possibleShowNames, MDoc, settings.Owner);

            IEnumerable <ShowConfiguration> addedTVShows = addedShows.OfType <ShowConfiguration>();

            if (addedTVShows.Any())
            {
                MDoc.TvLibrary.AddRange(addedTVShows);
                MDoc.ShowAddedOrEdited(false, false, false, settings.Owner);
                MDoc.ShowAddedOrEdited(true, false, false, settings.Owner);
                //add each new show into the shows being scanned
                foreach (ShowConfiguration si in addedTVShows)
                {
                    settings.Shows.Add(si);
                }
                LOGGER.Info("Added new shows called: {0}", addedTVShows.Select(s => s.ShowName).ToCsv());
            }


            IEnumerable <MovieConfiguration> addedMovies = addedShows.OfType <MovieConfiguration>();

            if (addedMovies.Any())
            {
                MDoc.FilmLibrary.AddRange(addedMovies);
                MDoc.MovieAddedOrEdited(false, false, false, settings.Owner);
                MDoc.MovieAddedOrEdited(true, false, false, settings.Owner);

                foreach (MovieConfiguration si in addedMovies)
                {
                    settings.Movies.Add(si);
                }
                LOGGER.Info("Added new movies called: {0}", addedMovies.Select(s => s.ShowName).ToCsv());
            }
        }
Beispiel #10
0
        private List <MovieConfiguration>?MatchMovies(FileInfo droppedFile)
        {
            MovieConfiguration?bestShow = FinderHelper.FindBestMatchingMovie(droppedFile, MDoc.FilmLibrary.Movies);

            if (bestShow is null)
            {
                return(null);
            }

            return(new List <MovieConfiguration> {
                bestShow
            });
        }
Beispiel #11
0
        protected void ProcessMissingItem(TVDoc.ScanSettings settings, ItemList newList, ItemList toRemove, ItemMissing me, ItemList thisRound, [NotNull] List <FileInfo> matchedFiles, bool useFullPath)
        {
            if (matchedFiles.Count == 1)
            {
                if (!OtherActionsMatch(matchedFiles[0], me, settings, useFullPath))
                {
                    if (!FinderHelper.BetterShowsMatch(matchedFiles[0], me.Episode.Show, useFullPath, MDoc))
                    {
                        toRemove.Add(me);
                        newList.AddRange(thisRound);
                    }
                    else
                    {
                        LOGGER.Warn($"Ignoring potential match for {me.Episode.Show.ShowName} S{me.Episode.AppropriateSeasonNumber} E{me.Episode.AppropriateEpNum}: with file {matchedFiles[0]?.FullName} as there are multiple shows that match for that file");
                        me.AddComment(
                            $"Ignoring potential match with file {matchedFiles[0]?.FullName} as there are multiple shows for that file");
                    }
                }
                else
                {
                    LOGGER.Warn($"Ignoring potential match for {me.Episode.Show.ShowName} S{me.Episode.AppropriateSeasonNumber} E{me.Episode.AppropriateEpNum}: with file {matchedFiles[0]?.FullName} as there are multiple actions for that file");
                    me.AddComment(
                        $"Ignoring potential match with file {matchedFiles[0]?.FullName} as there are multiple actions for that file");
                }
            }

            if (matchedFiles.Count > 1)
            {
                List <FileInfo> bestMatchedFiles = IdentifyBestMatches(matchedFiles);

                if (bestMatchedFiles.Count == 1)
                {
                    //We have one file that is better, lets use it
                    toRemove.Add(me);
                    newList.AddRange(thisRound);
                }
                else
                {
                    foreach (FileInfo matchedFile in matchedFiles)
                    {
                        LOGGER.Warn(
                            $"Ignoring potential match for {me.Episode.Show.ShowName} S{me.Episode.AppropriateSeasonNumber} E{me.Episode.AppropriateEpNum}: with file {matchedFile?.FullName} as there are multiple files for that action");

                        me.AddComment(
                            $"Ignoring potential match with file {matchedFile?.FullName} as there are multiple files for that action");
                    }
                }
            }
        }
        private static IEnumerable <Action> ReviewDirInDownloadDirectory(ICollection <ShowItem> showList, DirFilesCache dfc, List <FileInfo> filesThatMayBeNeeded, string subDirPath)
        {
            List <Action> returnActions = new List <Action>();

            //we are not checking for any file updates, so can return
            if (!TVSettings.Instance.RemoveDownloadDirectoriesFiles)
            {
                return(null);
            }

            if (!Directory.Exists(subDirPath))
            {
                return(null);
            }

            DirectoryInfo di = new DirectoryInfo(subDirPath);

            List <ShowItem> matchingShows = showList.Where(si => si.NameMatch(di, TVSettings.Instance.UseFullPathNameToMatchSearchFolders)).ToList();

            if (matchingShows.Any())
            {
                bool dirCanBeRemoved = TVSettings.Instance.RemoveDownloadDirectoriesFiles;

                foreach (ShowItem si in matchingShows)
                {
                    if (FinderHelper.FileNeeded(di, si, dfc))
                    {
                        LOGGER.Info($"Not removing {di.FullName} as it may be needed for {si.ShowName}");
                        dirCanBeRemoved = false;
                    }
                }

                foreach (FileInfo neededFile in filesThatMayBeNeeded)
                {
                    if (neededFile.DirectoryName.Contains(di.FullName))
                    {
                        LOGGER.Info($"Not removing {di.FullName} as it may be needed for {neededFile.FullName}");
                        dirCanBeRemoved = false;
                    }
                }

                if (dirCanBeRemoved)
                {
                    returnActions.Add(SetupDirectoryRemoval(di, matchingShows));
                }
            }

            return(returnActions);
        }
Beispiel #13
0
        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();
            }
        }
Beispiel #14
0
        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);
        }
Beispiel #15
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 : "-") + ")");
            }
        }
        private void ReviewFilesInDownloadDirectory(string dirPath, IDialogParent owner)
        {
            try
            {
                foreach (string filePath in Directory.GetFiles(dirPath, "*", SearchOption.AllDirectories).Where(File.Exists))
                {
                    if (currentSettings.Token.IsCancellationRequested)
                    {
                        return;
                    }

                    FileInfo fi = new FileInfo(filePath);

                    if (fi.IgnoreFile())
                    {
                        continue;
                    }

                    List <ShowConfiguration> matchingShows        = showList.Where(si => si.NameMatch(fi, TVSettings.Instance.UseFullPathNameToMatchSearchFolders)).ToList();
                    List <ShowConfiguration> matchingShowsNoDupes = FinderHelper.RemoveShortShows(matchingShows);
                    if (matchingShowsNoDupes.Any())
                    {
                        ReviewFileInDownloadDirectory(currentSettings.Unattended, fi, matchingShowsNoDupes, owner);
                    }

                    List <MovieConfiguration> matchingMovies        = movieList.Where(mi => mi.NameMatch(fi, TVSettings.Instance.UseFullPathNameToMatchSearchFolders)).ToList();
                    List <MovieConfiguration> matchingNoDupesMovies = FinderHelper.RemoveShortShows(matchingMovies);
                    if (matchingNoDupesMovies.Any())
                    {
                        ReviewFileInDownloadDirectory(currentSettings.Unattended, fi, matchingNoDupesMovies, owner);
                    }
                }
            }
            catch (UnauthorizedAccessException ex)
            {
                LOGGER.Warn(ex, $"Could not access files in {dirPath}");
            }
            catch (DirectoryNotFoundException ex)
            {
                LOGGER.Warn(ex, $"Could not access files in {dirPath}");
            }
            catch (IOException ex)
            {
                LOGGER.Warn(ex, $"Could not access files in {dirPath}");
            }
        }
        protected override void DoCheck(SetProgressDelegate prog, ICollection <ShowItem> showList, TVDoc.ScanSettings settings)
        {
            //for each directory in settings directory
            //for each file in directory
            //for each saved show (order by recent)
            //does show match selected file?
            //if so add series to list of series scanned
            if (!Active())
            {
                LOGGER.Info("Not looking for new shows as 'Auto-Add' is turned off");
                return;
            }

            //Don't support unattended mode
            if (settings.Unattended || settings.Hidden)
            {
                LOGGER.Info("Not looking for new shows as app is unattended");
                return;
            }

            List <string>   possibleShowNames = GetPossibleShowNameStrings();
            List <ShowItem> addedShows        = FinderHelper.FindShows(possibleShowNames, MDoc);

            if (addedShows.Count <= 0)
            {
                return;
            }

            lock (TheTVDB.SERIES_LOCK)
            {
                MDoc.Library.AddRange(addedShows);
                MDoc.ShowAddedOrEdited(false, false, false);
            }
            MDoc.ShowAddedOrEdited(true, false, false);

            LOGGER.Info("Added new shows called: {0}", string.Join(",", addedShows.Select(s => s.ShowName)));

            //add each new show into the shows being scanned
            foreach (ShowItem si in addedShows)
            {
                showList.Add(si);
            }
        }
        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);
        }
Beispiel #19
0
        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));
        }
Beispiel #20
0
        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 ReviewFileInDownloadDirectory(bool unattended, FileInfo fi, [NotNull] List <MovieConfiguration> matchingMovies, IDialogParent owner)
        {
            bool             fileCanBeDeleted = TVSettings.Instance.RemoveDownloadDirectoriesFilesMatchMovies;
            ProcessedEpisode firstMatchingPep = null;

            if (!matchingMovies.Any())
            {
                // Some sort of random file - ignore
                fileCanBeDeleted = false;
            }

            var 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 (fileCanBeDeleted)
            {
                LOGGER.Info(
                    $"Removing {fi.FullName} as it matches { matchingMovies.Select(s => s.ShowName).ToCsv()} and no episodes are needed");

                returnActions.Add(new ActionDeleteFile(fi, matchingMovies.LongestShowName(), TVSettings.Instance.Tidyup));
            }
            else
            {
                filesThatMayBeNeeded.Add(fi);
            }
        }
Beispiel #22
0
        public AutoAddShow(string hint, string filename)
        {
            InitializeComponent();
            ShowConfiguration  = new ShowConfiguration();
            MovieConfiguration = new MovieConfiguration();
            bool assumeMovie = FinderHelper.IgnoreHint(hint);

            lblFileName.Text = "Filename: " + filename;

            tvCodeFinder = new TheTvdbCodeFinder("")
            {
                Dock = DockStyle.Fill
            };
            movieCodeFinder = new TmdbCodeFinder("")
            {
                Dock = DockStyle.Fill
            };

            (!assumeMovie ? tpTV : tpMovie).Show();

            tvCodeFinder.SetHint(hint);
            movieCodeFinder.SetHint(hint);

            tvCodeFinder.SelectionChanged    += MTCCF_SelectionChanged;
            movieCodeFinder.SelectionChanged += MTCCF_SelectionChanged;

            pnlCF.SuspendLayout();
            pnlCF.Controls.Add(tvCodeFinder);
            pnlCF.ResumeLayout();

            panel1.SuspendLayout();
            panel1.Controls.Add(movieCodeFinder);
            panel1.ResumeLayout();

            ActiveControl = (!assumeMovie ? tvCodeFinder : movieCodeFinder); // set initial focus to the code entry/show finder control

            UpdateDirectoryDropDown(cbDirectory, TVSettings.Instance.LibraryFolders, TVSettings.Instance.DefShowLocation, TVSettings.Instance.DefShowAutoFolders && TVSettings.Instance.DefShowUseDefLocation, tpTV);
            UpdateDirectoryDropDown(cbMovieDirectory, TVSettings.Instance.MovieLibraryFolders, TVSettings.Instance.DefMovieDefaultLocation, true, tpMovie);

            originalHint = hint;
        }
Beispiel #23
0
        private void ReviewDirInDownloadDirectory(string subDirPath)
        {
            //we are not checking for any file updates, so can return
            if (!TVSettings.Instance.RemoveDownloadDirectoriesFiles)
            {
                return;
            }

            if (!Directory.Exists(subDirPath))
            {
                return;
            }

            DirectoryInfo di = new DirectoryInfo(subDirPath);

            FileInfo neededFile = filesThatMayBeNeeded.FirstOrDefault(info => info.DirectoryName.Contains(di.FullName));

            if (neededFile != null)
            {
                LOGGER.Info($"Not removing {di.FullName} as it may be needed for {neededFile.FullName}");
                return;
            }

            List <ShowItem> matchingShows = showList.Where(si => si.NameMatch(di, TVSettings.Instance.UseFullPathNameToMatchSearchFolders)).ToList();

            if (!matchingShows.Any())
            {
                return;
            }

            ShowItem firstMatchingShow = matchingShows.FirstOrDefault(si => FinderHelper.FileNeeded(di, si, dfc));

            if (firstMatchingShow != null)
            {
                LOGGER.Info($"Not removing {di.FullName} as it may be needed for {firstMatchingShow.ShowName}");
                return;
            }

            returnActions.Add(SetupDirectoryRemoval(di, matchingShows));
        }
        private void AddItemToListView(string filename, int seas, int ep, int maxEp, TVSettings.FilenameProcessorRE?matchRex, bool r)
        {
            IEnumerable <ShowConfiguration> matchingShows = FinderHelper.FindMatchingShows(filename, shows);
            string bestShowName = FinderHelper.FindBestMatchingShow(filename, shows)?.ShowName;

            string otherShowNames    = matchingShows.Select(item => item.ShowName).Where(s => s != bestShowName).ToCsv();
            string showDisplayString = otherShowNames.Any() ? bestShowName + " - (" + otherShowNames + ")" : bestShowName;

            ListViewItem lvi = new ListViewItem {
                Text = filename
            };

            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);
        }
        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();
        }
Beispiel #26
0
        public static List <MediaConfiguration> FindMedia([NotNull] IEnumerable <FileInfo> possibleShows, TVDoc doc, IDialogParent owner)
        {
            List <MediaConfiguration> addedShows = new List <MediaConfiguration>();

            foreach (FileInfo file in possibleShows)
            {
                string hint = file.RemoveExtension(TVSettings.Instance.UseFullPathNameToMatchSearchFolders) + ".";

                //remove any search folders  from the hint. They are probbably useless at helping specify the showname
                foreach (var path in TVSettings.Instance.DownloadFolders)
                {
                    if (hint.StartsWith(path, StringComparison.OrdinalIgnoreCase))
                    {
                        hint = hint.RemoveFirst(path.Length);
                    }
                }

                //If the hint contains certain terms then we'll ignore it
                if (TVSettings.Instance.IgnoredAutoAddHints.Contains(hint))
                {
                    Logger.Info(
                        $"Ignoring {hint} as it is in the list of ignored terms the user has selected to ignore from prior Auto Adds.");

                    continue;
                }

                //Remove any (nnnn) in the hint - probably a year
                string refinedHint = Regex.Replace(hint, @"\(\d{4}\)", "");

                //Remove anything we can from hint to make it cleaner and hence more likely to match
                refinedHint = RemoveSeriesEpisodeIndicators(refinedHint, doc.TvLibrary.SeasonWords());

                if (string.IsNullOrWhiteSpace(refinedHint))
                {
                    Logger.Info($"Ignoring {hint} as it refines to nothing.");
                    continue;
                }

                //if hint doesn't match existing added shows
                if (LookForSeries(refinedHint, addedShows))
                {
                    Logger.Info($"Ignoring {hint} as it matches shows already being added.");
                    continue;
                }
                if (LookForMovies(refinedHint, addedShows))
                {
                    Logger.Info($"Ignoring {hint} as it matches existing movies already being added: {addedShows.Where(si => si.NameMatch(refinedHint)).Select(s => s.ShowName).ToCsv()}");
                    continue;
                }

                //if hint doesn't match existing added shows
                if (LookForSeries(refinedHint, doc.TvLibrary.Shows))
                {
                    Logger.Info($"Ignoring {hint} as it matches shows already in the library.");
                    continue;
                }
                if (LookForMovies(refinedHint, doc.FilmLibrary.Movies))
                {
                    Logger.Info($"Ignoring {hint} as it matches existing movies already in the library: {doc.FilmLibrary.Movies.Where(si => si.NameMatch(refinedHint)).Select(s=>s.ShowName).ToCsv()}");
                    continue;
                }

                //If there are no LibraryFolders then we cant use the simplified UI
                if (TVSettings.Instance.LibraryFolders.Count + TVSettings.Instance.MovieLibraryFolders.Count == 0)
                {
                    MessageBox.Show(
                        "Please add some monitor (library) folders under 'Bulk Add Shows' to use the 'Auto Add' functionality (Alternatively you can add them or turn it off in settings).",
                        "Can't Auto Add Show", MessageBoxButtons.OK, MessageBoxIcon.Error);

                    continue;
                }

                bool assumeMovie = FinderHelper.IgnoreHint(hint) || !file.FileNameNoExt().ContainsAnyCharactersFrom("0123456789");

                if (assumeMovie && TVSettings.Instance.DefMovieDefaultLocation.HasValue() && TVSettings.Instance.DefMovieUseDefaultLocation && true)//todo use  TVSettings.Instance.AutomateAutoAddWhenOneMovieFound
                {
                    //TODO - Make generic, currently uses TMDB only
                    CachedMovieInfo?foundMovie = TMDB.LocalCache.Instance.GetMovie(refinedHint, null, TVSettings.Instance.TMDBLanguage, true, true);
                    if (foundMovie != null)
                    {
                        // no need to popup dialog
                        Logger.Info($"Auto Adding New Movie for '{refinedHint}' (directly) : {foundMovie.Name}");

                        MovieConfiguration newMovie = new MovieConfiguration();
                        newMovie.TmdbCode            = foundMovie.TmdbCode;
                        newMovie.UseAutomaticFolders = true;
                        newMovie.AutomaticFolderRoot = TVSettings.Instance.DefMovieDefaultLocation;
                        newMovie.Format = MovieConfiguration.MovieFolderFormat.singleDirectorySingleFile;
                        newMovie.UseCustomFolderNameFormat = false;
                        newMovie.ConfigurationProvider     = TVDoc.ProviderType.TMDB;

                        if (!hint.Contains(foundMovie?.Name ?? string.Empty, StringComparison.OrdinalIgnoreCase))
                        {
                            newMovie.AliasNames.Add(hint);
                        }


                        addedShows.Add(newMovie);
                        doc.Stats().AutoAddedMovies++;
                        continue;
                    }
                }
                //popup dialog
                AutoAddMedia askForMatch = new AutoAddMedia(refinedHint, file, assumeMovie);

                if (askForMatch.SingleTvShowFound && !askForMatch.SingleMovieFound && true) //todo use  TVSettings.Instance.AutomateAutoAddWhenOneShowFound
                {
                    // no need to popup dialog
                    Logger.Info($"Auto Adding New Show for '{refinedHint}' : {askForMatch.ShowConfiguration.CachedShow.Name}");
                    addedShows.Add(askForMatch.ShowConfiguration);
                    doc.Stats().AutoAddedShows++;
                }
                else if (askForMatch.SingleMovieFound && !askForMatch.SingleTvShowFound && true) //todo use  TVSettings.Instance.AutomateAutoAddWhenOneMovieFound
                {
                    // no need to popup dialog
                    Logger.Info($"Auto Adding New Movie for '{refinedHint}' : {askForMatch.MovieConfiguration.CachedMovie.Name}");
                    addedShows.Add(askForMatch.MovieConfiguration);
                    doc.Stats().AutoAddedMovies++;
                }
                else
                {
                    Logger.Info($"Auto Adding New Show/Movie by asking about for '{refinedHint}'");
                    owner.ShowChildDialog(askForMatch);
                    DialogResult dr = askForMatch.DialogResult;

                    if (dr == DialogResult.OK)
                    {
                        //If added add show ot collection
                        if (askForMatch.ShowConfiguration.Code > 0)
                        {
                            addedShows.Add(askForMatch.ShowConfiguration);
                            doc.Stats().AutoAddedShows++;
                        }
                        else if (askForMatch.MovieConfiguration.Code > 0)
                        {
                            addedShows.Add(askForMatch.MovieConfiguration);
                            doc.Stats().AutoAddedMovies++;
                        }
                    }
                    else if (dr == DialogResult.Abort)
                    {
                        Logger.Info("Skippng Auto Add Process");
                        break;
                    }
                    else if (dr == DialogResult.Ignore)
                    {
                        Logger.Info($"Permenantly Ignoring 'Auto Add' for: {hint}");
                        TVSettings.Instance.IgnoredAutoAddHints.Add(hint);
                    }
                    else
                    {
                        Logger.Info($"Cancelled Auto adding new show/movie {hint}");
                    }
                }

                askForMatch.Dispose();
            }

            return(addedShows);
        }
        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);
            }
        }
Beispiel #28
0
        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, [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);
            }
        }