示例#1
0
        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);
        }
示例#2
0
        /// <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);
        }
示例#3
0
        /// <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;
        }
示例#4
0
        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);
        }
示例#5
0
        /// <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;
        }
示例#6
0
        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;
        }
示例#7
0
        /// <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;

            }
        }
示例#8
0
        /// <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;
        }
示例#9
0
 /// <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;
 }
示例#10
0
        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];
        }
示例#13
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);
        }
示例#14
0
        /// <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);
        }
示例#15
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);
        }
示例#16
0
        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);
        }
示例#17
0
        /// <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;
                }
            }
        }