} // end Dispose() [overload 2] // verifyBackedUpFileAgainstRecord(): /// <summary>Makes sure a file still matches the info recorded about it in the database.</summary> /// <returns>A <c>bool</c> indicating whether the file record still matches the file to which it corresponds.</returns> /// <param name="groupRecordInfo">The group object to which the file belongs.</param> /// <param name="fileRecordInfo">The file record object corresponding to the file in question.</param> /// <param name="basePath">The base path with which the file record's [relative] path should be combined to find the file on disk.</param> private bool verifyBackedUpFileAgainstRecord(GroupRecord groupRecordInfo, FileBackupRecord fileRecordInfo, string basePath) { FileInfo backedUpFile = new FileInfo(Path.Combine(basePath, fileRecordInfo.Path)); // CHANGE CODE HERE: Handle exceptions if (!backedUpFile.Exists) { return(false); // Record is invalid because the previously backed-up file doesn't still exist. } else if (backedUpFile.LastWriteTimeUtc.Ticks != fileRecordInfo.LastModificationDate_UTC_Ticks) // CHANGE CODE HERE: Handle exceptions { return(false); // Record is invalid because the previously backed-up file's modification time indicates it has been changed since being put there. } else if (backedUpFile.Length != groupRecordInfo.FileSize) // CHANGE CODE HERE: Handle exceptions { return(false); // Record is invalid because the previously backed-up file size no longer matches what was stored in the database. } return(true); // File record matches the backed-up file. } // end verifyBackedUpFileAgainstRecord()
} // end getAvailableHardLinkMatch() // addFileBackupRecord(): /// <summary>Adds a new database record for a backed-up file.</summary> /// <param name="backedUpFileDestinationPath">Full path of the file being recorded.</param> /// <param name="fileSize">Size, in bytes, of the file being recorded.</param> /// <param name="fileHash">Hash string of the contents of the file being recorded.</param> /// <param name="fileLastModificationDateUTC">Last changed date of the file being recorded.</param> /// <param name="existingHardLinkGroupID">ID of the existing file group into which this file should be put, or <c>null</c> to put it in a new group.</param> public void addFileBackupRecord(string backedUpFileDestinationPath, long fileSize, string fileHash, DateTime fileLastModificationDateUTC, ObjectId existingHardLinkGroupID) { // Obtain the collection of hard link groups from the database. // CHANGE CODE HERE: handle exceptions ILiteCollection <GroupRecord> backupFileGroupCollection = database.GetCollection <GroupRecord>(GroupCollectionName); // Create a new FileBackupRecord object to store in the database. FileBackupRecord newFileBackupRecord = new FileBackupRecord() { Path = backedUpFileDestinationPath.Substring(backupsRootPath.Length), // Store only the portion of the path below the root backups directory. LastModificationDate_UTC_Ticks = fileLastModificationDateUTC.Ticks }; if (existingHardLinkGroupID == null) { // No existing group was specified. We will create a new file group in the database to put this file in. // Set up a new list of files, and insert it along with all the other needed data into a new GroupRecord object. List <FileBackupRecord> fileRecords = new List <FileBackupRecord>(); fileRecords.Add(newFileBackupRecord); GroupRecord newRecord = new GroupRecord { Hash = fileHash, FileSize = fileSize, Files = fileRecords, CreatedDate_UTC_Ticks = DateTime.Now.ToUniversalTime().Ticks }; // Insert the new group into the database. backupFileGroupCollection.Insert(newRecord); // CHANGE CODE HERE: handle exceptions // Make sure the database is indexed on the file hash value. backupFileGroupCollection.EnsureIndex(record => record.Hash); // CHANGE CODE HERE: handle exceptions } else { // An existing group ID was specified. We will insert the file into that group. // Get the existing database record for the group. GroupRecord databaseRecord = backupFileGroupCollection.FindById(existingHardLinkGroupID); // Add this file to that group's Files collection, and update the database. databaseRecord.Files.Add(newFileBackupRecord); backupFileGroupCollection.Update(databaseRecord); // CHANGE CODE HERE: handle exceptions } } // end addFileBackupRecord()
} // end Database constructor // getAvailableHardLinkMatch(): /// <summary> /// Attempts to find a match, among previously backed-up files, for the specified file hash, size, and last modification date. /// Allows limiting matches by age and by number of other files already grouped with the matching file. /// </summary> /// <returns>A <c>HardLinkMatch</c> object with info about the matching file if one is found, or <c>null</c> if no match is found.</returns> /// <param name="originFileHash">Hash of the file to be matched.</param> /// <param name="originFileSize">Size, in bytes, of the file to be matched.</param> /// <param name="originFileLastWriteTimeUTC">Modification date of the file to be matched.</param> /// <param name="maxHardLinksPerFile">Maximum number of matches allowed in a single file group.</param> /// <param name="maxDataAgeForNewHardLink_Days">Maximum age of a file allowed to be used as a match.</param> public HardLinkMatch getAvailableHardLinkMatch(string originFileHash, long originFileSize, DateTime originFileLastWriteTimeUTC, int?maxHardLinksPerFile, int?maxDataAgeForNewHardLink_Days) { // Variable to contain return value, defaulting to null. HardLinkMatch hardLinkMatch = null; // Translate the specified maximum age in days to a minimum date in ticks. long?minimumFileGroupCreationDate = (maxDataAgeForNewHardLink_Days == null) ? null : (DateTime.UtcNow.Ticks - maxDataAgeForNewHardLink_Days * TicksPerDay); // Obtain the collection of hard link groups from the databse. // CHANGE CODE HERE: Handle exceptions ILiteCollection <GroupRecord> backupFileGroupCollection = database.GetCollection <GroupRecord>(GroupCollectionName); // Get all hard link groups that match the hash and are new enough to be used. // Order them based on most likelihood of being the best possible match: // starting with the least-used ones and, within that, newest ones first. // CHANGE CODE HERE: Handle exceptions IOrderedEnumerable <GroupRecord> potentialFileGroups = backupFileGroupCollection .Query() .Where(fileGroup => (fileGroup.Hash == originFileHash)) .Where(fileGroup => (minimumFileGroupCreationDate == null || fileGroup.CreatedDate_UTC_Ticks >= minimumFileGroupCreationDate)) .ToList() .OrderBy(fileGroup => fileGroup.Files.Count) .ThenByDescending(fileGroup => fileGroup.CreatedDate_UTC_Ticks); // Iterate through each file group having a matching hash as found above, // checking for any files within that group that can be returned as a match. foreach (GroupRecord currentHardLinkGroupRecord in potentialFileGroups) { // Look at each file in this group (until we find a match). For each, verify the file still exists and is unchanged. // If it is changed or missing, delete its record from the file group in the database. // If one is verified and the number in the group is below the maximum (or there is no maximum), // or the group size drops below the maximum due to cleaning out no-longer-valid records, choose that one to return. for (int i = currentHardLinkGroupRecord.Files.Count - 1; i >= 0; i--) { // Get the current file to look at. FileBackupRecord fileBackupRecordToCheck = currentHardLinkGroupRecord.Files[i]; // Keep a record of the last verified file match in this group that was found. string lastValidPotentialMatch = null; // Figure out the full path to the file recorded in the databse. string backupRecordFileFullPath = Path.Combine(backupsRootPath, fileBackupRecordToCheck.Path); // CHANGE CODE HERE: Handle exceptions if (verifyBackedUpFileAgainstRecord(currentHardLinkGroupRecord, fileBackupRecordToCheck, backupsRootPath)) { // Database record still matches the previous backup file (backed-up file hasn't been modified or deleted since the record was made). // Now make sure it matches the origin file with respect to size and modification date. // (A size mismatch would be highly unlikely and result only from a hash collision. // But a modification date mismatch could occur if a file is changed and then changed back. // Because we want to preserve file dates in the backup copies, we will consider that a mismatch-- // don't want to "back up" a file by creating a hard link to another file with the same contents but a different date.) if (fileMatchesSizeAndDate(backupRecordFileFullPath, originFileSize, originFileLastWriteTimeUTC)) { // Database record points to a valid, matching previous backup file. // Remember this one as a possibly usable file to use as a hard link target. lastValidPotentialMatch = backupRecordFileFullPath; } else { // Something about the file pointed to in this record doesn't match the origin file (e.g., last modified date). // Can't use this one (or others that are hard links of the same physical file) as a hard link source. // Stop looking at records in this group of identical hard links and move on to the next group. break; } } else { // The file referred to by this database record no longer exists or has been modified. // This database record is no longer valid, so delete the record. currentHardLinkGroupRecord.Files.RemoveAt(i); backupFileGroupCollection.Update(currentHardLinkGroupRecord); // CHANGE CODE HERE: Handle exceptions } // If we have found a match and are under the max hard links allowed per physical copy, break out and stop looking for a link source. if (lastValidPotentialMatch != null && (maxHardLinksPerFile == null || currentHardLinkGroupRecord.Files.Count < maxHardLinksPerFile)) { hardLinkMatch = new HardLinkMatch() { ID = currentHardLinkGroupRecord.ID, MatchingFilePath = lastValidPotentialMatch }; break; } } // end for (int i = currentHardLinkGroupRecord.Files.Count - 1; i >= 0; i--) // If no records are left in this group, delete the whole group from the database. // If a match has been found, stop looking any further. if (currentHardLinkGroupRecord.Files.Count < 1) { backupFileGroupCollection.Delete(currentHardLinkGroupRecord.ID); // CHANGE CODE HERE: Handle exceptions } else if (hardLinkMatch != null) { break; } } // end foreach(GroupRecord currentHardLinkGroupRecord in potentialFileGroups) return(hardLinkMatch); } // end getAvailableHardLinkMatch()