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