public FileScanner(string startDir, Database database, Logger logger, string name, BackendBase[] backends, string fileIgnorePattern, HashSet<string> ignoredFiles, HashSet<string> ignoredFolders) { this.Database = database; this.treeTraverser = new TreeTraverser(startDir, fileIgnorePattern, ignoredFiles, ignoredFolders); this.fileDatabase = new FileDatabase(database); this.backends = backends; foreach (BackendBase backend in this.backends) { backend.CopyProgress += new BackendBase.CopyProgressEventHandler(Backend_CopyProgress); } this.Logger = logger; this.Name = name; }
public void Restore(bool overwrite, bool overwriteOnlyIfOlder, bool purge) { // We can only restore from one backend... if (this.backends.Length != 1) { throw new ArgumentException("Cannot perform a restore when more than one backend given"); } BackendBase backend = this.backends[0]; int curFolderId = 0; FileDatabase.FolderStatus folderStatus; FileDatabase.FileStatusWithLastModified fileStatus; bool copyFile = false; string fileMd5; string destMd5; DateTime copyFileLastModified; TreeTraverser.FolderEntry folder; TreeTraverser.FileEntry file; foreach (BackendBase.EntityRecord entity in backend.ListFilesFolders()) { if (this.Cancelled) { return; } if (entity.Type == BackendBase.Entity.Folder) { folder = this.treeTraverser.CreateFolderEntry(entity.Path); this.reportBackupAction(new BackupActionItem(null, folder.RelPath, BackupActionEntity.Folder, BackupActionOperation.Add)); if (!Directory.Exists(folder.FullPath)) { this.Logger.Info("Restoring folder: {0}", folder.RelPath); try { backend.RestoreFolder(folder.RelPath, folder.FullPath); } catch (BackupOperationException e) { this.handleOperationException(new BackupOperationException(folder.RelPath, e.Message)); } } // Regardless of whether the folder was created or not, we need some data for the file bit // Also, if the folder didn't exist in the database, now's the time to add it folderStatus = this.fileDatabase.InspectFolder(folder.RelPath); if (folderStatus.Id >= 0) { curFolderId = folderStatus.Id; } else { curFolderId = this.fileDatabase.AddFolder(folder.RelPath); } } else { file = this.treeTraverser.CreateFileEntry(entity.Path); // Ignore this: we don't want to restore our own backup DB if (file.RelPath == Path.GetFileName(Database.FilePath)) { continue; } destMd5 = null; this.reportBackupAction(new BackupActionItem(null, file.RelPath, BackupActionEntity.File, BackupActionOperation.Add)); if (File.Exists(file.FullPath)) { // Right, now we need to find some info about the file, if we can fileStatus = this.fileDatabase.InspectFileWithLastModified(curFolderId, file.RelPath, file.LastModified); if (overwrite) { switch (fileStatus.FileModStatus) { case FileDatabase.FileModStatus.New: // No record of the file in the database. Therefore we're copying it if the md5's don't match copyFile = true; break; case FileDatabase.FileModStatus.Older: // File on filesystem is older than in the database. Therefore we're copying it if the md5's don't match copyFile = true; break; case FileDatabase.FileModStatus.Newer: case FileDatabase.FileModStatus.Unmodified: // File on the filesystem is newer or the same age as in the database. Therefore copy only if !overwriteOnlyIfOlder and the md5s don't match copyFile = !overwriteOnlyIfOlder; break; } } else { copyFile = false; } if (copyFile) { fileMd5 = fileStatus.MD5 == null?backend.FileMD5(file.RelPath) : fileStatus.MD5; // If it's still null, we have no way of getting the md5 from anywhere, so just copy the damn thing // If not, test the file if (fileMd5 != null) { destMd5 = file.GetMD5((percent) => { this.reportBackupAction(new BackupActionItem(null, file.RelPath, BackupActionEntity.File, BackupActionOperation.Hash, null, percent)); return(!this.Cancelled); }); if (this.Cancelled) { return; } if (fileMd5 == destMd5) { copyFile = false; this.Logger.Info("Skipping file as identical: {0}", file.RelPath); } else { this.Logger.Info("File exists but is being overwritten: {0}", file.RelPath); } } else { this.Logger.Info("File exists but is being overwritten: {0}", file.RelPath); } } } else { // No file in dest, so automatically copy copyFile = true; // Still need to see if we can get its last modified time, if the record exists at all // We don't care about the modified status, so just feed in anything fileStatus = this.fileDatabase.InspectFileWithLastModified(curFolderId, file.RelPath, DateTime.Now); this.Logger.Info("Restoring file: {0}", file.RelPath); } // We need to set the utime of the existing/new file. // If we've got a DB record, use that. otherwise ask the backend. Otherwise now // The backend's FileLastModified defaults to DateTime.UtcNow, which is the default we want, so keep that copyFileLastModified = fileStatus.MD5 == null?backend.FileLastModified(file.RelPath) : fileStatus.LastModified; if (!copyFile) { // If the file exists, but we didn't write it, touch it instead if (File.Exists(file.FullPath)) { file.LastModified = copyFileLastModified; } } else { // So. we definitely decided to copy. try { backend.RestoreFile(file.RelPath, file.FullPath, copyFileLastModified); } catch (BackupOperationException e) { this.handleOperationException(e); } } // The only condition we need to update the DB is for insertions, as if the record exists already we match the dest to the DB // Therefore we need a file hash. Yay. if (fileStatus.MD5 == null) { // If we haven't found the MD5 of the file yet, calculate it now. Look at the file on the dest, as it's likely quicker if (destMd5 == null) { destMd5 = file.GetMD5((percent) => { this.reportBackupAction(new BackupActionItem(null, file.RelPath, BackupActionEntity.File, BackupActionOperation.Hash, null, percent)); return(!this.Cancelled); }); if (this.Cancelled) { return; } } this.fileDatabase.AddFile(curFolderId, file.RelPath, copyFileLastModified, destMd5); } } } if (purge) { IEnumerable <string> recordedFolders = this.fileDatabase.RecordedFolders().Select(x => x.Path); IEnumerable <string> recordedFiles = this.fileDatabase.RecordedFiles().Select(x => x.Path); foreach (TreeTraverser.FolderEntry purgeFolder in this.treeTraverser.ListFolders()) { foreach (TreeTraverser.FileEntry purgeFile in purgeFolder.GetFiles()) { if (!recordedFiles.Contains(purgeFile.RelPath)) { this.Logger.Info("Deleting file {0}:", purgeFile.RelPath); try { this.treeTraverser.DeleteFile(purgeFile.RelPath); } catch (BackupOperationException e) { this.handleOperationException(e); } } } this.Logger.Info("Deleting folder {0}:", purgeFolder.RelPath); if (!recordedFolders.Contains(purgeFolder.RelPath)) { this.treeTraverser.DeleteFolder(purgeFolder.RelPath); } } } }
private bool createFromAlternate(TreeTraverser.FileEntry file, string fileMD5, bool update, BackendBase backend) { // if update is true, we're updating the dest file. otherwise we're adding it // Return true if we made use of an alternate, or false if we did nothing string logAction = update ? "Updated" : "Added"; FileDatabase.FileRecord[] alternates = this.fileDatabase.SearchForAlternates(fileMD5).ToArray(); if (alternates.Length == 0) { return(false); } // Now, each alternate may not in fact exist on the backend, or may be changed. We can't assume stuff like this // So, loop through them all, and attempt to use it. Bail after the first successful one bool foundGoodAlternate = false; foreach (FileDatabase.FileRecord alternate in alternates) { // First, does it even exist on the backend? Skip to the next one if not if (!backend.TestFile(alternate.Path, file.LastModified, fileMD5)) { continue; } // Next: Is is a copy or a move? if (this.treeTraverser.FileExists(alternate.Path)) { // It's a copy this.reportBackupAction(new BackupActionItem(alternate.Path, file.RelPath, BackupActionEntity.File, BackupActionOperation.Copy, backend.Name)); if (backend.CreateFromAlternateCopy(file.RelPath, alternate.Path)) { this.Logger.Info("{0}: {1} file: {2} from alternate {3} (copy)", backend.Name, logAction, file.RelPath, alternate.Path); } else { backend.CreateFile(file.RelPath, file.FullPath, file.LastModified, fileMD5, file.Attributes); this.Logger.Info("{0}: {1} file: {2} (backend refused alternate {3})", backend.Name, logAction, file.RelPath, alternate.Path); } } else { // It's a move this.reportBackupAction(new BackupActionItem(alternate.Path, file.RelPath, BackupActionEntity.File, BackupActionOperation.Move, backend.Name)); backend.CreateFromAlternateMove(file.RelPath, alternate.Path); this.Logger.Info("{0}: {1} file: {2} from alternate {3} (move)", backend.Name, logAction, file.RelPath, alternate.Path); this.fileDatabase.DeleteFile(alternate.Id); } // We're all golden foundGoodAlternate = true; break; } return(foundGoodAlternate); }
public static void BackupDatabase(string databasePath, BackendBase[] backends) { foreach (BackendBase backend in backends) { string dbFile = Database.GetExportableFile(databasePath, backend.StripFilesFoldersOnDBBackup); backend.BackupDatabase(Path.GetFileName(databasePath), dbFile); } }
private bool createFromAlternate(TreeTraverser.FileEntry file, string fileMD5, bool update, BackendBase backend) { // if update is true, we're updating the dest file. otherwise we're adding it // Return true if we made use of an alternate, or false if we did nothing string logAction = update ? "Updated" : "Added"; FileDatabase.FileRecord[] alternates = this.fileDatabase.SearchForAlternates(fileMD5).ToArray(); if (alternates.Length == 0) return false; // Now, each alternate may not in fact exist on the backend, or may be changed. We can't assume stuff like this // So, loop through them all, and attempt to use it. Bail after the first successful one bool foundGoodAlternate = false; foreach (FileDatabase.FileRecord alternate in alternates) { // First, does it even exist on the backend? Skip to the next one if not if (!backend.TestFile(alternate.Path, file.LastModified, fileMD5)) continue; // Next: Is is a copy or a move? if (this.treeTraverser.FileExists(alternate.Path)) { // It's a copy this.reportBackupAction(new BackupActionItem(alternate.Path, file.RelPath, BackupActionEntity.File, BackupActionOperation.Copy, backend.Name)); if (backend.CreateFromAlternateCopy(file.RelPath, alternate.Path)) this.Logger.Info("{0}: {1} file: {2} from alternate {3} (copy)", backend.Name, logAction, file.RelPath, alternate.Path); else { backend.CreateFile(file.RelPath, file.FullPath, file.LastModified, fileMD5, file.Attributes); this.Logger.Info("{0}: {1} file: {2} (backend refused alternate {3})", backend.Name, logAction, file.RelPath, alternate.Path); } } else { // It's a move this.reportBackupAction(new BackupActionItem(alternate.Path, file.RelPath, BackupActionEntity.File, BackupActionOperation.Move, backend.Name)); backend.CreateFromAlternateMove(file.RelPath, alternate.Path); this.Logger.Info("{0}: {1} file: {2} from alternate {3} (move)", backend.Name, logAction, file.RelPath, alternate.Path); this.fileDatabase.DeleteFile(alternate.Id); } // We're all golden foundGoodAlternate = true; break; } return foundGoodAlternate; }