initial commit

This commit is contained in:
Timerix22 2023-09-28 03:51:05 +06:00
commit 6fc40bb831
15 changed files with 336 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
bin/
obj/
[Tt]mp/
[Tt]emp/
[Ll]og/
[Ll]ogs/
*.user
*.tmp
*.log

View File

@ -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/

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,5 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
</profile>
</component>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="fileGroupingEnabled" value="true" />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

29
Program.cs Normal file
View File

@ -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();
}

View File

@ -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<FileSnapshot> Files)
{
public static DirectorySnapshot Parse(DtsodV23 dtsod) =>
new DirectorySnapshot(
dtsod["name"],
dtsod["path"],
DateTime.ParseExact(
(string)dtsod["snapshot_time"],
MyTimeFormat.ForText,
CultureInfo.InvariantCulture),
((List<object>)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<FileSnapshot>();
foreach (var filePath in Directory.GetAllFiles(dirPath))
{
fileSnapshots.Add(FileSnapshot.Create(filePath));
}
return new DirectorySnapshot(name, dirPath, DateTime.UtcNow, fileSnapshots);
}
}

51
Storage/FileSize.cs Normal file
View File

@ -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);
}
}

38
Storage/FileSnapshot.cs Normal file
View File

@ -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)}
};
}

View File

@ -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<string, List<DirectorySnapshot>> Snapshots;
/// <summary>
/// creates StorageController and parses the program data
/// </summary>
/// <param name="dataDir">directory where program data is stored (default=$LocalAppData/SyncDirectory)</param>
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<string, List<DirectorySnapshot>>();
// 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<DirectorySnapshot> { 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<DirectorySnapshot>? 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<DirectorySnapshot> { 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;
}
}

17
SyncDirectory.csproj Normal file
View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DTLib" Version="1.3.0" />
<PackageReference Include="DTLib.Dtsod" Version="1.3.0" />
<PackageReference Include="DTLib.Logging" Version="1.3.0" />
</ItemGroup>
</Project>

21
SyncDirectory.sln Normal file
View File

@ -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