Формирование базового функционала клиентской части
This commit is contained in:
parent
10a422f75e
commit
cf31eb494d
5 changed files with 263 additions and 35 deletions
5
dub.json
5
dub.json
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"},
|
||||
|
|
|
|||
23
source/app.d
23
source/app.d
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
module dwatch.version_;
|
||||
|
||||
enum dwatchVersion = "0.0.8";
|
||||
enum dwatchVersion = "0.0.9";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue