Added current fileId to DiffTaskResult; implemented folder blocklist feature; fixed indentations of some files; bumped version

This commit is contained in:
Jonathan Treffler 2025-07-02 14:41:59 +02:00
parent 85d63d5ae2
commit acf8990de1
6 changed files with 253 additions and 212 deletions

View file

@ -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;
}
}