/// <summary> /// Deletes the Parent directory recursively if the directories are empty /// This function is reentrant /// </summary> /// <param name="filePath">File name with complete path to scan recursively up the parent chain of directories</param> private void DeleteParentDirectoryChainIfEmpty(string filePath) { if (String.IsNullOrWhiteSpace(filePath)) { return; } if (!Directory.Exists(Path.GetDirectoryName(filePath))) { return; } try { if (FilePaths.GetDirectoryFiles(Path.GetDirectoryName(filePath), "*.*", SearchOption.TopDirectoryOnly).ToList <string>().Count == 0) // there are no files in the directory { Log.AppLog.WriteEntry(this, "Deleting directory " + Path.GetDirectoryName(filePath), Log.LogEntryType.Debug); Directory.Delete(Path.GetDirectoryName(filePath)); // Delete the directory DeleteParentDirectoryChainIfEmpty(Path.GetDirectoryName(filePath)); // Kick it up one more level to check for empty directories } } catch (Exception ex) { Log.AppLog.WriteEntry(this, "Unable to read parent directory contents " + Path.GetDirectoryName(filePath) + "\r\n" + ex.ToString(), Log.LogEntryType.Warning); } }
/// <summary> /// Scans the Monitored directories and Manual Queue for new files to process /// Applies the filter tests /// Processes conversion task jobs for the files and adds them to the queue /// This function takes a lock on the Queue while modifying the queue and is thread safe /// </summary> public void ScanForFiles() { // Search the specific directories foreach (MonitorJobOptions monitorTask in MCEBuddyConf.GlobalMCEConfig.AllMonitorTasks) { IEnumerable <string> foundFiles = null; try { // Directory.EnumerateFiles throws an exception if it comes across a protected/unaccesible directory and the ENTIRE list is empty // Instead we need to handle exceptions, skip the protected file/directory and continue walking down the rest foundFiles = FilePaths.GetDirectoryFiles(monitorTask.searchPath, "*", (monitorTask.monitorSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)).OrderBy(File.GetLastWriteTime); // We sort the files by last modified time when scanning and adding (oldest to newest) if (foundFiles != null && foundFiles.Count() > 0) // Check for no files (could be due to security access/protection errors { foreach (string foundFile in foundFiles) { if (GlobalDefs.Shutdown) // Check for a shutdown command, this can be an intensive loop { return; // Exit - we're done here } Monitor.Enter(_monitorTaskFilterMismatchFiles); // Make this thread safe if (_monitorTaskFilterMismatchFiles.ContainsKey(foundFile)) // Check if this file has been processed by this monitor task and doesn't have a filter match, if so skip it (the filters do not change until the engine is stopped, settings changed, engine restarted which will create a new queue) { if (_monitorTaskFilterMismatchFiles[foundFile].Contains(monitorTask.taskName)) { Monitor.Exit(_monitorTaskFilterMismatchFiles); continue; } } Monitor.Exit(_monitorTaskFilterMismatchFiles); Monitor.Enter(_archivedFiles); if (_archivedFiles.Contains(foundFile)) // Check if the file has been marked as an archive file if so skip it { Monitor.Exit(_archivedFiles); continue; } Monitor.Exit(_archivedFiles); // First check if this file if archiving is enabled and if it is in the MCEBuddyArchive or custom archive directories (which contains converted files, if so skip them) if ((monitorTask.archiveMonitorOriginal || MCEBuddyConf.GlobalMCEConfig.GeneralOptions.archiveOriginal) && (Path.GetDirectoryName(foundFile).ToLower().Contains((string.IsNullOrEmpty(MCEBuddyConf.GlobalMCEConfig.GeneralOptions.archivePath) ? GlobalDefs.MCEBUDDY_ARCHIVE.ToLower() : MCEBuddyConf.GlobalMCEConfig.GeneralOptions.archivePath.ToLower())) || // Global archive path (Path.GetDirectoryName(foundFile).ToLower().Contains((string.IsNullOrEmpty(monitorTask.archiveMonitorPath) ? GlobalDefs.MCEBUDDY_ARCHIVE.ToLower() : monitorTask.archiveMonitorPath.ToLower()))) // Monitor task Archive path )) { Monitor.Enter(_archivedFiles); _archivedFiles.Add(foundFile); // add to the list Monitor.Exit(_archivedFiles); Log.AppLog.WriteEntry(this, "File " + foundFile + " is in the archive directory, skipping monitoring", Log.LogEntryType.Debug); continue; } // 1st Level Pattern Check - Found a file, filename pattern match from monitor task if (Util.Text.WildcardRegexPatternMatch(Path.GetFileName(foundFile), monitorTask.searchPattern)) // Check pattern match for Monitor Locations filter { // Take a lock here for EACH file before processing and modifying queue // This is done per file and not for all files so that we don't lock up the engine interfaces which need the same lock to respond to GUI queries // CheckAndAddFile is very intensive time consuming process for each file as it extracts metadata and compares filters // Also EnumerateFiles is a very intensive process for very large nested and remote directories which will lock up the thread if the lock is taken Monitor.Enter(Queue); // Take a lock on the queue before modifying the queue CheckAndAddFile(foundFile, monitorTask); // Check history, monitor and conversion task filters and creates conversion job for file Monitor.Exit(Queue); // Release the lock on the queue after modifying the queue } else // File type mismatch, log and keep track of them for each monitor task processed { Monitor.Enter(_monitorTaskFilterMismatchFiles); // Make it thread safe if (!_monitorTaskFilterMismatchFiles.ContainsKey(foundFile)) /// Check if this file does not have a key, then create one { _monitorTaskFilterMismatchFiles.Add(foundFile, new List <string>()); // Make a new key for the file } _monitorTaskFilterMismatchFiles[foundFile].Add(monitorTask.taskName); // Add this task for the file as a filter mismatch Monitor.Exit(_monitorTaskFilterMismatchFiles); Log.AppLog.WriteEntry(this, "File " + Path.GetFileName(foundFile) + " did not match wildcard" + " " + monitorTask.searchPattern + " for monitor task " + monitorTask.taskName, Log.LogEntryType.Debug); } } } else { Log.AppLog.WriteEntry(this, "No accessible files founds in location " + monitorTask.searchPath + " for monitor task " + monitorTask.taskName, Log.LogEntryType.Information); } } catch (Exception ex) { Log.AppLog.WriteEntry(this, "Unable to search for files in location " + monitorTask.searchPath + " for monitor task " + monitorTask.taskName + "\r\nERROR : " + ex.Message, Log.LogEntryType.Warning); foundFiles = null; try { Monitor.Exit(Queue); } // Release queue lock catch { } try { Monitor.Exit(_monitorTaskFilterMismatchFiles); } // Release monitor mismatch list lock catch { } try { Monitor.Exit(_archivedFiles); } // Release archived list lock catch { } } } // Read the manual queue - manual selections are always first Ini iniQueue = new Ini(GlobalDefs.ManualQueueFile); SortedList <string, string> manualQueue = iniQueue.GetSectionKeyValuePairs("ManualQueue"); foreach (KeyValuePair <string, string> manualFile in manualQueue) { string filePath = manualFile.Value; // Due to INI restriction only the Value can hold special characters like ];= which maybe contained in the filename, hence the Value is used to capture the "TRUE" filename (Key and Section have restrictions) if (GlobalDefs.Shutdown) // Check for a shutdown command, this can be an intensive loop { return; // Exit - we're done here } // Check for valid entry if (String.IsNullOrWhiteSpace(filePath)) { continue; } string destinationPath = Path.GetDirectoryName(filePath); if (String.IsNullOrWhiteSpace(destinationPath)) // check for a null directory name here (happens with some root level network paths) { iniQueue.DeleteSection(filePath); // Remove the key from the manual file continue; } // Connect network drives if needed for each manual entry GeneralOptions go = MCEBuddyConf.GlobalMCEConfig.GeneralOptions; if (Util.Net.IsUNCPath(destinationPath)) { if (!String.IsNullOrWhiteSpace(go.userName)) { ConnectNet(destinationPath, go.domainName, go.userName, go.password); } else { Log.AppLog.WriteEntry(this, "No network authentication username found, defaulting to Guest authentication", Log.LogEntryType.Warning); ConnectNet(destinationPath, "", GlobalDefs.DEFAULT_NETWORK_USERNAME, ""); } } if (Directory.Exists(filePath)) // After we are connected, check if the target is a directory (accidentally happens), we don't process it { Log.AppLog.WriteEntry(this, "Manually selected file " + filePath + " is a directory, skipping", Log.LogEntryType.Warning); iniQueue.DeleteSection(filePath); // Remove the key from the manual file continue; } if (!File.Exists(filePath)) // After we are connected, check if the file actually exists { Log.AppLog.WriteEntry(this, "Manually selected file " + filePath + " does not exist or MCEBuddy doesn't have read permissions, skipping", Log.LogEntryType.Warning); iniQueue.DeleteSection(filePath); // Remove the key from the manual file continue; } Log.AppLog.WriteEntry(this, "Manually selected file " + filePath + " added to queue", Log.LogEntryType.Debug); // Take a lock here for EACH file before processing and modifying queue // This is done per file and not for all files so that we don't lock up the engine interfaces which need the same lock to respond to GUI queries // CheckAndAddFile is very intensive time consuming process for each file as it extracts metadata and compares filters try { Monitor.Enter(Queue); // Take a lock on the queue before modifying the queue CheckAndAddFile(filePath, null); Monitor.Exit(Queue); // Release the lock on the queue after modifying the queue } catch (Exception ex) { Log.AppLog.WriteEntry(this, "Add manual files terminated.\r\nERROR : " + ex.Message, Log.LogEntryType.Warning); try { Monitor.Exit(Queue); } // Release queue lock catch { } } } }