Compare commits
7 commits
renovate/n
...
main
Author | SHA1 | Date | |
---|---|---|---|
0f7a992ae6 | |||
e5eb5ea651 | |||
b778ae7f6b | |||
f37bf01597 | |||
f031591753 | |||
acf8990de1 | |||
85d63d5ae2 |
27 changed files with 1269 additions and 1038 deletions
2
.l10nignore
Normal file
2
.l10nignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
js/
|
||||
vendor/
|
|
@ -5,7 +5,7 @@
|
|||
<name>Groupfolder Filesystem Snapshots</name>
|
||||
<summary>Allows restoring a groupfolder to a previous snapshot in the filesystem</summary>
|
||||
<description>App proving a PHP API for other apps, that allows (partially) restoring a groupfolder to a previous snapshot in the filesystem. Requires a filesystem with snapshot support (tested with and made for ZFS). It is made for other apps to integrate with, IT DOES NOT WORK STANDALONE</description>
|
||||
<version>1.3.1</version>
|
||||
<version>1.4.1</version>
|
||||
<licence>agpl</licence>
|
||||
<author homepage="https://verdigado.com/">verdigado eG</author>
|
||||
<author mail="jonathan.treffler@verdigado.com">Jonathan Treffler</author>
|
||||
|
@ -13,7 +13,7 @@
|
|||
<category>files</category>
|
||||
<bugs>https://git.verdigado.com/verdigado-public/nextcloud_groupfolder_filesystem_snapshots/issues</bugs>
|
||||
<dependencies>
|
||||
<nextcloud min-version="28" max-version="29"/>
|
||||
<nextcloud min-version="29" max-version="30"/>
|
||||
<database>pgsql</database>
|
||||
<database>sqlite</database>
|
||||
<database>mysql</database>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
before_cmds = [
|
||||
"composer install",
|
||||
"npm install",
|
||||
"npm ci",
|
||||
"npm run build",
|
||||
]
|
||||
|
|
0
l10n/.gitkeep
Normal file
0
l10n/.gitkeep
Normal file
9
l10n/de.js
Normal file
9
l10n/de.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
OC.L10N.register(
|
||||
"groupfolder_filesystem_snapshots",
|
||||
{
|
||||
"Automated hourly backup" : "Stündliches automatisches Backup",
|
||||
"Automated daily backup" : "Tägliches automatisches Backup",
|
||||
"Automated weekly backup" : "Wöchentliches automatisches Backup",
|
||||
"Automated monthly backup" : "Monatliches automatisches Backup"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
7
l10n/de.json
Normal file
7
l10n/de.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{ "translations": {
|
||||
"Automated hourly backup" : "Stündliches automatisches Backup",
|
||||
"Automated daily backup" : "Tägliches automatisches Backup",
|
||||
"Automated weekly backup" : "Wöchentliches automatisches Backup",
|
||||
"Automated monthly backup" : "Monatliches automatisches Backup"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
9
l10n/de_DE.js
Normal file
9
l10n/de_DE.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
OC.L10N.register(
|
||||
"groupfolder_filesystem_snapshots",
|
||||
{
|
||||
"Automated hourly backup" : "Stündliches automatisches Backup",
|
||||
"Automated daily backup" : "Tägliches automatisches Backup",
|
||||
"Automated weekly backup" : "Wöchentliches automatisches Backup",
|
||||
"Automated monthly backup" : "Monatliches automatisches Backup"
|
||||
},
|
||||
"nplurals=2; plural=(n != 1);");
|
7
l10n/de_DE.json
Normal file
7
l10n/de_DE.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{ "translations": {
|
||||
"Automated hourly backup" : "Stündliches automatisches Backup",
|
||||
"Automated daily backup" : "Tägliches automatisches Backup",
|
||||
"Automated weekly backup" : "Wöchentliches automatisches Backup",
|
||||
"Automated monthly backup" : "Monatliches automatisches Backup"
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
}
|
|
@ -19,29 +19,29 @@ class AdminSettingsController extends Controller {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function index(): JSONResponse {
|
||||
return new JSONResponse($this->settingsService->getAppValues());
|
||||
}
|
||||
/**
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function index(): JSONResponse {
|
||||
return new JSONResponse($this->settingsService->getAppValues());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function show($key): JSONResponse {
|
||||
return new JSONResponse($this->settingsService->getAppValue($key));
|
||||
}
|
||||
/**
|
||||
* @param $key
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function show($key): JSONResponse {
|
||||
return new JSONResponse($this->settingsService->getAppValue($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @param $value
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function update($key, $value): JSONResponse {
|
||||
/**
|
||||
* @param $key
|
||||
* @param $value
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function update($key, $value): JSONResponse {
|
||||
return new JSONResponse($this->settingsService->setAppValue($key, $value));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,25 +7,25 @@ use OCP\AppFramework\Db\Entity;
|
|||
|
||||
class DiffTask extends Entity implements JsonSerializable {
|
||||
|
||||
protected $userId;
|
||||
protected $groupfolderId;
|
||||
protected $userId;
|
||||
protected $groupfolderId;
|
||||
protected $relativePath;
|
||||
protected $snapshotId;
|
||||
protected $timestamp;
|
||||
protected $snapshotId;
|
||||
protected $timestamp;
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('id','integer');
|
||||
$this->addType('groupfolderId','integer');
|
||||
}
|
||||
public function __construct() {
|
||||
$this->addType('id','integer');
|
||||
$this->addType('groupfolderId','integer');
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'userId' => $this->userId,
|
||||
'groupfolderId' => $this->groupfolderId,
|
||||
'groupfolderId' => $this->groupfolderId,
|
||||
'relativePath' => $this->relativePath,
|
||||
'snapshotId' => $this->snapshotId,
|
||||
'timestamp' => $this->timestamp,
|
||||
];
|
||||
}
|
||||
'snapshotId' => $this->snapshotId,
|
||||
'timestamp' => $this->timestamp,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -9,29 +9,29 @@ use OCP\AppFramework\Db\QBMapper;
|
|||
*/
|
||||
class DiffTaskMapper extends QBMapper {
|
||||
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'groupfolder_snapshots_tasks', DiffTask::class);
|
||||
}
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'groupfolder_snapshots_tasks', DiffTask::class);
|
||||
}
|
||||
|
||||
public function find(int $id, string $userId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
public function find(int $id, string $userId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
|
||||
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
|
||||
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
public function findAll(string $userId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
public function findAll(string $userId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
}
|
|
@ -7,45 +7,49 @@ use OCP\AppFramework\Db\Entity;
|
|||
|
||||
class DiffTaskResult extends Entity implements JsonSerializable {
|
||||
|
||||
protected $taskId;
|
||||
protected $timestamp;
|
||||
protected $type;
|
||||
protected $taskId;
|
||||
protected $timestamp;
|
||||
protected $type;
|
||||
|
||||
protected $beforeFileExists;
|
||||
protected $beforePath;
|
||||
protected $beforeSize;
|
||||
protected $beforeFileExists;
|
||||
protected $beforePath;
|
||||
protected $beforeSize;
|
||||
|
||||
protected $currentFileExists;
|
||||
protected $currentPath;
|
||||
protected $currentSize;
|
||||
protected $currentFileExists;
|
||||
protected $currentFileId;
|
||||
|
||||
protected $currentPath;
|
||||
protected $currentSize;
|
||||
protected $reverted;
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('id','integer');
|
||||
$this->addType('taskId','integer');
|
||||
$this->addType('beforeFileExists','boolean');
|
||||
$this->addType('beforeSize','integer');
|
||||
$this->addType('currentFileExists','boolean');
|
||||
$this->addType('currentSize','integer');
|
||||
public function __construct() {
|
||||
$this->addType('id','integer');
|
||||
$this->addType('taskId','integer');
|
||||
$this->addType('beforeFileExists','boolean');
|
||||
$this->addType('beforeSize','integer');
|
||||
$this->addType('currentFileExists','boolean');
|
||||
$this->addType('currentFileId','integer');
|
||||
$this->addType('currentSize','integer');
|
||||
$this->addType('reverted','boolean');
|
||||
}
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'taskId' => $this->taskId,
|
||||
'type' => $this->type,
|
||||
'before' => [
|
||||
'fileExists' => $this->beforeFileExists,
|
||||
'path' => $this->beforePath,
|
||||
'size' => $this->beforeSize,
|
||||
],
|
||||
'current' => [
|
||||
'fileExists' => $this->currentFileExists,
|
||||
'path' => $this->currentPath,
|
||||
'size' => $this->currentSize,
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'taskId' => $this->taskId,
|
||||
'type' => $this->type,
|
||||
'before' => [
|
||||
'fileExists' => $this->beforeFileExists,
|
||||
'path' => $this->beforePath,
|
||||
'size' => $this->beforeSize,
|
||||
],
|
||||
'current' => [
|
||||
'fileExists' => $this->currentFileExists,
|
||||
'fileId' => $this->currentFileId,
|
||||
'path' => $this->currentPath,
|
||||
'size' => $this->currentSize,
|
||||
],
|
||||
'reverted' => $this->reverted,
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
|
@ -9,29 +9,29 @@ use OCP\AppFramework\Db\QBMapper;
|
|||
*/
|
||||
class DiffTaskResultMapper extends QBMapper {
|
||||
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'groupfolder_snapshots_task_results', DiffTaskResult::class);
|
||||
}
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'groupfolder_snapshots_task_results', DiffTaskResult::class);
|
||||
}
|
||||
|
||||
public function find(int $id) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
public function find(int $id) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
|
||||
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
public function findAll(int $taskId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
public function findAll(int $taskId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('task_id', $qb->createNamedParameter($taskId)));
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('task_id', $qb->createNamedParameter($taskId)));
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function markReverted(int $id) {
|
||||
$taskResult = $this->find($id);
|
||||
|
|
|
@ -5,16 +5,30 @@ namespace OCA\GroupfolderFilesystemSnapshots\Entity;
|
|||
use JsonSerializable;
|
||||
|
||||
class Snapshot implements JsonSerializable {
|
||||
/** @var string */
|
||||
private $id;
|
||||
public function __construct(
|
||||
private string $id,
|
||||
private string $name,
|
||||
private ?\DateTimeImmutable $createdTimestamp = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __construct(string $id) {
|
||||
$this->id = $id;
|
||||
}
|
||||
public function getId(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed {
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getCreatedTimestamp(): ?\DateTimeImmutable {
|
||||
return $this->createdTimestamp;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): mixed {
|
||||
return [
|
||||
'id' => $this->id
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'createdTimestamp' => $this->createdTimestamp?->getTimestamp(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,34 +3,34 @@
|
|||
namespace OCA\GroupfolderFilesystemSnapshots\Helpers;
|
||||
|
||||
class FileHelper {
|
||||
private static function seperateFilesFromFolders($parentDir, $items) {
|
||||
$files = [];
|
||||
$folders = [];
|
||||
private static function seperateFilesFromFolders($parentDir, $items) {
|
||||
$files = [];
|
||||
$folders = [];
|
||||
|
||||
foreach($items as $item) {
|
||||
if(is_dir($parentDir . DIRECTORY_SEPARATOR . $item)) {
|
||||
$folders[] = $item;
|
||||
} else {
|
||||
$files[] = $item;
|
||||
}
|
||||
}
|
||||
foreach($items as $item) {
|
||||
if(is_dir($parentDir . DIRECTORY_SEPARATOR . $item)) {
|
||||
$folders[] = $item;
|
||||
} else {
|
||||
$files[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return array($files, $folders);
|
||||
}
|
||||
return array($files, $folders);
|
||||
}
|
||||
|
||||
public static function getFilesAndFolders($dir) {
|
||||
$scan = array_diff(scandir($dir), array('..', '.'));
|
||||
public static function getFilesAndFolders($dir) {
|
||||
$scan = array_diff(scandir($dir), array('..', '.'));
|
||||
|
||||
return self::seperateFilesFromFolders($dir, $scan);
|
||||
}
|
||||
return self::seperateFilesFromFolders($dir, $scan);
|
||||
}
|
||||
|
||||
public static function getFilesizesOfFiles($prefix, array $files) {
|
||||
$result = array();
|
||||
public static function getFilesizesOfFiles($prefix, array $files) {
|
||||
$result = array();
|
||||
|
||||
foreach($files as $index=>$file) {
|
||||
$result[$index] = filesize($prefix . DIRECTORY_SEPARATOR . $file);
|
||||
}
|
||||
foreach($files as $index=>$file) {
|
||||
$result[$index] = filesize($prefix . DIRECTORY_SEPARATOR . $file);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -70,12 +70,12 @@ class PathManager {
|
|||
private function checkIfGroupfolderExists(int $groupfolderId): bool {
|
||||
$storageId = $this->getRootFolderStorageId();
|
||||
if ($storageId === null) {
|
||||
return "storage Id null";
|
||||
return false;
|
||||
}
|
||||
|
||||
$folder = $this->groupfolderFolderManager->getFolder($groupfolderId, $storageId);
|
||||
if ($folder === false) {
|
||||
return "Folder does not exist";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -2,16 +2,21 @@
|
|||
|
||||
namespace OCA\GroupfolderFilesystemSnapshots\Manager;
|
||||
|
||||
use OCA\GroupfolderFilesystemSnapshots\Manager\PathManager;
|
||||
use OCP\IL10N;
|
||||
|
||||
use OCA\GroupfolderFilesystemSnapshots\Manager\PathManager;
|
||||
use OCA\GroupfolderFilesystemSnapshots\Service\SettingsService;
|
||||
use OCA\GroupfolderFilesystemSnapshots\Entity\Snapshot;
|
||||
|
||||
class SnapshotManager {
|
||||
private PathManager $pathManager;
|
||||
private string $snapshotNamingScheme = "";
|
||||
|
||||
|
||||
public function __construct(PathManager $pathManager){
|
||||
$this->pathManager = $pathManager;
|
||||
public function __construct(
|
||||
protected readonly IL10N $l10n,
|
||||
private readonly PathManager $pathManager,
|
||||
private readonly SettingsService $settingsService,
|
||||
){
|
||||
$this->snapshotNamingScheme = $this->settingsService->getAppValue("snapshot_naming_scheme");
|
||||
}
|
||||
|
||||
private function validSnapshotId(string $snapshotId) {
|
||||
|
@ -27,20 +32,94 @@ class SnapshotManager {
|
|||
}
|
||||
}
|
||||
|
||||
private function createSnapshotEntity(string $id): Snapshot {
|
||||
if ($this->snapshotNamingScheme === "zfs-auto-snapshot" && str_starts_with($id, "zfs-auto-snap")) {
|
||||
if (str_starts_with($id, "zfs-auto-snap_hourly-")) {
|
||||
$name = $this->l10n->t("Automated hourly backup");
|
||||
$datetimestring = str_replace("zfs-auto-snap_hourly-", "", $id);
|
||||
} elseif (str_starts_with($id, "zfs-auto-snap_daily-")) {
|
||||
$name = $this->l10n->t("Automated daily backup");
|
||||
$datetimestring = str_replace("zfs-auto-snap_daily-", "", $id);
|
||||
} elseif (str_starts_with($id, "zfs-auto-snap_weekly-")) {
|
||||
$name = $this->l10n->t("Automated weekly backup");
|
||||
$datetimestring = str_replace("zfs-auto-snap_weekly-", "", $id);
|
||||
} elseif (str_starts_with($id, "zfs-auto-snap_monthly-")) {
|
||||
$name = $this->l10n->t("Automated monthly backup");
|
||||
$datetimestring = str_replace("zfs-auto-snap_monthly-", "", $id);
|
||||
}
|
||||
|
||||
if(isset($datetimestring)) {
|
||||
$datetimearray = explode("-", $datetimestring);
|
||||
$timestring = array_pop($datetimearray);
|
||||
|
||||
$year = (int)$datetimearray[0];
|
||||
$month = (int)$datetimearray[1];
|
||||
$day = (int)$datetimearray[2];
|
||||
$hour = (int)substr($timestring, 0, 2);
|
||||
$minute = (int)substr($timestring, 2, 2);
|
||||
|
||||
$createdTimestamp = (new \DateTimeImmutable())
|
||||
->setDate($year, $month, $day)
|
||||
->setTime($hour, $minute);
|
||||
}
|
||||
|
||||
return new Snapshot(
|
||||
id: $id,
|
||||
name: $name ?: $id,
|
||||
createdTimestamp: $createdTimestamp,
|
||||
);
|
||||
} else {
|
||||
return new Snapshot(
|
||||
id: $id,
|
||||
name: $id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function get(string $snapshotId) {
|
||||
if(self::snapshotExists($snapshotId)) {
|
||||
return new Snapshot($snapshotId);
|
||||
return $this->createSnapshotEntity($snapshotId);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getAll() {
|
||||
function getAll(): array {
|
||||
$snapshots = [];
|
||||
|
||||
$iterator = new \FilesystemIterator($this->pathManager->getFilesystemSnapshotsPath());
|
||||
foreach ($iterator as $fileinfo) {
|
||||
if(!$fileinfo->isDir()) continue;
|
||||
yield new Snapshot($fileinfo->getFilename());
|
||||
}
|
||||
|
||||
foreach ($iterator as $fileinfo) {
|
||||
if(!$fileinfo->isDir()) continue;
|
||||
$snapshots[] = $this->createSnapshotEntity($fileinfo->getFilename());
|
||||
}
|
||||
|
||||
return $snapshots;
|
||||
}
|
||||
|
||||
function getAllGenerator(){
|
||||
$iterator = new \FilesystemIterator($this->pathManager->getFilesystemSnapshotsPath());
|
||||
|
||||
foreach ($iterator as $fileinfo) {
|
||||
if(!$fileinfo->isDir()) continue;
|
||||
yield $this->createSnapshotEntity($fileinfo->getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var $subPathFilter Only return snapshots that have this subfolder in the specified groupfolder
|
||||
*/
|
||||
function getFilteredGenerator(int $groupfolderId, string $subDirectoryFilter) {
|
||||
$iterator = new \FilesystemIterator($this->pathManager->getFilesystemSnapshotsPath());
|
||||
|
||||
$groupfolderSubdirectoryPath = $this->pathManager->getGroupFolderDirectory($groupfolderId, $subDirectoryFilter);
|
||||
|
||||
foreach ($iterator as $fileinfo) {
|
||||
if(!$fileinfo->isDir()) continue;
|
||||
$snapshotId = $fileinfo->getFilename();
|
||||
$filterFullPath = $this->pathManager->convertToSnapshotPath($groupfolderSubdirectoryPath, $snapshotId);
|
||||
if(!(is_dir($filterFullPath))) continue;
|
||||
yield $this->createSnapshotEntity($snapshotId);
|
||||
}
|
||||
}
|
||||
}
|
33
lib/Migration/Version140Date20250701164500.php
Normal file
33
lib/Migration/Version140Date20250701164500.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\GroupfolderFilesystemSnapshots\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
use OCP\Migration\IOutput;
|
||||
|
||||
class Version140Date20250701164500 extends SimpleMigrationStep {
|
||||
const RESULTS_TABLE = "groupfolder_snapshots_task_results";
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
$table = $schema->getTable(self::RESULTS_TABLE);
|
||||
|
||||
if(!$table->hasColumn('current_file_id')) {
|
||||
$table->addColumn('current_file_id', Types::BIGINT, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
|
@ -5,227 +5,223 @@ namespace OCA\GroupfolderFilesystemSnapshots;
|
|||
use OCA\GroupfolderFilesystemSnapshots\Helpers\FileHelper;
|
||||
|
||||
class RecursiveDiff {
|
||||
private $scan1files = [];
|
||||
private $scan1folders = [];
|
||||
|
||||
public string $dir1;
|
||||
public string $dir2;
|
||||
private $scan2files = [];
|
||||
private $scan2folders = [];
|
||||
|
||||
private $prefix;
|
||||
private $subJobs = [];
|
||||
|
||||
private $newResultCallback;
|
||||
private $progressCallback;
|
||||
private $subJobProgress = [];
|
||||
private $progress = 0;
|
||||
|
||||
private $scan1files = [];
|
||||
private $scan1folders = [];
|
||||
public function __construct(
|
||||
public readonly string $dir1,
|
||||
public readonly string $dir2,
|
||||
private readonly string $prefix = "",
|
||||
private readonly array $folderBlocklist = [],
|
||||
private $newResultCallback,
|
||||
private $progressCallback,
|
||||
){}
|
||||
|
||||
private $scan2files = [];
|
||||
private $scan2folders = [];
|
||||
public function scan() {
|
||||
$scan_num_files = 0;
|
||||
|
||||
private $subJobs = [];
|
||||
if(file_exists($this->dir1) && is_dir($this->dir1)) {
|
||||
[$this->scan1files, $this->scan1folders] = FileHelper::getFilesAndFolders($this->dir1);
|
||||
}
|
||||
|
||||
private $subJobProgress = [];
|
||||
private $progress = 0;
|
||||
if(file_exists($this->dir2) && is_dir($this->dir2)) {
|
||||
[$this->scan2files, $this->scan2folders] = FileHelper::getFilesAndFolders($this->dir2);
|
||||
}
|
||||
|
||||
public function __construct($dir1, $dir2, $prefix = "", $newResultCallback, $progressCallback){
|
||||
$this->dir1 = $dir1;
|
||||
$this->dir2 = $dir2;
|
||||
$this->prefix = $prefix;
|
||||
$scan_num_files += sizeof($this->scan1files);
|
||||
$scan_num_files += sizeof($this->scan2files);
|
||||
|
||||
$this->newResultCallback = $newResultCallback;
|
||||
$this->progressCallback = $progressCallback;
|
||||
}
|
||||
$allSubfolders = array_unique(array_merge($this->scan1folders, $this->scan2folders));
|
||||
|
||||
public function scan() {
|
||||
$scan_num_files = 0;
|
||||
foreach($allSubfolders as $key=>$folder) {
|
||||
$subdir1 = $this->dir1 . DIRECTORY_SEPARATOR . $folder;
|
||||
$subdir2 = $this->dir2 . DIRECTORY_SEPARATOR . $folder;
|
||||
$subprefix = $this->prefix . DIRECTORY_SEPARATOR . $folder;
|
||||
$subFolderBlocklist = $this->folderBlocklist[$folder] ?? [];
|
||||
|
||||
if(file_exists($this->dir1) && is_dir($this->dir1)) {
|
||||
[$this->scan1files, $this->scan1folders] = FileHelper::getFilesAndFolders($this->dir1);
|
||||
}
|
||||
if($subFolderBlocklist === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(file_exists($this->dir2) && is_dir($this->dir2)) {
|
||||
[$this->scan2files, $this->scan2folders] = FileHelper::getFilesAndFolders($this->dir2);
|
||||
}
|
||||
$newJob = new RecursiveDiff($subdir1, $subdir2, $subprefix, $subFolderBlocklist, $this->newResultCallback, function($numDoneFiles) use ($key) {
|
||||
$this->subJobProgress[$key] = $numDoneFiles;
|
||||
|
||||
$scan_num_files += sizeof($this->scan1files);
|
||||
$scan_num_files += sizeof($this->scan2files);
|
||||
$this->updateProgress();
|
||||
});
|
||||
|
||||
$allSubfolders = array_unique(array_merge($this->scan1folders, $this->scan2folders));
|
||||
$this->subJobs[] = $newJob;
|
||||
|
||||
foreach($allSubfolders as $key=>$folder) {
|
||||
$subdir1 = $this->dir1 . DIRECTORY_SEPARATOR . $folder;
|
||||
$subdir2 = $this->dir2 . DIRECTORY_SEPARATOR . $folder;
|
||||
$subprefix = $this->prefix . DIRECTORY_SEPARATOR . $folder;
|
||||
$scan_num_files += $newJob->scan();
|
||||
}
|
||||
|
||||
$newJob = new RecursiveDiff($subdir1, $subdir2, $subprefix, $this->newResultCallback, function($numDoneFiles) use ($key) {
|
||||
$this->subJobProgress[$key] = $numDoneFiles;
|
||||
return $scan_num_files;
|
||||
}
|
||||
|
||||
$this->updateProgress();
|
||||
});
|
||||
private function updateProgress() {
|
||||
($this->progressCallback)(array_sum($this->subJobProgress) + $this->progress);
|
||||
}
|
||||
|
||||
$this->subJobs[] = $newJob;
|
||||
function diff() {
|
||||
$diff = [];
|
||||
|
||||
$scan_num_files += $newJob->scan();
|
||||
}
|
||||
foreach($this->subJobs as $job) {
|
||||
$result = $job->diff();
|
||||
array_push($diff, ...$result);
|
||||
}
|
||||
|
||||
return $scan_num_files;
|
||||
}
|
||||
$fileCreations = array_diff($this->scan2files, $this->scan1files);
|
||||
$fileCreationsFilesizes = FileHelper::getFilesizesOfFiles($this->dir2, $fileCreations);
|
||||
|
||||
private function updateProgress() {
|
||||
($this->progressCallback)(array_sum($this->subJobProgress) + $this->progress);
|
||||
}
|
||||
$fileDeletions = array_diff($this->scan1files, $this->scan2files);
|
||||
$fileDeletionsFilesizes = FileHelper::getFilesizesOfFiles($this->dir1, $fileDeletions);
|
||||
|
||||
function diff() {
|
||||
$diff = [];
|
||||
$filePossibleEdits = array_intersect($this->scan1files, $this->scan2files);
|
||||
|
||||
foreach($this->subJobs as $job) {
|
||||
$result = $job->diff();
|
||||
array_push($diff, ...$result);
|
||||
}
|
||||
/*$diff[] = [
|
||||
"type" => "DEBUG",
|
||||
"prefix" => $this->prefix,
|
||||
"fileCreations" => $fileCreations,
|
||||
"fileCreationsFilesizes" => $fileCreationsFilesizes,
|
||||
"fileDeletions" => $fileDeletions,
|
||||
"fileDeletionsFilesizes" => $fileDeletionsFilesizes,
|
||||
//"folderCreations" => $folderCreations,
|
||||
//"folderDeletions" => $folderDeletions,
|
||||
"allSubfolders" => $allSubfolders,
|
||||
];*/
|
||||
|
||||
$fileCreations = array_diff($this->scan2files, $this->scan1files);
|
||||
$fileCreationsFilesizes = FileHelper::getFilesizesOfFiles($this->dir2, $fileCreations);
|
||||
// search for creations and deletions, that are actually renames
|
||||
foreach($fileCreations as $creationIndex=>$creation) {
|
||||
$creationPath = $this->dir2 . DIRECTORY_SEPARATOR . $creation;
|
||||
$creationSize = $fileCreationsFilesizes[$creationIndex];
|
||||
|
||||
$fileDeletions = array_diff($this->scan1files, $this->scan2files);
|
||||
$fileDeletionsFilesizes = FileHelper::getFilesizesOfFiles($this->dir1, $fileDeletions);
|
||||
$renameContenders = array_keys($fileDeletionsFilesizes, $creationSize);
|
||||
|
||||
$filePossibleEdits = array_intersect($this->scan1files, $this->scan2files);
|
||||
if(sizeof($renameContenders) != 0) {
|
||||
/*$diff[] = [
|
||||
"type" => "DEBUG",
|
||||
"comparing" => [
|
||||
"creation" => $creationIndex,
|
||||
"deletions" => $renameContenders,
|
||||
],
|
||||
];*/
|
||||
|
||||
/*$diff[] = [
|
||||
"type" => "DEBUG",
|
||||
"prefix" => $this->prefix,
|
||||
"fileCreations" => $fileCreations,
|
||||
"fileCreationsFilesizes" => $fileCreationsFilesizes,
|
||||
"fileDeletions" => $fileDeletions,
|
||||
"fileDeletionsFilesizes" => $fileDeletionsFilesizes,
|
||||
//"folderCreations" => $folderCreations,
|
||||
//"folderDeletions" => $folderDeletions,
|
||||
"allSubfolders" => $allSubfolders,
|
||||
];*/
|
||||
$creationSHA = sha1_file($creationPath);
|
||||
foreach($renameContenders as $contender) {
|
||||
$deletion = $fileDeletions[$contender];
|
||||
$deletionPath = $this->dir1 . DIRECTORY_SEPARATOR . $deletion;
|
||||
$deletionSHA = sha1_file($deletionPath);
|
||||
|
||||
// search for creations and deletions, that are actually renames
|
||||
foreach($fileCreations as $creationIndex=>$creation) {
|
||||
$creationPath = $this->dir2 . DIRECTORY_SEPARATOR . $creation;
|
||||
$creationSize = $fileCreationsFilesizes[$creationIndex];
|
||||
if($deletionSHA == $creationSHA) {
|
||||
($this->newResultCallback)(
|
||||
type: "RENAME",
|
||||
beforeFileExists: True,
|
||||
beforePath: $this->prefix . DIRECTORY_SEPARATOR . $deletion,
|
||||
beforeSize: $creationSize,
|
||||
currentFileExists: True,
|
||||
currentPath: $this->prefix . DIRECTORY_SEPARATOR . $creation,
|
||||
currentSize: $creationSize,
|
||||
);
|
||||
|
||||
$renameContenders = array_keys($fileDeletionsFilesizes, $creationSize);
|
||||
unset($fileCreations[$creationIndex]);
|
||||
unset($fileDeletions[$contender]);
|
||||
|
||||
if(sizeof($renameContenders) != 0) {
|
||||
/*$diff[] = [
|
||||
"type" => "DEBUG",
|
||||
"comparing" => [
|
||||
"creation" => $creationIndex,
|
||||
"deletions" => $renameContenders,
|
||||
],
|
||||
];*/
|
||||
$this->progress += 2;
|
||||
$this->updateProgress();
|
||||
|
||||
$creationSHA = sha1_file($creationPath);
|
||||
foreach($renameContenders as $contender) {
|
||||
$deletion = $fileDeletions[$contender];
|
||||
$deletionPath = $this->dir1 . DIRECTORY_SEPARATOR . $deletion;
|
||||
$deletionSHA = sha1_file($deletionPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($deletionSHA == $creationSHA) {
|
||||
($this->newResultCallback)(
|
||||
type: "RENAME",
|
||||
beforeFileExists: True,
|
||||
beforePath: $this->prefix . DIRECTORY_SEPARATOR . $deletion,
|
||||
beforeSize: $creationSize,
|
||||
currentFileExists: True,
|
||||
currentPath: $this->prefix . DIRECTORY_SEPARATOR . $creation,
|
||||
currentSize: $creationSize,
|
||||
);
|
||||
foreach($fileCreations as $index=>$creation) {
|
||||
($this->newResultCallback)(
|
||||
type: "CREATION",
|
||||
beforeFileExists: False,
|
||||
beforePath: NULL,
|
||||
beforeSize: NULL,
|
||||
currentFileExists: True,
|
||||
currentPath: $this->prefix . DIRECTORY_SEPARATOR . $creation,
|
||||
currentSize: $fileCreationsFilesizes[$index],
|
||||
);
|
||||
|
||||
unset($fileCreations[$creationIndex]);
|
||||
unset($fileDeletions[$contender]);
|
||||
$this->progress++;
|
||||
$this->updateProgress();
|
||||
}
|
||||
|
||||
$this->progress += 2;
|
||||
$this->updateProgress();
|
||||
foreach($fileDeletions as $index=>$deletion) {
|
||||
($this->newResultCallback)(
|
||||
type: "DELETION",
|
||||
beforeFileExists: True,
|
||||
beforePath: $this->prefix . DIRECTORY_SEPARATOR . $deletion,
|
||||
beforeSize: $fileDeletionsFilesizes[$index],
|
||||
currentFileExists: False,
|
||||
currentPath: NULL,
|
||||
currentSize: NULL,
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->progress++;
|
||||
$this->updateProgress();
|
||||
}
|
||||
|
||||
foreach($fileCreations as $index=>$creation) {
|
||||
($this->newResultCallback)(
|
||||
type: "CREATION",
|
||||
beforeFileExists: False,
|
||||
beforePath: NULL,
|
||||
beforeSize: NULL,
|
||||
currentFileExists: True,
|
||||
currentPath: $this->prefix . DIRECTORY_SEPARATOR . $creation,
|
||||
currentSize: $fileCreationsFilesizes[$index],
|
||||
);
|
||||
foreach($filePossibleEdits as $possibleEdit) {
|
||||
$file1 = $this->dir1 . DIRECTORY_SEPARATOR . $possibleEdit;
|
||||
$file2 = $this->dir2 . DIRECTORY_SEPARATOR . $possibleEdit;
|
||||
$file1Size = filesize($file1);
|
||||
$file2Size = filesize($file2);
|
||||
|
||||
$this->progress++;
|
||||
$this->updateProgress();
|
||||
}
|
||||
$this->progress += 2;
|
||||
$this->updateProgress();
|
||||
|
||||
foreach($fileDeletions as $index=>$deletion) {
|
||||
($this->newResultCallback)(
|
||||
type: "DELETION",
|
||||
beforeFileExists: True,
|
||||
beforePath: $this->prefix . DIRECTORY_SEPARATOR . $deletion,
|
||||
beforeSize: $fileDeletionsFilesizes[$index],
|
||||
currentFileExists: False,
|
||||
currentPath: NULL,
|
||||
currentSize: NULL,
|
||||
);
|
||||
if(filemtime($file1) == filemtime($file2)) {
|
||||
//not different because same mtime
|
||||
continue;
|
||||
} else {
|
||||
// mtime different, but could just have gotten touched without modifications
|
||||
if($file1Size == $file2Size) {
|
||||
// if filesize is the same check for binary differences
|
||||
$handle1 = fopen($file1, 'rb');
|
||||
$handle2 = fopen($file2, 'rb');
|
||||
|
||||
$this->progress++;
|
||||
$this->updateProgress();
|
||||
}
|
||||
$filesdifferent = false;
|
||||
|
||||
foreach($filePossibleEdits as $possibleEdit) {
|
||||
$file1 = $this->dir1 . DIRECTORY_SEPARATOR . $possibleEdit;
|
||||
$file2 = $this->dir2 . DIRECTORY_SEPARATOR . $possibleEdit;
|
||||
$file1Size = filesize($file1);
|
||||
$file2Size = filesize($file2);
|
||||
while(!feof($handle1)) {
|
||||
if(fread($handle1, 8192) != fread($handle2, 8192)) {
|
||||
// files are different
|
||||
$filesdifferent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->progress += 2;
|
||||
$this->updateProgress();
|
||||
fclose($handle1);
|
||||
fclose($handle2);
|
||||
|
||||
if(filemtime($file1) == filemtime($file2)) {
|
||||
//not different because same mtime
|
||||
continue;
|
||||
} else {
|
||||
// mtime different, but could just have gotten touched without modifications
|
||||
if($file1Size == $file2Size) {
|
||||
// if filesize is the same check for binary differences
|
||||
$handle1 = fopen($file1, 'rb');
|
||||
$handle2 = fopen($file2, 'rb');
|
||||
|
||||
$filesdifferent = false;
|
||||
|
||||
while(!feof($handle1)) {
|
||||
if(fread($handle1, 8192) != fread($handle2, 8192)) {
|
||||
// files are different
|
||||
$filesdifferent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle1);
|
||||
fclose($handle2);
|
||||
|
||||
if(!$filesdifferent) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!$filesdifferent) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
($this->newResultCallback)(
|
||||
type: "EDIT",
|
||||
beforeFileExists: True,
|
||||
beforePath: $this->prefix . DIRECTORY_SEPARATOR . $possibleEdit,
|
||||
beforeSize: $file1Size,
|
||||
currentFileExists: True,
|
||||
currentPath: $this->prefix . DIRECTORY_SEPARATOR . $possibleEdit,
|
||||
currentSize: $file2Size,
|
||||
);
|
||||
}
|
||||
($this->newResultCallback)(
|
||||
type: "EDIT",
|
||||
beforeFileExists: True,
|
||||
beforePath: $this->prefix . DIRECTORY_SEPARATOR . $possibleEdit,
|
||||
beforeSize: $file1Size,
|
||||
currentFileExists: True,
|
||||
currentPath: $this->prefix . DIRECTORY_SEPARATOR . $possibleEdit,
|
||||
currentSize: $file2Size,
|
||||
);
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
return $diff;
|
||||
}
|
||||
}
|
|
@ -6,27 +6,27 @@ use OCP\IURLGenerator;
|
|||
use OCP\Settings\IIconSection;
|
||||
|
||||
class SnapshotsSection implements IIconSection {
|
||||
private IL10N $l;
|
||||
private IURLGenerator $urlGenerator;
|
||||
private IL10N $l;
|
||||
private IURLGenerator $urlGenerator;
|
||||
|
||||
public function __construct(IL10N $l, IURLGenerator $urlGenerator) {
|
||||
$this->l = $l;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
public function __construct(IL10N $l, IURLGenerator $urlGenerator) {
|
||||
$this->l = $l;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function getIcon(): string {
|
||||
return $this->urlGenerator->imagePath('core', 'actions/settings-dark.svg');
|
||||
}
|
||||
public function getIcon(): string {
|
||||
return $this->urlGenerator->imagePath('core', 'actions/settings-dark.svg');
|
||||
}
|
||||
|
||||
public function getID(): string {
|
||||
return 'groupfolder_filesystem_snapshots';
|
||||
}
|
||||
public function getID(): string {
|
||||
return 'groupfolder_filesystem_snapshots';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->l->t('Groupfolder Filesystem Snapshots');
|
||||
}
|
||||
public function getName(): string {
|
||||
return $this->l->t('Groupfolder Filesystem Snapshots');
|
||||
}
|
||||
|
||||
public function getPriority(): int {
|
||||
return 98;
|
||||
}
|
||||
public function getPriority(): int {
|
||||
return 98;
|
||||
}
|
||||
}
|
|
@ -58,7 +58,7 @@ class DiffTaskResultService {
|
|||
$diffTaskResult = $this->find($id);
|
||||
|
||||
if($diffTaskResult->getReverted()) {
|
||||
throw new AlreadyRevertedException;
|
||||
throw new AlreadyRevertedException();
|
||||
}
|
||||
|
||||
$taskId = $diffTaskResult->getTaskId();
|
||||
|
@ -66,35 +66,35 @@ class DiffTaskResultService {
|
|||
|
||||
$snapshotPath = $this->pathManager->getGroupFolderSnapshotDirectory($diffTask->getGroupfolderId(), $diffTask->getRelativePath(), $diffTask->getSnapshotId());
|
||||
|
||||
$gruenerFolder = $this->pathManager->getGroupfolderMountById($diffTask->getGroupfolderId())->get($diffTask->getRelativePath());
|
||||
$parentFolder = $this->pathManager->getGroupfolderMountById($diffTask->getGroupfolderId())->get($diffTask->getRelativePath());
|
||||
|
||||
switch($diffTaskResult->getType()) {
|
||||
case "CREATION":
|
||||
$currentFile = $this->getSubfolderMustExist($gruenerFolder, $diffTaskResult->getCurrentPath());
|
||||
$currentFile = $this->getSubfolderMustExist($parentFolder, $diffTaskResult->getCurrentPath());
|
||||
$currentFile->delete();
|
||||
break;
|
||||
case "RENAME":
|
||||
$currentFile = $this->getSubfolderMustExist($gruenerFolder, $diffTaskResult->getCurrentPath());
|
||||
$beforeDirectory = $this->getOrCreateSubdirectoryByFilepath($gruenerFolder, $diffTaskResult->getBeforePath());
|
||||
$currentFile = $this->getSubfolderMustExist($parentFolder, $diffTaskResult->getCurrentPath());
|
||||
$beforeDirectory = $this->getOrCreateSubdirectoryByFilepath($parentFolder, $diffTaskResult->getBeforePath());
|
||||
|
||||
$currentFile->move($beforeDirectory->getPath() . DIRECTORY_SEPARATOR . basename($diffTaskResult->getBeforePath()));
|
||||
break;
|
||||
case "DELETION":
|
||||
$beforeFileFilesystemPath = $snapshotPath . DIRECTORY_SEPARATOR . $diffTaskResult->getBeforePath();
|
||||
|
||||
$beforeDirectory = $this->getOrCreateSubdirectoryByFilepath($gruenerFolder, $diffTaskResult->getBeforePath());
|
||||
$beforeDirectory = $this->getOrCreateSubdirectoryByFilepath($parentFolder, $diffTaskResult->getBeforePath());
|
||||
$restoredFile = $beforeDirectory->newFile(basename($diffTaskResult->getBeforePath()));
|
||||
|
||||
$this->copyFilesystemFileToNextcloudFile($beforeFileFilesystemPath, $restoredFile);
|
||||
break;
|
||||
case "EDIT":
|
||||
$currentFile = $this->getSubfolderMustExist($gruenerFolder, $diffTaskResult->getCurrentPath());
|
||||
$currentFile = $this->getSubfolderMustExist($parentFolder, $diffTaskResult->getCurrentPath());
|
||||
$beforeFileFilesystemPath = $snapshotPath . DIRECTORY_SEPARATOR . $diffTaskResult->getBeforePath();
|
||||
|
||||
$this->copyFilesystemFileToNextcloudFile($beforeFileFilesystemPath, $currentFile);
|
||||
break;
|
||||
default:
|
||||
throw new \Exception;
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
return $this->mapper->markReverted($id);
|
||||
|
@ -104,7 +104,7 @@ class DiffTaskResultService {
|
|||
if($parent->nodeExists($path)) {
|
||||
return $parent->get($path);
|
||||
} else {
|
||||
throw new ChangesMadeSinceDiffException;
|
||||
throw new ChangesMadeSinceDiffException();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ class DiffTaskResultService {
|
|||
if($temp instanceof \OCP\Files\Folder) {
|
||||
$beforeDirectory = $temp;
|
||||
} else {
|
||||
throw new ChangesMadeSinceDiffException;
|
||||
throw new ChangesMadeSinceDiffException();
|
||||
}
|
||||
} else {
|
||||
$beforeDirectory = $beforeDirectory->newFolder($subdir);
|
||||
|
|
|
@ -58,7 +58,8 @@ class DiffTaskService {
|
|||
}
|
||||
}
|
||||
|
||||
function create(string $relativePathInGroupfolder, int $groupfolderId, string $snapshotId, string $userId, Callable $progressCallback = null): ?DiffTask {
|
||||
function create(string $relativePathInGroupfolder, int $groupfolderId, string $snapshotId, string $userId, array $folderBlocklist, Callable $progressCallback = null): ?DiffTask {
|
||||
$parentNode = $this->pathManager->getGroupfolderMountById($groupfolderId)->get($relativePathInGroupfolder);
|
||||
$snapshotPath = $this->pathManager->getGroupFolderSnapshotDirectory($groupfolderId, $relativePathInGroupfolder, $snapshotId);
|
||||
$groupfolderPath = $this->pathManager->getGroupFolderDirectory($groupfolderId, $relativePathInGroupfolder);
|
||||
|
||||
|
@ -80,16 +81,25 @@ class DiffTaskService {
|
|||
$snapshotPath,
|
||||
$groupfolderPath,
|
||||
"",
|
||||
function(string $type, bool $beforeFileExists, ?string $beforePath, ?int $beforeSize, bool $currentFileExists, ?string $currentPath, ?int $currentSize) use ($task) {
|
||||
$folderBlocklist,
|
||||
function(string $type, bool $beforeFileExists, ?string $beforePath, ?int $beforeSize, bool $currentFileExists, ?string $currentPath, ?int $currentSize) use ($task, $parentNode) {
|
||||
$newResult = new DiffTaskResult();
|
||||
$newResult->setTaskId($task->getId());
|
||||
$newResult->setType($type);
|
||||
|
||||
$newResult->setBeforeFileExists($beforeFileExists);
|
||||
$newResult->setBeforePath($beforePath);
|
||||
$newResult->setBeforeSize($beforeSize);
|
||||
if($beforeFileExists) {
|
||||
$newResult->setBeforePath($beforePath);
|
||||
$newResult->setBeforeSize($beforeSize);
|
||||
}
|
||||
|
||||
$newResult->setCurrentFileExists($currentFileExists);
|
||||
$newResult->setCurrentPath($currentPath);
|
||||
$newResult->setCurrentSize($currentSize);
|
||||
if($currentFileExists) {
|
||||
$newResult->setCurrentFileId($parentNode->get($currentPath)?->getId());
|
||||
$newResult->setCurrentPath($currentPath);
|
||||
$newResult->setCurrentSize($currentSize);
|
||||
}
|
||||
|
||||
$newResult = $this->diffTaskResultMapper->insert($newResult);
|
||||
},
|
||||
function($numDoneFiles) use ($progressCallback, &$numFiles) {
|
||||
|
@ -97,8 +107,7 @@ class DiffTaskService {
|
|||
($progressCallback)([
|
||||
"overallFiles" => $numFiles,
|
||||
"doneFiles" => $numDoneFiles,
|
||||
"progress" => number_format(($numDoneFiles / $numFiles),2),
|
||||
"progressPercent" => (number_format(($numDoneFiles / $numFiles),2) * 100) . "%",
|
||||
"progress" => floor(($numDoneFiles / $numFiles) * 100) / 100,
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
@ -113,7 +122,6 @@ class DiffTaskService {
|
|||
"overallFiles" => $numFiles,
|
||||
"doneFiles" => $numFiles,
|
||||
"progress" => 1.0,
|
||||
"progressPercent" => "100.00%",
|
||||
"result" => $task,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use OCP\IConfig;
|
|||
|
||||
class SettingsService {
|
||||
|
||||
private static array $VALID_APP_SETTINGS = ["filesystem_mountpoint_path", "filesystem_snapshots_path"];
|
||||
private static array $VALID_APP_SETTINGS = ["filesystem_mountpoint_path", "filesystem_snapshots_path", "snapshot_naming_scheme"];
|
||||
|
||||
public function __construct(private IConfig $config) {
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class SettingsService {
|
|||
}
|
||||
}
|
||||
|
||||
public function setAppValue(string $key, string $value): string {
|
||||
public function setAppValue(string $key, string $value): string {
|
||||
if(in_array($key, self::$VALID_APP_SETTINGS)) {
|
||||
if($value !== '') {
|
||||
$this->config->setAppValue(Application::APP_ID, $key, $value);
|
||||
|
@ -38,5 +38,5 @@ class SettingsService {
|
|||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,21 +6,21 @@ use OCP\Settings\ISettings;
|
|||
|
||||
class SnapshotsAdmin implements ISettings {
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm() {
|
||||
return new TemplateResponse('groupfolder_filesystem_snapshots', 'settings/admin');
|
||||
}
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm() {
|
||||
return new TemplateResponse('groupfolder_filesystem_snapshots', 'settings/admin');
|
||||
}
|
||||
|
||||
public function getSection() {
|
||||
return 'groupfolder_filesystem_snapshots';
|
||||
}
|
||||
public function getSection() {
|
||||
return 'groupfolder_filesystem_snapshots';
|
||||
}
|
||||
|
||||
public function getPriority() {
|
||||
return 10;
|
||||
}
|
||||
public function getPriority() {
|
||||
return 10;
|
||||
}
|
||||
}
|
1303
package-lock.json
generated
1303
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -36,6 +36,6 @@
|
|||
"devDependencies": {
|
||||
"@nextcloud/babel-config": "^1.2.0",
|
||||
"@nextcloud/browserslist-config": "^3.0.1",
|
||||
"@nextcloud/webpack-vue-config": "^6.0.0"
|
||||
"@nextcloud/webpack-vue-config": "^5.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
name="Groupfolder Filesystem Snapshots"
|
||||
:limit-width="false">
|
||||
<div v-if="!loading">
|
||||
<Field :is="setting.sensitive ? NcPasswordField : NcTextField"
|
||||
<Field :is="getAppSettingComponent(setting)"
|
||||
v-for="setting in app_settings"
|
||||
v-bind="getAppSettingProps(setting)"
|
||||
:key="setting.id"
|
||||
class="settings_field"
|
||||
:value="settings?.[setting.id]"
|
||||
:label="setting.name"
|
||||
@update:modelValue="(newValue) => updateSetting(setting.id, newValue)"
|
||||
@update:value="(newValue) => updateSetting(setting.id, newValue)" />
|
||||
</div>
|
||||
</NcSettingsSection>
|
||||
|
@ -18,7 +19,7 @@
|
|||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import debounceFunction from 'debounce-fn';
|
||||
import { NcSettingsSection, NcTextField, NcPasswordField } from "@nextcloud/vue"
|
||||
import { NcSettingsSection, NcTextField, NcPasswordField, NcSelect } from "@nextcloud/vue"
|
||||
|
||||
import { adminSettingsApi } from "./adminSettingsApi.js";
|
||||
|
||||
|
@ -26,10 +27,53 @@ let loading = ref(true);
|
|||
let settings = ref({});
|
||||
|
||||
const app_settings = [
|
||||
{id: "filesystem_mountpoint_path", name: "Filesystem Mountpoint Path"},
|
||||
{id: "filesystem_snapshots_path", name: "Filesystem Snapshots Path"},
|
||||
{id: "filesystem_mountpoint_path", name: "Filesystem Mountpoint Path", type: "text" },
|
||||
{id: "filesystem_snapshots_path", name: "Filesystem Snapshots Path", type: "text"},
|
||||
{
|
||||
id: "snapshot_naming_scheme",
|
||||
name: "Snapshot Naming Scheme",
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
id: '',
|
||||
label: 'None',
|
||||
},
|
||||
{
|
||||
id: 'zfs-auto-snapshot',
|
||||
label: 'zfs-auto-snapshot',
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
const getAppSettingComponent = function(appSetting) {
|
||||
if(appSetting.type === "select") {
|
||||
return NcSelect;
|
||||
} else {
|
||||
if(appSetting?.sensitive) {
|
||||
return NcPasswordField;
|
||||
} else {
|
||||
return NcTextField;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getAppSettingProps = function(appSetting) {
|
||||
if(appSetting.type === "select") {
|
||||
return {
|
||||
options: appSetting.options,
|
||||
label: "label",
|
||||
inputLabel: appSetting.name,
|
||||
reduce: (option) => ( option.id )
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
label: appSetting.name,
|
||||
clearable: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
adminSettingsApi.getAllSettings().then((result) => {
|
||||
settings.value = result;
|
||||
loading.value = false;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue