initial commit
This commit is contained in:
commit
6fc40bb831
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
[Tt]mp/
|
||||||
|
[Tt]emp/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
*.user
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
10
.idea/.idea.SyncDirectory/.idea/.gitignore
vendored
Normal file
10
.idea/.idea.SyncDirectory/.idea/.gitignore
vendored
Normal 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/
|
||||||
4
.idea/.idea.SyncDirectory/.idea/encodings.xml
Normal file
4
.idea/.idea.SyncDirectory/.idea/encodings.xml
Normal 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>
|
||||||
8
.idea/.idea.SyncDirectory/.idea/indexLayout.xml
Normal file
8
.idea/.idea.SyncDirectory/.idea/indexLayout.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/.idea.SyncDirectory/.idea/markdown.xml
Normal file
6
.idea/.idea.SyncDirectory/.idea/markdown.xml
Normal 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>
|
||||||
6
.idea/.idea.SyncDirectory/.idea/misc.xml
Normal file
6
.idea/.idea.SyncDirectory/.idea/misc.xml
Normal 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>
|
||||||
6
.idea/.idea.SyncDirectory/.idea/vcs.xml
Normal file
6
.idea/.idea.SyncDirectory/.idea/vcs.xml
Normal 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
29
Program.cs
Normal 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();
|
||||||
|
}
|
||||||
47
Storage/DirectorySnapshot.cs
Normal file
47
Storage/DirectorySnapshot.cs
Normal 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
51
Storage/FileSize.cs
Normal 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
38
Storage/FileSnapshot.cs
Normal 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)}
|
||||||
|
};
|
||||||
|
}
|
||||||
79
Storage/StorageController.cs
Normal file
79
Storage/StorageController.cs
Normal 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
17
SyncDirectory.csproj
Normal 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
21
SyncDirectory.sln
Normal 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
|
||||||
Loading…
Reference in New Issue
Block a user