From 5d1ee6aaa0b97919629c83a90745a1407ddfd66c Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Fri, 7 Jul 2023 17:49:08 +0100 Subject: [PATCH] Symlink improvement (#57) * build: upgrade dependencies * build: upgrade dependencies to new versions * test: satisfy mess detector * ci: upgrade ci * ci: remove scrutinizer * test: create testbed for new functionality of #51 * feature: sync sym links closes #51 * wip: symlink source uses real path for #56 * feature: improve handling of symlinks closes #56 * feature: do not mention skipped links * tweak: split long line --- src/Command/SyncCommand.php | 20 ++++++++-- src/SymlinkSync.php | 23 ++++++++++- test/phpunit/SymlinkSyncTest.php | 47 ++++++++++++++++++++++ test/phpunit/SyncTestCase.php | 67 ++++++++++---------------------- 4 files changed, 104 insertions(+), 53 deletions(-) diff --git a/src/Command/SyncCommand.php b/src/Command/SyncCommand.php index 3584bcf..b043f36 100644 --- a/src/Command/SyncCommand.php +++ b/src/Command/SyncCommand.php @@ -108,13 +108,25 @@ private function performSymlinkSync( $sync = new SymlinkSync($source, $destination); $sync->exec(); + $countDirectories = count($sync->getLinkedDirectoriesList()); + $countFiles = count($sync->getLinkedFilesList()); + $countSkipped = count($sync->getSkippedList()); + $countFailed = count($sync->getFailedList()); + + if($countDirectories + $countFiles + $countFailed === 0 + && $countSkipped > 0) { + return; + } + if(!$arguments->contains("silent")) { - $this->write("Linked: directories "); - $this->write((string)count($sync->getLinkedDirectoriesList())); + $this->write("Linked: directories "); + $this->write((string)$countDirectories); $this->write(", files "); - $this->write((string)count($sync->getLinkedFilesList())); + $this->write((string)$countFiles); + $this->write(", skipped "); + $this->write((string)$countSkipped); $this->write(", failed "); - $this->write((string)count($sync->getFailedList())); + $this->write((string)$countFailed); $this->writeLine("."); } } diff --git a/src/SymlinkSync.php b/src/SymlinkSync.php index 26cc820..ce7978a 100644 --- a/src/SymlinkSync.php +++ b/src/SymlinkSync.php @@ -18,12 +18,26 @@ public function exec(int $settings = 0):void { $this->skipped = []; $this->failed = []; + $targetSource = realpath($this->source); + + if(is_link($this->destination)) { + $linkTarget = readlink($this->destination); + + if($targetSource !== $linkTarget) { + unlink($this->destination); + } + else { + array_push($this->skipped, $this->destination); + return; + } + } + if(is_dir($this->source)) { if(!is_dir(dirname($this->destination))) { mkdir(dirname($this->destination), recursive: true); } - if(symlink($this->source, $this->destination)) { + if(symlink($targetSource, $this->destination)) { array_push($this->linkedDirectories, $this->destination); } else { @@ -35,7 +49,7 @@ public function exec(int $settings = 0):void { mkdir(dirname($this->destination), recursive: true); } - if(symlink($this->source, $this->destination)) { + if(symlink($targetSource, $this->destination)) { array_push($this->linkedFiles, $this->destination); } else { @@ -66,4 +80,9 @@ public function getCombinedLinkedList():array { public function getFailedList():array { return $this->failed; } + + /** @return array */ + public function getSkippedList():array { + return $this->skipped; + } } diff --git a/test/phpunit/SymlinkSyncTest.php b/test/phpunit/SymlinkSyncTest.php index a3febf9..61c35f1 100644 --- a/test/phpunit/SymlinkSyncTest.php +++ b/test/phpunit/SymlinkSyncTest.php @@ -43,4 +43,51 @@ public function testSymLink_file():void { $linkedAll = $sut->getCombinedLinkedList(); self::assertCount(1, $linkedAll); } + + public function testSymLink_changeDirPath():void { + $baseDir = $this->getRandomTmp(); + $sourceDirOld = "$baseDir/data/upload-old"; + $sourceDirNew = "$baseDir/data/upload-new"; + $destDir = "$baseDir/www/data/upload"; + + $relativeFilePath = "subdir1/subdir2/example.file"; + $filePath = "$sourceDirOld/$relativeFilePath"; + if(!is_dir(dirname($filePath))) { + mkdir(dirname($filePath), recursive: true); + } + file_put_contents($filePath, "Hello, Sync!"); + + $sut = new SymlinkSync($sourceDirOld, $destDir); + $sut->exec(); + + self::assertFileExists("$destDir/$relativeFilePath"); + rename($sourceDirOld, $sourceDirNew); + self::assertFileDoesNotExist("$destDir/$relativeFilePath"); + + $sut = new SymlinkSync($sourceDirNew, $destDir); + $sut->exec(); + self::assertFileExists("$destDir/$relativeFilePath"); + } + + public function testSymLink_multipleCallsToExec():void { + $baseDir = $this->getRandomTmp(); + $sourceDir = "$baseDir/data/upload"; + $destDir = "$baseDir/www/data/upload"; + + $filePath = "$sourceDir/subdir1/subdir2/example.file"; + if(!is_dir(dirname($filePath))) { + mkdir(dirname($filePath), recursive: true); + } + file_put_contents($filePath, "Hello, Sync!"); + + $sut = new SymlinkSync($sourceDir, $destDir); + $sut->exec(); + self::assertCount(1, $sut->getCombinedLinkedList()); + self::assertCount(0, $sut->getFailedList()); + self::assertCount(0, $sut->getSkippedList()); + $sut->exec(); + self::assertCount(0, $sut->getLinkedFilesList()); + self::assertCount(0, $sut->getFailedList()); + self::assertCount(1, $sut->getSkippedList()); + } } diff --git a/test/phpunit/SyncTestCase.php b/test/phpunit/SyncTestCase.php index dbb907e..18a25c5 100644 --- a/test/phpunit/SyncTestCase.php +++ b/test/phpunit/SyncTestCase.php @@ -1,5 +1,6 @@ $file) { - /** @var $file SplFileInfo */ - if($file->getFilename() === "." - || $file->getFilename() === "..") { - continue; - } - - if($file->isDir()) { - rmdir($filePath); - } - else { - unlink($filePath); - } - } + $this->recursiveDeleteDirectory($baseTmp); } protected function getRandomTmp():string { @@ -116,35 +95,29 @@ protected function getRandomSubdirectoryFromDirectory(string $dir):string { } protected function recursiveDeleteDirectory(string $dir):void { - $directory = new RecursiveDirectoryIterator( - $dir, - RecursiveDirectoryIterator::SKIP_DOTS - | RecursiveDirectoryIterator::KEY_AS_PATHNAME - | RecursiveDirectoryIterator::CURRENT_AS_FILEINFO - ); - $iterator = new RecursiveIteratorIterator( - $directory, - RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach($iterator as $pathName => $file) { - /** @var $file SplFileInfo */ - if($file->getFilename() === "." - || $file->getFilename() === "..") { - continue; - } + if (!is_dir($dir)) { + throw new Exception('Invalid directory to delete: $dir'); + } - if(is_dir($pathName)) { - rmdir($pathName); + $directory = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO); + $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::CHILD_FIRST); + + /** + * @var string $pathName + * @var SplFileInfo $fileInfo + */ + foreach ($iterator as $pathName => $fileInfo) { + if($fileInfo->isLink() || $fileInfo->isFile()) { + if(!unlink($pathName)) { + throw new Exception('Failed to delete file: ' . $pathName); + } } else { - unlink($pathName); + if(!rmdir($pathName)) { + throw new Exception('Failed to delete directory: ' . $pathName); + } } } - - if(is_dir($dir)) { - rmdir($dir); - } } protected static function assertDirectoryContentsIdentical(