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
86 changes: 43 additions & 43 deletions Parallel.Cli/Commands/CleanCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public CleanCommand() : base("clean", "Cleans up the file system by removing old
{
ParallelConfig config = ParallelConfig.Load();
if (days <= config.RetentionPeriod) days = config.RetentionPeriod;
CommandLine.WriteLine($"Scanning for cleanable files older than {days:N0} days old...", ConsoleColor.DarkGray);

if (string.IsNullOrEmpty(path))
{
Expand All @@ -54,72 +55,71 @@ await System.Threading.Tasks.Parallel.ForEachAsync(config.CleanDirectories, Para

private async Task CleanDirectoryAsync(ParallelConfig config, string path, int days, bool recursive, bool verbose)
{
CommandLine.WriteLine($"Scanning for cleanable files older than {days:N0} days old in {path}...", ConsoleColor.DarkGray);
if (!Directory.Exists(path))
{
CommandLine.WriteLine($"The provided path was not found!", ConsoleColor.Yellow);
CommandLine.WriteLine($"Unable to find path: '{path}'", ConsoleColor.Yellow);
return;
}

UnixTime minTime = UnixTime.FromMilliseconds(UnixTime.Now.TotalMilliseconds - (days * UnixTime.Day));
IEnumerable<FileInfo> cleanableFiles = FileScanner.GetCleanableFiles(path, minTime, recursive);
if (!cleanableFiles.Any()) CommandLine.WriteLine($"No cleanable files were found in the provided path.", ConsoleColor.Green);

await System.Threading.Tasks.Parallel.ForEachAsync(cleanableFiles, ParallelConfig.Options, (fi, ct) =>
if (cleanableFiles.Any())
{
if (fi.Exists)
await System.Threading.Tasks.Parallel.ForEachAsync(cleanableFiles, ParallelConfig.Options, (fi, ct) =>
{
try
{
_freedBytes += fi.Length;
_filesCount++;
fi.Delete();
}
catch (Exception ex)
if (fi.Exists)
{
CommandLine.WriteLine($"Unable to remove file: {fi.FullName}", ConsoleColor.Yellow);
Log.Warning($"{ex.GetBaseException().Message}");
try
{
_freedBytes += fi.Length;
_filesCount++;
fi.Delete();
}
catch (Exception ex)
{
CommandLine.WriteLine($"Unable to remove file: {fi.FullName}", ConsoleColor.Yellow);
Log.Warning($"{ex.GetBaseException().Message}");
}
}
}

return ValueTask.CompletedTask;
});
return ValueTask.CompletedTask;
});
}

IEnumerable<DirectoryInfo> directories = FileScanner.GetEmptyDirectories(path, recursive);
if (!directories.Any()) CommandLine.WriteLine($"No empty directories were found in the provided path.", ConsoleColor.Green);

await System.Threading.Tasks.Parallel.ForEachAsync(directories, ParallelConfig.Options, (di, ct) =>
if (!directories.Any())
{
if (di.Exists && !di.EnumerateFiles().Any())
System.Threading.Tasks.Parallel.ForEach(directories, ParallelConfig.Options, (di) =>
{
if (di.Exists && !di.EnumerateFiles().Any())
{
try
{
Log.Debug($"Removing empty directory: {di?.FullName}");
di?.Delete(true);
}
catch (Exception ex)
{
Log.Error($"{ex.GetBaseException().Message}");
}
}
});

DirectoryInfo currentDir = new DirectoryInfo(path);
SearchOption option = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
if (!currentDir.EnumerateFiles("*", option).Any())
{
try
{
Log.Debug($"Removing empty directory: {di?.FullName}");
di?.Delete(true);
Log.Debug($"Removing empty directory: {currentDir.FullName}");
currentDir.Delete(true);
_dirsCount++;
}
catch (Exception ex)
{
Log.Error($"{ex.GetBaseException().Message}");
Log.Warning($"{ex.GetBaseException().Message}");
}
}

return ValueTask.CompletedTask;
});

DirectoryInfo currentDir = new DirectoryInfo(path);
SearchOption option = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
if (!currentDir.EnumerateFiles("*", option).Any())
{
try
{
Log.Debug($"Removing empty directory: {currentDir.FullName}");
currentDir.Delete(true);
_dirsCount++;
}
catch (Exception ex)
{
Log.Warning($"{ex.GetBaseException().Message}");
}
}
}
}
Expand Down
17 changes: 9 additions & 8 deletions Parallel.Cli/Commands/DiskCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,28 @@ namespace Parallel.Cli.Commands
{
public class DiskCommand : Command
{
private readonly Argument<string> configArg = new("config", "The vault configuration to use.");
private readonly Option<string> _configOpt = new(["--config", "-c"], "The vault configuration to use.");

public DiskCommand() : base("disk", "Shows the current disk usage.")
{
this.AddArgument(configArg);
this.SetHandler(async (vault) =>
this.AddOption(_configOpt);
this.SetHandler(async (config) =>
{
CommandLine.WriteLine($"Retrieving vault information...", ConsoleColor.DarkGray);
LocalVaultConfig? config = ParallelConfig.GetVault(vault);
if (config == null)
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;
}

await DisplayDiskInformationAsync(config);
}, configArg);
await DisplayDiskInformationAsync(vault);
}, _configOpt);
}

private async Task DisplayDiskInformationAsync(LocalVaultConfig vault)
{
CommandLine.WriteLine($"Retrieving vault information...", ConsoleColor.DarkGray);
ISyncManager syncManager = SyncManager.CreateNew(vault);
if (!await syncManager.ConnectAsync())
{
Expand Down
17 changes: 12 additions & 5 deletions Parallel.Cli/Commands/PullCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2025 Kyle Ebbinga

using System.CommandLine;
using Newtonsoft.Json.Linq;
using Parallel.Cli.Utils;
using Parallel.Core.Diagnostics;
using Parallel.Core.IO;
Expand All @@ -24,10 +25,11 @@ public PullCommand() : base("pull", "Pulls changes from a vault.")
this.AddOption(_forceOpt);
this.SetHandler(async (path, config, force) =>
{
LocalVaultConfig? vault = ParallelConfig.GetVault(config);
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);
CommandLine.WriteLine($"No vault was found!", ConsoleColor.Yellow);
return;
}

Expand All @@ -37,6 +39,7 @@ public PullCommand() : base("pull", "Pulls changes from a vault.")

private async Task PullPathAsync(LocalVaultConfig vault, string path, bool force)
{
CommandLine.WriteLine($"Retrieving vault information...", ConsoleColor.DarkGray);
ISyncManager syncManager = SyncManager.CreateNew(vault);
if (!await syncManager.ConnectAsync())
{
Expand All @@ -54,12 +57,13 @@ private async Task PullPathAsync(LocalVaultConfig vault, string path, bool force
IEnumerable<SystemFile> files = await syncManager.Database.GetFilesAsync(fullPath);
if (!files.Any())
{
CommandLine.WriteLine("The provided directory has not been pushed!", ConsoleColor.Yellow);
CommandLine.WriteLine(vault, "No files were found!", ConsoleColor.Yellow);
await syncManager.DisconnectAsync();
return;
}

List<SystemFile> pullFiles = new List<SystemFile>();
await System.Threading.Tasks.Parallel.ForEachAsync(files, ParallelConfig.Options, async (file, ct) =>
System.Threading.Tasks.Parallel.ForEach(files, ParallelConfig.Options, (file) =>
{
if (!File.Exists(file.LocalPath) || FileScanner.HasChanged(file, new SystemFile(file.LocalPath)) || force) pullFiles.Add(file);
});
Expand All @@ -74,7 +78,8 @@ private async Task PullFileAsync(ISyncManager syncManager, string fullPath, bool
SystemFile? remoteFile = await syncManager.Database.GetFileAsync(fullPath);
if (remoteFile == null)
{
CommandLine.WriteLine("The provided file has not been pushed!", ConsoleColor.Yellow);
CommandLine.WriteLine("The provided file was not found!", ConsoleColor.Yellow);
await syncManager.DisconnectAsync();
return;
}

Expand All @@ -87,6 +92,8 @@ private async Task PullFileAsync(ISyncManager syncManager, string fullPath, bool

Log.Debug($"Pulling '{fullPath}'");
await syncManager.PullFilesAsync([remoteFile], new ProgressLogger());
await syncManager.DisconnectAsync();

CommandLine.WriteLine(syncManager.RemoteVault, $"Successfully pulled file from '{syncManager.RemoteVault.FileSystem.RootDirectory}'.", ConsoleColor.Green);
}
}
Expand Down
3 changes: 3 additions & 0 deletions Parallel.Cli/Commands/PushCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ private Task SyncSystemAsync()

private async Task SyncPathAsync(string path)
{
CommandLine.WriteLine($"Retrieving vault information...", ConsoleColor.DarkGray);
await Program.Settings.ForEachVaultAsync(async vault =>
{
ISyncManager syncManager = SyncManager.CreateNew(vault);
Expand All @@ -65,12 +66,14 @@ await Program.Settings.ForEachVaultAsync(async vault =>
if (!backupFolders.Any(dir => fullPath.StartsWith(dir, StringComparison.OrdinalIgnoreCase)))
{
CommandLine.WriteLine(vault, $"The provided {(isFile ? "file" : "folder")} is not set to be backed up!", ConsoleColor.Yellow);
await syncManager.DisconnectAsync();
return;
}

if (FileScanner.IsIgnored(fullPath, ignoredFolders))
{
CommandLine.WriteLine(vault, $"The provided {(isFile ? "file" : "folder")} is set to be ignored!", ConsoleColor.Yellow);
await syncManager.DisconnectAsync();
return;
}

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

using System.CommandLine;

namespace Parallel.Cli.Commands
{
public class RemapCommand : Command
{
public RemapCommand() : base("remap", "Remaps paths in the vault.")
{
}
}
}
2 changes: 1 addition & 1 deletion Parallel.Cli/Utils/TextWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Parallel.Cli.Utils
{
public class TextWriter
public abstract class TextWriter
{
public static string CreateTxtFile(string text)
{
Expand Down
50 changes: 33 additions & 17 deletions Parallel.Core/Database/Contexts/SqliteContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System.Data;
using System.Diagnostics;
using Dapper;
using Newtonsoft.Json.Linq;
using Parallel.Core.IO;
using Parallel.Core.IO.Blobs;
using Parallel.Core.Models;
using Parallel.Core.Settings;
using Parallel.Core.Utils;
Expand All @@ -26,11 +28,6 @@ public SqliteContext(string filePath)
FilePath = filePath;
}

public void Dispose()
{
// TODO release managed resources here
}

#region Base

/// <inheritdoc />
Expand All @@ -45,7 +42,8 @@ public async Task InitializeAsync()
Log.Information("Creating index database...");

using IDbConnection connection = CreateConnection();
await connection.ExecuteAsync("CREATE TABLE IF NOT EXISTS `files` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `localpath` TEXT NOT NULL, `remotepath` TEXT NOT NULL, `lastwrite` LONG INTEGER NOT NULL, `lastupdate` LONG INTEGER NOT NULL, `localsize` LONG INTEGER NOT NULL, `remotesize` LONG INTEGER NOT NULL, `type` TEXT NOT NULL DEFAULT Other CHECK(`type` IN ('Document', 'Photo', 'Music', 'Video', 'Other')), `hidden` INTEGER NOT NULL DEFAULT 0, `readonly` INTEGER NOT NULL DEFAULT 0, `deleted` INTEGER NOT NULL DEFAULT 0, `checksum` TEXT, PRIMARY KEY(`id`));");
await connection.ExecuteAsync("CREATE TABLE IF NOT EXISTS `objects` (`id` TEXT NOT NULL, `hash` TEXT NOT NULL, orderIndex INTEGER NOT NULL, UNIQUE (id, orderIndex));");
await connection.ExecuteAsync("CREATE TABLE IF NOT EXISTS `files` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `localpath` TEXT NOT NULL, `remotepath` TEXT NOT NULL, `lastwrite` LONG INTEGER NOT NULL, `lastupdate` LONG INTEGER NOT NULL, `localsize` LONG INTEGER NOT NULL, `remotesize` LONG INTEGER NOT NULL, `type` TEXT NOT NULL DEFAULT Other CHECK(`type` IN ('Document', 'Photo', 'Music', 'Video', 'Other')), `hidden` INTEGER NOT NULL DEFAULT 0, `readonly` INTEGER NOT NULL DEFAULT 0, `deleted` INTEGER NOT NULL DEFAULT 0, `checksum` BLOB, PRIMARY KEY(`id`));");
await connection.ExecuteAsync("CREATE TABLE IF NOT EXISTS `history` (`timestamp` LONG INTEGER NOT NULL, `path` TEXT NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`timestamp`));");
}

Expand Down Expand Up @@ -86,24 +84,24 @@ public async Task<long> GetTotalFilesAsync(bool deleted)
public async Task<IEnumerable<SystemFile>> 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<SystemFile>(sql);
string sql = "SELECT * FROM files WHERE localpath LIKE @Path OR remotepath LIKE @Path ORDER BY lastupdate DESC";
return await connection.QueryAsync<SystemFile>(sql, new { Path = $"%{path}%" });
}

/// <inheritdoc />
public async Task<IEnumerable<SystemFile>> GetFilesAsync(string path, bool deleted)
{
using IDbConnection connection = CreateConnection();
string sql = $"SELECT * FROM files WHERE deleted = {deleted} ORDER BY lastupdate DESC";
return await connection.QueryAsync<SystemFile>(sql);
string sql = $"SELECT * FROM files WHERE deleted = @deleted ORDER BY lastupdate DESC";
return await connection.QueryAsync<SystemFile>(sql,new { deleted });
}

/// <inheritdoc />
public async Task<SystemFile?> GetFileAsync(string path)
{
using IDbConnection connection = CreateConnection();
string sql = $"SELECT (id, name, localpath, remotepath, lastwrite, lastupdate, LocalSize, RemoteSize, type, hidden, readonly, deleted, checksum) FROM files WHERE localpath LIKE \"%{path}%\" OR remotepath LIKE \"%{path}%\" ORDER BY lastupdate DESC";
return await connection.QuerySingleOrDefaultAsync<SystemFile>(sql);
string sql = $"SELECT (id, name, localpath, remotepath, lastwrite, lastupdate, localsize, remotesize, type, hidden, readonly, deleted, checksum) FROM files WHERE localpath LIKE \"%@path%\" OR remotepath LIKE \"%@path%\" ORDER BY lastupdate DESC";
return await connection.QuerySingleOrDefaultAsync<SystemFile>(sql, new { path });
}

#endregion
Expand All @@ -113,11 +111,9 @@ public async Task<IEnumerable<SystemFile>> GetFilesAsync(string path, bool delet
/// <inheritdoc />
public async Task<bool> AddHistoryAsync(string path, HistoryType type)
{
using (IDbConnection connection = CreateConnection())
{
string sql = @"INSERT OR REPLACE INTO history (timestamp, name, path, type) VALUES(@Timestamp, @Name, @Path, @Type);";
return await connection.ExecuteAsync(sql, new { Timestamp = UnixTime.Now.TotalMilliseconds, Name = Path.GetFileName(path), Path = path, Type = type }) > 0;
}
using IDbConnection connection = CreateConnection();
string sql = @"INSERT OR REPLACE INTO history (timestamp, name, path, type) VALUES(@Timestamp, @Name, @Path, @Type);";
return await connection.ExecuteAsync(sql, new { Timestamp = UnixTime.Now.TotalMilliseconds, Name = Path.GetFileName(path), Path = path, Type = type }) > 0;
}

/// <inheritdoc />
Expand All @@ -133,5 +129,25 @@ public async Task<bool> AddHistoryAsync(string path, HistoryType type)
}

#endregion

#region Objects

/// <inheritdoc />
public async Task<bool> AddObjectAsync(string id, string hash, int index)
{
using IDbConnection connection = CreateConnection();
string sql = "INSERT OR REPLACE INTO objects (id, hash, orderIndex) VALUES (@id, @hash, @index);";
return await connection.ExecuteAsync(sql, new { id, hash, index }) > 0;
}

/// <inheritdoc />
public async Task<IEnumerable<string>> GetObjectsAsync(string id)
{
using IDbConnection connection = CreateConnection();
string sql = "SELECT (hash) FROM objects WHERE id = @id ORDER BY orderIndex ASC;";
return await connection.QueryAsync<string>(sql, new { id });
}

#endregion
}
}
Loading
Loading