diff --git a/appinfo/info.xml b/appinfo/info.xml
index 7b0d2c6..bf96d33 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -5,7 +5,7 @@
Groupfolder Filesystem Snapshots
Allows restoring a groupfolder to a previous snapshot in the filesystem
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
- 1.3.1
+ 1.4.0
agpl
verdigado eG
Jonathan Treffler
diff --git a/lib/Db/DiffTaskResult.php b/lib/Db/DiffTaskResult.php
index b6865a9..7ea190a 100644
--- a/lib/Db/DiffTaskResult.php
+++ b/lib/Db/DiffTaskResult.php
@@ -16,7 +16,9 @@ class DiffTaskResult extends Entity implements JsonSerializable {
protected $beforeSize;
protected $currentFileExists;
- protected $currentPath;
+ protected $currentFileId;
+
+ protected $currentPath;
protected $currentSize;
protected $reverted;
@@ -26,6 +28,7 @@ class DiffTaskResult extends Entity implements JsonSerializable {
$this->addType('beforeFileExists','boolean');
$this->addType('beforeSize','integer');
$this->addType('currentFileExists','boolean');
+ $this->addType('currentFileId','integer');
$this->addType('currentSize','integer');
$this->addType('reverted','boolean');
}
@@ -42,6 +45,7 @@ class DiffTaskResult extends Entity implements JsonSerializable {
],
'current' => [
'fileExists' => $this->currentFileExists,
+ 'fileId' => $this->currentFileId,
'path' => $this->currentPath,
'size' => $this->currentSize,
],
diff --git a/lib/Migration/Version140Date20250701164500.php b/lib/Migration/Version140Date20250701164500.php
new file mode 100644
index 0000000..bebdd12
--- /dev/null
+++ b/lib/Migration/Version140Date20250701164500.php
@@ -0,0 +1,33 @@
+getTable(self::RESULTS_TABLE);
+
+ if(!$table->hasColumn('current_file_id')) {
+ $table->addColumn('current_file_id', Types::BIGINT, [
+ 'notnull' => false,
+ ]);
+ }
+
+ return $schema;
+ }
+}
diff --git a/lib/RecursiveDiff.php b/lib/RecursiveDiff.php
index 98ed344..9196eee 100644
--- a/lib/RecursiveDiff.php
+++ b/lib/RecursiveDiff.php
@@ -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);
+ }
+
+ if(file_exists($this->dir2) && is_dir($this->dir2)) {
+ [$this->scan2files, $this->scan2folders] = FileHelper::getFilesAndFolders($this->dir2);
+ }
- private $subJobProgress = [];
- private $progress = 0;
+ $scan_num_files += sizeof($this->scan1files);
+ $scan_num_files += sizeof($this->scan2files);
+
+ $allSubfolders = array_unique(array_merge($this->scan1folders, $this->scan2folders));
- public function __construct($dir1, $dir2, $prefix = "", $newResultCallback, $progressCallback){
- $this->dir1 = $dir1;
- $this->dir2 = $dir2;
- $this->prefix = $prefix;
+ 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] ?? [];
- $this->newResultCallback = $newResultCallback;
- $this->progressCallback = $progressCallback;
- }
+ if($subFolderBlocklist === true) {
+ continue;
+ }
+
+ $newJob = new RecursiveDiff($subdir1, $subdir2, $subprefix, $subFolderBlocklist, $this->newResultCallback, function($numDoneFiles) use ($key) {
+ $this->subJobProgress[$key] = $numDoneFiles;
- public function scan() {
- $scan_num_files = 0;
+ $this->updateProgress();
+ });
- if(file_exists($this->dir1) && is_dir($this->dir1)) {
- [$this->scan1files, $this->scan1folders] = FileHelper::getFilesAndFolders($this->dir1);
- }
-
- if(file_exists($this->dir2) && is_dir($this->dir2)) {
- [$this->scan2files, $this->scan2folders] = FileHelper::getFilesAndFolders($this->dir2);
- }
+ $this->subJobs[] = $newJob;
- $scan_num_files += sizeof($this->scan1files);
- $scan_num_files += sizeof($this->scan2files);
-
- $allSubfolders = array_unique(array_merge($this->scan1folders, $this->scan2folders));
+ $scan_num_files += $newJob->scan();
+ }
- foreach($allSubfolders as $key=>$folder) {
- $subdir1 = $this->dir1 . DIRECTORY_SEPARATOR . $folder;
- $subdir2 = $this->dir2 . DIRECTORY_SEPARATOR . $folder;
- $subprefix = $this->prefix . DIRECTORY_SEPARATOR . $folder;
-
- $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];
+
+ $renameContenders = array_keys($fileDeletionsFilesizes, $creationSize);
- $fileDeletions = array_diff($this->scan1files, $this->scan2files);
- $fileDeletionsFilesizes = FileHelper::getFilesizesOfFiles($this->dir1, $fileDeletions);
+ if(sizeof($renameContenders) != 0) {
+ /*$diff[] = [
+ "type" => "DEBUG",
+ "comparing" => [
+ "creation" => $creationIndex,
+ "deletions" => $renameContenders,
+ ],
+ ];*/
- $filePossibleEdits = array_intersect($this->scan1files, $this->scan2files);
+ $creationSHA = sha1_file($creationPath);
+ foreach($renameContenders as $contender) {
+ $deletion = $fileDeletions[$contender];
+ $deletionPath = $this->dir1 . DIRECTORY_SEPARATOR . $deletion;
+ $deletionSHA = sha1_file($deletionPath);
- /*$diff[] = [
- "type" => "DEBUG",
- "prefix" => $this->prefix,
- "fileCreations" => $fileCreations,
- "fileCreationsFilesizes" => $fileCreationsFilesizes,
- "fileDeletions" => $fileDeletions,
- "fileDeletionsFilesizes" => $fileDeletionsFilesizes,
- //"folderCreations" => $folderCreations,
- //"folderDeletions" => $folderDeletions,
- "allSubfolders" => $allSubfolders,
- ];*/
+ 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,
+ );
- // search for creations and deletions, that are actually renames
- foreach($fileCreations as $creationIndex=>$creation) {
- $creationPath = $this->dir2 . DIRECTORY_SEPARATOR . $creation;
- $creationSize = $fileCreationsFilesizes[$creationIndex];
-
- $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');
+ if(!$filesdifferent) {
+ continue;
+ }
+ }
+ }
- $filesdifferent = false;
+
+ ($this->newResultCallback)(
+ type: "EDIT",
+ beforeFileExists: True,
+ beforePath: $this->prefix . DIRECTORY_SEPARATOR . $possibleEdit,
+ beforeSize: $file1Size,
+ currentFileExists: True,
+ currentPath: $this->prefix . DIRECTORY_SEPARATOR . $possibleEdit,
+ currentSize: $file2Size,
+ );
+ }
- while(!feof($handle1)) {
- if(fread($handle1, 8192) != fread($handle2, 8192)) {
- // files are different
- $filesdifferent = true;
- break;
- }
- }
-
- fclose($handle1);
- fclose($handle2);
-
- 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,
- );
- }
-
- return $diff;
- }
+ return $diff;
+ }
}
\ No newline at end of file
diff --git a/lib/Sections/SnapshotsSection.php b/lib/Sections/SnapshotsSection.php
index ca5800e..75f6ac5 100644
--- a/lib/Sections/SnapshotsSection.php
+++ b/lib/Sections/SnapshotsSection.php
@@ -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;
+ }
}
\ No newline at end of file
diff --git a/lib/Service/DiffTaskService.php b/lib/Service/DiffTaskService.php
index 16ebb46..c4d7032 100644
--- a/lib/Service/DiffTaskService.php
+++ b/lib/Service/DiffTaskService.php
@@ -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" => round(($numDoneFiles / $numFiles),2),
]);
}
},
@@ -113,7 +122,6 @@ class DiffTaskService {
"overallFiles" => $numFiles,
"doneFiles" => $numFiles,
"progress" => 1.0,
- "progressPercent" => "100.00%",
"result" => $task,
]);
}