diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index d0e3562797309..4699dc5d3922c 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -581,6 +581,78 @@ public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $t return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); } + public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, ?ICacheEntry $sourceCacheEntry = null): bool { + $sourceCache = $sourceStorage->getCache(); + if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class) && $sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()) { + $this->getCache()->moveFromCache($sourceCache, $sourceInternalPath, $targetInternalPath); + // Do not import any data when source and target bucket are identical. + return true; + } + if (!$sourceCacheEntry) { + $sourceCacheEntry = $sourceCache->get($sourceInternalPath); + } + + $this->copyObjects($sourceStorage, $sourceCache, $sourceCacheEntry); + if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) { + /** @var ObjectStoreStorage $sourceStorage */ + $sourceStorage->setPreserveCacheOnDelete(true); + } + if ($sourceCacheEntry->getMimeType() === ICacheEntry::DIRECTORY_MIMETYPE) { + $sourceStorage->rmdir($sourceInternalPath); + } else { + $sourceStorage->unlink($sourceInternalPath); + } + if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) { + /** @var ObjectStoreStorage $sourceStorage */ + $sourceStorage->setPreserveCacheOnDelete(false); + } + $this->getCache()->moveFromCache($sourceCache, $sourceInternalPath, $targetInternalPath); + + return true; + } + + /** + * Copy the object(s) of a file or folder into this storage, without touching the cache + */ + private function copyObjects(IStorage $sourceStorage, ICache $sourceCache, ICacheEntry $sourceCacheEntry) { + $copiedFiles = []; + try { + foreach ($this->getAllChildObjects($sourceCache, $sourceCacheEntry) as $file) { + $sourceStream = $sourceStorage->fopen($file->getPath(), 'r'); + if (!$sourceStream) { + throw new \Exception("Failed to open source file {$file->getPath()} ({$file->getId()})"); + } + $this->objectStore->writeObject($this->getURN($file->getId()), $sourceStream, $file->getMimeType()); + if (is_resource($sourceStream)) { + fclose($sourceStream); + } + $copiedFiles[] = $file->getId(); + } + } catch (\Exception $e) { + foreach ($copiedFiles as $fileId) { + try { + $this->objectStore->deleteObject($this->getURN($fileId)); + } catch (\Exception $e) { + // ignore + } + } + throw $e; + } + } + + /** + * @return \Iterator + */ + private function getAllChildObjects(ICache $cache, ICacheEntry $entry): \Iterator { + if ($entry->getMimeType() === FileInfo::MIMETYPE_FOLDER) { + foreach ($cache->getFolderContentsById($entry->getId()) as $child) { + yield from $this->getAllChildObjects($cache, $child); + } + } else { + yield $entry; + } + } + public function copy($source, $target) { $source = $this->normalizePath($source); $target = $this->normalizePath($target); diff --git a/tests/lib/Files/ObjectStore/ObjectStoreStoragesDifferentBucketTest.php b/tests/lib/Files/ObjectStore/ObjectStoreStoragesDifferentBucketTest.php new file mode 100644 index 0000000000000..a0e18a5557b65 --- /dev/null +++ b/tests/lib/Files/ObjectStore/ObjectStoreStoragesDifferentBucketTest.php @@ -0,0 +1,41 @@ +objectStore1 = new StorageObjectStore($baseStorage1); + $config['objectstore'] = $this->objectStore1; + $this->storage1 = new ObjectStoreStorageOverwrite($config); + + $baseStorage2 = new Temporary(); + $this->objectStore2 = new StorageObjectStore($baseStorage2); + $config['objectstore'] = $this->objectStore2; + $this->storage2 = new ObjectStoreStorageOverwrite($config); + } +} diff --git a/tests/lib/Files/ObjectStore/ObjectStoreStoragesSameBucketTest.php b/tests/lib/Files/ObjectStore/ObjectStoreStoragesSameBucketTest.php new file mode 100644 index 0000000000000..19a1f4b7bc5ad --- /dev/null +++ b/tests/lib/Files/ObjectStore/ObjectStoreStoragesSameBucketTest.php @@ -0,0 +1,33 @@ +objectStore = new StorageObjectStore($baseStorage); + $config['objectstore'] = $this->objectStore; + // storage1 and storage2 share the same object store. + $this->storage1 = new ObjectStoreStorageOverwrite($config); + $this->storage2 = new ObjectStoreStorageOverwrite($config); + } +} diff --git a/tests/lib/Files/Storage/StoragesTest.php b/tests/lib/Files/Storage/StoragesTest.php new file mode 100644 index 0000000000000..d157d288f2c6e --- /dev/null +++ b/tests/lib/Files/Storage/StoragesTest.php @@ -0,0 +1,108 @@ +storage1) && is_null($this->storage2)) { + return; + } + $this->storage1->getCache()->clear(); + $this->storage2->getCache()->clear(); + + parent::tearDown(); + } + + public function testMoveFileFromStorage() { + $source = 'source.txt'; + $target = 'target.txt'; + $this->storage2->file_put_contents($source, 'foo'); + + $this->storage1->moveFromStorage($this->storage2, $source, $target); + + $this->assertTrue($this->storage1->file_exists($target), $target.' was not created'); + $this->assertFalse($this->storage2->file_exists($source), $source.' still exists'); + $this->assertEquals('foo', $this->storage1->file_get_contents($target)); + } + + public function testMoveDirectoryFromStorage() { + $this->storage2->mkdir('source'); + $this->storage2->file_put_contents('source/test1.txt', 'foo'); + $this->storage2->file_put_contents('source/test2.txt', 'qwerty'); + $this->storage2->mkdir('source/subfolder'); + $this->storage2->file_put_contents('source/subfolder/test.txt', 'bar'); + + $this->storage1->moveFromStorage($this->storage2, 'source', 'target'); + + $this->assertTrue($this->storage1->file_exists('target')); + $this->assertTrue($this->storage1->file_exists('target/test1.txt')); + $this->assertTrue($this->storage1->file_exists('target/test2.txt')); + $this->assertTrue($this->storage1->file_exists('target/subfolder')); + $this->assertTrue($this->storage1->file_exists('target/subfolder/test.txt')); + + $this->assertFalse($this->storage2->file_exists('source')); + $this->assertFalse($this->storage2->file_exists('source/test1.txt')); + $this->assertFalse($this->storage2->file_exists('source/test2.txt')); + $this->assertFalse($this->storage2->file_exists('source/subfolder')); + $this->assertFalse($this->storage2->file_exists('source/subfolder/test.txt')); + + $this->assertEquals('foo', $this->storage1->file_get_contents('target/test1.txt')); + $this->assertEquals('qwerty', $this->storage1->file_get_contents('target/test2.txt')); + $this->assertEquals('bar', $this->storage1->file_get_contents('target/subfolder/test.txt')); + } + + public function testCopyFileFromStorage() { + $source = 'source.txt'; + $target = 'target.txt'; + $this->storage2->file_put_contents($source, 'foo'); + + $this->storage1->copyFromStorage($this->storage2, $source, $target); + + $this->assertTrue($this->storage1->file_exists($target), $target.' was not created'); + $this->assertTrue($this->storage2->file_exists($source), $source.' was deleted'); + $this->assertEquals('foo', $this->storage1->file_get_contents($target)); + } + + public function testCopyDirectoryFromStorage() { + $this->storage2->mkdir('source'); + $this->storage2->file_put_contents('source/test1.txt', 'foo'); + $this->storage2->file_put_contents('source/test2.txt', 'qwerty'); + $this->storage2->mkdir('source/subfolder'); + $this->storage2->file_put_contents('source/subfolder/test.txt', 'bar'); + + $this->storage1->copyFromStorage($this->storage2, 'source', 'target'); + + $this->assertTrue($this->storage1->file_exists('target')); + $this->assertTrue($this->storage1->file_exists('target/test1.txt')); + $this->assertTrue($this->storage1->file_exists('target/test2.txt')); + $this->assertTrue($this->storage1->file_exists('target/subfolder')); + $this->assertTrue($this->storage1->file_exists('target/subfolder/test.txt')); + + $this->assertTrue($this->storage2->file_exists('source')); + $this->assertTrue($this->storage2->file_exists('source/test1.txt')); + $this->assertTrue($this->storage2->file_exists('source/test2.txt')); + $this->assertTrue($this->storage2->file_exists('source/subfolder')); + $this->assertTrue($this->storage2->file_exists('source/subfolder/test.txt')); + + $this->assertEquals('foo', $this->storage1->file_get_contents('target/test1.txt')); + $this->assertEquals('qwerty', $this->storage1->file_get_contents('target/test2.txt')); + $this->assertEquals('bar', $this->storage1->file_get_contents('target/subfolder/test.txt')); + } +}