private OrgItem UpdateItemFromPrevious(OrgPath orgPath, OrgItem reuseItem, bool threaded, bool fast, bool skipMatching) { switch (reuseItem.Action) { case OrgAction.Delete: OrgItem copyItem = new OrgItem(reuseItem); copyItem.SourcePath = orgPath.Path; copyItem.BuildDestination(); return(copyItem); case OrgAction.Move: case OrgAction.Copy: if (reuseItem.Category == FileCategory.MovieVideo) { OrgItem item; CreateMovieAction(orgPath, orgPath.Path, out item, threaded, fast, skipMatching, reuseItem.Movie); return(item); } else if (reuseItem.Category == FileCategory.TvVideo) { if (!Organization.Shows.Contains(reuseItem.TvEpisode.Show) && !temporaryShows.Contains(reuseItem.TvEpisode.Show)) { lock (directoryScanLock) temporaryShows.Add(reuseItem.TvEpisode.Show); } } break; default: break; } return(null); }
/// <summary> /// Creates a delete action for a file. /// </summary> /// <param name="scanResults">The current scan action list to add action to.</param> /// <param name="file">The file to be deleted</param> /// <param name="fileCat">The category of the file</param> private OrgItem BuildDeleteAction(OrgPath file, FileCategory fileCat) { OrgItem newItem; if (file.AllowDelete) { newItem = new OrgItem(OrgAction.Delete, file.Path, fileCat, file.OrgFolder); newItem.Enable = true; } else { newItem = new OrgItem(OrgAction.None, file.Path, fileCat, file.OrgFolder); } return(newItem); }
/// <summary> /// Thread wrapper for processing delegate. /// </summary> /// <param name="stateInfo">Arguments for processing</param> private void ProcessThread(object stateInfo) { object[] args = (object[])stateInfo; OrgPath orgPath = (OrgPath)args[0]; int totalPaths = (int)args[1]; int updateNumber = (int)args[2]; object processSpecificArgs = args[3]; lock (processingLock) ++numItemStarted; process(orgPath, updateNumber, totalPaths, this.ProcessNumber, ref numItemProcessed, ref numItemStarted, processSpecificArgs); lock (processingLock) ++numItemProcessed; }
public static readonly int SAMPLE_FILE_SIZE_THRESHOLD = 10485760; // 10 MB /// <summary> /// Categorize a file based on extension /// </summary> /// <param name="file">The file name string</param> /// <param name="matchTo">String to match TV categorization to</param> /// <returns>The file category</returns> public static FileCategory CategorizeFile(OrgPath file, string matchTo) { // Check if ignored if (file.OrgFolder != null && file.OrgFolder.IsIgnored(file.Path)) { return(FileCategory.Ignored); } // Check against each type foreach (string ext in Settings.VideoFileTypes) { if (FileTypeMatch(ext, file.Path)) { // Check if sample! if (matchTo.ToLower().Contains("sample") || (new FileInfo(file.Path)).Length < SAMPLE_FILE_SIZE_THRESHOLD) { return(FileCategory.Trash); } if (IsTvEpisode(matchTo)) { return(FileCategory.TvVideo); } else { return(FileCategory.MovieVideo); } } } foreach (string ext in Settings.DeleteFileTypes) { if (FileTypeMatch(ext, file.Path)) { return(FileCategory.Trash); } } foreach (string ext in Settings.IgnoreFileTypes) { if (FileTypeMatch(ext, file.Path)) { return(FileCategory.Ignored); } } return(FileCategory.Unknown); }
/// <summary> /// Categorize a file based on extension /// </summary> /// <param name="file">The file name string</param> /// <param name="matchTo">String to match TV categorization to</param> /// <returns>The file category</returns> public static FileCategory CategorizeFile(OrgPath file, string matchTo) { // Check if ignored if (file.OrgFolder != null && file.OrgFolder.IsIgnored(file.Path)) return FileCategory.Ignored; // Check against each type foreach (string ext in Settings.VideoFileTypes) { if (FileTypeMatch(ext, file.Path)) { // Check if sample! if (matchTo.ToLower().Contains("sample")) return FileCategory.Trash; if (IsTvEpisode(matchTo)) return FileCategory.TvVideo; else return FileCategory.MovieVideo; } } foreach (string ext in Settings.DeleteFileTypes) { if (FileTypeMatch(ext, file.Path)) return FileCategory.Trash; } foreach (string ext in Settings.IgnoreFileTypes) { if (FileTypeMatch(ext, file.Path)) return FileCategory.Ignored; } return FileCategory.Unknown; }
private OrgItem UpdateItemFromPrevious(OrgPath orgPath, OrgItem reuseItem, bool threaded, bool fast, bool skipMatching) { switch (reuseItem.Action) { case OrgAction.Delete: OrgItem copyItem = new OrgItem(reuseItem); copyItem.SourcePath = orgPath.Path; copyItem.BuildDestination(); return copyItem; case OrgAction.Move: case OrgAction.Copy: if (reuseItem.Category == FileCategory.MovieVideo) { OrgItem item; CreateMovieAction(orgPath, orgPath.Path, out item, threaded, fast, skipMatching, reuseItem.Movie); return item; } else if (reuseItem.Category == FileCategory.TvVideo) { if (!Organization.Shows.Contains(reuseItem.TvEpisode.Show) && !temporaryShows.Contains(reuseItem.TvEpisode.Show)) lock (directoryScanLock) temporaryShows.Add(reuseItem.TvEpisode.Show); } break; default: break; } return null; }
/// <summary> /// Directory scan processing method (thread) for a single file path. /// </summary> /// <param name="orgPath">Organization path instance to be processed</param> /// <param name="pathNum">The path's number out of total being processed</param> /// <param name="totalPaths">Total number of paths being processed</param> /// <param name="updateNumber">The identifier for the OrgProcessing instance</param> /// <param name="background">Whether processing is running as a background operation</param> /// <param name="subSearch">Whether processing is sub-search(TV only)</param> /// <param name="processComplete">Delegate to be called by processing when completed</param> /// <param name="numItemsProcessed">Number of paths that have been processed - used for progress updates</param> private void ThreadProcess(OrgPath orgPath, int pathNum, int totalPaths, int updateNumber, ref int numItemsProcessed, ref int numItemsStarted, object processSpecificArgs) { // Process specific arguments object[] args = (object[])processSpecificArgs; bool tvOnlyCheck = (bool)args[0]; bool skipMatching = (bool)args[1]; bool fast = (bool)args[2]; int procNumber = (int)args[3]; int pass = (int)args[4]; bool reuseResults = (bool)args[5]; // Check if item is already processed if (this.Items[pathNum].Action != OrgAction.TBD) return; // Check if file is in the queue bool alreadyQueued = false; if (itemsInQueue != null) for (int i = 0; i < itemsInQueue.Count; i++) if (itemsInQueue[i].SourcePath == orgPath.Path) { OrgItem newItem = new OrgItem(itemsInQueue[i]); newItem.Action = OrgAction.Queued; newItem.Enable = true; UpdateResult(newItem, pathNum, procNumber); alreadyQueued = true; break; } // If item is already in the queue, skip it if (alreadyQueued) ProcessUpdate(orgPath.Path, totalPaths, pass); else { // Update progress lock (directoryScanLock) this.Items[pathNum].Action = OrgAction.Processing; // Process path bool fromLog; OrgItem results = ProcessPath(orgPath, tvOnlyCheck, skipMatching || (!fast && pass == 0), fast || pass == 0, true, procNumber, reuseResults, out fromLog); // Update results and progress if (results != null && (results.Action != OrgAction.None || results.Category == FileCategory.Ignored || fast || pass == 1)) { UpdateResult(results, pathNum, procNumber); ProcessUpdate(orgPath.Path, totalPaths, pass); if (!fromLog && (results.Action != OrgAction.None && (results.Category == FileCategory.MovieVideo || results.Category == FileCategory.TvVideo))) { Organization.AddDirScanLogItem(new OrgItem(results)); } } else lock (directoryScanLock) this.Items[pathNum].Action = OrgAction.TBD; } }
/// <summary> /// Tries to create a movie action item for a scan from a file. /// </summary> /// <param name="file">The file to create movie action from</param> /// <param name="item">The resulting movie action</param> /// <returns>Whether the file was matched to a movie</returns> private bool CreateMovieAction(OrgPath file, string matchString, out OrgItem item, bool threaded, bool fast, bool skipMatching, Movie knownMovie) { // Initialize item item = new OrgItem(OrgAction.None, file.Path, FileCategory.MovieVideo, file.OrgFolder); // Check if sample! if (matchString.ToLower().Contains("sample")) { item.Action = OrgAction.Delete; return false; } // Try to match file to movie string search = Path.GetFileNameWithoutExtension(matchString); // Search for match to movie Movie searchResult = null; bool searchSucess = false; if (!skipMatching) searchSucess = SearchHelper.MovieSearch.ContentMatch(search, string.Empty, string.Empty, fast, threaded, out searchResult, knownMovie); // Add closest match item if (searchSucess) { // Get root folder ContentRootFolder rootFolder; string path; if (Settings.GetMovieFolderForContent(searchResult, out rootFolder)) searchResult.RootFolder = rootFolder.FullPath; else { searchResult.RootFolder = NO_MOVIE_FOLDER; item.Action = OrgAction.NoRootFolder; } if (item.Action != OrgAction.NoRootFolder) item.Action = file.Copy ? OrgAction.Copy : OrgAction.Move; item.DestinationPath = searchResult.BuildFilePath(file.Path); if (File.Exists(item.DestinationPath)) item.Action = OrgAction.AlreadyExists; searchResult.Path = searchResult.BuildFolderPath(); item.Movie = searchResult; if (item.Action == OrgAction.AlreadyExists || item.Action == OrgAction.NoRootFolder) item.Enable = false; else item.Enable = true; return true; } return false; }
/// <summary> /// Creates a delete action for a file. /// </summary> /// <param name="scanResults">The current scan action list to add action to.</param> /// <param name="file">The file to be deleted</param> /// <param name="fileCat">The category of the file</param> private OrgItem BuildDeleteAction(OrgPath file, FileCategory fileCat) { OrgItem newItem; if (file.AllowDelete) { newItem = new OrgItem(OrgAction.Delete, file.Path, fileCat, file.OrgFolder); newItem.Enable = true; } else { newItem = new OrgItem(OrgAction.None, file.Path, fileCat, file.OrgFolder); } return newItem; }
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; }
void worker_DoWork(object sender, DoWorkEventArgs e) { bool custom = (bool)e.Argument; OrgPath path; if (custom) { string matchTo = this.MatchString; if (!this.MatchString.Contains('.')) matchTo += ".avi"; path = new OrgPath(matchTo, false, true, new OrgFolder()); } else path = this.SelectedScanDirPath; App.Current.Dispatcher.BeginInvoke((Action)delegate { this.MatchProcessing.Clear(); }); bool fromLog; OrgItem item = scan.ProcessPath(path, false, false, false, false, 0, false, out fromLog); App.Current.Dispatcher.BeginInvoke((Action)delegate { this.MatchProcessing.Add("Final result: " + item.ToString()); }); }
private void UpdateScanDirPaths() { this.ScanDirPaths.Clear(); List<OrgItem> autoMoves; List<OrgPath> paths = scan.GetFolderFiles(Settings.ScanDirectories.ToList(), true, true, out autoMoves); foreach (OrgPath path in paths) { FileCategory fileCat = FileHelper.CategorizeFile(path, path.Path); if (fileCat == FileCategory.TvVideo || fileCat == FileCategory.MovieVideo) this.ScanDirPaths.Add(path); } if (this.ScanDirPaths.Count > 0) this.SelectedScanDirPath = this.ScanDirPaths[0]; }
/// <summary> /// Update processing method (thread) for single content folder in root. /// </summary> /// <param name="orgPath">Organization path instance to be processed</param> /// <param name="pathNum">The path's number out of total being processed</param> /// <param name="totalPaths">Total number of paths being processed</param> /// <param name="processNumber">The identifier for the OrgProcessing instance</param> /// <param name="numItemsProcessed">Number of paths that have been processed - used for progress updates</param> /// <param name="numItemsStarted">Number of paths that have had been added to thread pool for processing</param> /// <param name="processSpecificArgs">Arguments specific to this process</param> private void UpdateProcess(OrgPath orgPath, int pathNum, int totalPaths, int processNumber, ref int numItemsProcessed, ref int numItemsStarted, object processSpecificArgs) { // Check for cancellation - this method is called from thread pool, so cancellation could have occured by the time this is run if (updateCancelled || this.updateNumber != processNumber) { return; } // First pass run does quick folder update by skipping online database searching bool firstPass = (bool)processSpecificArgs; string passString = firstPass ? " (First Pass)" : " (Second Pass)"; // Set processing messge string progressMsg = "Updating of '" + orgPath.RootFolder.FullPath + "'" + passString + " - '" + Path.GetFileName(orgPath.Path) + "' started"; OnUpdateProgressChange(this, false, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); // Get content collection to add content to ContentCollection content = GetContentCollection(); // Check if folder already has a match to existing content bool contentExists = false; bool contentComplete = false; Content newContent = null; int index = 0; for (int j = 0; j < content.Count; j++) { if (Path.Equals(orgPath.Path, content[j].Path)) { contentExists = true; content[j].Found = true; if (!string.IsNullOrEmpty(content[j].DatabaseName)) { contentComplete = true; } newContent = content[j]; index = j; break; } } // Set completed progess message progressMsg = "Updating of '" + orgPath.RootFolder.FullPath + "'" + passString + " - '" + Path.GetFileName(orgPath.Path) + "' complete"; // Check if content found if (contentExists && contentComplete) { // Check if content needs updating if ((DateTime.Now - newContent.LastUpdated).TotalDays > 7 && this.ContentType == ContentType.TvShow) { newContent.UpdateInfoFromDatabase(); } // Update progress if (this.updateNumber == processNumber) { OnUpdateProgressChange(this, false, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); } return; } // Folder wasn't matched to an existing content instance, try tmatch folder to content from online database Content match; bool matchSucess; switch (this.ContentType) { case ContentType.TvShow: TvShow showMatch; matchSucess = SearchHelper.TvShowSearch.PathMatch(orgPath.RootFolder.FullPath, orgPath.Path, firstPass, true, out showMatch); match = showMatch; break; case ContentType.Movie: Movie movieMatch; matchSucess = SearchHelper.MovieSearch.PathMatch(orgPath.RootFolder.FullPath, orgPath.Path, firstPass, true, out movieMatch); match = movieMatch; break; default: throw new Exception("unknown content type"); } // Check that current process hasn't been replaced - search can be slow, so update may have been cancelled by the time it gets here if (updateCancelled || this.updateNumber != processNumber) { return; } // Folder already existed, but wasn't previously match to valid content if (contentExists && matchSucess) { switch (this.ContentType) { case ContentType.TvShow: ((TvShow)newContent).CloneAndHandlePath((TvShow)match, true); ((TvShow)newContent).UpdateMissing(); break; case ContentType.Movie: ((Movie)newContent).CloneAndHandlePath((Movie)match, true); break; default: throw new Exception("unknown content type"); } newContent.LastUpdated = DateTime.Now; } else if (matchSucess) { newContent = match; } else { switch (this.ContentType) { case ContentType.TvShow: newContent = new TvShow(string.Empty, 0, 0, orgPath.Path, orgPath.RootFolder.FullPath); break; case ContentType.Movie: newContent = new Movie(string.Empty, 0, 0, orgPath.Path, orgPath.RootFolder.FullPath); break; default: throw new Exception("unknown content type"); } } // Set found flag newContent.Found = true; // Add content to list if new if (!contentExists) { content.Add(newContent); } // Update progress OnUpdateProgressChange(this, true, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); }
/// <summary> /// Tries to create a movie action item for a scan from a file. /// </summary> /// <param name="file">The file to create movie action from</param> /// <param name="item">The resulting movie action</param> /// <returns>Whether the file was matched to a movie</returns> private bool CreateMovieAction(OrgPath file, string matchString, out OrgItem item, bool threaded, bool fast, bool skipMatching, Movie knownMovie) { // Initialize item item = new OrgItem(OrgAction.None, file.Path, FileCategory.MovieVideo, file.OrgFolder); // Check if sample! if (matchString.ToLower().Contains("sample")) { item.Action = OrgAction.Delete; return(false); } // Try to match file to movie string search = Path.GetFileNameWithoutExtension(matchString); // Search for match to movie Movie searchResult = null; bool searchSucess = false; if (!skipMatching) { searchSucess = SearchHelper.MovieSearch.ContentMatch(search, string.Empty, string.Empty, fast, threaded, out searchResult, knownMovie); } // Add closest match item if (searchSucess) { // Get root folder ContentRootFolder rootFolder; string path; if (Settings.GetMovieFolderForContent(searchResult, out rootFolder)) { searchResult.RootFolder = rootFolder.FullPath; } else { searchResult.RootFolder = NO_MOVIE_FOLDER; item.Action = OrgAction.NoRootFolder; } if (item.Action != OrgAction.NoRootFolder) { item.Action = file.Copy ? OrgAction.Copy : OrgAction.Move; } item.DestinationPath = searchResult.BuildFilePath(file.Path); if (File.Exists(item.DestinationPath)) { item.Action = OrgAction.AlreadyExists; } searchResult.Path = searchResult.BuildFolderPath(); item.Movie = searchResult; if (item.Action == OrgAction.AlreadyExists || item.Action == OrgAction.NoRootFolder) { item.Enable = false; } else { item.Enable = true; } return(true); } return(false); }
/// <summary> /// Update processing method (thread) for single content folder in root. /// </summary> /// <param name="orgPath">Organization path instance to be processed</param> /// <param name="pathNum">The path's number out of total being processed</param> /// <param name="totalPaths">Total number of paths being processed</param> /// <param name="processNumber">The identifier for the OrgProcessing instance</param> /// <param name="numItemsProcessed">Number of paths that have been processed - used for progress updates</param> /// <param name="numItemsStarted">Number of paths that have had been added to thread pool for processing</param> /// <param name="processSpecificArgs">Arguments specific to this process</param> private void UpdateProcess(OrgPath orgPath, int pathNum, int totalPaths, int processNumber, ref int numItemsProcessed, ref int numItemsStarted, object processSpecificArgs) { // Check for cancellation - this method is called from thread pool, so cancellation could have occured by the time this is run if (updateCancelled || this.updateNumber != processNumber) return; // First pass run does quick folder update by skipping online database searching bool firstPass = (bool)processSpecificArgs; string passString = firstPass ? " (First Pass)" : " (Second Pass)"; // Set processing messge string progressMsg = "Updating of '" + orgPath.RootFolder.FullPath + "'" + passString + " - '" + Path.GetFileName(orgPath.Path) + "' started"; OnUpdateProgressChange(this, false, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); // Get content collection to add content to ContentCollection content = GetContentCollection(); // Check if folder already has a match to existing content bool contentExists = false; bool contentComplete = false; Content newContent = null; int index = 0; for (int j = 0; j < content.Count; j++) if (Path.Equals(orgPath.Path, content[j].Path)) { contentExists = true; content[j].Found = true; if (!string.IsNullOrEmpty(content[j].DatabaseName)) contentComplete = true; newContent = content[j]; index = j; break; } // Set completed progess message progressMsg = "Updating of '" + orgPath.RootFolder.FullPath + "'" + passString + " - '" + Path.GetFileName(orgPath.Path) + "' complete"; // Check if content found if (contentExists && contentComplete) { // Check if content needs updating if ((DateTime.Now - newContent.LastUpdated).TotalDays > 7 && this.ContentType == ContentType.TvShow) newContent.UpdateInfoFromDatabase(); // Update progress if (this.updateNumber == processNumber) OnUpdateProgressChange(this, false, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); return; } // Folder wasn't matched to an existing content instance, try tmatch folder to content from online database Content match; bool matchSucess; switch (this.ContentType) { case ContentType.TvShow: TvShow showMatch; matchSucess = SearchHelper.TvShowSearch.PathMatch(orgPath.RootFolder.FullPath, orgPath.Path, firstPass, true, out showMatch); match = showMatch; break; case ContentType.Movie: Movie movieMatch; matchSucess = SearchHelper.MovieSearch.PathMatch(orgPath.RootFolder.FullPath, orgPath.Path, firstPass, true, out movieMatch); match = movieMatch; break; default: throw new Exception("unknown content type"); } // Check that current process hasn't been replaced - search can be slow, so update may have been cancelled by the time it gets here if (updateCancelled || this.updateNumber != processNumber) return; // Folder already existed, but wasn't previously match to valid content if (contentExists && matchSucess) { switch (this.ContentType) { case ContentType.TvShow: ((TvShow)newContent).CloneAndHandlePath((TvShow)match, true); ((TvShow)newContent).UpdateMissing(); break; case ContentType.Movie: ((Movie)newContent).CloneAndHandlePath((Movie)match, true); break; default: throw new Exception("unknown content type"); } newContent.LastUpdated = DateTime.Now; } else if (matchSucess) newContent = match; else switch (this.ContentType) { case ContentType.TvShow: newContent = new TvShow(string.Empty, 0, 0, orgPath.Path, orgPath.RootFolder.FullPath); break; case ContentType.Movie: newContent = new Movie(string.Empty, 0, 0, orgPath.Path, orgPath.RootFolder.FullPath); break; default: throw new Exception("unknown content type"); } // Set found flag newContent.Found = true; // Add content to list if new if (!contentExists) content.Add(newContent); // Update progress OnUpdateProgressChange(this, true, CalcProgress(numItemsProcessed, numItemsStarted, totalPaths), progressMsg); }
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> /// Directory scan processing method (thread) for a single file path. /// </summary> /// <param name="orgPath">Organization path instance to be processed</param> /// <param name="pathNum">The path's number out of total being processed</param> /// <param name="totalPaths">Total number of paths being processed</param> /// <param name="updateNumber">The identifier for the OrgProcessing instance</param> /// <param name="background">Whether processing is running as a background operation</param> /// <param name="subSearch">Whether processing is sub-search(TV only)</param> /// <param name="processComplete">Delegate to be called by processing when completed</param> /// <param name="numItemsProcessed">Number of paths that have been processed - used for progress updates</param> private void ThreadProcess(OrgPath orgPath, int pathNum, int totalPaths, int updateNumber, ref int numItemsProcessed, ref int numItemsStarted, object processSpecificArgs) { // Process specific arguments object[] args = (object[])processSpecificArgs; bool tvOnlyCheck = (bool)args[0]; bool skipMatching = (bool)args[1]; bool fast = (bool)args[2]; int procNumber = (int)args[3]; int pass = (int)args[4]; bool reuseResults = (bool)args[5]; // Check if item is already processed if (this.Items[pathNum].Action != OrgAction.TBD) { return; } // Check if file is in the queue bool alreadyQueued = false; if (itemsInQueue != null) { for (int i = 0; i < itemsInQueue.Count; i++) { if (itemsInQueue[i].SourcePath == orgPath.Path) { OrgItem newItem = new OrgItem(itemsInQueue[i]); newItem.Action = OrgAction.Queued; newItem.Enable = true; UpdateResult(newItem, pathNum, procNumber); alreadyQueued = true; break; } } } // If item is already in the queue, skip it if (alreadyQueued) { ProcessUpdate(orgPath.Path, totalPaths, pass); } else { // Update progress lock (directoryScanLock) this.Items[pathNum].Action = OrgAction.Processing; // Process path bool fromLog; OrgItem results = ProcessPath(orgPath, tvOnlyCheck, skipMatching || (!fast && pass == 0), fast || pass == 0, true, procNumber, reuseResults, out fromLog); // Update results and progress if (results != null && (results.Action != OrgAction.None || results.Category == FileCategory.Ignored || fast || pass == 1)) { UpdateResult(results, pathNum, procNumber); ProcessUpdate(orgPath.Path, totalPaths, pass); if (!fromLog && (results.Action != OrgAction.None && (results.Category == FileCategory.MovieVideo || results.Category == FileCategory.TvVideo))) { Organization.AddDirScanLogItem(new OrgItem(results)); } } else { lock (directoryScanLock) this.Items[pathNum].Action = OrgAction.TBD; } } }