From 6fc40bb8314717082c30e487fde21821c7c33fbf Mon Sep 17 00:00:00 2001 From: Timerix22 Date: Thu, 28 Sep 2023 03:51:05 +0600 Subject: [PATCH] initial commit --- .gitignore | 9 +++ .idea/.idea.SyncDirectory/.idea/.gitignore | 10 +++ .idea/.idea.SyncDirectory/.idea/encodings.xml | 4 + .../.idea.SyncDirectory/.idea/indexLayout.xml | 8 ++ .../inspectionProfiles/Project_Default.xml | 5 ++ .idea/.idea.SyncDirectory/.idea/markdown.xml | 6 ++ .idea/.idea.SyncDirectory/.idea/misc.xml | 6 ++ .idea/.idea.SyncDirectory/.idea/vcs.xml | 6 ++ Program.cs | 29 +++++++ Storage/DirectorySnapshot.cs | 47 +++++++++++ Storage/FileSize.cs | 51 ++++++++++++ Storage/FileSnapshot.cs | 38 +++++++++ Storage/StorageController.cs | 79 +++++++++++++++++++ SyncDirectory.csproj | 17 ++++ SyncDirectory.sln | 21 +++++ 15 files changed, 336 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.idea.SyncDirectory/.idea/.gitignore create mode 100644 .idea/.idea.SyncDirectory/.idea/encodings.xml create mode 100644 .idea/.idea.SyncDirectory/.idea/indexLayout.xml create mode 100644 .idea/.idea.SyncDirectory/.idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/.idea.SyncDirectory/.idea/markdown.xml create mode 100644 .idea/.idea.SyncDirectory/.idea/misc.xml create mode 100644 .idea/.idea.SyncDirectory/.idea/vcs.xml create mode 100644 Program.cs create mode 100644 Storage/DirectorySnapshot.cs create mode 100644 Storage/FileSize.cs create mode 100644 Storage/FileSnapshot.cs create mode 100644 Storage/StorageController.cs create mode 100644 SyncDirectory.csproj create mode 100644 SyncDirectory.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aaaceb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +bin/ +obj/ +[Tt]mp/ +[Tt]emp/ +[Ll]og/ +[Ll]ogs/ +*.user +*.tmp +*.log diff --git a/.idea/.idea.SyncDirectory/.idea/.gitignore b/.idea/.idea.SyncDirectory/.idea/.gitignore new file mode 100644 index 0000000..72429eb --- /dev/null +++ b/.idea/.idea.SyncDirectory/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/.idea.SyncDirectory.iml +/contentModel.xml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/.idea.SyncDirectory/.idea/encodings.xml b/.idea/.idea.SyncDirectory/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.SyncDirectory/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.SyncDirectory/.idea/indexLayout.xml b/.idea/.idea.SyncDirectory/.idea/indexLayout.xml new file mode 100644 index 0000000..f5a863a --- /dev/null +++ b/.idea/.idea.SyncDirectory/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.SyncDirectory/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.SyncDirectory/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..8d66637 --- /dev/null +++ b/.idea/.idea.SyncDirectory/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.SyncDirectory/.idea/markdown.xml b/.idea/.idea.SyncDirectory/.idea/markdown.xml new file mode 100644 index 0000000..00405d5 --- /dev/null +++ b/.idea/.idea.SyncDirectory/.idea/markdown.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.SyncDirectory/.idea/misc.xml b/.idea/.idea.SyncDirectory/.idea/misc.xml new file mode 100644 index 0000000..3ce3588 --- /dev/null +++ b/.idea/.idea.SyncDirectory/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.SyncDirectory/.idea/vcs.xml b/.idea/.idea.SyncDirectory/.idea/vcs.xml new file mode 100644 index 0000000..9661ac7 --- /dev/null +++ b/.idea/.idea.SyncDirectory/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..96a59f6 --- /dev/null +++ b/Program.cs @@ -0,0 +1,29 @@ +global using System; +global using System.Text; +global using System.Collections.Generic; +global using DTLib.Filesystem; +global using File = DTLib.Filesystem.File; +global using Directory = DTLib.Filesystem.Directory; +using DTLib.Ben.Demystifier; +using SyncDirectory.Storage; + +Console.InputEncoding = Encoding.UTF8; +Console.OutputEncoding = Encoding.UTF8; +Console.Title = "SyncDirectory"; + +try +{ + var storage = new StorageController( +#if DEBUG + "SyncDirectory-data-debug" +#endif + ); + storage.CreateSnapshot("tmp", "tmp"); +} +catch (Exception ex) +{ + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.ToStringDemystified()); + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine(); +} diff --git a/Storage/DirectorySnapshot.cs b/Storage/DirectorySnapshot.cs new file mode 100644 index 0000000..f22b4b7 --- /dev/null +++ b/Storage/DirectorySnapshot.cs @@ -0,0 +1,47 @@ +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using DTLib; +using DTLib.Dtsod; + +namespace SyncDirectory.Storage; + +public record DirectorySnapshot( + string Name, + IOPath Path, + DateTime SnapshotTimeUTC, + IReadOnlyCollection Files) +{ + public static DirectorySnapshot Parse(DtsodV23 dtsod) => + new DirectorySnapshot( + dtsod["name"], + dtsod["path"], + DateTime.ParseExact( + (string)dtsod["snapshot_time"], + MyTimeFormat.ForText, + CultureInfo.InvariantCulture), + ((List)dtsod["files"]) + .Select(fd=>FileSnapshot.Parse((DtsodV23)fd)) + .ToImmutableList() + ); + + public DtsodV23 ToDtsod() => + new() + { + {"name", Name}, + {"path", Path.ToString()}, + {"snapshot_time", SnapshotTimeUTC.ToString(MyTimeFormat.ForText)}, + {"files", Files.Select(fs=>fs.ToDtsod()).ToImmutableList()} + }; + + public static DirectorySnapshot Create(string name, IOPath dirPath) + { + var fileSnapshots = new List(); + foreach (var filePath in Directory.GetAllFiles(dirPath)) + { + fileSnapshots.Add(FileSnapshot.Create(filePath)); + } + + return new DirectorySnapshot(name, dirPath, DateTime.UtcNow, fileSnapshots); + } +} \ No newline at end of file diff --git a/Storage/FileSize.cs b/Storage/FileSize.cs new file mode 100644 index 0000000..cd0ba51 --- /dev/null +++ b/Storage/FileSize.cs @@ -0,0 +1,51 @@ +using System.Globalization; + +namespace SyncDirectory.Storage; + +public class FileSize +{ + public long Bytes { get; } + public double KiloBytes => Bytes / 1024.0; + public double MegaBytes => KiloBytes / 1024.0; + public double GigaBytes => MegaBytes / 1024.0; + + public FileSize(long bytes) => Bytes = bytes; + + public static FileSize Get(IOPath filePath) => new(File.GetSize(filePath)); + + public static FileSize Parse(string str) + { + var caseInvariant = str.ToLowerInvariant().AsSpan(); + long multi = 1; + int skipLast = 0; + if (caseInvariant[^1] == 'b') + skipLast++; + switch (caseInvariant[^(1 + skipLast)]) + { + case 'k': + multi = 1024; + skipLast++; + break; + case 'm': + multi = 1024*1024; + skipLast++; + break; + case 'b': + multi = 1024*1024*1024; + skipLast++; + break; + } + + var numberStr = caseInvariant[..^skipLast].ToString(); + double number = Convert.ToDouble(numberStr); + return new FileSize((long)(number * multi + 0.5)); + } + + public override string ToString() + { + if (GigaBytes > 1) return GigaBytes.ToString(CultureInfo.InvariantCulture) + "G"; + if (MegaBytes > 1) return MegaBytes.ToString(CultureInfo.InvariantCulture) + "M"; + if (KiloBytes > 1) return KiloBytes.ToString(CultureInfo.InvariantCulture) + "K"; + return Bytes.ToString(CultureInfo.InvariantCulture); + } +} \ No newline at end of file diff --git a/Storage/FileSnapshot.cs b/Storage/FileSnapshot.cs new file mode 100644 index 0000000..67e3910 --- /dev/null +++ b/Storage/FileSnapshot.cs @@ -0,0 +1,38 @@ +using System.Globalization; +using DTLib; +using DTLib.Dtsod; + +namespace SyncDirectory.Storage; + +public record FileSnapshot( + IOPath Path, + FileSize Size, + DateTime ModifyTimeUTC, + DateTime SnapshotTimeUTC) +{ + public static FileSnapshot Create(IOPath filePath) + { + var creationTime = System.IO.File.GetCreationTimeUtc(filePath.Str); + var modifyTime = System.IO.File.GetLastWriteTimeUtc(filePath.Str); + // sometimes LastWriteTime can be less then CreationTime when unpacking archives + var latest = modifyTime > creationTime ? modifyTime : creationTime; + return new FileSnapshot(filePath, FileSize.Get(filePath), latest, DateTime.UtcNow); + } + + public static FileSnapshot Parse(DtsodV23 dtsod) => + new FileSnapshot( + dtsod["path"], + FileSize.Parse(dtsod["size"]), + DateTime.ParseExact((string)dtsod["modify_time"], MyTimeFormat.ForText, CultureInfo.InvariantCulture), + DateTime.ParseExact((string)dtsod["snapshot_time"], MyTimeFormat.ForText, CultureInfo.InvariantCulture) + ); + + public DtsodV23 ToDtsod() => + new() + { + {"path", Path.ToString()}, + {"size", Size.ToString()}, + {"modify_time", ModifyTimeUTC.ToString(MyTimeFormat.ForText)}, + {"snapshot_time", SnapshotTimeUTC.ToString(MyTimeFormat.ForText)} + }; +} \ No newline at end of file diff --git a/Storage/StorageController.cs b/Storage/StorageController.cs new file mode 100644 index 0000000..f7d2b84 --- /dev/null +++ b/Storage/StorageController.cs @@ -0,0 +1,79 @@ +using System.Linq; +using DTLib; +using DTLib.Dtsod; + +namespace SyncDirectory.Storage; + +public class StorageController +{ + public readonly IOPath DataDir; + public readonly IOPath SnapshotsDir; + private Dictionary> Snapshots; + + /// + /// creates StorageController and parses the program data + /// + /// directory where program data is stored (default=$LocalAppData/SyncDirectory) + public StorageController(string? dataDir = null) + { + if (dataDir == null) + { + string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + DataDir = Path.Concat(localAppData, "SyncDirectory"); + } + else DataDir = dataDir; + SnapshotsDir = Path.Concat(DataDir, "snapshots"); + Snapshots = new Dictionary>(); + + // reading snapshots from SnapshotsDir + Directory.Create(SnapshotsDir); + foreach (var snapshotFile in Directory.GetAllFiles(SnapshotsDir)) + { + string content = File.ReadAllText(snapshotFile); + DtsodV23 dtsod = new DtsodV23(content); + var snapshot = DirectorySnapshot.Parse(dtsod); + if (!Snapshots.TryAdd(snapshot.Name, new List { snapshot })) + Snapshots[snapshot.Name].Add(snapshot); + } + + // sorting snapshots by time + foreach (var snapshotCollection in Snapshots) + { + Snapshots[snapshotCollection.Key] = snapshotCollection.Value + .OrderBy(s => s.SnapshotTimeUTC) + .AsParallel() + .ToList(); + } + } + + public bool TryGetSnapshots(string name, out List? snapshots) + => Snapshots.TryGetValue(name, out snapshots!); + + public bool TryGetLatestSnapshot(string name, out DirectorySnapshot? snapshot) + { + if (TryGetSnapshots(name, out var snapshots)) + { + snapshot = snapshots![^1]; + return true; + } + + snapshot = null; + return false; + } + + public DirectorySnapshot CreateSnapshot(string dirName, IOPath dirPath) + { + var snapshot = DirectorySnapshot.Create(dirName, dirPath); + if (!Snapshots.TryAdd(snapshot.Name, new List { snapshot })) + Snapshots[snapshot.Name].Add(snapshot); + + // saving to file + string fileName = $"{Path.ReplaceRestrictedChars(dirName)}-{snapshot.SnapshotTimeUTC.ToString(MyTimeFormat.ForFileNames)}.dtsod"; + IOPath filePath = Path.Concat(SnapshotsDir, dirName, fileName); + var dtsod = snapshot.ToDtsod(); + var text = dtsod.ToString(); + File.WriteAllText(filePath, text); + + return snapshot; + } +} \ No newline at end of file diff --git a/SyncDirectory.csproj b/SyncDirectory.csproj new file mode 100644 index 0000000..5ad8bf7 --- /dev/null +++ b/SyncDirectory.csproj @@ -0,0 +1,17 @@ + + + + Exe + net7.0 + disable + enable + + + + + + + + + + diff --git a/SyncDirectory.sln b/SyncDirectory.sln new file mode 100644 index 0000000..671aa09 --- /dev/null +++ b/SyncDirectory.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyncDirectory", "SyncDirectory.csproj", "{C6D68C05-3F35-4894-8CD9-202C32C90DAC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "git_configs", "git_configs", "{BCB701ED-7E3E-454D-AFCE-35C76A891E83}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C6D68C05-3F35-4894-8CD9-202C32C90DAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6D68C05-3F35-4894-8CD9-202C32C90DAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6D68C05-3F35-4894-8CD9-202C32C90DAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6D68C05-3F35-4894-8CD9-202C32C90DAC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal