227 lines
No EOL
6.1 KiB
PHP
227 lines
No EOL
6.1 KiB
PHP
<?php
|
|
|
|
namespace OCA\GroupfolderFilesystemSnapshots;
|
|
|
|
use OCA\GroupfolderFilesystemSnapshots\Helpers\FileHelper;
|
|
|
|
class RecursiveDiff {
|
|
private $scan1files = [];
|
|
private $scan1folders = [];
|
|
|
|
private $scan2files = [];
|
|
private $scan2folders = [];
|
|
|
|
private $subJobs = [];
|
|
|
|
private $subJobProgress = [];
|
|
private $progress = 0;
|
|
|
|
public function __construct(
|
|
public readonly string $dir1,
|
|
public readonly string $dir2,
|
|
private readonly string $prefix = "",
|
|
private readonly array $folderBlocklist = [],
|
|
private $newResultCallback,
|
|
private $progressCallback,
|
|
){}
|
|
|
|
public function scan() {
|
|
$scan_num_files = 0;
|
|
|
|
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);
|
|
}
|
|
|
|
$scan_num_files += sizeof($this->scan1files);
|
|
$scan_num_files += sizeof($this->scan2files);
|
|
|
|
$allSubfolders = array_unique(array_merge($this->scan1folders, $this->scan2folders));
|
|
|
|
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($subFolderBlocklist === true) {
|
|
continue;
|
|
}
|
|
|
|
$newJob = new RecursiveDiff($subdir1, $subdir2, $subprefix, $subFolderBlocklist, $this->newResultCallback, function($numDoneFiles) use ($key) {
|
|
$this->subJobProgress[$key] = $numDoneFiles;
|
|
|
|
$this->updateProgress();
|
|
});
|
|
|
|
$this->subJobs[] = $newJob;
|
|
|
|
$scan_num_files += $newJob->scan();
|
|
}
|
|
|
|
return $scan_num_files;
|
|
}
|
|
|
|
private function updateProgress() {
|
|
($this->progressCallback)(array_sum($this->subJobProgress) + $this->progress);
|
|
}
|
|
|
|
function diff() {
|
|
$diff = [];
|
|
|
|
foreach($this->subJobs as $job) {
|
|
$result = $job->diff();
|
|
array_push($diff, ...$result);
|
|
}
|
|
|
|
$fileCreations = array_diff($this->scan2files, $this->scan1files);
|
|
$fileCreationsFilesizes = FileHelper::getFilesizesOfFiles($this->dir2, $fileCreations);
|
|
|
|
$fileDeletions = array_diff($this->scan1files, $this->scan2files);
|
|
$fileDeletionsFilesizes = FileHelper::getFilesizesOfFiles($this->dir1, $fileDeletions);
|
|
|
|
$filePossibleEdits = array_intersect($this->scan1files, $this->scan2files);
|
|
|
|
/*$diff[] = [
|
|
"type" => "DEBUG",
|
|
"prefix" => $this->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 = $this->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 = $this->dir1 . DIRECTORY_SEPARATOR . $deletion;
|
|
$deletionSHA = sha1_file($deletionPath);
|
|
|
|
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,
|
|
);
|
|
|
|
unset($fileCreations[$creationIndex]);
|
|
unset($fileDeletions[$contender]);
|
|
|
|
$this->progress += 2;
|
|
$this->updateProgress();
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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],
|
|
);
|
|
|
|
$this->progress++;
|
|
$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,
|
|
);
|
|
|
|
$this->progress++;
|
|
$this->updateProgress();
|
|
}
|
|
|
|
foreach($filePossibleEdits as $possibleEdit) {
|
|
$file1 = $this->dir1 . DIRECTORY_SEPARATOR . $possibleEdit;
|
|
$file2 = $this->dir2 . DIRECTORY_SEPARATOR . $possibleEdit;
|
|
$file1Size = filesize($file1);
|
|
$file2Size = filesize($file2);
|
|
|
|
$this->progress += 2;
|
|
$this->updateProgress();
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
($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;
|
|
}
|
|
} |