/// <summary> /// Run through all TV shows all looks for episodes that may need to be renamed and for missing episodes. /// For missing episodes it attempts to match them to files from the search directories. /// </summary> /// <param name="shows">Shows to scan</param> /// <param name="queuedItems">Items currently in queue (to be skipped)</param> /// <returns></returns> public List <OrgItem> RunScan(List <Content> shows, List <OrgItem> queuedItems) { // Set running flag scanRunning = true; // Initialiaze scan items List <OrgItem> missingCheckItem = new List <OrgItem>(); // Initialize item numbers int number = 0; // Go through each show for (int i = 0; i < shows.Count; i++) { TvShow show = (TvShow)shows[i]; if (cancelRequested) { break; } OnProgressChange(ScanProcess.TvRename, shows[i].DatabaseName, (int)Math.Round((double)i / (shows.Count) * 30) + 70); // Go each show foreach (TvEpisode ep in show.Episodes) { // Check for cancellation if (cancelRequested) { break; } // Skipped ignored episodes if (ep.Ignored) { continue; } // Init found flag bool found = false; // Rename check if (ep.Missing == MissingStatus.Located) { if (shows[i].DoRenaming) { found = true; TvEpisode ep2 = null; if (ep.File.MultiPart) { if (ep.File.Part == 1) { foreach (TvEpisode epEnumerated in show.Episodes) { if (epEnumerated.Season == ep.Season && epEnumerated.DisplayNumber == ep.DisplayNumber + 1) { ep2 = epEnumerated; break; } } } else { continue; } } // Build desired path string builtPath = show.BuildFilePath(ep, ep2, Path.GetExtension(ep.File.FilePath)); // Check if rename needed (or move within folder) if (ep.File.FilePath != builtPath) { OrgItem newItem = new OrgItem(OrgStatus.Organization, OrgAction.Rename, ep.File.FilePath, builtPath, ep, ep2, FileCategory.TvVideo, null); newItem.Enable = true; if (!show.DoRenaming) { newItem.Category = FileCategory.Ignored; } newItem.Number = number++; missingCheckItem.Add(newItem); } } else { continue; } } else { continue; } // Add empty item for missing if (!found && ep.Aired && show.DoMissingCheck) { OrgItem newItem = new OrgItem(OrgStatus.Missing, OrgAction.None, ep, null, FileCategory.TvVideo, null); if (!show.DoRenaming) { newItem.Category = FileCategory.Ignored; } newItem.Number = number++; missingCheckItem.Add(newItem); } } } // Convert all TV folders to org folders List <OrgFolder> tvFoldersAsOrgFolders = new List <OrgFolder>(); foreach (ContentRootFolder tvFolder in Settings.TvFolders) { OrgFolder orgFolder = new OrgFolder(tvFolder.FullPath, false, false, false, false); tvFoldersAsOrgFolders.Add(orgFolder); } // Update progress OnProgressChange(ScanProcess.TvRename, string.Empty, 100); // Clear flags scanRunning = false; cancelRequested = false; // Return results return(missingCheckItem); }
public OrgItem ProcessPath(OrgPath orgPath, bool tvOnlyCheck, bool skipMatching, bool fast, bool threaded, int procNumber, bool allowFromLog, out bool fromLog) { fromLog = false; FileCategory fileCat = FileHelper.CategorizeFile(orgPath, orgPath.Path); // Search through dir scan log for matching source if (allowFromLog) { for (int i = 0; i < Organization.DirScanLog.Count; i++) { if (Organization.DirScanLog[i].SourcePath == orgPath.Path) { fromLog = true; if (fileCat != Organization.DirScanLog[i].Category) { break; } OrgItem newItem = UpdateItemFromPrevious(orgPath, Organization.DirScanLog[i], threaded, false, false); if (newItem != null && newItem.Action != OrgAction.None) { if (newItem.CanEnable) { newItem.Enable = true; } return(newItem); } } } } // If similar to earlier item, wait for it to complete before continuing while (!skipMatching && orgPath.SimilarTo >= 0 && orgPath.SimilarTo < this.Items.Count && (this.Items[orgPath.SimilarTo].Action == OrgAction.TBD || this.Items[orgPath.SimilarTo].Action == OrgAction.Processing)) { // Check for cancellation if (scanCanceled || procNumber < scanNumber) { return(null); } Thread.Sleep(50); } // If similar to earlier item, check if we can use it's info if (orgPath.SimilarTo > 0 && orgPath.SimilarTo < this.Items.Count) { OrgItem newItem = UpdateItemFromPrevious(orgPath, this.Items[orgPath.SimilarTo], threaded, fast, skipMatching); if (newItem != null) { return(newItem); } } // Create default none item OrgItem noneItem = new OrgItem(OrgAction.None, orgPath.Path, fileCat, orgPath.OrgFolder); // Setup match to filename and folder name (if it's in a folder inside of downloads) string pathBase; if (!string.IsNullOrEmpty(orgPath.OrgFolder.FolderPath)) { pathBase = orgPath.Path.Replace(orgPath.OrgFolder.FolderPath, ""); } else { pathBase = orgPath.Path; } string[] pathSplit; if (!string.IsNullOrEmpty(orgPath.OrgFolder.FolderPath)) { pathSplit = pathBase.Split('\\'); } else { pathSplit = orgPath.Path.Split('\\'); } pathSplit[pathSplit.Length - 1] = Path.GetFileNameWithoutExtension(pathSplit[pathSplit.Length - 1]); List <string> possibleMatchPaths = new List <string>(); // Looking to remove dummy video files by rip (e.g. "ERTG.mp4" inside "The Matrix 1999 hd ERGT rip/" folder) List <string> validSplits = new List <string>(); for (int i = pathSplit.Length - 1; i > 0; i--) { if (string.IsNullOrWhiteSpace(pathSplit[i])) { continue; } bool containedInOther = false; for (int j = i - 1; j > 0; j--) { if (pathSplit[j].Length > pathSplit[i].Length && pathSplit[j].ToLower().Contains(pathSplit[i].ToLower())) { containedInOther = true; break; } } if (!containedInOther) { validSplits.Add(pathSplit[i]); possibleMatchPaths.Add(pathSplit[i] + Path.GetExtension(orgPath.Path)); } } for (int i = validSplits.Count - 1; i >= 0; i--) { string build = string.Empty; for (int j = validSplits.Count - 1; j >= i; j--) { build += validSplits[j] + " "; } build = build.Trim(); build += Path.GetExtension(orgPath.Path); if (!possibleMatchPaths.Contains(build)) { possibleMatchPaths.Add(build); } } // Try to match to each string foreach (string matchString in possibleMatchPaths) { OnDebugNotificationd("Attempting to match to path: \" " + matchString + "\""); // Get simple file name string simpleFile = FileHelper.BasicSimplify(Path.GetFileNameWithoutExtension(matchString), false); OnDebugNotificationd("Simplifies to: \" " + simpleFile + "\""); // Categorize current match string FileCategory matchFileCat = FileHelper.CategorizeFile(orgPath, matchString); OnDebugNotificationd("Classified as: " + matchFileCat); // Check tv if (tvOnlyCheck && matchFileCat != FileCategory.TvVideo) { continue; } // Check for cancellation if (scanCanceled || procNumber < scanNumber) { return(null); } // Set whether item is for new show bool newShow = false; // Check for cancellation if (scanCanceled || procNumber < scanNumber) { return(null); } // Add appropriate action based on file category switch (matchFileCat) { // TV item case FileCategory.TvVideo: // Try to match file to existing show Dictionary <TvShow, MatchCollection> matches = new Dictionary <TvShow, MatchCollection>(); for (int j = 0; j < Organization.Shows.Count; j++) { MatchCollection match = Organization.Shows[j].MatchFileToContent(matchString); if (match != null && match.Count > 0) { matches.Add((TvShow)Organization.Shows[j], match); } } // Try to match to temporary show lock (directoryScanLock) foreach (TvShow show in temporaryShows) { MatchCollection match = show.MatchFileToContent(matchString); if (match != null && match.Count > 0 && !matches.ContainsKey(show)) { matches.Add(show, match); } } // Debug notification for show matches string matchNotification = "Show name matches found: "; if (matches.Count == 0) { matchNotification += "NONE"; } else { foreach (TvShow match in matches.Keys) { matchNotification += match.DisplayName + ", "; } matchNotification = matchNotification.Substring(0, matchNotification.Length - 2); } OnDebugNotificationd(matchNotification); // Check for matches to existing show TvShow bestMatch = null; if (matches.Count > 0) { // Find best match, based on length int longestMatch = 2; // minimum of 3 letters must match (acronym) foreach (KeyValuePair <TvShow, MatchCollection> match in matches) { foreach (Match m in match.Value) { // Check that match is not part of a word int matchWordCnt = 0; for (int l = m.Index; l < simpleFile.Length; l++) { if (simpleFile[l] == ' ') { break; } else { ++matchWordCnt; } } if (m.Value.Trim().Length > longestMatch && m.Length >= matchWordCnt) { longestMatch = m.Length; bestMatch = match.Key; } } } if (bestMatch != null) { OnDebugNotificationd("Matched to show " + bestMatch.DisplayName); } } // Episode not matched to a TV show, search database! if (bestMatch == null && !skipMatching) { OnDebugNotificationd("Not matched to existing show; searching database."); // Setup search string string showFile = Path.GetFileNameWithoutExtension(matchString); // Perform search for matching TV show if (SearchHelper.TvShowSearch.ContentMatch(showFile, string.Empty, string.Empty, fast, threaded, out bestMatch)) { ContentRootFolder defaultTvFolder; string path = NO_TV_FOLDER; if (Settings.GetTvFolderForContent(bestMatch, out defaultTvFolder)) { path = defaultTvFolder.FullPath; } bestMatch.RootFolder = path; bestMatch.Path = bestMatch.BuildFolderPath(); // Save show in temporary shows list (in case there are more files that may match to it during scan) lock (directoryScanLock) if (!temporaryShows.Contains(bestMatch)) { temporaryShows.Add(bestMatch); } newShow = true; } else { bestMatch = null; } } else if (temporaryShows.Contains(bestMatch)) { newShow = true; } // Episode has been matched to a TV show if (bestMatch != null) { // Try to get episode information from file int seasonNum, episodeNum1, episodeNum2; if (FileHelper.GetEpisodeInfo(matchString, bestMatch.DatabaseName, out seasonNum, out episodeNum1, out episodeNum2)) { // No season match means file only had episode number, allow this for single season shows if (seasonNum == -1) { int maxSeason = 0; foreach (int season in bestMatch.Seasons) { if (season > maxSeason) { maxSeason = season; } } if (maxSeason == 1) { seasonNum = 1; } } // Try to get the episode from the show TvEpisode episode1, episode2 = null; if (bestMatch.FindEpisode(seasonNum, episodeNum1, false, out episode1)) { if (episodeNum2 != -1) { bestMatch.FindEpisode(seasonNum, episodeNum2, false, out episode2); } OrgAction action = orgPath.Copy ? OrgAction.Copy : OrgAction.Move; // If item episode already exists set action to duplicate if (episode1.Missing == MissingStatus.Located) { action = OrgAction.AlreadyExists; } // Build action and add it to results string destination = bestMatch.BuildFilePath(episode1, episode2, Path.GetExtension(orgPath.Path)); OrgItem newItem = new OrgItem(action, orgPath.Path, destination, episode1, episode2, fileCat, orgPath.OrgFolder); if (destination.StartsWith(NO_TV_FOLDER)) { newItem.Action = OrgAction.NoRootFolder; } if (newItem.Action == OrgAction.AlreadyExists || newItem.Action == OrgAction.NoRootFolder) { newItem.Enable = false; } else { newItem.Enable = true; } newItem.Category = matchFileCat; newItem.IsNewShow = newShow; return(newItem); } else { OnDebugNotificationd("Couldn't find episode for season " + seasonNum + " episods " + episodeNum1); } } } // No match to TV show if (!tvOnlyCheck && !fast) { // Try to match to a movie OrgItem movieItem; if (CreateMovieAction(orgPath, matchString, out movieItem, threaded, fast, skipMatching, null)) { noneItem.Clone(movieItem); } } break; // Movie item case FileCategory.MovieVideo: // Create action OrgItem item; CreateMovieAction(orgPath, matchString, out item, threaded, fast, skipMatching, null); // If delete action created (for sample file) if (item.Action == OrgAction.Delete) { return(BuildDeleteAction(orgPath, fileCat)); } else if (item.Action != OrgAction.None) { return(item); } break; // Trash case FileCategory.Trash: return(BuildDeleteAction(orgPath, matchFileCat)); // Ignore case FileCategory.Ignored: return(new OrgItem(OrgAction.None, orgPath.Path, matchFileCat, orgPath.OrgFolder)); // Unknown default: return(new OrgItem(OrgAction.None, orgPath.Path, matchFileCat, orgPath.OrgFolder)); } // Check for cancellation if (scanCanceled || procNumber < scanNumber) { return(noneItem); } } // If no match on anything set none action return(noneItem); }
/// <summary> /// Open file dialog so user can locate episode in file directory. /// </summary> /// <param name="showActionModifer">Whether to display action modifier after path is selected</param> /// <param name="copyAction">Whether to copy file, move otherwise</param> /// <param name="items">Organization items to add move/copy action to once located</param> /// <returns>true if locate was sucessful</returns> public bool UserLocate(bool showActionModifer, bool copyAction, out List <OrgItem> items) { // Initialize org items items = new List <OrgItem>(); // Open file dialog VistaOpenFileDialog ofd = new VistaOpenFileDialog(); ofd.Filter = "All Files|*.*"; if (!(bool)ofd.ShowDialog()) { return(false); } // Try to get episode information from file int fileEpSeason, fileEpNumber1, fileEpNumber2; bool fileInfoFound = FileHelper.GetEpisodeInfo(ofd.FileName, this.Show.DatabaseName, out fileEpSeason, out fileEpNumber1, out fileEpNumber2); // Assign episodes TvEpisode ep1 = this; TvEpisode ep2 = null; // Locate could be for double episode file, only taken if one of the episode from file matches selected if (fileInfoFound && fileEpSeason == this.Season) { if (fileEpNumber1 == this.DisplayNumber) { if (fileEpNumber2 > 0) { show.FindEpisode(ep1.Season, fileEpNumber2, false, out ep2); } else { ep2 = null; } } else if (fileEpNumber2 == this.DisplayNumber) { if (fileEpNumber1 > 0 && show.FindEpisode(this.Season, fileEpNumber1, false, out ep1)) { ep2 = this; } else { ep1 = this; } } } // Build org item OrgAction action = copyAction ? OrgAction.Copy : OrgAction.Move; string destination = show.BuildFilePath(ep1, ep2, Path.GetExtension(ofd.FileName)); OrgItem item = new OrgItem(OrgStatus.Found, action, ofd.FileName, destination, ep1, ep2, FileCategory.TvVideo, null); // Display modifier if (showActionModifer) { OrgItemEditorWindow editor = new OrgItemEditorWindow(item); editor.ShowDialog(); // Add results if valid if (editor.Results == null) { return(false); } items.Add(editor.Results); return(true); } items.Add(item); return(true); }