private void ProcessSmallFiles(List <BackupFileModel> smallFiles, ref int category, string smallFilesTarPath, string currentBkpDir, SessionFileFindHelper findHelper, int filesCount, BackupInfo currentBkp, ref int linkedCount, ref int processed) { if (smallFiles.Count == 0) { return; } WriteLog("Small files hash calculation...", ++category); var cnt = smallFiles .AsParallel() .Select(x => { try { return(x.FileInfo.FastHashStr); } catch (Exception exception) { Console.WriteLine(exception); return("invalid hash: " + exception.Message); } }) .Count(x => x.StartsWith("invalid hash")); if (cnt > 0) { WriteLog($"Found {cnt} invalid records", ++category); } WriteLog($"{smallFiles.Count} files will be transferred in a batch as tar.gz", ++category); var sw = System.Diagnostics.Stopwatch.StartNew(); bool created; var tmpTarPath = smallFilesTarPath + ".tmp"; var archivedCount = 0; using (var tar = new TarGzHelper(tmpTarPath)) { foreach (var file in smallFiles) { try { var processedLocal = Interlocked.Increment(ref processed); var newFileWin = Path.Combine(currentBkpDir, file.RelativePathWin); var newFileRelativeName = newFileWin.Replace(currentBkpDir, string.Empty); var existingFileWin = findHelper.FindByLengthAndHash(file.FileInfo); if (existingFileWin != null) { _hardLinkHelper.AddHardLinkToQueue(existingFileWin, newFileWin); linkedCount++; WriteLog($"[{processedLocal} of {filesCount}] {{link}} {newFileRelativeName} to {existingFileWin}", Interlocked.Increment(ref category)); } else { var relFileName = file.RelativePathUnix; using (var fl = file.FileInfo.FileInfo.OpenRead()) { tar.AddFile(relFileName, fl); ++archivedCount; } WriteLog($"[{processedLocal} of {filesCount}] {{tar}} {newFileRelativeName} ", Interlocked.Increment(ref category)); } var o = new BackupFileInfo { Path = newFileRelativeName, Hash = file.FileInfo.FastHashStr, Length = file.FileInfo.FileInfo.Length, IsLink = existingFileWin != null, }; currentBkp.AddFile(o); } catch (Exception e) { Console.WriteLine(e); } } created = tar.IsArchiveCreated; } sw.Stop(); if (created) { var tarAndSendDuration = sw.Elapsed; WriteLog("Unpacking small files", Interlocked.Increment(ref category)); sw = System.Diagnostics.Stopwatch.StartNew(); File.Move(tmpTarPath, smallFilesTarPath); _hardLinkHelper.UnpackTar(smallFilesTarPath); sw.Stop(); WriteLog($"{archivedCount} files archived and transferred in {tarAndSendDuration:g} and unpacked in {sw.Elapsed:g}", ++category); } }
public async Task DoBackup() { await Task.Yield(); Validate(); var category = 0; var localFiles = GetFilesToBackup(ref category); WriteLog("Discovering backups...", ++category); var newBkpDate = DateTime.Now; var newBkpName = newBkpDate.ToString(BackupFolderNamingDateFormat, CultureInfo.InvariantCulture); var prevBkps = BackupInfo.DiscoverBackups(_destination).ToList(); if (_backupRoots != null) { foreach (var root in _backupRoots) { prevBkps.AddRange(BackupInfo.DiscoverBackups(root)); } } WriteLog($"Found {prevBkps.Count} backups", ++category); var currentBkpDir = Path.Combine(_destination, newBkpName); var filesCount = localFiles.Count; var currentBkp = new BackupInfo(currentBkpDir) { DateTime = newBkpDate, AttributesAvailable = true, }; var prevBackupFiles = GetFilesFromPrevBackups(prevBkps, ref category); var copiedCount = 0; var linkedCount = 0; var svcDir = currentBkp.CreateFolders(); currentBkp.CreateIncompleteAttribute(); var smallFilesTarPath = Path.Combine(svcDir, "small-files.tar.gz"); var directoriesTarPath = Path.Combine(svcDir, "dir-tree.tar.gz"); var findHelper = new SessionFileFindHelper(currentBkp, prevBackupFiles); WriteLog("Backing up...", ++category); var processed = 0; { // collect changes var byLength = prevBackupFiles .GroupBy(x => x.Item1.Hash) .SelectMany(x => x) .GroupBy(x => x.Item1.Length) .ToDictionary(x => x.Key, x => x.ToList()); var lengthMatch = 0; var lengthFailMatch = 0; var lengthMismatch = 0; var hashMatch = 0; var fails = 0; foreach (var localFileInfo in localFiles) { if (byLength.TryGetValue(localFileInfo.FileInfo.FileInfo.Length, out var files)) { lengthMatch++; try { var backupFile = files.FirstOrDefault(x => x.Item1.Hash == localFileInfo.FileInfo.FastHashStr); if (backupFile == null) { lengthFailMatch++; } else { hashMatch++; } } catch { fails++; } } else { lengthMismatch++; } } WriteLog($"match: {lengthMatch}; mismatch: {lengthMismatch}; fail match: {lengthFailMatch}; hash: {hashMatch}; fails: {fails}", Interlocked.Increment(ref category)); } throw null; CreateDirectories(localFiles, directoriesTarPath, ref category); var smallFiles = GetFilesForCompression(localFiles); ProcessSmallFiles(smallFiles, ref category, smallFilesTarPath, currentBkpDir, findHelper, filesCount, currentBkp, ref linkedCount, ref processed); foreach (var localFileInfo in localFiles) { try { var processedLocal = Interlocked.Increment(ref processed); var newFile = Path.Combine(currentBkpDir, localFileInfo.RelativePathWin); var newFileRelativeName = newFile.Replace(currentBkpDir, string.Empty); var newDir = Path.GetDirectoryName(newFile); if (newDir == null) { throw new InvalidOperationException("Cannot get file's directory"); } if (!Directory.Exists(newDir)) { Directory.CreateDirectory(newDir); } var existingFile = findHelper.FindByLengthAndHash(localFileInfo.FileInfo); if (existingFile != null) { WriteLog($"[{processedLocal} of {filesCount}] {{link}} {localFileInfo.RelativePathWin} ", Interlocked.Increment(ref category)); _hardLinkHelper.AddHardLinkToQueue(existingFile, newFile); linkedCount++; } else { void ProgressCallback(double progress) { WriteLogExt($"{progress:F2} %"); } WriteLog($"[{processedLocal} of {filesCount}] {localFileInfo.RelativePathWin} ", Interlocked.Increment(ref category)); var copiedHash = HashSumHelper.CopyUnbufferedAndComputeHashAsyncXX(localFileInfo.FileInfo.FileName, newFile, ProgressCallback, _allowSimultaneousReadWrite).Result; if (localFileInfo.FileInfo.FastHashStr == string.Concat(copiedHash.Select(b => $"{b:X}"))) { copiedCount++; } else { WriteLog($"{localFileInfo.RelativePathWin} copy failed", Interlocked.Increment(ref category)); System.Diagnostics.Debugger.Break(); } new FileInfo(newFile).Attributes |= FileAttributes.ReadOnly; } var o = new BackupFileInfo { Path = newFileRelativeName, Hash = localFileInfo.FileInfo.FastHashStr, Length = localFileInfo.FileInfo.FileInfo.Length, IsLink = existingFile != null, }; currentBkp.AddFile(o); } catch (Exception e) { Console.WriteLine(); Console.WriteLine(e); Console.WriteLine(); } } WriteLog("Writing hardlinks to target", Interlocked.Increment(ref category)); try { _hardLinkHelper.CreateHardLinks(); } finally { currentBkp.WriteToDisk(); currentBkp.DeleteIncompleteAttribute(); } var log = "Backup done."; if (copiedCount > 0) { log += $" {copiedCount} files copied"; } if (linkedCount > 0) { log += $" {linkedCount} files linked"; } WriteLog(log, ++category); }