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
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ insert_final_newline = false

# Indent style for other files

[*.{csproj,resx,xml}]
[*.{csproj,resx,xml,json,jsonc}]
indent_style = space
indent_size = 2
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ StyleCopReport.xml
*.vspscc
*.vssscc
.builds
builds
*.pidb
*.svclog
*.scc
Expand Down
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
# cssync

| c | s | sync |
|:-----:|:-------:|:----:|
| Cloud | Storage | Sync |

<div>
<a>
<img src="https://img.shields.io/github/last-commit/Akeoott/cssync?style=for-the-badge&logoSize=auto&labelColor=%23201a19&color=%23ffb4a2"/>
</a>
<a>
<img src="https://img.shields.io/github/stars/Akeoott/cssync?style=for-the-badge&labelColor=%231d1b16&color=%23e6c419"/>
</a>
<a>
<img src="https://img.shields.io/github/repo-size/Akeoott/cssync?style=for-the-badge&labelColor=%231a1b1f&color=%23a8c7ff"/>
</a>
</div>
<br>

| c | s | sync |
|:-----:|:-------:|:----:|
| Cloud | Storage | Sync |

> [!WARNING]
> This project is WIP and POC

Written in C# for simplicity, performance and safety.<br>
Aiming for cross platform compatibility and support for various cloud storage services using rclone.
Aiming for cross platform compatibility and support for various cloud storage services.

## Structure

This project is separated in to parts.

| cssync.Backend | cssync.Cli / cssync.Gui |
|:--------------:|:-----------------------:|
| Handles logic | Handle user input |
| cssync.Backend | cssync.Gui |
|:---------------------:|:---------------------------:|
| Handles all the logic | Handle user input |
| Allow cli access | Provide graphical interface |

> [!NOTE]
> Planing to make backend fully independent from CLI and GUI,
> by just accepting args when running in a terminal.
> Gui is planned and will happen once the backend is finished.
> [!INFO]
> The backend is currently in development and the GUI will come in the future.
15 changes: 0 additions & 15 deletions cssync.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cssync.Backend", "src\cssync.Backend\cssync.Backend.csproj", "{7B696CFB-B168-4EF0-9152-9F12A7728080}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cssync.Cli", "src\cssync.Cli\cssync.Cli.csproj", "{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -31,24 +29,11 @@ Global
{7B696CFB-B168-4EF0-9152-9F12A7728080}.Release|x64.Build.0 = Release|Any CPU
{7B696CFB-B168-4EF0-9152-9F12A7728080}.Release|x86.ActiveCfg = Release|Any CPU
{7B696CFB-B168-4EF0-9152-9F12A7728080}.Release|x86.Build.0 = Release|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Debug|x64.ActiveCfg = Debug|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Debug|x64.Build.0 = Debug|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Debug|x86.ActiveCfg = Debug|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Debug|x86.Build.0 = Debug|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Release|Any CPU.Build.0 = Release|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Release|x64.ActiveCfg = Release|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Release|x64.Build.0 = Release|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Release|x86.ActiveCfg = Release|Any CPU
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{7B696CFB-B168-4EF0-9152-9F12A7728080} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{FAB0E13E-20E0-4AD6-8F44-F79D952DFD1A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
EndGlobal
52 changes: 32 additions & 20 deletions src/cssync.Backend/Cssync.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
// Copyright (c) Ame (Akeoott) <ame@akeoot.org>. Licensed under the GPLv3 License.
// See the LICENSE file in the repository root for full license text.

using Newtonsoft.Json.Linq;

using cssync.Backend.helper;

namespace cssync.Backend;

/// <summary>
/// Reads config and acts on the data it contains
/// </summary>
public static class Cssync
{
internal const string cssyncOptions = """
Usage:
> cssync [option] [scope] [args]
Options:
> cssync help | See all options
> cssync list | Display entire json config
> cssync run | Run a variable
> cssync edit | Edit a value in config
> cssync append | Append a value to config
> cssync remove | Remove a value from config
Scopes:
help | See usage and expanded info for each option
variables | Configure your Variables (rclone command presets)
timer | Configure the Timer (when commands periodically execute)
""";

public static async Task<string> RunCssync(string option = "help", string? scope = null, params string[] args)
public static async Task RunCssync()
{
if (!await Json.GetConfigStatus())
{
Log.Info("cssync is currently disabled by config");
}

var cts = new CancellationTokenSource();
var token = cts.Token;

Task task = Task.Run(() => CssyncLoop(token), token);

while (true)
{
if (!await Json.GetConfigStatus())
{
cts.Cancel();
await Task.WhenAll(task);
Log.Info("cssync was stopped by config");
return;
}
await Task.Delay(5000);
}
}

private static async Task CssyncLoop(CancellationToken token)
{
string warningMsg = "Cssync has not been implemented yet! Please check the main branch out for the latest updates.";
Log.Warn(warningMsg);
return warningMsg;
// TODO
}
}
56 changes: 52 additions & 4 deletions src/cssync.Backend/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,63 @@

using cssync.Backend.helper;

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace cssync.Backend;

internal class MainBackend
{
internal static async Task Main()
[DllImport("libc")]
private static extern int isatty(int fd);
public static async Task<bool> HasTerminal()
{
try
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return !Console.IsOutputRedirected && !Console.IsInputRedirected;
}
else
{
return isatty(0) == 1; // stdin
}
}
catch
{
return false;
}
}

internal static async Task Main(string[] args)
{
// NOTE: Backend may or may not become completely independent for automated use.
// As long as thats no the case, this may stay unchanged.
// TODO: Rewrite pending once appropriate...
if (!await HasTerminal())
{
Log.Critical("This program must be run from a terminal.");
Thread.Sleep(1000);
return;
}

Log.Debug("Current process: {ProcessName}", Process.GetCurrentProcess());
switch (args.Length)
{
case 0:
ParseInput.NoArguments();
break;

case 1:
await ParseInput.SingleArgument(args[0]);
break;

case 2:
await ParseInput.TwoArguments(args[0], args[1]);
break;

default:
Console.WriteLine($"Too many arguments. Use --help for usage.");
break;
}

// await Cssync.RunCssync();
}
}
1 change: 1 addition & 0 deletions src/cssync.Backend/cssync.Backend.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>

</Project>
104 changes: 45 additions & 59 deletions src/cssync.Backend/helper/Json.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
// Copyright (c) Ame (Akeoott) <ame@akeoot.org>. Licensed under the GPLv3 License.
// See the LICENSE file in the repository root for full license text.

using System.Text.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace cssync.Backend.helper;

internal class Config
{
public required Dictionary<string, List<string>> Variables { get; set; }
public required Dictionary<string, List<int>> Timer { get; set; }
public bool Run { get; set; } = false;
public JObject Sections { get; set; } = new JObject();
}

internal class Json
internal static class Json
{
internal static readonly string configPath = AppDomain.CurrentDomain.BaseDirectory + "config.json";
internal static readonly JsonSerializerOptions options = new() { WriteIndented = true };
private static readonly string configPath = AppDomain.CurrentDomain.BaseDirectory + "config.json";
private static DateTime _lastConfigWriteTime = DateTime.MinValue;

/// <summary>
/// Get current serialized configuration of cssync.
/// </summary>
/// <returns>Serialized config</returns>
public static async Task<string> GetSerializedConfig()
=> Serialize(await Deserialize());
internal static async Task<string> Serialize(Config config)
=> JsonConvert.SerializeObject(config, Formatting.Indented);

/// <summary>
/// Serializes input from the Config class
/// </summary>
/// <param name="input">json config</param>
/// <returns>string containing serialized config</returns>
internal static string Serialize(Config input)
{
Log.Debug("Serializing config");
return JsonSerializer.Serialize(input, options);
}

/// <summary>
/// Deserializes config located next to application.
/// Generates config if not found.
/// </summary>
/// <returns>Config of application</returns>
internal static async Task<Config> Deserialize()
{
int attempts = 0;
Expand All @@ -49,61 +30,66 @@ internal static async Task<Config> Deserialize()
{
await GenConfig();
}

try
{
Log.Debug("Deserializing config");

string json = await File.ReadAllTextAsync(configPath);
return JsonSerializer.Deserialize<Config>(json)
?? throw new InvalidDataException("Output is null or empty");
return JsonConvert.DeserializeObject<Config>(json) ?? new Config();
}
catch (Exception ex) when (ex is FileNotFoundException || ex is JsonException || ex is InvalidDataException)
catch (Exception ex) when (ex is FileNotFoundException || ex is JsonException)
{
attempts++;
Log.Critical("Loading config failed (attempt {attempts}/3). {ex}: {ex.Message}", attempts, ex, ex.Message);
await Task.Delay(100);
}
}
throw new InvalidOperationException($"Something went wrong. Failed to get config after {attempts} attempts");

throw new InvalidOperationException($"Failed to load config after {attempts} attempts");
}

internal static async Task WriteConfig(Config config)
{
try
{
string json = JsonConvert.SerializeObject(config, Formatting.Indented);
await File.WriteAllTextAsync(configPath, json);
Log.Debug("Wrote config to disk");
}
catch (Exception ex)
{
Log.Critical("Failed to write config. {ex}: {ex.Message}", ex, ex.Message);
}
}

/// <summary>
/// Generates a json file in application directory if no file was found.
/// </summary>
internal static async Task GenConfig()
{
if (File.Exists(configPath))
{
Log.Info("Config exists");
return;
}
else

Log.Warn("Config doesn't exist. Generating config");
var config = new Config
{
Log.Warn("Config doesn't exist. Generating config");
Config config = new()
Sections = new JObject
{
Variables = [],
Timer = [],
};
await WriteConfig(config);
}
["Variables"] = new JObject(),
["Timers"] = new JObject()
}
};
await WriteConfig(config);
Log.Info("Successfully generated config");
}

/// <summary>
/// Writes config to file asynchronously
/// </summary>
/// <param name="config">Config to write</param>
internal static async Task WriteConfig(Config config)
internal static async Task<bool> GetConfigStatus()
{
try
{
string jsonString = Serialize(config);
await File.WriteAllTextAsync(configPath, jsonString);
Log.Debug("Wrote to config");
}
catch (Exception ex)
{
Log.Critical("Failed to write config. {ex}: {ex.Message}", ex, ex.Message);
}
DateTime currentWriteTime = File.GetLastWriteTimeUtc(configPath);
if (currentWriteTime == _lastConfigWriteTime) return false;
_lastConfigWriteTime = currentWriteTime;

var config = await Deserialize();
return config.Run;
}
}
Loading
Loading