Merge branch 'nightly' into 'unstable'
Nightly See merge request termidesk/va/dwatch!11
This commit is contained in:
commit
bc6ef2c62f
7 changed files with 368 additions and 27 deletions
10
config.json
10
config.json
|
|
@ -3,15 +3,7 @@
|
|||
"logfile": "/var/log/dwatch.log",
|
||||
"watch": [
|
||||
{
|
||||
"path": "/home",
|
||||
"rules": {
|
||||
"tracking": [
|
||||
"/alexander/Programming"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/etc"
|
||||
"path": "/home/alexander/Programming/work/dwatch"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
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": "578e6a3358aa670543a220b73865f5f44e52a2a7"
|
||||
"version": "3eaae341aab5797b210903b3ee5ad429c6b33fbe"
|
||||
},
|
||||
"commandr": "~>1.1.0",
|
||||
"sdiff": {
|
||||
|
|
@ -44,4 +45,4 @@
|
|||
],
|
||||
"targetPath": "bin",
|
||||
"targetType": "executable"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
"fileVersion": 1,
|
||||
"versions": {
|
||||
"arsd-official": "12.0.0",
|
||||
"cdcdb": {"version":"578e6a3358aa670543a220b73865f5f44e52a2a7","repository":"git+https://git.zhirov.kz/dwatch/cdcdb.git"},
|
||||
"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"},
|
||||
|
|
|
|||
91
source/app.d
91
source/app.d
|
|
@ -35,20 +35,59 @@ int main(string[] args)
|
|||
} else {
|
||||
program.summary("CLI интерфейс для просмотра изменений в текстовых файлах")
|
||||
.add(new Command("file", "Работа с файлами")
|
||||
// .add(new Argument("filter", "Фильтр поиска файла")
|
||||
// .optional
|
||||
// )
|
||||
.add(new Command("list", "Получить список файлов")
|
||||
.add(new Flag("l", "long", "Печать длинного хеша")
|
||||
.optional
|
||||
)
|
||||
.add(new Argument("name", "Фильтр по части имени файла")
|
||||
.optional
|
||||
)
|
||||
)
|
||||
.add(new Command("snapshots", "Получить список снимков")
|
||||
.add(new Flag("l", "long", "Печать длинного хеша")
|
||||
.optional
|
||||
)
|
||||
.add(new Argument("name", "Имя или хеш")
|
||||
.required
|
||||
)
|
||||
)
|
||||
.add(new Command("remove", "Удалить снимки")
|
||||
.add(new Argument("name", "Имя или хеш")
|
||||
.required
|
||||
)
|
||||
)
|
||||
.add(new Command("export", "Выгрузить снимки")
|
||||
.add(new Flag("l", "long", "Печать длинного хеша")
|
||||
.optional
|
||||
)
|
||||
.add(new Argument("name", "Имя или хеш")
|
||||
.required
|
||||
)
|
||||
.add(new Argument("path", "Путь для сохранения архива")
|
||||
.required
|
||||
)
|
||||
)
|
||||
)
|
||||
.add(new Command("snapshot", "Работа со снимками")
|
||||
.add(new Command("show", "Получить снимок")
|
||||
.add(new Argument("snaphash", "Хеш снимка")
|
||||
.required
|
||||
)
|
||||
)
|
||||
.add(new Command("diff", "Получить изменения снимка")
|
||||
.add(new Argument("snaphash1", "Хеш снимка")
|
||||
.required
|
||||
)
|
||||
.add(new Argument("snaphash2", "Хеш относительного снимка")
|
||||
.optional
|
||||
)
|
||||
)
|
||||
.add(new Command("remove", "Удалить снимок")
|
||||
.add(new Argument("snaphash", "Хеш снимка")
|
||||
.required
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// dwatch snapshot show <snaphash>
|
||||
// dwatch snapshot diff <snaphash1> [<snaphash2>]
|
||||
// dwatch snapshot remove <snaphash>
|
||||
// dwatch file list <name-filter>
|
||||
// dwatch file snapshots [-c|--count] <filename|filehash>
|
||||
// dwatch file remove <filename|filehash>
|
||||
// dwatch file export <filename|filehash>
|
||||
}
|
||||
|
||||
ProgramArgs argumets = program.parse(args);
|
||||
|
|
@ -74,6 +113,36 @@ 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>
|
||||
file.on("list", fList => cli.fileList(
|
||||
fList.flag("long"),
|
||||
fList.arg("name")
|
||||
))
|
||||
// dwatch file snapshots [-c|--count] <filename|filehash>
|
||||
.on("snapshots", fSnapshots => cli.fileSnapshots(
|
||||
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.flag("long"),
|
||||
fExport.arg("name"),
|
||||
fExport.arg("path")
|
||||
));
|
||||
}
|
||||
).on("snapshot", (snapshot) {
|
||||
// dwatch snapshot show <snaphash>
|
||||
snapshot.on("show", sSnapshot => cli.snapshotShow(sSnapshot.arg("snaphash")))
|
||||
// dwatch snapshot diff <snaphash1> [<snaphash2>]
|
||||
.on("diff", sDiff => cli.snapshotDiff(sDiff.arg("snaphash1"), sDiff.arg("snaphash2")))
|
||||
// dwatch snapshot remove <snaphash>
|
||||
.on("remove", sRemove => cli.snapshotRemove(sRemove.arg("snaphash")));
|
||||
});
|
||||
}
|
||||
} catch (DWException e) {
|
||||
e.print();
|
||||
|
|
|
|||
|
|
@ -2,11 +2,289 @@ module dwatch.cli.core;
|
|||
|
||||
import dwatch.lib;
|
||||
|
||||
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:
|
||||
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) {
|
||||
import std.stdio : writeln;
|
||||
writeln("CLI находится в разработке");
|
||||
_storage = new Storage(config.database);
|
||||
}
|
||||
|
||||
void close() {
|
||||
_storage.close();
|
||||
}
|
||||
|
||||
void fileList(bool longID, string name) {
|
||||
StorageFile[] storageFiles;
|
||||
|
||||
if (name.length) {
|
||||
storageFiles = _storage.findFile(name);
|
||||
} else {
|
||||
storageFiles = _storage.getFiles();
|
||||
}
|
||||
|
||||
foreach (StorageFile file; storageFiles) {
|
||||
Identifier id = file.id();
|
||||
writefln("\t%s %s",
|
||||
longID ? id.toString() : id.compact(),
|
||||
file.name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void fileSnapshots(bool longID, string nameHash) {
|
||||
StorageFile storageFile = getFile(nameHash);
|
||||
if (storageFile is null) return;
|
||||
|
||||
Snapshot[] snapshots = storageFile.snapshots();
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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(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(string hash) {
|
||||
Snapshot snapshot = getSnapshot(hash);
|
||||
if (snapshot is null) {
|
||||
writeln("Не удалось получить снимок");
|
||||
return;
|
||||
}
|
||||
string data = cast(string) snapshot.data();
|
||||
data.write;
|
||||
}
|
||||
|
||||
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(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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ private {
|
|||
// Дочерний процесс записи снимков в базу данных
|
||||
void worker(Tid mainTid, string database) {
|
||||
auto storage = new Storage(database, true, 22);
|
||||
scope(exit) storage.close();
|
||||
|
||||
storage.setupCDC(2048, 8192, 65_536, 0x7FFF, 0x7FF);
|
||||
|
||||
while (true) {
|
||||
|
|
|
|||
|
|
@ -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