/// <summary> /// Attempts to delete files/directories given in the list of DirectoryEntryInfo items to prune. /// <para/>returns the number of items that were successfully deleted. /// </summary> /// <param name="pruneItemList">Gives the list of DirectoryEntryInfo items that are to be removed from the file system.</param> /// <param name="deleteEmitter">Gives the IMesgEmitter that will recieve messages about the successfull deletions</param> /// <param name="issueEmitter">Gives the IMesgEmitter that will receive any messages about failures while attempting to delete each item.</param> /// <param name="reason">Gives the string description for the reason that these items are to be deleted. Null or Empty will be replaced with [NoReasonGiven]</param> /// <returns>The number of items that were successfully deleted.</returns> public int DeletePrunedItems(List <DirectoryEntryInfo> pruneItemList, Logging.IMesgEmitter deleteEmitter, Logging.IMesgEmitter issueEmitter, string reason = null) { int deletedItemCount = 0; // actually the count of the number of items that we have attempted to delete reason = reason.MapNullOrEmptyTo("[NoReasonGiven]"); for (int idx = 0; idx < pruneItemList.Count; idx++) { DirectoryEntryInfo entryToDelete = pruneItemList[idx]; double ageInDays = entryToDelete.CreationAge.TotalDays; if (entryToDelete.IsFile) { try { System.IO.File.Delete(entryToDelete.Path); deletedItemCount++; deleteEmitter.Emit("Pruned file:'{0}', size:{1}, age:{2:f6} days, reason:{3}", entryToDelete.Path, entryToDelete.Length, ageInDays, reason); } catch (System.Exception ex) { issueEmitter.Emit("Prune failed to delete file:'{0}', error:'{1}'", entryToDelete.Path, ex.Message); } } else if (entryToDelete.IsDirectory) { try { System.IO.Directory.Delete(entryToDelete.Path); deletedItemCount++; deleteEmitter.Emit("Pruned directory:'{0}', size:{1}, age:{2:f6} days, reason:{3}", entryToDelete.Path, entryToDelete.Length, ageInDays, reason); } catch (System.Exception ex) { issueEmitter.Emit("Prune failed to delete directory:'{0}', error:'{1}'", entryToDelete.Path, ex.Message); } } else { issueEmitter.Emit("Prune cannot delete unknown tree node at path:'{0}', reason:{1}", entryToDelete.Path, reason); } } return(deletedItemCount); }
/// <summary> /// Inner method used to implement the setup operation. /// </summary> protected void InnerSetup(Config config) { // if needed, clear the prior state. if (setupPerformed) Clear(); // record the given configuration this.config = config; excludedFileSet = new System.Collections.Specialized.StringCollection(); excludedFileSet.AddRange(config.excludeFileNamesSet.ToArray()); string dirPath = config.dirPath; // try to add a DirectoryEntryInfo record for each of the files that are in the directory try { DirectoryEntryInfo basePathInfo = new DirectoryEntryInfo(dirPath); if (basePathInfo.Exists) { if (basePathInfo.IsFile) throw new SetupFailureException(Utils.Fcns.CheckedFormat("target path '{0}' does not specify a directory.", dirPath)); } else { if (config.createDirectoryIfNeeded) System.IO.Directory.CreateDirectory(dirPath); else throw new SetupFailureException(Utils.Fcns.CheckedFormat("target path '{0}' does not exist.", dirPath)); } // directory exists or has been created - now scan it and record each of the entries that are found therein DirectoryInfo dirInfo = new DirectoryInfo(dirPath); FileSystemInfo [] directoryFSIArray = dirInfo.GetFileSystemInfos(); foreach (FileSystemInfo fsi in directoryFSIArray) { string path = fsi.FullName; string name = fsi.Name; if (!excludedFileSet.Contains(name) && !excludedFileSet.Contains(path)) AddDirEntry(path, true); } if (numBadDirEntries != 0) logger.Error.Emit("Setup Failure: There are bad directory entries in dir '{0}'", dirPath); } catch (SetupFailureException sfe) { SetSetupFaultCode(sfe.Message); } catch (System.Exception ex) { SetSetupFaultCode(Utils.Fcns.CheckedFormat("Setup Failure: encountered unexpected exception '{0}' while processing dir '{1}'", ex.Message, dirPath)); } if (!SetupFailed) { // perform an additional set of tests if (string.IsNullOrEmpty(config.fileNamePrefix) || string.IsNullOrEmpty(config.fileNameSuffix)) SetSetupFaultCode("Setup Failure: Invalid file name fields in configuration"); else if (config.advanceRules.fileAgeLimitInSec < 0.0) SetSetupFaultCode("Setup Failure: Config: advanceRules.fileAgeLimitInSec is negative"); else if (config.purgeRules.dirNumFilesLimit > 0 && config.purgeRules.dirNumFilesLimit < ConfigPurgeNumFilesMinValue) SetSetupFaultCode("Setup Failure: Config: purgeRules.dirNumFilesLimit is too small"); else if (config.purgeRules.dirNumFilesLimit > 0 && config.purgeRules.dirNumFilesLimit > ConfigPurgeNumFilesMaxValue) SetSetupFaultCode("Setup Failure: Config: purgeRules.dirNumFilesLimit is too large"); else if (config.purgeRules.dirTotalSizeLimit < 0) SetSetupFaultCode("Setup Failure: Config: purgeRules.dirTotalSizeLimit is negative"); else if (config.purgeRules.fileAgeLimitInSec < 0.0) SetSetupFaultCode("Setup Failure: Config: purgeRules.maxFileAgeLimitInSec is negative"); } DirectoryEntryInfo activeFileInfo = new DirectoryEntryInfo(); if (!SetupFailed) { switch (config.fileNamePattern) { case FileNamePattern.ByDate: numFileNumberDigits = 0; break; case FileNamePattern.Numeric2DecimalDigits: numFileNumberDigits = 2; maxFileNumber = 100; break; case FileNamePattern.Numeric3DecimalDigits: numFileNumberDigits = 3; maxFileNumber = 1000; break; case FileNamePattern.Numeric4DecimalDigits: numFileNumberDigits = 4; maxFileNumber = 10000; break; default: SetSetupFaultCode("Setup Failure: Invalid file name pattern in configuration"); break; } // go through the directory file info entries (acquired above) from newest to oldest // and retain the newest valid file that matches the name pattern for this content // manager. This file will become the initial active file. activeFileEntryID = DirEntryID_Invalid; activeFileNumber = 0; bool matchFound = false; IList<Int64> itemKeys = dirEntryIDListSortedByCreatedFTimeUtc.Keys; IList<List<int>> itemValues = dirEntryIDListSortedByCreatedFTimeUtc.Values; for (int idx = dirEntryIDListSortedByCreatedFTimeUtc.Count - 1; !matchFound && idx >= 0; idx--) { Int64 itemFTime = itemKeys[idx]; List<int> itemEntryIDList = itemValues[idx]; foreach (int itemEntryID in itemEntryIDList) { activeFileEntryID = itemEntryID; if (IsDirEntryIDValid(activeFileEntryID)) activeFileInfo = dirEntryList[activeFileEntryID]; else { activeFileInfo.Clear(); Utils.Asserts.TakeBreakpointAfterFault("Setup: entry ID in ListSortedByCreated is not valid"); continue; } // verify that the entry is a file if (!activeFileInfo.IsFile) { Utils.Asserts.TakeBreakpointAfterFault("Setup: entry ID in ListSortedByCreated is not a file"); continue; } // divide the name into prefix, middle and suffix fields string fileName = activeFileInfo.Name; bool fileNameIsValidMatch = true; int fileNamePrefixLen = config.fileNamePrefix.Length; int fileNameSuffixLen = config.fileNameSuffix.Length; int split1Idx = Math.Min(fileNamePrefixLen, fileName.Length); // prevent attempting to call Substring with a second arg that is beyond the end of the string. string prefix = fileName.Substring(0, split1Idx); string rest = fileName.Substring(split1Idx); int restLen = rest.Length; string middle = string.Empty, suffix = string.Empty; if (restLen >= fileNameSuffixLen) { int splitPoint = restLen - fileNameSuffixLen; middle = rest.Substring(0, splitPoint); suffix = rest.Substring(splitPoint); } else { // this file name does not match requirements - exclude from search for current active file fileNameIsValidMatch = false; } // test if the prefix and suffix's match if (prefix != config.fileNamePrefix || suffix != config.fileNameSuffix) fileNameIsValidMatch = false; // test if the middle is valid if (numFileNumberDigits > 0) { int testFileNumber = -1; bool match = int.TryParse(middle, out testFileNumber); if (testFileNumber >= 0 && middle.Length == numFileNumberDigits && match) activeFileNumber = testFileNumber; else fileNameIsValidMatch = false; } else { // for FileNamePattern.ByDate files, we assume that the middle is valid if it is not empty if (middle.Length == 0) fileNameIsValidMatch = false; } matchFound = fileNameIsValidMatch; if (matchFound) break; } } if (!matchFound && dirEntryIDListSortedByCreatedFTimeUtc.Count != 0) logger.Warning.Emit("Setup Warning: no valid active file found in non-empty directory '{0}'", dirPath); } if (!SetupFailed && config.enableAutomaticCleanup) { for (int limit = 0; IsDirectoryCleanupNeeded && (limit < config.maxAutoCleanupDeletes); limit++) PerformIncrementalCleanup(); } if (SetupFailed) { logger.Error.Emit("Directory is not usable: path:'{0}' fault:'{1}'", dirPath, setupFaultCode); } else { logger.Debug.Emit("Directory is usable: path:'{0}' number of files:{1} active file:'{2}'", dirPath, dirEntryIDListSortedByName.Count, activeFileInfo.Name); } setupPerformed = true; }
/// <summary> /// Internal method that is used to generate the next Active file name. May delete the oldest file in order to reuse the name when appropriate. /// </summary> protected void GenerateNextActiveFile() { // any current active entry is no longer active activeFileEntryID = DirEntryID_Invalid; // increment the file number (for numeric files) activeFileNumber += 1; if (activeFileNumber >= maxFileNumber) activeFileNumber = 0; string formatStr = string.Empty; string middleStr = string.Empty; switch (config.fileNamePattern) { default: case FileNamePattern.ByDate: { DateTime dtNow = DateTime.Now; middleStr = Utils.Dates.CvtToString(ref dtNow, Utils.Dates.DateTimeFormat.ShortWithMSec); } break; case FileNamePattern.Numeric2DecimalDigits: case FileNamePattern.Numeric3DecimalDigits: case FileNamePattern.Numeric4DecimalDigits: { formatStr = Utils.Fcns.CheckedFormat("D{0}", numFileNumberDigits); middleStr = activeFileNumber.ToString(formatStr); } break; } string name = config.fileNamePrefix + middleStr + config.fileNameSuffix; // we have a full path now. int entryID = FindDirEntryByFileName(name); bool nameWasInMap = (IsDirEntryIDValid(entryID) ? RemoveDirEntry(entryID) : false); // if the file is already known string filePath = System.IO.Path.Combine(config.dirPath, name); DirectoryEntryInfo entryInfo = new DirectoryEntryInfo(filePath); FileSystemInfo entryFSI = entryInfo.FileSystemInfo; Utils.Asserts.LogIfConditionIsNotTrue((nameWasInMap == entryInfo.Exists), "GenerateNextActiveFile: name already exists only if it was removed from map"); if (entryInfo.Exists) { // delete the file (failure means we cannot use this name...) double fileAgeInHours = entryInfo.CreationAge.TotalHours; Int64 fileSize = entryInfo.Length; logger.Trace.Emit("GenerateNextActiveFile: Attempting to delete prior file:'{0}' size:{1} age:{2} hours", entryInfo.Name, fileSize, fileAgeInHours.ToString("f3")); try { entryFSI.Delete(); logger.Debug.Emit("GenerateNextActiveFile: Deleted prior file:'{0}' size:{1} age:{2} hours", entryInfo.Name, fileSize, fileAgeInHours.ToString("f3")); } catch (System.Exception ex) { logger.Error.Emit("GenerateNextActiveFile: failed to delete prior file:'{1}', error:'{1}'", entryInfo.Name, ex.Message); return; } } // the name has been generated and if it existed, it has been deleted // create the entry, add it to the maps and set the active entry to refer to the new entry activeFileEntryID = AddDirEntry(entryInfo.Path, false); DirectoryEntryInfo activeFileEntry = dirEntryList[activeFileEntryID]; logger.Debug.Emit("GenerateNextActiveFile active file is now:'{0}' id:{1}", activeFileEntry.Name, activeFileEntryID); }