Формирование базового функционала клиентской части

This commit is contained in:
Alexander Zhirov 2025-11-14 18:59:09 +03:00
parent 10a422f75e
commit cf31eb494d
Signed by: alexander
GPG key ID: C8D8BE544A27C511
5 changed files with 263 additions and 35 deletions

View file

@ -17,9 +17,10 @@
],
"copyright": "Copyright © 2025, Alexander Zhirov",
"dependencies": {
"archive": "~>0.7.1",
"cdcdb": {
"repository": "git+https://git.zhirov.kz/dwatch/cdcdb.git",
"version": "a6cccc04b10ad03cc86b5f05ffe4de2b28c664fb"
"version": "3eaae341aab5797b210903b3ee5ad429c6b33fbe"
},
"commandr": "~>1.1.0",
"sdiff": {
@ -44,4 +45,4 @@
],
"targetPath": "bin",
"targetType": "executable"
}
}

View file

@ -1,7 +1,7 @@
{
"fileVersion": 1,
"versions": {
"arsd-official": "12.0.0",
"archive": "0.7.1",
"commandr": "1.1.0",
"d2sqlite3": "1.0.0",
"sdiff": {"version":"~0.1.0","repository":"git+https://git.zhirov.kz/dlang/sdiff.git"},

View file

@ -44,7 +44,7 @@ int main(string[] args)
)
)
.add(new Command("snapshots", "Получить список снимков")
.add(new Flag("c", "count", "Количество снимков")
.add(new Flag("l", "long", "Печать длинного хеша")
.optional
)
.add(new Argument("name", "Имя или хеш")
@ -57,9 +57,15 @@ int main(string[] args)
)
)
.add(new Command("export", "Выгрузить снимки")
.add(new Flag("l", "long", "Печать длинного хеша")
.optional
)
.add(new Argument("name", "Имя или хеш")
.required
)
.add(new Argument("path", "Путь для сохранения архива")
.required
)
)
)
.add(new Command("snapshot", "Работа со снимками")
@ -107,6 +113,7 @@ int main(string[] args)
daemon.run();
} else {
DWCLI cli = new DWCLI(config);
scope(exit) cli.close();
argumets.on(
"file", (file) {
// dwatch file list [-l|--long] <name-filter>
@ -116,21 +123,25 @@ int main(string[] args)
))
// dwatch file snapshots [-c|--count] <filename|filehash>
.on("snapshots", fSnapshots => cli.fileSnapshots(
fSnapshots.flag("count"),
fSnapshots.flag("long"),
fSnapshots.arg("name")
))
// dwatch file remove <filename|filehash>
.on("remove", fRemove => cli.fileRemove(fRemove.arg("name")))
// dwatch file export <filename|filehash>
.on("export", fExport => cli.fileExport(fExport.arg("name")));
.on("export", fExport => cli.fileExport(
fExport.flag("long"),
fExport.arg("name"),
fExport.arg("path")
));
}
).on("snapshot", (snapshot) {
// dwatch snapshot show <snaphash>
snapshot.on("show", sSnapshot => cli.snapshotShow())
snapshot.on("show", sSnapshot => cli.snapshotShow(sSnapshot.arg("snaphash")))
// dwatch snapshot diff <snaphash1> [<snaphash2>]
.on("diff", sDiff => cli.snapshotDiff())
.on("diff", sDiff => cli.snapshotDiff(sDiff.arg("snaphash1"), sDiff.arg("snaphash2")))
// dwatch snapshot remove <snaphash>
.on("remove", sRemove => cli.snapshotRemove());
.on("remove", sRemove => cli.snapshotRemove(sRemove.arg("snaphash")));
});
}
} catch (DWException e) {

View file

@ -1,50 +1,160 @@
module dwatch.cli.core;
import dwatch.lib;
import cdcdb;
import std.stdio : writefln, writeln;
import cdcdb;
import sdiff;
import archive.targz : TarGzArchive;
import archive.tar : TarPermissions;
import std.stdio : writefln, writeln, write;
import std.ascii : isHexDigit;
import std.format : format;
import std.datetime.date : DateTime;
import std.conv : octal;
import std.file : fwrite = write, exists, mkdir;
import std.path : buildPath, baseName, extension, buildNormalizedPath, absolutePath;
import core.sys.posix.unistd : getuid;
final class DWCLI {
private:
string _database;
Storage _storage;
bool isHexHash(const(char)[] s) @safe pure nothrow @nogc {
auto len = s.length;
if (len == 0 || len > 32) {
return false;
}
foreach (c; s) {
if (!isHexDigit(c)) return false;
}
return true;
}
Snapshot getSnapshot(string hash) {
if (!isHexHash(hash)) {
writeln("Хеш снимка некорректен");
return null;
}
Identifier id;
try {
id = Identifier(hash);
} catch (Exception e) {
writeln(e.msg);
return null;
}
Snapshot snapshot = _storage.getSnapshot(id);
if (snapshot is null) {
Snapshot[] snapshots = _storage.findSnapshot(id);
if (snapshots.length == 0) {
writeln("Снимок не найден");
return null;
}
if (snapshots.length > 1) {
writefln("Было найдено снимков: %s. Укажите полный хеш для точного совпадения.", snapshots.length);
return null;
}
snapshot = snapshots[0];
}
return snapshot;
}
StorageFile getFile(string nameHash) {
StorageFile storageFile =
isHexHash(nameHash) ? _storage.getFile(Identifier(nameHash)) : _storage.getFile(nameHash);
if (storageFile is null) {
StorageFile[] storageFiles =
isHexHash(nameHash) ? _storage.findFile(Identifier(nameHash)) : _storage.findFile(nameHash);
if (storageFiles.length == 0) {
writeln("Файл не найден");
return null;
}
if (storageFiles.length > 1) {
writefln("Было найдено файлов: %s", storageFiles.length);
foreach(size_t key, StorageFile sf; storageFiles) {
writefln(" %d.\t%s\t%s",
key + 1,
sf.id.toString,
sf.name
);
}
writeln("Укажите полный хеш или корректное имя для точного совпадения.");
return null;
}
storageFile = storageFiles[0];
}
return storageFile;
}
public:
this(const DWConfig config) {
_database = config.database;
_storage = new Storage(config.database);
}
void close() {
_storage.close();
}
void fileList(bool longID, string name) {
auto storage = new Storage(_database);
scope(exit) storage.close();
StorageFile[] storageFiles;
if (name.length) {
storageFiles = storage.findFile(name);
storageFiles = _storage.findFile(name);
} else {
storageFiles = storage.getFiles();
storageFiles = _storage.getFiles();
}
foreach (StorageFile file; storageFiles) {
Identifier id = file.id();
writefln("\t[%s] %s",
writefln("\t%s %s",
longID ? id.toString() : id.compact(),
file.name()
);
}
}
void fileSnapshots(bool count, string name) {
auto storage = new Storage(_database);
scope(exit) storage.close();
void fileSnapshots(bool longID, string nameHash) {
StorageFile storageFile = getFile(nameHash);
if (storageFile is null) return;
StorageFile[] storageFile = storage.findFile(Identifier(name));
Snapshot[] snapshots = storageFile[0].snapshots();
Snapshot[] snapshots = storageFile.snapshots();
foreach (Snapshot snapshot; snapshots) {
writefln("\t[%s] Time: %s\tProcess: %s\tUser: %s\tReal user: %s",
snapshot.id.compact,
snapshot.created,
if (snapshots.length == 0) {
writefln("%s %s - снимков не найдено",
longID ? storageFile.id.toString() : storageFile.id.compact(),
storageFile.name,
);
return;
}
writefln("%s %s - найдено снимков: %d",
longID ? storageFile.id.toString() : storageFile.id.compact(),
storageFile.name,
snapshots.length
);
foreach (size_t key, Snapshot snapshot; snapshots) {
auto current = snapshot.created;
auto dt = cast(DateTime) snapshot.created;
string date = format("%04d-%02d-%02d %02d:%02d:%02d",
dt.year, dt.month, dt.day,
current.hour, current.minute, current.second);
Identifier id = snapshot.id();
writefln(" %d.\t%s\t%s\t%s\t%s\t%s",
key + 1,
longID ? id.toString() : id.compact(),
date,
snapshot.process,
snapshot.uidName,
snapshot.ruidName
@ -52,23 +162,129 @@ public:
}
}
void fileRemove(string name) {
void fileRemove(string nameHash) {
if (getuid() != 0) {
writeln("Удаление файла требует права суперпользователя");
return;
}
StorageFile storageFile = getFile(nameHash);
if (storageFile is null) return;
_storage.deleteFile(storageFile.id) ?
writefln("Файл %s был успешно удален", storageFile.id.toString()) :
writefln("Файл %s не был удален", storageFile.id.toString());
}
void fileExport(string name) {
void fileExport(bool longID, string nameHash, string path) {
if (!exists(path)) {
writeln("Путь для сохранения архива не существует");
return;
}
StorageFile storageFile = getFile(nameHash);
if (storageFile is null) return;
Snapshot[] snapshots = storageFile.snapshots();
string fileID = longID ? storageFile.id.toString() : storageFile.id.compact();
if (snapshots.length == 0) {
writefln("%s %s - снимков не найдено", fileID, storageFile.name);
return;
}
string filename = baseName(storageFile.name());
string tarGzArchive = buildPath(path.absolutePath.buildNormalizedPath, "%s-%s.tar.gz".format(fileID, filename));
TarGzArchive archive = new TarGzArchive();
foreach (size_t key, Snapshot snapshot; snapshots) {
auto current = snapshot.created;
auto dt = cast(DateTime) snapshot.created;
string date = format("%04d%02d%02d.%02d%02d%02d",
dt.year, dt.month, dt.day,
current.hour, current.minute, current.second);
string snapshotID = longID ? snapshot.id().toString() : snapshot.id().compact();
string snapshotName = "%s-%s-%s".format(date, snapshotID, filename);
TarGzArchive.File file = new TarGzArchive.File(snapshotName);
file.permissions = TarPermissions.FILE | octal!644;
file.data = snapshot.data();
archive.addFile(file);
writefln(" %d. Архивирование снимка: %s", key, snapshotName);
}
fwrite(tarGzArchive, cast(ubyte[]) archive.serialize());
writeln("Снимки выгружены в архив: ", tarGzArchive);
}
void snapshotShow() {
void snapshotShow(string hash) {
Snapshot snapshot = getSnapshot(hash);
if (snapshot is null) {
writeln("Не удалось получить снимок");
return;
}
string data = cast(string) snapshot.data();
data.write;
}
void snapshotDiff() {
void snapshotDiff(string hash1, string hash2) {
Snapshot snapshot1 = getSnapshot(hash1);
Snapshot snapshot2;
if (snapshot1 is null) {
writeln("Не удалось получить основной снимок");
return;
}
if (hash2.length > 0) {
snapshot2 = getSnapshot(hash2);
if (snapshot2 is null) {
writeln("Не удалось получить относительный снимок");
return;
}
if (snapshot1.file != snapshot2.file) {
writeln("Относительный снимок не пренадлежит файлу основного снимка");
return;
}
} else {
// Поиск ближайшего относительного снимка
StorageFile storageFile = _storage.getFile(snapshot1.file());
Snapshot[] snapshots = storageFile.snapshots();
foreach (Snapshot snapshot; snapshots) {
if (snapshot.id == snapshot1.id) {
break;
}
snapshot2 = snapshot;
}
}
MMFile snapshotData1 = new MMFile(snapshot1.data());
MMFile snapshotData2;
if (snapshot2 is null) {
snapshotData2 = new MMFile("");
} else {
snapshotData2 = new MMFile(snapshot2.data());
}
snapshotData2.diff(snapshotData1).write;
}
void snapshotRemove() {
void snapshotRemove(string hash) {
if (getuid() != 0) {
writeln("Удаление снимка требует права суперпользователя");
return;
}
Snapshot snapshot = getSnapshot(hash);
if (snapshot is null) {
writeln("Не удалось получить снимок");
return;
}
_storage.deleteSnapshot(snapshot.id) ?
writefln("Снимок %s был успешно удален", snapshot.id.toString()) :
writefln("Снимок %s не был удален", snapshot.id.toString());
}
}

View file

@ -1,3 +1,3 @@
module dwatch.version_;
enum dwatchVersion = "0.0.8";
enum dwatchVersion = "0.0.9";