Example #1
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
            });
        }
Example #2
0
        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);
            }
        }
Example #3
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);
            }
        }
Example #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);
        }
        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);
        }
Example #6
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();
            }
        }
Example #7
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);
        }
Example #8
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 : "-") + ")");
            }
        }
Example #9
0
        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);
        }
Example #10
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));
        }
Example #11
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 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();
        }
Example #13
0
        // 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);
            }
        }
Example #16
0
        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);
            }
        }
Example #17
0
        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);
        }
Example #19
0
        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
        }
Example #20
0
        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);
        }
Example #21
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, 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);
        }