Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions Parallel.Cli/Commands/DiskCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,24 @@ private async Task DisplayDiskInformationAsync(LocalVaultConfig vault)

IDatabase db = syncManager.Database;
long localSize = await db.GetLocalSizeAsync();
long remoteSize = await db.GetRemoteSizeAsync();
long totalLocalFiles = await db.GetTotalFilesAsync(false);
long totalDeletedFiles = await db.GetTotalFilesAsync(true);
long totalObjects = await db.GetTotalObjectsAsync();

CommandLine.WriteLine($"Using vault '{vault.Name}' ({vault.Id}):");
CommandLine.WriteLine($"Service Type: {vault.FileSystem.Service}");
CommandLine.WriteLine($"Root Directory: {vault.FileSystem.RootDirectory}");
CommandLine.WriteLine($"Managed Files: {(totalLocalFiles + totalDeletedFiles):N0}");
CommandLine.WriteLine($"Local Files: {totalLocalFiles:N0}");
CommandLine.WriteLine($"Deleted Files: {totalDeletedFiles:N0}");
CommandLine.WriteLine($"Total Objects: {totalObjects:N0}");
CommandLine.WriteLine($"Local Size: {Formatter.FromBytes(localSize)}");
CommandLine.WriteLine($"Remote Size: {Formatter.FromBytes(remoteSize)}");
CommandLine.WriteLine($"Space Saved: {Math.Round((localSize - remoteSize) / (double)localSize * 100, 2)}%");

if (vault.FileSystem.Service.Equals(FileService.Local))
{
DriveInfo drive = new(vault.FileSystem.RootDirectory);
long diskUsage = drive.TotalSize - drive.TotalFreeSpace;
CommandLine.WriteLine($"Total Usage: {Formatter.FromBytes(diskUsage)} ({Math.Round(diskUsage / (double)drive.TotalSize * 100, 1)}%)");
CommandLine.WriteLine($"Disk Usage: {Formatter.FromBytes(diskUsage - remoteSize)} ({Math.Round((diskUsage - remoteSize) / (double)drive.TotalSize * 100, 1)}%)");
CommandLine.WriteLine($"Disk Free: {Formatter.FromBytes(drive.TotalFreeSpace)} ({Math.Round(drive.TotalFreeSpace / (double)drive.TotalSize * 100, 1)}%)");
CommandLine.WriteLine($"Disk Total: {Formatter.FromBytes(drive.TotalSize)}");
}
Expand Down
3 changes: 2 additions & 1 deletion Parallel.Cli/Commands/PullCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ private async Task PullPathAsync(LocalVaultConfig vault, string path, bool force
return;
}

CommandLine.WriteLine(vault, $"Scanning for files in {path}...", ConsoleColor.DarkGray);
IEnumerable<SystemFile> files = await syncManager.Database.GetFilesAsync(fullPath);
if (!files.Any())
{
Expand All @@ -69,7 +70,7 @@ private async Task PullPathAsync(LocalVaultConfig vault, string path, bool force
});

Log.Debug($"Pulling {pullFiles.Count} files...");
await syncManager.PullFilesAsync(pullFiles.ToArray(), new ProgressLogger());
await syncManager.PullFilesAsync(pullFiles.ToArray(), new ProgressReport(vault, files.Count()));
CommandLine.WriteLine(vault, $"Successfully pulled {pullFiles.Count:N0} files from '{vault.FileSystem.RootDirectory}'.", ConsoleColor.Green);
}

Expand Down
75 changes: 75 additions & 0 deletions Parallel.Cli/Commands/RemapCommand.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,88 @@
// Copyright 2025 Kyle Ebbinga

using System.CommandLine;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics;
using Parallel.Cli.Utils;
using Parallel.Core.IO.Syncing;
using Parallel.Core.Models;
using Parallel.Core.Security;
using Parallel.Core.Settings;
using SQLitePCL;

namespace Parallel.Cli.Commands
{
public class RemapCommand : Command
{
private readonly Argument<string> _sourceArg = new("source", "The source path to change.");
private readonly Argument<string> _targetArg = new("target", "The target path to change to.");
private readonly Option<string> _optionOpt = new("config", "The vault configuration to use.");

private Stopwatch _sw = new Stopwatch();

public RemapCommand() : base("remap", "Remaps paths in the vault.")
{
this.AddArgument(_sourceArg);
this.AddArgument(_targetArg);
this.SetHandler(async (config, source, target) =>
{
_sw = Stopwatch.StartNew();
LocalVaultConfig? vault = ParallelConfig.Load().Vaults.FirstOrDefault();
if (!string.IsNullOrEmpty(config)) vault = ParallelConfig.GetVault(config);
if (vault == null)
{
CommandLine.WriteLine($"Unable to find vault with name: '{vault}'", ConsoleColor.Yellow);
return;
}

if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(target))
{
CommandLine.WriteLine("The source and target paths must be specified!", ConsoleColor.Yellow);
return;
}

await RemapPathAsync(vault, source, target);
}, _optionOpt, _sourceArg, _targetArg);

}

private async Task RemapPathAsync(LocalVaultConfig vault, string source, string target)
{
ISyncManager syncManager = SyncManager.CreateNew(vault);
if (!await syncManager.ConnectAsync())
{
CommandLine.WriteLine(vault, $"Failed to connect to vault '{vault.Name}'!", ConsoleColor.Red);
return;
}

CommandLine.WriteLine(vault, $"Scanning for files in {source}...", ConsoleColor.DarkGray);
IEnumerable<SystemFile> files = await syncManager.Database.GetFilesAsync(source);
if (!files.Any())
{
CommandLine.WriteLine(vault, "No files were found!", ConsoleColor.Yellow);
return;
}

int progress = 0;
int total = files.Count();

CommandLine.WriteLine(vault, $"Remapping '{source}' to '{target}'...");
await System.Threading.Tasks.Parallel.ForEachAsync(files, async (file, ct) =>
{
CommandLine.ProgressBar(progress++, total, _sw.Elapsed);

string newPath = file.LocalPath.Replace(source, target);
string newId = HashGenerator.CreateSHA1(newPath);
await syncManager.Database.RemapObjectsAsync(file.Id, newId);
await syncManager.Database.RemoveFileAsync(file);

file.Id = newId;
file.LocalPath = newPath;
await syncManager.Database.AddFileAsync(file);
});

await syncManager.DisconnectAsync();
CommandLine.WriteLine(vault, $"Successfully remapped {files.Count():N0} files to '{target}'.", ConsoleColor.Green);
}
}
}
8 changes: 8 additions & 0 deletions Parallel.Cli/Commands/SyncCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2025 Kyle Ebbinga

namespace Parallel.Cli.Commands
{
public class SyncCommand
{
}
}
2 changes: 1 addition & 1 deletion Parallel.Cli/Commands/UnzipCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private void DecompressFile(string path, bool keep)
}
}

CommandLine.ProgressBar(_tasks.Count(t => t.IsCompleted), _totalTasks, _sw?.Elapsed ?? TimeSpan.Zero, ConsoleColor.DarkGray);
CommandLine.ProgressBar(_tasks.Count(t => t.IsCompleted), _totalTasks, _sw?.Elapsed ?? TimeSpan.Zero);
}
}
}
7 changes: 7 additions & 0 deletions Parallel.Cli/Commands/VaultsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class VaultsCommand : Command

private Command addCmd = new("add", "Adds a new vault configuration.");
private Command editCmd = new("edit", "Edits a vault configuration.");
private readonly Command findCmd = new("find", "Finds vault configurations in a location.");
private Command viewCmd = new("view", "Shows the vault configuration.");
private Command setCmd = new("set", "Sets a new vault configuration.");
private Command delCmd = new("delete", "Deletes a vault configuration.");
Expand Down Expand Up @@ -69,6 +70,12 @@ public VaultsCommand() : base("vaults", "View or edit the vaults.")
CommandLine.WriteLine($"Saved new storage vault: '{localVault.Name}' ({localVault.Id})");
});

this.AddCommand(findCmd);
findCmd.SetHandler(() =>
{

});

this.AddCommand(viewCmd);
viewCmd.AddArgument(configArg);
viewCmd.SetHandler(async (vault) =>
Expand Down
6 changes: 3 additions & 3 deletions Parallel.Cli/Commands/ZipCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ public ZipCommand() : base("zip", "Zips files in a directory.")
return;
}

CommandLine.WriteLine($"Zipping {files.Length.ToString("N0")} files...", ConsoleColor.DarkGray);
CommandLine.WriteLine($"Zipping {files.Length:N0} files...", ConsoleColor.DarkGray);
_totalTasks = files.Length;
_tasks.AddRange(files.Select(file => Task.Run(() => CompressFile(file, keep))));
await Task.WhenAll(_tasks);

CommandLine.WriteLine($"Successfully zipped {files.Length.ToString("N0")} files in {_sw.Elapsed}.", ConsoleColor.Green);
CommandLine.WriteLine($"Successfully zipped {files.Length:N0} files in {_sw.Elapsed}.", ConsoleColor.Green);
}, sourceArg, keepOpt);
}

Expand All @@ -60,7 +60,7 @@ private void CompressFile(string path, bool keep)
}
}

CommandLine.ProgressBar(_tasks.Count(t => t.IsCompleted), _totalTasks, _sw.Elapsed, ConsoleColor.DarkGray);
CommandLine.ProgressBar(_tasks.Count(t => t.IsCompleted), _totalTasks, _sw.Elapsed);
}
}
}
8 changes: 3 additions & 5 deletions Parallel.Cli/Utils/CommandLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,15 @@ public static void WriteLine(object value, ConsoleColor color = ConsoleColor.Gra
}
}

public static void ProgressBar(double part, double total, TimeSpan elapsed, ConsoleColor color = ConsoleColor.Gray)
public static void ProgressBar(double part, double total, TimeSpan elapsed)
{
double percent = part / total;
string percentStr = $"> Progress: {Convert.ToInt32(percent * 100).ToString("D2")}%";

TimeSpan remaining;
double remainingMs = elapsed.TotalMilliseconds * (total - part) / part;
if (remainingMs <= TimeSpan.MaxValue.TotalMilliseconds)
remaining = TimeSpan.FromMilliseconds(remainingMs);
else
remaining = TimeSpan.MaxValue;
if (remainingMs <= TimeSpan.MaxValue.TotalMilliseconds) remaining = TimeSpan.FromMilliseconds(remainingMs);
else remaining = TimeSpan.MaxValue;

string remainingStr = $"{remaining.Hours:00}:{remaining.Minutes:00}:{remaining.Seconds:00} remaining";
int barWidth = Console.WindowWidth - percentStr.Length - remainingStr.Length - 4;
Expand Down
24 changes: 24 additions & 0 deletions Parallel.Core/Database/Contexts/SqliteContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ public async Task<bool> AddFileAsync(SystemFile file)
return await connection.ExecuteAsync(sql, new { file.Id, file.Name, file.LocalPath, file.RemotePath, LastWrite = file.LastWrite.TotalMilliseconds, LastUpdate = UnixTime.Now.TotalMilliseconds, file.LocalSize, file.RemoteSize, Type = file.Type.ToString(), file.Hidden, file.ReadOnly, file.Deleted, file.CheckSum }) > 0;
}

/// <inheritdoc />
public async Task RemoveFileAsync(SystemFile file)
{
using IDbConnection connection = CreateConnection();
string sql = $"DELETE FROM files WHERE id = @Id;";
await connection.ExecuteAsync(sql, new { file.Id });
}

public async Task<long> GetLocalSizeAsync()
{
using IDbConnection connection = CreateConnection();
Expand Down Expand Up @@ -148,6 +156,22 @@ public async Task<IEnumerable<string>> GetObjectsAsync(string id)
return await connection.QueryAsync<string>(sql, new { id });
}

/// <inheritdoc />
public async Task<long> GetTotalObjectsAsync()
{
using IDbConnection connection = CreateConnection();
string sql = $"SELECT COUNT(*) FROM objects;";
return await connection.QuerySingleOrDefaultAsync<long>(sql);
}

/// <inheritdoc />
public async Task<bool> RemapObjectsAsync(string oldId, string newId)
{
using IDbConnection connection = CreateConnection();
string sql = "UPDATE objects SET id = @newId WHERE id = @oldId;";
return await connection.ExecuteAsync(sql, new { oldId, newId }) > 0;
}

#endregion
}
}
8 changes: 6 additions & 2 deletions Parallel.Core/Database/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ public interface IDatabase
/// <returns>True if successful, false otherwise</returns>
Task<bool> AddFileAsync(SystemFile file);

Task RemoveFileAsync(SystemFile file);

Task<IEnumerable<SystemFile>> GetFilesAsync(string path);
Task<IEnumerable<SystemFile>> GetFilesAsync(string path, bool deleted);
Task<SystemFile?> GetFileAsync(string path);

Task<long> GetLocalSizeAsync();
Task<long> GetRemoteSizeAsync();
Task<long> GetTotalFilesAsync(bool deleted);

#endregion
Expand All @@ -106,7 +107,10 @@ public interface IDatabase
Task<bool> AddObjectAsync(string id, string hash, int index);
Task<IEnumerable<string>> GetObjectsAsync(string id);

#endregion
Task<long> GetTotalObjectsAsync();

Task<bool> RemapObjectsAsync(string oldId, string newId);

#endregion
}
}
4 changes: 2 additions & 2 deletions Parallel.Core/Diagnostics/IProgressReporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ namespace Parallel.Core.Diagnostics
public enum ProgressOperation
{
Archiving,
Downloading,
Uploading,
Pulling,
Pushing,
Compressing,
Decompressing,
Syncing
Expand Down
2 changes: 1 addition & 1 deletion Parallel.Core/IO/Blobs/BlobStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public abstract class BlobStorage
/// <param name="tempObjDir">The temp directory to send chunked objects to.</param>
/// <param name="progress"></param>
/// <returns></returns>
public static async Task<FileManifest> CreateManifestAsync(IFileSystem fileSystem, string sourcePath, string tempObjDir, IProgressReporter progress)
public static async Task<FileManifest> CreateManifestAsync(IStorageProvider fileSystem, string sourcePath, string tempObjDir, IProgressReporter progress)
{
List<string> chunkHashes = new List<string>();
await using FileStream fs = File.OpenRead(sourcePath);
Expand Down
2 changes: 1 addition & 1 deletion Parallel.Core/IO/PathBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public static string GetDatabaseFile(LocalVaultConfig localVault)
}

/// <summary>
/// Builds the path on the remote <see cref="IFileSystem"/>.
/// Builds the path on the remote <see cref="IStorageProvider"/>.
/// </summary>
/// <param name="path"></param>
/// <param name="credentials"></param>
Expand Down
20 changes: 10 additions & 10 deletions Parallel.Core/IO/Syncing/BaseSyncManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,29 @@ public abstract class BaseSyncManager : ISyncManager
public IDatabase Database { get; set; }

/// <inheritdoc />
public IFileSystem FileSystem { get; set; }
public IStorageProvider Storage { get; set; }

/// <summary>
///
/// </summary>
/// <param name="localVault"></param>
public BaseSyncManager(LocalVaultConfig localVault)
{
FileSystem = FileSystemManager.CreateNew(localVault);
Storage = StorageProvider.CreateNew(localVault);
LocalVault = localVault;
}

/// <inheritdoc />
public async Task<bool> ConnectAsync()
{
string root = PathBuilder.GetRootDirectory(LocalVault);
if (!await FileSystem.ExistsAsync(root))
if (!await Storage.ExistsAsync(root))
{
await FileSystem.CreateDirectoryAsync(root);
await Storage.CreateDirectoryAsync(root);
Log.Debug($"Created root directory: {root}");
}

if (!await FileSystem.ExistsAsync(PathBuilder.GetConfigurationFile(LocalVault)))
if (!await Storage.ExistsAsync(PathBuilder.GetConfigurationFile(LocalVault)))
{
RemoteVault = new RemoteVaultConfig(LocalVault);
RemoteVault.IgnoreDirectories.Add(PathBuilder.GetRootDirectory(LocalVault));
Expand All @@ -59,15 +59,15 @@ public async Task<bool> ConnectAsync()
}
else
{
await FileSystem.DownloadFilesAsync([new SystemFile(TempConfigFile, PathBuilder.GetConfigurationFile(LocalVault))], new NullProgressReporter());
await Storage.DownloadFilesAsync([new SystemFile(TempConfigFile, PathBuilder.GetConfigurationFile(LocalVault))], new NullProgressReporter());
RemoteVaultConfig? config = RemoteVaultConfig.Load(TempConfigFile);
if(config == null) return false;
RemoteVault = config;

Log.Debug($"Downloaded config file: {TempConfigFile}");
}

if (!await FileSystem.ExistsAsync(PathBuilder.GetDatabaseFile(LocalVault)))
if (!await Storage.ExistsAsync(PathBuilder.GetDatabaseFile(LocalVault)))
{
Database = new SqliteContext(TempDbFile);
await Database.InitializeAsync();
Expand All @@ -76,7 +76,7 @@ public async Task<bool> ConnectAsync()
}
else
{
await FileSystem.DownloadFilesAsync([new SystemFile(TempDbFile, PathBuilder.GetDatabaseFile(LocalVault))], new NullProgressReporter());
await Storage.DownloadFilesAsync([new SystemFile(TempDbFile, PathBuilder.GetDatabaseFile(LocalVault))], new NullProgressReporter());
Database = new SqliteContext(TempDbFile);

Log.Debug($"Downloaded db file: {TempDbFile}");
Expand All @@ -92,8 +92,8 @@ public async Task DisconnectAsync()
Log.Debug($"Uploaded db file: {TempDbFile}");

SystemFile[] tempFiles = [new SystemFile(TempConfigFile, PathBuilder.GetConfigurationFile(LocalVault)), new SystemFile(TempDbFile, PathBuilder.GetDatabaseFile(LocalVault))];
await FileSystem.UploadFilesAsync(tempFiles, new NullProgressReporter());
FileSystem.Dispose();
await Storage.UploadFilesAsync(tempFiles, new NullProgressReporter());
Storage.Dispose();
}

/// <inheritdoc />
Expand Down
Loading
Loading