From e0b149941c93b44925e9bab0ca5853f2cfb77273 Mon Sep 17 00:00:00 2001 From: Nicolas Widart Date: Tue, 10 Oct 2017 12:42:21 +0200 Subject: [PATCH] Extracting folder move operations to a FolderMover class. This improves maintainability as well as doesn't update the db row until the folder was actually successfully moved on disk. This keeps db / filesystem in sync. --- .../Events/Handlers/MoveFolderOnDisk.php | 92 ---------- .../Media/Providers/MediaServiceProvider.php | 1 - Modules/Media/Services/FolderMover.php | 120 +++++++++++++ Modules/Media/Services/Mover.php | 10 ++ .../Tests/EloquentFolderRepositoryTest.php | 104 ------------ Modules/Media/Tests/FolderMoverTest.php | 157 ++++++++++++++++++ 6 files changed, 287 insertions(+), 197 deletions(-) delete mode 100644 Modules/Media/Events/Handlers/MoveFolderOnDisk.php create mode 100644 Modules/Media/Services/FolderMover.php create mode 100644 Modules/Media/Services/Mover.php create mode 100644 Modules/Media/Tests/FolderMoverTest.php diff --git a/Modules/Media/Events/Handlers/MoveFolderOnDisk.php b/Modules/Media/Events/Handlers/MoveFolderOnDisk.php deleted file mode 100644 index 25d5f4d98..000000000 --- a/Modules/Media/Events/Handlers/MoveFolderOnDisk.php +++ /dev/null @@ -1,92 +0,0 @@ -filesystem = $filesystem; - $this->file = $file; - } - - public function handle(FolderStartedMoving $event) - { - $this->moveOriginal($event); - - $this->renameDatabaseReferences($event); - } - - private function moveOriginal(FolderStartedMoving $event) - { - $this->move($event->previousData['path']->getRelativeUrl(), $event->folder->path->getRelativeUrl()); - } - - private function renameDatabaseReferences(FolderStartedMoving $event) - { - $previousPath = $event->previousData['path']->getRelativeUrl(); - $newPath = $event->folder->path->getRelativeUrl(); - - $this->replacePathReferences($event->folder->id, $previousPath, $newPath); - } - - private function replacePathReferences($folderId, $previousPath, $newPath) - { - $medias = $this->file->allChildrenOf($folderId); - - foreach ($medias as $media) { - $oldPath = $media->path->getRelativeUrl(); - - $media->update([ - 'path' => $this->removeDoubleSlashes(str_replace($previousPath, $newPath, $oldPath)), - ]); - if ($media->isFolder() === true) { - $this->replacePathReferences($media->id, $previousPath, $newPath); - } - } - } - - private function move($fromPath, $toPath) - { - $this->filesystem->disk($this->getConfiguredFilesystem()) - ->move( - $this->getDestinationPath($fromPath), - $this->getDestinationPath($toPath) - ); - } - - private function getDestinationPath($path) : string - { - if ($this->getConfiguredFilesystem() === 'local') { - return basename(public_path()) . $path; - } - - return $path; - } - - /** - * @return string - */ - private function getConfiguredFilesystem() : string - { - return config('asgard.media.config.filesystem'); - } - - private function removeDoubleSlashes(string $string) : string - { - return str_replace('//', '/', $string); - } -} diff --git a/Modules/Media/Providers/MediaServiceProvider.php b/Modules/Media/Providers/MediaServiceProvider.php index f78da7acd..ee9944d62 100644 --- a/Modules/Media/Providers/MediaServiceProvider.php +++ b/Modules/Media/Providers/MediaServiceProvider.php @@ -85,7 +85,6 @@ public function boot(DispatcherContract $events) $events->listen(FolderIsDeleting::class, DeleteFolderOnDisk::class); $events->listen(FolderIsDeleting::class, DeleteAllChildrenOfFolder::class); $events->listen(FileStartedMoving::class, MoveFileOnDisk::class); - $events->listen(FolderStartedMoving::class, MoveFolderOnDisk::class); $this->app[TagManager::class]->registerNamespace(new File()); $this->registerThumbnails(); diff --git a/Modules/Media/Services/FolderMover.php b/Modules/Media/Services/FolderMover.php new file mode 100644 index 000000000..aab218c6b --- /dev/null +++ b/Modules/Media/Services/FolderMover.php @@ -0,0 +1,120 @@ +filesystem = $filesystem; + $this->file = $file; + $this->folder = $folder; + } + + public function move(File $folder, File $destination) : bool + { + $movedOnDisk = $this->moveOriginalOnDisk($folder, $destination); + + if ($movedOnDisk === false) { + return false; + } + $folder = $this->moveDatabase($folder, $destination); + + $this->renameDatabaseReferences($folder); + + return true; + } + + private function moveOriginalOnDisk(File $folder, File $destination) : bool + { + $this->fromPath = $folder->path->getRelativeUrl(); + $this->toPath = $this->getNewPathFor($folder->filename, $destination); + + return $this->moveDirectory($this->fromPath, $this->toPath); + } + + private function moveDatabase(File $folder, File $destination) : File + { + return $this->folder->move($folder, $destination); + } + + private function renameDatabaseReferences(File $folder) + { + $this->replacePathReferences($folder->id, $this->fromPath, $this->toPath); + } + + private function replacePathReferences($folderId, $previousPath, $newPath) + { + $medias = $this->file->allChildrenOf($folderId); + + foreach ($medias as $media) { + $oldPath = $media->path->getRelativeUrl(); + + $media->update([ + 'path' => $this->removeDoubleSlashes(str_replace($previousPath, $newPath, $oldPath)), + ]); + if ($media->isFolder() === true) { + $this->replacePathReferences($media->id, $previousPath, $newPath); + } + } + } + + private function moveDirectory($fromPath, $toPath) : bool + { + try { + $this->filesystem->disk($this->getConfiguredFilesystem()) + ->move( + $this->getDestinationPath($fromPath), + $this->getDestinationPath($toPath) + ); + } catch (FileExistsException $e) { + return false; + } + return true; + } + + private function getDestinationPath($path) : string + { + if ($this->getConfiguredFilesystem() === 'local') { + return basename(public_path()) . $path; + } + + return $path; + } + + private function getConfiguredFilesystem() : string + { + return config('asgard.media.config.filesystem'); + } + + private function getNewPathFor(string $filename, File $folder) : string + { + return $this->removeDoubleSlashes($folder->path->getRelativeUrl() . '/' . str_slug($filename)); + } + + private function removeDoubleSlashes(string $string) : string + { + return str_replace('//', '/', $string); + } +} diff --git a/Modules/Media/Services/Mover.php b/Modules/Media/Services/Mover.php new file mode 100644 index 000000000..8f83e7a00 --- /dev/null +++ b/Modules/Media/Services/Mover.php @@ -0,0 +1,10 @@ +assertCount(2, File::all()); } - /** @test */ - public function it_can_move_folder_in_database() - { - $folder = $this->folder->create(['name' => 'My Folder']); - $folderTwo = $this->folder->create(['name' => 'Future Child folder']); - - $this->assertEquals('/assets/media/future-child-folder', $folderTwo->path->getRelativeUrl()); - $this->folder->move($folderTwo, $folder); - $this->assertEquals('/assets/media/my-folder/future-child-folder', $folderTwo->path->getRelativeUrl()); - } - - /** @test */ - public function it_can_move_folder_on_disk() - { - $folder = $this->folder->create(['name' => 'My Folder']); - $folderTwo = $this->folder->create(['name' => 'Future Child folder']); - - $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/future-child-folder'))); - $this->folder->move($folderTwo, $folder); - $this->assertTrue( - $this->app['files']->isDirectory(public_path('/assets/media/my-folder/future-child-folder')), - 'Folder was not moved' - ); - } - - /** @test */ - public function it_can_move_folder_with_folders_and_files_in_it_database() - { - $mainFolder = $this->folder->create(['name' => 'My Folder']); - $folderTwo = $this->folder->create(['name' => 'Second folder']); - $folderThird = $this->folder->create(['name' => 'Third folder', 'parent_id' => $folderTwo->id]); - $file = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $folderTwo->id); - $fileTwo = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-other-file.jpg'), $folderThird->id); - - $this->assertEquals('/assets/media/second-folder', $folderTwo->path->getRelativeUrl()); - $this->assertEquals('/assets/media/second-folder/third-folder', $folderThird->path->getRelativeUrl()); - $this->assertEquals('/assets/media/second-folder/my-file.jpg', $file->path->getRelativeUrl()); - $this->assertEquals('/assets/media/second-folder/third-folder/my-other-file.jpg', $fileTwo->path->getRelativeUrl()); - - $this->folder->move($folderTwo, $mainFolder); - - $folderTwo->refresh(); - $folderThird->refresh(); - $file->refresh(); - $fileTwo->refresh(); - $this->assertEquals('/assets/media/my-folder/second-folder', $folderTwo->path->getRelativeUrl()); - $this->assertEquals('/assets/media/my-folder/second-folder/third-folder', $folderThird->path->getRelativeUrl()); - $this->assertEquals('/assets/media/my-folder/second-folder/my-file.jpg', $file->path->getRelativeUrl()); - $this->assertEquals('/assets/media/my-folder/second-folder/third-folder/my-other-file.jpg', $fileTwo->path->getRelativeUrl()); - } - - /** @test */ - public function it_can_move_folder_back_to_root_folder() - { - $parentFolder = $this->folder->create(['name' => 'My Folder', 'parent_id' => 0]); - $folder = $this->folder->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]); - - $file = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $folder->id); - - $this->assertEquals('/assets/media/my-folder/child-folder', $folder->path->getRelativeUrl()); - $this->assertEquals('/assets/media/my-folder/child-folder/my-file.jpg', $file->path->getRelativeUrl()); - $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/my-folder/child-folder'))); - $this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.jpg'))); - - $this->folder->move($folder, $this->makeRootFolder()); - - $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/child-folder'))); - $this->assertTrue($this->app['files']->exists(public_path('/assets/media/child-folder/my-file.jpg'))); - $this->assertEquals('/assets/media/child-folder', $folder->path->getRelativeUrl()); - $file->refresh(); - $this->assertEquals('/assets/media/child-folder/my-file.jpg', $file->path->getRelativeUrl()); - } - - /** @test */ - public function it_can_move_folder_with_folders_and_files_in_it_disk() - { - $mainFolder = $this->folder->create(['name' => 'My Folder']); - $folderTwo = $this->folder->create(['name' => 'Second folder']); - $folderThird = $this->folder->create(['name' => 'Third folder', 'parent_id' => $folderTwo->id]); - $file = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $folderTwo->id); - $fileTwo = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-other-file.jpg'), $folderThird->id); - - $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/second-folder'))); - $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/second-folder/third-folder'))); - $this->assertTrue($this->app['files']->exists(public_path('/assets/media/second-folder/my-file.jpg'))); - $this->assertTrue($this->app['files']->exists(public_path('/assets/media/second-folder/third-folder/my-other-file.jpg'))); - - $this->folder->move($folderTwo, $mainFolder); - - $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/my-folder/second-folder'))); - $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/my-folder/second-folder/third-folder'))); - $this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/second-folder/my-file.jpg'))); - $this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/second-folder/third-folder/my-other-file.jpg'))); - } - private function createFile($fileName = 'random/name.jpg') { return File::create([ @@ -436,13 +341,4 @@ private function createFile($fileName = 'random/name.jpg') 'folder_id' => 0, ]); } - - private function makeRootFolder() : File - { - return new File([ - 'id' => 0, - 'folder_id' => 0, - 'path' => config('asgard.media.config.files-path'), - ]); - } } diff --git a/Modules/Media/Tests/FolderMoverTest.php b/Modules/Media/Tests/FolderMoverTest.php new file mode 100644 index 000000000..3487669ad --- /dev/null +++ b/Modules/Media/Tests/FolderMoverTest.php @@ -0,0 +1,157 @@ +resetDatabase(); + + $this->mover = app(FolderMover::class); + $this->folder = app(FolderRepository::class); + $this->app['config']->set('asgard.media.config.files-path', '/assets/media/'); + } + + public function tearDown() + { + if ($this->app['files']->isDirectory(public_path('assets')) === true) { + $this->app['files']->deleteDirectory(public_path('assets')); + } + } + + /** @test */ + public function it_can_move_folder_on_disk() + { + $folder = $this->folder->create(['name' => 'My Folder']); + $folderTwo = $this->folder->create(['name' => 'Future Child folder']); + + $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/future-child-folder'))); + $this->mover->move($folderTwo, $folder); + $this->assertTrue( + $this->app['files']->isDirectory(public_path('/assets/media/my-folder/future-child-folder')), + 'Folder was not moved' + ); + } + + /** @test */ + public function it_can_move_folder_in_database() + { + $folder = $this->folder->create(['name' => 'My Folder']); + $folderTwo = $this->folder->create(['name' => 'Future Child folder']); + + $this->assertEquals('/assets/media/future-child-folder', $folderTwo->path->getRelativeUrl()); + $this->mover->move($folderTwo, $folder); + $this->assertEquals('/assets/media/my-folder/future-child-folder', $folderTwo->path->getRelativeUrl()); + } + + /** @test */ + public function it_can_move_folder_with_folders_and_files_in_it_database() + { + $mainFolder = $this->folder->create(['name' => 'My Folder']); + $folderTwo = $this->folder->create(['name' => 'Second folder']); + $folderThird = $this->folder->create(['name' => 'Third folder', 'parent_id' => $folderTwo->id]); + $file = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $folderTwo->id); + $fileTwo = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-other-file.jpg'), $folderThird->id); + + $this->assertEquals('/assets/media/second-folder', $folderTwo->path->getRelativeUrl()); + $this->assertEquals('/assets/media/second-folder/third-folder', $folderThird->path->getRelativeUrl()); + $this->assertEquals('/assets/media/second-folder/my-file.jpg', $file->path->getRelativeUrl()); + $this->assertEquals('/assets/media/second-folder/third-folder/my-other-file.jpg', $fileTwo->path->getRelativeUrl()); + + $this->mover->move($folderTwo, $mainFolder); + + $folderTwo->refresh(); + $folderThird->refresh(); + $file->refresh(); + $fileTwo->refresh(); + $this->assertEquals('/assets/media/my-folder/second-folder', $folderTwo->path->getRelativeUrl()); + $this->assertEquals('/assets/media/my-folder/second-folder/third-folder', $folderThird->path->getRelativeUrl()); + $this->assertEquals('/assets/media/my-folder/second-folder/my-file.jpg', $file->path->getRelativeUrl()); + $this->assertEquals('/assets/media/my-folder/second-folder/third-folder/my-other-file.jpg', $fileTwo->path->getRelativeUrl()); + } + + /** @test */ + public function it_can_move_folder_back_to_root_folder() + { + $parentFolder = $this->folder->create(['name' => 'My Folder', 'parent_id' => 0]); + $folder = $this->folder->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]); + + $file = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $folder->id); + + $this->assertEquals('/assets/media/my-folder/child-folder', $folder->path->getRelativeUrl()); + $this->assertEquals('/assets/media/my-folder/child-folder/my-file.jpg', $file->path->getRelativeUrl()); + $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/my-folder/child-folder'))); + $this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.jpg'))); + + $this->mover->move($folder, $this->makeRootFolder()); + + $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/child-folder'))); + $this->assertTrue($this->app['files']->exists(public_path('/assets/media/child-folder/my-file.jpg'))); + $this->assertEquals('/assets/media/child-folder', $folder->path->getRelativeUrl()); + $file->refresh(); + $this->assertEquals('/assets/media/child-folder/my-file.jpg', $file->path->getRelativeUrl()); + } + + /** @test */ + public function it_can_move_folder_with_folders_and_files_in_it_disk() + { + $mainFolder = $this->folder->create(['name' => 'My Folder']); + $folderTwo = $this->folder->create(['name' => 'Second folder']); + $folderThird = $this->folder->create(['name' => 'Third folder', 'parent_id' => $folderTwo->id]); + $file = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $folderTwo->id); + $fileTwo = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-other-file.jpg'), $folderThird->id); + + $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/second-folder'))); + $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/second-folder/third-folder'))); + $this->assertTrue($this->app['files']->exists(public_path('/assets/media/second-folder/my-file.jpg'))); + $this->assertTrue($this->app['files']->exists(public_path('/assets/media/second-folder/third-folder/my-other-file.jpg'))); + + $this->mover->move($folderTwo, $mainFolder); + + $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/my-folder/second-folder'))); + $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/my-folder/second-folder/third-folder'))); + $this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/second-folder/my-file.jpg'))); + $this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/second-folder/third-folder/my-other-file.jpg'))); + } + + /** @test */ + public function it_does_not_move_folder_if_folder_name_exists_at_location() + { + $mainFolder = $this->folder->create(['name' => 'My Folder']); + $folderTwo = $this->folder->create(['name' => 'Child Folder']); + $folderThird = $this->folder->create(['name' => 'Child Folder', 'parent_id' => $mainFolder->id]); + + $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/my-folder/child-folder'))); + + $this->mover->move($folderTwo, $mainFolder); + + $this->assertTrue($this->app['files']->isDirectory(public_path('/assets/media/child-folder'))); + $this->assertEquals('/assets/media/child-folder', $folderTwo->path->getRelativeUrl()); + } + + private function makeRootFolder() : File + { + return new File([ + 'id' => 0, + 'folder_id' => 0, + 'path' => config('asgard.media.config.files-path'), + ]); + } +}