dir1 = $dir1; $this->dir2 = $dir2; $this->prefix = $prefix; $this->newResultCallback = $newResultCallback; $this->progressCallback = $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; $newJob = new RecursiveDiff($subdir1, $subdir2, $subprefix, $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; } }