/// <summary> /// Sort sources by root volume /// </summary> /// <param name="sources">List of sources</param> /// <returns>Dictionary of volumes, with list of sources as values</returns> private static Dictionary <string, List <string> > SortByVolume(IEnumerable <string> sources) { var sourcesByVolume = new Dictionary <string, List <string> >(); foreach (var path in sources) { // get NTFS volume root var volumeRoot = USNJournal.GetVolumeRootFromPath(path); if (!sourcesByVolume.TryGetValue(volumeRoot, out var list)) { list = new List <string>(); sourcesByVolume.Add(volumeRoot, list); } list.Add(path); } return(sourcesByVolume); }
/// <summary> /// Returns true if path was enumerated by journal service /// </summary> /// <param name="path"></param> /// <returns></returns> public bool IsPathEnumerated(string path) { // get NTFS volume root var volumeRoot = USNJournal.GetVolumeRootFromPath(path); // get volume data if (!m_volumeDataDict.TryGetValue(volumeRoot, out var volumeData)) { return(false); } if (volumeData.IsFullScan) { return(true); // do not append from previous set, already scanned } if (volumeData.Files.Contains(path)) { return(true); // do not append from previous set, already scanned } foreach (var folder in volumeData.Folders) { if (m_token.IsCancellationRequested) { break; } if (path.Equals(folder, Utility.Utility.ClientFilenameStringComparison)) { return(true); // do not append from previous set, already scanned } if (Utility.Utility.IsPathBelowFolder(path, folder)) { return(true); // do not append from previous set, already scanned } } return(false); // append from previous set }
/// <summary> /// Initialize list of modified files / folder for each volume /// </summary> /// <param name="emitFilter"></param> /// <param name="fileAttributeFilter"></param> /// <param name="skipFilesLargerThan"></param> /// <param name="prevJournalData"></param> /// <returns></returns> private Dictionary <string, VolumeData> Initialize(IFilter emitFilter, FileAttributes fileAttributeFilter, long skipFilesLargerThan, IEnumerable <USNJournalDataEntry> prevJournalData) { if (prevJournalData == null) { throw new UsnJournalSoftFailureException(Strings.USNHelper.PreviousBackupNoInfo); } var result = new Dictionary <string, VolumeData>(); // get hash identifying current source filter / sources configuration // ReSharper disable once PossibleMultipleEnumeration var configHash = Utility.Utility.ByteArrayAsHexString(MD5HashHelper.GetHash(new string[] { emitFilter == null ? string.Empty : emitFilter.ToString(), string.Join("; ", m_sources), fileAttributeFilter.ToString(), skipFilesLargerThan.ToString() })); // create lookup for journal data var journalDataDict = prevJournalData.ToDictionary(data => data.Volume); // iterate over volumes foreach (var sourcesPerVolume in SortByVolume(m_sources)) { if (m_token.IsCancellationRequested) { break; } var volume = sourcesPerVolume.Key; var volumeSources = sourcesPerVolume.Value; var volumeData = new VolumeData { Volume = volume, JournalData = null }; result[volume] = volumeData; try { // prepare journal data entry to store with current fileset var journal = new USNJournal(volume); var nextData = new USNJournalDataEntry { Volume = volume, JournalId = journal.JournalId, NextUsn = journal.NextUsn, ConfigHash = configHash }; // add new data to result set volumeData.JournalData = nextData; // only use change journal if: // - journal ID hasn't changed // - nextUsn isn't zero (we use this as magic value to force a rescan) // - the configuration (sources or filters) hasn't changed if (!journalDataDict.TryGetValue(volume, out var prevData)) { throw new UsnJournalSoftFailureException(Strings.USNHelper.PreviousBackupNoInfo); } if (prevData.JournalId != nextData.JournalId) { throw new UsnJournalSoftFailureException(Strings.USNHelper.JournalIdChanged); } if (prevData.NextUsn == 0) { throw new UsnJournalSoftFailureException(Strings.USNHelper.NextUsnZero); } if (prevData.ConfigHash != nextData.ConfigHash) { throw new UsnJournalSoftFailureException(Strings.USNHelper.ConfigHashChanged); } var changedFiles = new HashSet <string>(Utility.Utility.ClientFilenameStringComparer); var changedFolders = new HashSet <string>(Utility.Utility.ClientFilenameStringComparer); // obtain changed files and folders, per volume foreach (var source in volumeSources) { if (m_token.IsCancellationRequested) { break; } foreach (var entry in journal.GetChangedFileSystemEntries(source, prevData.NextUsn)) { if (m_token.IsCancellationRequested) { break; } if (entry.Item2.HasFlag(USNJournal.EntryType.File)) { changedFiles.Add(entry.Item1); } else { changedFolders.Add(Util.AppendDirSeparator(entry.Item1)); } } } // At this point we have: // - a list of folders (changedFolders) that were possibly modified // - a list of files (changedFiles) that were possibly modified // // With this, we need still need to do the following: // // 1. Simplify the folder list, such that it only contains the parent-most entries // (eg. { "C:\A\B\", "C:\A\B\C\", "C:\A\B\D\E\" } => { "C:\A\B\" } volumeData.Folders = Utility.Utility.SimplifyFolderList(changedFolders).ToList(); // 2. Our list of files may contain entries inside one of the simplified folders (from step 1., above). // Since that folder is going to be fully scanned, those files can be removed. // Note: it would be wrong to use the result from step 2. as the folder list! The entries removed // between 1. and 2. are *excluded* folders, and files below them are to be *excluded*, too. volumeData.Files = new HashSet <string>(Utility.Utility.GetFilesNotInFolders(changedFiles, volumeData.Folders)); // Record success for volume volumeData.IsFullScan = false; } catch (Exception e) { // full scan is required this time (eg. due to missing journal entries) volumeData.Exception = e; volumeData.IsFullScan = true; volumeData.Folders = new List <string>(); volumeData.Files = new HashSet <string>(); // use original sources foreach (var path in volumeSources) { var isFolder = path.EndsWith(Util.DirectorySeparatorString, StringComparison.Ordinal); if (isFolder) { volumeData.Folders.Add(path); } else { volumeData.Files.Add(path); } } } } return(result); }