diff --git a/appinfo/info.xml b/appinfo/info.xml
index 054b554..63d18f4 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -14,4 +14,8 @@
+
+ OCA\GroupfolderFilesystemSnapshots\Settings\SnapshotsAdmin
+ OCA\GroupfolderFilesystemSnapshots\Sections\SnapshotsSection
+
diff --git a/lib/Entity/Snapshot.php b/lib/Entity/Snapshot.php
new file mode 100644
index 0000000..e571a7d
--- /dev/null
+++ b/lib/Entity/Snapshot.php
@@ -0,0 +1,20 @@
+id = $id;
+ }
+
+ public function jsonSerialize(): mixed {
+ return [
+ 'id' => $this->id
+ ];
+ }
+}
diff --git a/lib/Manager/DiffManager.php b/lib/Manager/DiffManager.php
new file mode 100644
index 0000000..f0f0852
--- /dev/null
+++ b/lib/Manager/DiffManager.php
@@ -0,0 +1,208 @@
+seperateFilesFromFolders($dir, $scan);
+ }
+
+ private function getFilesizesOfFiles($prefix, array $files) {
+ $result = array();
+ foreach($files as $index=>$file) {
+ $result[$index] = filesize($prefix . DIRECTORY_SEPARATOR . $file);
+ }
+
+ return $result;
+ }
+
+ function diffDirectories($dir1, $dir2, $prefix = "") {
+ $diff = [];
+
+ $scan1files = [];
+ $scan1folders = [];
+ if(file_exists($dir1) && is_dir($dir1)) {
+ list($scan1files, $scan1folders) = $this->getFilesAndFolders($dir1);
+ }
+
+ $scan2files = [];
+ $scan2folders = [];
+ if(file_exists($dir2) && is_dir($dir2)) {
+ list($scan2files, $scan2folders) = $this->getFilesAndFolders($dir2);
+ }
+
+ $fileCreations = array_diff($scan2files, $scan1files);
+ $fileCreationsFilesizes = $this->getFilesizesOfFiles($dir2, $fileCreations);
+
+ $fileDeletions = array_diff($scan1files, $scan2files);
+ $fileDeletionsFilesizes = $this->getFilesizesOfFiles($dir1, $fileDeletions);
+
+ $filePossibleEdits = array_intersect($scan1files, $scan2files);
+
+ $allSubfolders = array_unique(array_merge($scan1folders, $scan2folders));
+
+ /*$diff[] = [
+ "type" => "DEBUG",
+ "prefix" => $prefix,
+ "fileCreations" => $fileCreations,
+ "fileCreationsFilesizes" => $fileCreationsFilesizes,
+ "fileDeletions" => $fileDeletions,
+ "fileDeletionsFilesizes" => $fileDeletionsFilesizes,
+ //"folderCreations" => $folderCreations,
+ //"folderDeletions" => $folderDeletions,
+ "allSubfolders" => $allSubfolders,
+ ];*/
+
+ // search for creations and deletions, that are actually renames
+ foreach($fileCreations as $creationIndex=>$creation) {
+ $creationPath = $dir2 . DIRECTORY_SEPARATOR . $creation;
+ $creationSize = $fileCreationsFilesizes[$creationIndex];
+
+ $renameContenders = array_keys($fileDeletionsFilesizes, $creationSize);
+
+ if(sizeof($renameContenders) != 0) {
+ /*$diff[] = [
+ "type" => "DEBUG",
+ "comparing" => [
+ "creation" => $creationIndex,
+ "deletions" => $renameContenders,
+ ],
+ ];*/
+
+ $creationSHA = sha1_file($creationPath);
+ foreach($renameContenders as $contender) {
+ $deletion = $fileDeletions[$contender];
+ $deletionPath = $dir1 . DIRECTORY_SEPARATOR . $deletion;
+ $deletionSHA = sha1_file($deletionPath);
+
+ if($deletionSHA == $creationSHA) {
+ $diff[] = [
+ "type" => "RENAME",
+ "before" => [
+ "exists" => True,
+ "path" => $prefix . DIRECTORY_SEPARATOR . $deletion,
+ "size" => $creationSize,
+ ],
+ "afterwards" => [
+ "exists" => True,
+ "path" => $prefix . DIRECTORY_SEPARATOR . $creation,
+ "size" => $creationSize,
+ ]
+
+ ];
+
+ unset($fileCreations[$creationIndex]);
+ unset($fileDeletions[$contender]);
+
+ break;
+ }
+ }
+ }
+ }
+
+ foreach($fileCreations as $index=>$creation) {
+ $diff[] = [
+ "type" => "CREATION",
+ "before" => [
+ "exists" => False,
+ ],
+ "afterwards" => [
+ "exists" => True,
+ "path" => $prefix . DIRECTORY_SEPARATOR . $creation,
+ "size" => $fileCreationsFilesizes[$index],
+ ]
+ ];
+ }
+
+ foreach($fileDeletions as $index=>$deletion) {
+ $diff[] = [
+ "type" => "DELETION",
+ "before" => [
+ "exists" => True,
+ "path" => $prefix . DIRECTORY_SEPARATOR . $deletion,
+ "size" => $fileDeletionsFilesizes[$index],
+ ],
+ "afterwards" => [
+ "exists" => False,
+ ],
+ ];
+ }
+
+ foreach($filePossibleEdits as $possibleEdit) {
+ $file1 = $dir1 . DIRECTORY_SEPARATOR . $possibleEdit;
+ $file2 = $dir2 . DIRECTORY_SEPARATOR . $possibleEdit;
+ $file1Size = filesize($file1);
+ $file2Size = filesize($file2);
+
+ 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;
+ }
+ }
+ }
+
+
+ $diff[] = [
+ "type" => "EDIT",
+ "before" => [
+ "path" => $prefix . DIRECTORY_SEPARATOR . $possibleEdit,
+ "size" => $file1Size,
+ ],
+ "afterwards" => [
+ "path" => $prefix . DIRECTORY_SEPARATOR . $possibleEdit,
+ "size" => $file2Size,
+ ]
+
+ ];
+ }
+
+ foreach($allSubfolders as $folder) {
+ array_push($diff, ...($this->diffDirectories($dir1 . DIRECTORY_SEPARATOR . $folder, $dir2 . DIRECTORY_SEPARATOR . $folder, $prefix . DIRECTORY_SEPARATOR . $folder)));
+ }
+
+ return $diff;
+ }
+}
\ No newline at end of file
diff --git a/lib/Manager/PathManager.php b/lib/Manager/PathManager.php
new file mode 100644
index 0000000..662bbca
--- /dev/null
+++ b/lib/Manager/PathManager.php
@@ -0,0 +1,78 @@
+config = $config;
+ $this->groupfolderFolderManager = $manager;
+ $this->rootFolder = $rootFolder;
+ }
+
+ public function getFilesystemSnapshotPath() {
+ return self::FILESYSTEM_SNAPSHOT_PATH;
+ }
+
+ // Nextcloud general
+ public function getDataDirectory() {
+ return $this->config->getSystemValue("datadirectory");
+ }
+
+ private function getRootFolderStorageId(): ?int {
+ return $this->rootFolder->getMountPoint()->getNumericStorageId();
+ }
+
+ // Snapshots
+ public function getSnapshotPath(string $snapshotId) {
+ return realpath(self::FILESYSTEM_SNAPSHOT_PATH . DIRECTORY_SEPARATOR . $snapshotId . DIRECTORY_SEPARATOR);
+ }
+
+ public function convertToSnapshotPath(string $filesystemPath, string $snapshotId) {
+ $filesystemPath = realpath($filesystemPath);
+
+ if(!str_starts_with($filesystemPath, self::FILESYSTEM_ROOT_PATH)) {
+ return false;
+ }
+
+ return str_replace(self::FILESYSTEM_ROOT_PATH, $this->getSnapshotPath($snapshotId) . DIRECTORY_SEPARATOR, $filesystemPath);
+ }
+
+ // Groupfolders
+ private function checkIfGroupfolderExists(int $groupfolderId): bool {
+ $storageId = $this->getRootFolderStorageId();
+ if ($storageId === null) {
+ return "storage Id null";
+ }
+
+ $folder = $this->groupfolderFolderManager->getFolder($groupfolderId, $storageId);
+ if ($folder === false) {
+ return "Folder does not exist";
+ }
+
+ return true;
+ }
+
+ public function getGroupFolderDirectory(int $groupfolderId) {
+ $folderExistsCheck = $this->checkIfGroupfolderExists($groupfolderId);
+ if(!$folderExistsCheck) {
+ return $folderExistsCheck;
+ }
+
+ return realpath($this->getDataDirectory() . DIRECTORY_SEPARATOR . "__groupfolders" . DIRECTORY_SEPARATOR . $groupfolderId . DIRECTORY_SEPARATOR);
+ }
+
+ public function getGroupFolderSnapshotDirectory(int $groupfolderId, string $snapshotId) {
+ return $this->convertToSnapshotPath($this->getGroupFolderDirectory($groupfolderId), $snapshotId);
+ }
+}
\ No newline at end of file
diff --git a/lib/Manager/SnapshotManager.php b/lib/Manager/SnapshotManager.php
index ef88bf3..b71fe5a 100644
--- a/lib/Manager/SnapshotManager.php
+++ b/lib/Manager/SnapshotManager.php
@@ -2,15 +2,55 @@
namespace OCA\GroupfolderFilesystemSnapshots\Manager;
-class SnapshotManager {
- function get() {
- $filesystem_path = "/srv/nextcloud/files/";
- $filesystem_snapshot_path = "/srv/nextcloud/files/.zfs/snapshot/";
+use OCA\GroupfolderFilesystemSnapshots\Manager\PathManager;
+use OCA\GroupfolderFilesystemSnapshots\Manager\DiffManager;
- $iterator = new \FilesystemIterator($filesystem_snapshot_path);
- foreach ($iterator as $fileinfo) {
- if(!$fileinfo->isDir()) continue;
- yield $fileinfo->getFilename();
+use OCA\GroupfolderFilesystemSnapshots\Entity\Snapshot;
+
+class SnapshotManager {
+ private PathManager $pathManager;
+ private DiffManager $diffManager;
+
+
+ public function __construct(PathManager $pathManager, DiffManager $diffManager){
+ $this->pathManager = $pathManager;
+ $this->diffManager = $diffManager;
+ }
+
+ private function validSnapshotId(string $snapshotId) {
+ return (preg_match("/^[a-zA-Z0-9-_]+$/", $snapshotId) == 1);
+ }
+
+ function snapshotExists(string $snapshotId): bool {
+ if($this->validSnapshotId($snapshotId)) {
+ $path = $this->pathManager->getSnapshotPath($snapshotId);
+ return (file_exists($path) && is_dir($path));
+ } else {
+ return false;
}
}
+
+ function get(string $snapshotId) {
+ if(self::snapshotExists($snapshotId)) {
+ return new Snapshot($snapshotId);
+ } else {
+ return false;
+ }
+
+ }
+
+ function getAll() {
+ $iterator = new \FilesystemIterator($this->pathManager->getFilesystemSnapshotPath());
+ foreach ($iterator as $fileinfo) {
+ if(!$fileinfo->isDir()) continue;
+ yield new Snapshot($fileinfo->getFilename());
+ }
+ }
+
+ function getDiff(int $groupfolderId, string $snapshotId) {
+ $groupfolderPath = $this->pathManager->getGroupFolderDirectory($groupfolderId);
+ $snapshotPath = $this->pathManager->getGroupFolderSnapshotDirectory($groupfolderId, $snapshotId);
+
+ return $this->diffManager->diffDirectories($snapshotPath, $groupfolderPath);
+ }
}
\ No newline at end of file
diff --git a/lib/Sections/SnapshotsSection.php b/lib/Sections/SnapshotsSection.php
new file mode 100644
index 0000000..ca5800e
--- /dev/null
+++ b/lib/Sections/SnapshotsSection.php
@@ -0,0 +1,32 @@
+l = $l;
+ $this->urlGenerator = $urlGenerator;
+ }
+
+ public function getIcon(): string {
+ return $this->urlGenerator->imagePath('core', 'actions/settings-dark.svg');
+ }
+
+ public function getID(): string {
+ return 'groupfolder_filesystem_snapshots';
+ }
+
+ public function getName(): string {
+ return $this->l->t('Groupfolder Filesystem Snapshots');
+ }
+
+ public function getPriority(): int {
+ return 98;
+ }
+}
\ No newline at end of file
diff --git a/lib/Settings/SnapshotsAdmin.php b/lib/Settings/SnapshotsAdmin.php
new file mode 100644
index 0000000..eeeedd2
--- /dev/null
+++ b/lib/Settings/SnapshotsAdmin.php
@@ -0,0 +1,33 @@
+config = $config;
+ }
+
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ $parameters = [
+ 'Filesystem Snapshots Path' => $this->config->getSystemValue('snapshots_path', true),
+ ];
+
+ return new TemplateResponse('settings', 'settings/admin', $parameters, '');
+ }
+
+ public function getSection() {
+ return 'groupfolder_filesystem_snapshots';
+ }
+
+ public function getPriority() {
+ return 10;
+ }
+}
\ No newline at end of file