/// <summary>
        /// Returns MD5 hash of filter expression
        /// </summary>
        /// <returns>MD5 hash of filter expression</returns>
        public string GetFilterHash()
        {
            var hash = MD5HashHelper.GetHash(new[] { First.GetFilterHash(), Second.GetFilterHash() });

            return(Utility.ByteArrayAsHexString(hash));
        }
Beispiel #2
0
        /// <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);
        }