diff --git a/Parallel.Cli/Commands/CleanCommand.cs b/Parallel.Cli/Commands/CleanCommand.cs index 324a208..6b5cb35 100644 --- a/Parallel.Cli/Commands/CleanCommand.cs +++ b/Parallel.Cli/Commands/CleanCommand.cs @@ -2,7 +2,6 @@ using System.CommandLine; using Parallel.Cli.Utils; -using Parallel.Core.IO.Backup; using Parallel.Core.IO.Scanning; using Parallel.Core.IO.Syncing; using Parallel.Core.Settings; @@ -13,6 +12,8 @@ namespace Parallel.Cli.Commands public class CleanCommand : Command { private long _freedBytes = 0; + private int _filesCount = 0; + private int _dirsCount = 0; private readonly Option _sourceOpt = new(["--path", "-p"], "The source path to clean."); private readonly Option _daysOpt = new(["--days", "-d"], "The amount of days to hang onto files."); @@ -39,6 +40,7 @@ public CleanCommand() : base("clean", "Cleans up the file system by removing old await CleanDirectoryAsync(config, path, days, recursive, verbose); } + CommandLine.WriteLine($"Successfully cleaned {_filesCount:N0} files and {_dirsCount:N0} directories, ({Formatter.FromBytes(_freedBytes)} removed)", ConsoleColor.Green); }, _sourceOpt, _daysOpt, _recursiveOpt, _verboseOpt); } @@ -62,7 +64,6 @@ private async Task CleanDirectoryAsync(ParallelConfig config, string path, int d UnixTime minTime = UnixTime.FromMilliseconds(UnixTime.Now.TotalMilliseconds - (days * UnixTime.Day)); IEnumerable cleanableFiles = FileScanner.GetCleanableFiles(path, minTime, recursive); if (!cleanableFiles.Any()) CommandLine.WriteLine($"No cleanable files were found in the provided path.", ConsoleColor.Green); - int filesCount = cleanableFiles.Count(); await System.Threading.Tasks.Parallel.ForEachAsync(cleanableFiles, ParallelConfig.Options, (fi, ct) => { @@ -71,6 +72,7 @@ await System.Threading.Tasks.Parallel.ForEachAsync(cleanableFiles, ParallelConfi try { _freedBytes += fi.Length; + _filesCount++; fi.Delete(); } catch (Exception ex) @@ -85,11 +87,10 @@ await System.Threading.Tasks.Parallel.ForEachAsync(cleanableFiles, ParallelConfi IEnumerable directories = FileScanner.GetEmptyDirectories(path, recursive); if (!directories.Any()) CommandLine.WriteLine($"No empty directories were found in the provided path.", ConsoleColor.Green); - int directoriesCount = directories.Count(); await System.Threading.Tasks.Parallel.ForEachAsync(directories, ParallelConfig.Options, (di, ct) => { - if (di.Exists && di.EnumerateFiles().Any()) + if (di.Exists && !di.EnumerateFiles().Any()) { try { @@ -98,7 +99,7 @@ await System.Threading.Tasks.Parallel.ForEachAsync(directories, ParallelConfig.O } catch (Exception ex) { - Log.Warning($"{ex.GetBaseException().Message}"); + Log.Error($"{ex.GetBaseException().Message}"); } } @@ -113,15 +114,13 @@ await System.Threading.Tasks.Parallel.ForEachAsync(directories, ParallelConfig.O { Log.Debug($"Removing empty directory: {currentDir.FullName}"); currentDir.Delete(true); - directoriesCount++; + _dirsCount++; } catch (Exception ex) { Log.Warning($"{ex.GetBaseException().Message}"); } } - - if(filesCount > 0 || directoriesCount > 0) CommandLine.WriteLine($"Successfully cleaned {filesCount:N0} files and {directoriesCount:N0} directories, ({Formatter.FromBytes(_freedBytes)} removed)", ConsoleColor.Green); } } } \ No newline at end of file diff --git a/Parallel.Cli/Commands/DiskCommand.cs b/Parallel.Cli/Commands/DiskCommand.cs index 63a0ad4..df4e46f 100644 --- a/Parallel.Cli/Commands/DiskCommand.cs +++ b/Parallel.Cli/Commands/DiskCommand.cs @@ -5,7 +5,6 @@ using Newtonsoft.Json.Linq; using Parallel.Cli.Utils; using Parallel.Core.Database; -using Parallel.Core.IO.Backup; using Parallel.Core.IO.FileSystem; using Parallel.Core.IO.Syncing; using Parallel.Core.Settings; @@ -36,7 +35,7 @@ public DiskCommand() : base("disk", "Shows the current disk usage.") private async Task DisplayDiskInformationAsync(LocalVaultConfig vault) { - ISyncManager syncManager = new FileSyncManager(vault); + ISyncManager syncManager = SyncManager.CreateNew(vault); if (!await syncManager.ConnectAsync()) { CommandLine.WriteLine(vault, $"Failed to connect to vault '{vault.Name}'!", ConsoleColor.Red); diff --git a/Parallel.Cli/Commands/DuplicatesCommand.cs b/Parallel.Cli/Commands/DuplicatesCommand.cs index ac1956d..d5f1393 100644 --- a/Parallel.Cli/Commands/DuplicatesCommand.cs +++ b/Parallel.Cli/Commands/DuplicatesCommand.cs @@ -2,7 +2,6 @@ using System.CommandLine; using Parallel.Cli.Utils; -using Parallel.Core.IO.Backup; using Parallel.Core.IO.Scanning; using Parallel.Core.Models; using Parallel.Core.Settings; diff --git a/Parallel.Cli/Commands/PullCommand.cs b/Parallel.Cli/Commands/PullCommand.cs index 23d40ba..fe71931 100644 --- a/Parallel.Cli/Commands/PullCommand.cs +++ b/Parallel.Cli/Commands/PullCommand.cs @@ -1,9 +1,93 @@ // Copyright 2025 Kyle Ebbinga +using System.CommandLine; +using Parallel.Cli.Utils; +using Parallel.Core.Diagnostics; +using Parallel.Core.IO; +using Parallel.Core.IO.Scanning; +using Parallel.Core.IO.Syncing; +using Parallel.Core.Models; +using Parallel.Core.Settings; + namespace Parallel.Cli.Commands { - public class PullCommand + public class PullCommand : Command { - + private readonly Option _sourceArg = new(["--path", "-p"], "The source path to sync."); + private readonly Option _configOpt = new(["--config", "-c"], "The vault configuration to use."); + private readonly Option _forceOpt = new(["--force", "-f"], "Forces the pull overwriting any files."); + + public PullCommand() : base("pull", "Pulls changes from a vault.") + { + this.AddOption(_sourceArg); + this.AddOption(_configOpt); + this.AddOption(_forceOpt); + this.SetHandler(async (path, config, force) => + { + LocalVaultConfig? vault = ParallelConfig.GetVault(config); + if (vault == null) + { + CommandLine.WriteLine($"Unable to find vault with name: '{vault}'", ConsoleColor.Yellow); + return; + } + + await PullPathAsync(vault, path, force); + }, _sourceArg, _configOpt, _forceOpt); + } + + private async Task PullPathAsync(LocalVaultConfig vault, string path, bool force) + { + ISyncManager syncManager = SyncManager.CreateNew(vault); + if (!await syncManager.ConnectAsync()) + { + CommandLine.WriteLine(vault, $"Failed to connect to vault '{vault.Name}'!", ConsoleColor.Red); + return; + } + + string fullPath = Path.GetFullPath(path); + if (PathBuilder.IsFile(fullPath)) + { + await PullFileAsync(syncManager, fullPath, force); + return; + } + + IEnumerable files = await syncManager.Database.GetFilesAsync(fullPath); + if (!files.Any()) + { + CommandLine.WriteLine("The provided directory has not been pushed!", ConsoleColor.Yellow); + return; + } + + List pullFiles = new List(); + await System.Threading.Tasks.Parallel.ForEachAsync(files, ParallelConfig.Options, async (file, ct) => + { + if (!File.Exists(file.LocalPath) || FileScanner.HasChanged(file, new SystemFile(file.LocalPath)) || force) pullFiles.Add(file); + }); + + Log.Debug($"Pulling {pullFiles.Count} files..."); + await syncManager.PullFilesAsync(pullFiles.ToArray(), new ProgressLogger()); + CommandLine.WriteLine(vault, $"Successfully pulled {pullFiles.Count:N0} files from '{vault.FileSystem.RootDirectory}'.", ConsoleColor.Green); + } + + private async Task PullFileAsync(ISyncManager syncManager, string fullPath, bool force) + { + SystemFile? remoteFile = await syncManager.Database.GetFileAsync(fullPath); + if (remoteFile == null) + { + CommandLine.WriteLine("The provided file has not been pushed!", ConsoleColor.Yellow); + return; + } + + SystemFile localFile = new SystemFile(fullPath); + if (!(FileScanner.HasChanged(localFile, remoteFile) || force)) + { + CommandLine.WriteLine("Cannot overwrite an existing file!", ConsoleColor.Yellow); + return; + } + + Log.Debug($"Pulling '{fullPath}'"); + await syncManager.PullFilesAsync([remoteFile], new ProgressLogger()); + CommandLine.WriteLine(syncManager.RemoteVault, $"Successfully pulled file from '{syncManager.RemoteVault.FileSystem.RootDirectory}'.", ConsoleColor.Green); + } } } \ No newline at end of file diff --git a/Parallel.Cli/Commands/PushCommand.cs b/Parallel.Cli/Commands/PushCommand.cs index b5d18e0..f5d388d 100644 --- a/Parallel.Cli/Commands/PushCommand.cs +++ b/Parallel.Cli/Commands/PushCommand.cs @@ -4,7 +4,6 @@ using Parallel.Cli.Utils; using Parallel.Core.Diagnostics; using Parallel.Core.IO; -using Parallel.Core.IO.Backup; using Parallel.Core.IO.Scanning; using Parallel.Core.IO.Syncing; using Parallel.Core.Models; @@ -22,7 +21,7 @@ public class PushCommand : Command private readonly Option _configOpt = new(["--config", "-c"], "The vault configuration to use."); private readonly Option _verboseOpt = new(["--verbose", "-v"], "Shows verbose output."); - public PushCommand() : base("push", "Pushes changed files to one vault or multiple.") + public PushCommand() : base("push", "Pushes changed files to vaults.") { this.AddOption(_sourceArg); this.AddOption(_configOpt); @@ -50,7 +49,7 @@ private async Task SyncPathAsync(string path) { await Program.Settings.ForEachVaultAsync(async vault => { - ISyncManager syncManager = new FileSyncManager(vault); + ISyncManager syncManager = SyncManager.CreateNew(vault); if (!await syncManager.ConnectAsync()) { CommandLine.WriteLine(vault, $"Failed to connect to vault '{vault.Name}'!", ConsoleColor.Red); diff --git a/Parallel.Cli/Commands/VaultsCommand.cs b/Parallel.Cli/Commands/VaultsCommand.cs index b981eb6..8ad97f2 100644 --- a/Parallel.Cli/Commands/VaultsCommand.cs +++ b/Parallel.Cli/Commands/VaultsCommand.cs @@ -3,7 +3,6 @@ using System.CommandLine; using Parallel.Cli.Utils; using Parallel.Core.Database; -using Parallel.Core.IO.Backup; using Parallel.Core.IO.FileSystem; using Parallel.Core.IO.Syncing; using Parallel.Core.Security; @@ -82,7 +81,7 @@ public VaultsCommand() : base("vaults", "View or edit the vaults.") return; } - ISyncManager syncManager = new FileSyncManager(config); + ISyncManager syncManager = SyncManager.CreateNew(config); if (!await syncManager.ConnectAsync()) { CommandLine.WriteLine(config, $"Failed to connect to vault '{config.Name}'!", ConsoleColor.Red); diff --git a/Parallel.Cli/Program.cs b/Parallel.Cli/Program.cs index b179be2..6aa1da6 100644 --- a/Parallel.Cli/Program.cs +++ b/Parallel.Cli/Program.cs @@ -16,8 +16,11 @@ public static async Task Main(string[] args) Settings = ParallelConfig.Load(); string logFile = Path.Combine(PathBuilder.ProgramData, "Logs", "latest.txt"); if (File.Exists(logFile)) File.Delete(logFile); + #if DEBUG + Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger(); + #else Log.Logger = new LoggerConfiguration().MinimumLevel.Warning().WriteTo.File(logFile).CreateLogger(); - //Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger(); + #endif AssemblyName assembly = Assembly.GetExecutingAssembly().GetName(); Log.Information($"{assembly.Name} [Version {assembly.Version}]"); diff --git a/Parallel.Core/Database/Contexts/SqliteContext.cs b/Parallel.Core/Database/Contexts/SqliteContext.cs index 923820d..dc74ff8 100644 --- a/Parallel.Core/Database/Contexts/SqliteContext.cs +++ b/Parallel.Core/Database/Contexts/SqliteContext.cs @@ -82,6 +82,14 @@ public async Task GetTotalFilesAsync(bool deleted) return await connection.QuerySingleOrDefaultAsync(sql, new { deleted }); } + /// + public async Task> GetFilesAsync(string path) + { + using IDbConnection connection = CreateConnection(); + string sql = $"SELECT * FROM files WHERE localpath LIKE \"%{path}%\" OR remotepath LIKE \"%{path}%\" ORDER BY lastupdate DESC"; + return await connection.QueryAsync(sql); + } + /// public async Task> GetFilesAsync(string path, bool deleted) { diff --git a/Parallel.Core/Database/IDatabase.cs b/Parallel.Core/Database/IDatabase.cs index 3327075..4564eb8 100644 --- a/Parallel.Core/Database/IDatabase.cs +++ b/Parallel.Core/Database/IDatabase.cs @@ -96,6 +96,7 @@ public interface IDatabase : IDisposable #endregion + Task> GetFilesAsync(string path); Task> GetFilesAsync(string path, bool deleted); Task GetFileAsync(string path); } diff --git a/Parallel.Core/IO/Blobs/BlobStorage.cs b/Parallel.Core/IO/Blobs/BlobStorage.cs new file mode 100644 index 0000000..630a143 --- /dev/null +++ b/Parallel.Core/IO/Blobs/BlobStorage.cs @@ -0,0 +1,81 @@ +// Copyright 2025 Kyle Ebbinga + +using Parallel.Core.Diagnostics; +using Parallel.Core.Security; + +namespace Parallel.Core.IO.Blobs +{ + /// + /// Represents the way to chunk files into blobs for syncing. + /// + public class BlobStorage + { + /// + /// The size, in bytes, to use for chunks of a file. + /// + public int ChunkSize { get; set; } + + /// + /// Gets the temp directory for storing blobs. + /// + public string TempDirectory { get; set; } + + public BlobStorage(string tempDir, int chunkSize = 4194304) + { + TempDirectory = tempDir; + ChunkSize = chunkSize; + } + + /// + /// Chunks a file into hashes for blob storage. + /// + /// The source path of the file. + /// The destination to send chunked objects to. + /// + /// + public async Task> ChunkFileAsync(string sourcePath, string destPath, IProgressReporter progress) + { + List chunkHashes = new List(); + await using FileStream fs = File.OpenRead(sourcePath); + byte[] buffer = new byte[ChunkSize]; + int bytesRead = 0; + + while ((bytesRead = await fs.ReadAsync(buffer)) > 0) + { + byte[] chunkData = new byte[bytesRead]; + Buffer.BlockCopy(buffer, 0, chunkData, 0, bytesRead); + + string hash = HashGenerator.CreateSHA256(chunkData); + string chunkPath = PathBuilder.GetObjectPath(destPath, hash); + + if(!File.Exists(chunkPath)) await File.WriteAllBytesAsync(chunkPath, chunkData); + chunkHashes.Add(hash); + } + + Log.Debug($"Wrote {chunkHashes.Count} hashes to {destPath}"); + return chunkHashes; + } + + /// + /// Assembles a file from the chunked hashes. + /// + /// + /// The path to the chunked objects' folder. + /// + public async Task AssembleFileAsync(IEnumerable chunkHashes, string sourcePath, string createFilePath) + { + Log.Debug($"Assembling '{createFilePath}' from {chunkHashes.Count()} hashes."); + await using FileStream createStream = File.Create(createFilePath); + foreach (string hash in chunkHashes) + { + string chunkPath = PathBuilder.GetObjectPath(sourcePath, hash); + if(!File.Exists(chunkPath)) throw new FileNotFoundException($"Missing chunk for hash: {hash}"); + + await using FileStream chunkStream = File.OpenRead(chunkPath); + await chunkStream.CopyToAsync(createStream); + } + + await createStream.FlushAsync(); + } + } +} \ No newline at end of file diff --git a/Parallel.Core/IO/PathBuilder.cs b/Parallel.Core/IO/PathBuilder.cs index 4cf7b6d..b2b12fd 100644 --- a/Parallel.Core/IO/PathBuilder.cs +++ b/Parallel.Core/IO/PathBuilder.cs @@ -167,5 +167,11 @@ public static bool IsFile(string path) { return !Directory.Exists(path) && File.Exists(path); } + + public static string GetObjectPath(string basePath, string hash) + { + if (hash.Length < 8) throw new ArgumentException("Hash too short for sharding", nameof(hash)); + return Path.Combine(basePath, hash.Substring(0, 2), hash.Substring(2, 2), hash.Substring(4, 2), hash.Substring(6, 2), hash); + } } } \ No newline at end of file diff --git a/Parallel.Core/IO/Scanning/FileScanner.cs b/Parallel.Core/IO/Scanning/FileScanner.cs index 2fe63db..f4a210d 100644 --- a/Parallel.Core/IO/Scanning/FileScanner.cs +++ b/Parallel.Core/IO/Scanning/FileScanner.cs @@ -5,7 +5,6 @@ using System.Text; using Newtonsoft.Json.Linq; using Parallel.Core.Database; -using Parallel.Core.IO.Backup; using Parallel.Core.IO.Syncing; using Parallel.Core.Models; using Parallel.Core.Settings; @@ -60,7 +59,7 @@ public async Task GetFileChangesAsync(string path, string[] ignore IEnumerable remoteFiles = await _db.GetFilesAsync(path, false); await System.Threading.Tasks.Parallel.ForEachAsync(remoteFiles, ParallelConfig.Options, async (remoteFile, ct) => { - if (File.Exists(remoteFile.LocalPath) && remoteFile.RemotePath != null) + if (File.Exists(remoteFile.LocalPath)) { SystemFile localFile = new SystemFile(remoteFile.LocalPath); if (IsIgnored(localFile.LocalPath, ignoreFolders)) @@ -87,17 +86,7 @@ await System.Threading.Tasks.Parallel.ForEachAsync(remoteFiles, ParallelConfig.O } }); - // foreach (SystemFile remoteFile in remoteFiles) - // { - // - // } - Log.Debug($"{localFiles.Count} files are untracked! Adding..."); - // foreach (var file in localFiles) - // { - // - // } - await System.Threading.Tasks.Parallel.ForEachAsync(localFiles, ParallelConfig.Options, async (file, ct) => { if (File.Exists(file) && !IsIgnored(file, ignoreFolders)) @@ -115,12 +104,14 @@ await System.Threading.Tasks.Parallel.ForEachAsync(localFiles, ParallelConfig.Op /// /// Gets if a file has changed. /// - /// The base file to compare. - /// The remote file to compare to. + /// The source file to compare. + /// The target file to compare to. /// True is success, otherwise false. - public static bool HasChanged(SystemFile localFile, SystemFile? remoteFile) + public static bool HasChanged(SystemFile sourcePath, SystemFile? targetPath) { - return remoteFile == null || (localFile.LastWrite.TotalMilliseconds > remoteFile.LastWrite.TotalMilliseconds && !localFile.CheckSum.SequenceEqual(remoteFile.CheckSum)); + Console.WriteLine($"{sourcePath.Name}: {targetPath} == null || ({sourcePath.LastWrite.TotalMilliseconds} > {targetPath.LastWrite.TotalMilliseconds} && {!sourcePath.CheckSum.SequenceEqual(targetPath.CheckSum)}"); + + return targetPath == null || (sourcePath.LastWrite.TotalMilliseconds > targetPath.LastWrite.TotalMilliseconds && !sourcePath.CheckSum.SequenceEqual(targetPath.CheckSum)); } @@ -164,7 +155,7 @@ public static IEnumerable GetEmptyDirectories(string path, bool r foreach (DirectoryInfo di in directory.EnumerateDirectories("*", options)) { - var files = di.EnumerateFiles("*", options); + IEnumerable files = di.EnumerateFiles("*", options); Log.Debug($"{files.Count()} files: {di.FullName}"); if (!files.Any()) list.Add(di); } diff --git a/Parallel.Core/IO/Syncing/BaseSyncManager.cs b/Parallel.Core/IO/Syncing/BaseSyncManager.cs index c9745c4..116060e 100644 --- a/Parallel.Core/IO/Syncing/BaseSyncManager.cs +++ b/Parallel.Core/IO/Syncing/BaseSyncManager.cs @@ -9,7 +9,7 @@ namespace Parallel.Core.IO.Syncing { /// - /// Represents the base way of backing up files to an associated file system. + /// Represents the base functionality for syncing files to an associated file system. /// public abstract class BaseSyncManager : ISyncManager { @@ -40,19 +40,6 @@ public BaseSyncManager(LocalVaultConfig localVault) } /// - public async Task InitializeAsync() - { - try - { - return true; - } - catch (Exception ex) - { - Log.Error(ex.GetBaseException().ToString()); - return false; - } - } - public async Task ConnectAsync() { string root = PathBuilder.GetRootDirectory(LocalVault); @@ -101,7 +88,8 @@ public async Task ConnectAsync() /// public async Task DisconnectAsync() { - await FileSystem.UploadFilesAsync([new SystemFile(TempConfigFile, PathBuilder.GetConfigurationFile(LocalVault)), new SystemFile(TempDbFile, PathBuilder.GetDatabaseFile(LocalVault))], new ProgressLogger()); + SystemFile[] tempFiles = [new SystemFile(TempConfigFile, PathBuilder.GetConfigurationFile(LocalVault)), new SystemFile(TempDbFile, PathBuilder.GetDatabaseFile(LocalVault))]; + await FileSystem.UploadFilesAsync(tempFiles, new ProgressLogger()); FileSystem.Dispose(); } diff --git a/Parallel.Core/IO/Syncing/BlobSyncManager.cs b/Parallel.Core/IO/Syncing/BlobSyncManager.cs new file mode 100644 index 0000000..7ae3b69 --- /dev/null +++ b/Parallel.Core/IO/Syncing/BlobSyncManager.cs @@ -0,0 +1,43 @@ +// Copyright 2025 Kyle Ebbinga + +using System.Reflection.Metadata; +using Parallel.Core.Diagnostics; +using Parallel.Core.IO.Blobs; +using Parallel.Core.Models; +using Parallel.Core.Settings; + +namespace Parallel.Core.IO.Syncing +{ + /// + /// Represents the way to sync files with content assigned binary objects. + /// + public class BlobSyncManager : BaseSyncManager + { + private readonly BlobStorage _blobStorage; + private string _hashes; + + /// + /// Initializes a new instance of the class. + /// + /// + public BlobSyncManager(LocalVaultConfig localVault) : base(localVault) + { + _blobStorage = new BlobStorage(TempDirectory); + _hashes = Path.Combine(TempDirectory, "Hashes.json"); + } + + /// + public override async Task PushFilesAsync(SystemFile[] files, IProgressReporter progress) + { + IEnumerable hashes = await _blobStorage.ChunkFileAsync(files.First().LocalPath, Path.Combine(TempDirectory, "objects"), new ProgressLogger()); + File.WriteAllText(_hashes, JsonConvert.SerializeObject(hashes, Formatting.Indented)); + } + + /// + public override async Task PullFilesAsync(SystemFile[] files, IProgressReporter progress) + { + IEnumerable? hashes = JsonConvert.DeserializeObject>(await File.ReadAllTextAsync(_hashes)); + await _blobStorage.AssembleFileAsync(hashes, Path.Combine(TempDirectory, "objects"), files.First().LocalPath); + } + } +} \ No newline at end of file diff --git a/Parallel.Core/IO/Syncing/DeltaSyncManager.cs b/Parallel.Core/IO/Syncing/DeltaSyncManager.cs index a9155a8..be0b55c 100644 --- a/Parallel.Core/IO/Syncing/DeltaSyncManager.cs +++ b/Parallel.Core/IO/Syncing/DeltaSyncManager.cs @@ -7,7 +7,7 @@ namespace Parallel.Core.IO.Syncing { /// - /// Represents the way to clone files to an associated file system using file deltas. + /// Represents the way to sync files to an associated file system using file deltas. /// public class DeltaSyncManager : BaseSyncManager { diff --git a/Parallel.Core/IO/Syncing/FileSyncManager.cs b/Parallel.Core/IO/Syncing/FileSyncManager.cs index c2a135a..5b0eabe 100644 --- a/Parallel.Core/IO/Syncing/FileSyncManager.cs +++ b/Parallel.Core/IO/Syncing/FileSyncManager.cs @@ -1,18 +1,14 @@ // Copyright 2025 Kyle Ebbinga -using Newtonsoft.Json.Linq; using Parallel.Core.Database; using Parallel.Core.Diagnostics; -using Parallel.Core.Events; -using Parallel.Core.IO.FileSystem; -using Parallel.Core.IO.Syncing; using Parallel.Core.Models; using Parallel.Core.Settings; -namespace Parallel.Core.IO.Backup +namespace Parallel.Core.IO.Syncing { /// - /// Represents the way to archive files to an associated file system. + /// Represents the way to sync whole files to an associated file system. /// public class FileSyncManager : BaseSyncManager { @@ -56,10 +52,7 @@ await System.Threading.Tasks.Parallel.ForEachAsync(files, ParallelConfig.Options /// public override async Task PullFilesAsync(SystemFile[] files, IProgressReporter progress) { - SystemFile[] restoreFiles = files.Where(f => f.Deleted).ToArray(); - - if (!restoreFiles.Any()) return; - await FileSystem.DownloadFilesAsync(restoreFiles, progress); + await FileSystem.DownloadFilesAsync(files, progress); } } } \ No newline at end of file diff --git a/Parallel.Core/IO/Syncing/SyncManager.cs b/Parallel.Core/IO/Syncing/SyncManager.cs index 49f4b8c..9151632 100644 --- a/Parallel.Core/IO/Syncing/SyncManager.cs +++ b/Parallel.Core/IO/Syncing/SyncManager.cs @@ -1,15 +1,8 @@ // Copyright 2025 Kyle Ebbinga -using Newtonsoft.Json.Linq; using Parallel.Core.Settings; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Parallel.Core.IO.Syncing; -namespace Parallel.Core.IO.Backup +namespace Parallel.Core.IO.Syncing { /// /// Represents the way manage s. diff --git a/Parallel.Core/Security/HashGenerator.cs b/Parallel.Core/Security/HashGenerator.cs index 71fa07f..6fda9c4 100644 --- a/Parallel.Core/Security/HashGenerator.cs +++ b/Parallel.Core/Security/HashGenerator.cs @@ -71,6 +71,16 @@ public static string CreateSHA1(string value) return Convert.ToHexString(SHA1.HashData(Encoding.ASCII.GetBytes(value))).ToLower(); } + /// + /// Computes a SHA256 hash from bytes. + /// + /// The string to hash. + /// A hash as a string. + public static string CreateSHA256(byte[] value) + { + return Convert.ToHexString(SHA256.HashData(value)).ToLower(); + } + /// /// Computes a SHA256 hash from a string. ///