diff --git a/apps/files_external/lib/Lib/Storage/AmazonS3.php b/apps/files_external/lib/Lib/Storage/AmazonS3.php index e5fa98313d523..67438502207f7 100644 --- a/apps/files_external/lib/Lib/Storage/AmazonS3.php +++ b/apps/files_external/lib/Lib/Storage/AmazonS3.php @@ -573,7 +573,11 @@ public function touch($path, $mtime = null) { return true; } - public function copy($source, $target, $isFile = null) { + /** + * @param string $source + * @param string $target + */ + public function copy($source, $target, bool $preserveMtime = false, ?bool $isFile = null): bool { $source = $this->normalizePath($source); $target = $this->normalizePath($target); @@ -607,7 +611,7 @@ public function copy($source, $target, $isFile = null) { foreach ($this->getDirectoryContent($source) as $item) { $childSource = $source . '/' . $item['name']; $childTarget = $target . '/' . $item['name']; - $this->copy($childSource, $childTarget, $item['mimetype'] !== FileInfo::MIMETYPE_FOLDER); + $this->copy($childSource, $childTarget, $preserveMtime, $item['mimetype'] !== FileInfo::MIMETYPE_FOLDER); } } diff --git a/apps/files_external/lib/Lib/Storage/SFTP.php b/apps/files_external/lib/Lib/Storage/SFTP.php index 3fa04209fa005..746c0fc7ccabe 100644 --- a/apps/files_external/lib/Lib/Storage/SFTP.php +++ b/apps/files_external/lib/Lib/Storage/SFTP.php @@ -519,9 +519,13 @@ public function writeStream(string $path, $stream, int $size = null): int { } } - public function copy($source, $target) { + /** + * @param string $source + * @param string $target + */ + public function copy($source, $target, bool $preserveMtime = false): bool { if ($this->is_dir($source) || $this->is_dir($target)) { - return parent::copy($source, $target); + return parent::copy($source, $target, $preserveMtime); } else { $absSource = $this->absPath($source); $absTarget = $this->absPath($target); diff --git a/apps/files_external/lib/Lib/Storage/Swift.php b/apps/files_external/lib/Lib/Storage/Swift.php index 7283e5ae7b12a..e5da595ad7d99 100644 --- a/apps/files_external/lib/Lib/Storage/Swift.php +++ b/apps/files_external/lib/Lib/Storage/Swift.php @@ -483,7 +483,11 @@ public function touch($path, $mtime = null) { } } - public function copy($source, $target) { + /** + * @param string $source + * @param string $target + */ + public function copy($source, $target, bool $preserveMtime = false): bool { $source = $this->normalizePath($source); $target = $this->normalizePath($target); @@ -502,6 +506,12 @@ public function copy($source, $target) { // invalidate target object to force repopulation on fetch $this->objectCache->remove($target); $this->objectCache->remove($target . '/'); + if ($preserveMtime) { + $mTime = $this->filemtime($source); + if (is_int($mTime)) { + $this->touch($target, $mTime); + } + } } catch (BadResponseError $e) { \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), [ 'exception' => $e, @@ -534,7 +544,7 @@ public function copy($source, $target) { $source = $source . '/' . $file; $target = $target . '/' . $file; - $this->copy($source, $target); + $this->copy($source, $target, $preserveMtime); } } else { //file does not exist diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index 59bd7e0f5ef53..953e78375cedb 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -54,7 +54,7 @@ use Test\Traits\MountProviderTrait; class TemporaryNoCross extends Temporary { - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = null) { + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, bool $preserveMtime = false): bool { return Common::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime); } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 9c3bf0f7e9a13..61a6b38b164a8 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -413,6 +413,7 @@ 'OCP\\Files\\Storage\\IChunkedFileWrite' => $baseDir . '/lib/public/Files/Storage/IChunkedFileWrite.php', 'OCP\\Files\\Storage\\IDisableEncryptionStorage' => $baseDir . '/lib/public/Files/Storage/IDisableEncryptionStorage.php', 'OCP\\Files\\Storage\\ILockingStorage' => $baseDir . '/lib/public/Files/Storage/ILockingStorage.php', + 'OCP\\Files\\Storage\\IMtimePreserving' => $baseDir . '/lib/public/Files/Storage/IMtimePreserving.php', 'OCP\\Files\\Storage\\INotifyStorage' => $baseDir . '/lib/public/Files/Storage/INotifyStorage.php', 'OCP\\Files\\Storage\\IReliableEtagStorage' => $baseDir . '/lib/public/Files/Storage/IReliableEtagStorage.php', 'OCP\\Files\\Storage\\IStorage' => $baseDir . '/lib/public/Files/Storage/IStorage.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index b222594af754b..3ce8e8675e7dc 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -446,6 +446,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Files\\Storage\\IChunkedFileWrite' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IChunkedFileWrite.php', 'OCP\\Files\\Storage\\IDisableEncryptionStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IDisableEncryptionStorage.php', 'OCP\\Files\\Storage\\ILockingStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/ILockingStorage.php', + 'OCP\\Files\\Storage\\IMtimePreserving' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IMtimePreserving.php', 'OCP\\Files\\Storage\\INotifyStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/INotifyStorage.php', 'OCP\\Files\\Storage\\IReliableEtagStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IReliableEtagStorage.php', 'OCP\\Files\\Storage\\IStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IStorage.php', diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index 7eb284fc77463..7793c02be5cea 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -596,7 +596,7 @@ public function copyFromStorage( $sourceInternalPath, $targetInternalPath, $preserveMtime = false - ) { + ): bool { if ($sourceStorage->instanceOfStorage(ObjectStoreStorage::class)) { /** @var ObjectStoreStorage $sourceStorage */ if ($sourceStorage->getObjectStore()->getStorageId() === $this->getObjectStore()->getStorageId()) { @@ -614,10 +614,10 @@ public function copyFromStorage( } } - return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime); } - public function copy($source, $target) { + public function copy($source, $target, bool $preserveMtime = false): bool { $source = $this->normalizePath($source); $target = $this->normalizePath($target); diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index 65c2580e61c4c..1662d5cc7a279 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -60,6 +60,7 @@ use OCP\Files\InvalidPathException; use OCP\Files\ReservedWordException; use OCP\Files\Storage\ILockingStorage; +use OCP\Files\Storage\IMtimePreserving; use OCP\Files\Storage\IStorage; use OCP\Files\Storage\IWriteStreamStorage; use OCP\Lock\ILockingProvider; @@ -78,7 +79,7 @@ * Some \OC\Files\Storage\Common methods call functions which are first defined * in classes which extend it, e.g. $this->stat() . */ -abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage { +abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage, IMtimePreserving { use LocalTempFileTrait; protected $cache; @@ -222,23 +223,27 @@ public function rename($source, $target) { $this->remove($target); $this->removeCachedFile($source); - return $this->copy($source, $target) and $this->remove($source); + return $this->copy($source, $target, true) && $this->remove($source); } - public function copy($source, $target) { + public function copy($source, $target, bool $preserveMtime = false): bool { if ($this->is_dir($source)) { $this->remove($target); $dir = $this->opendir($source); $this->mkdir($target); while ($file = readdir($dir)) { if (!Filesystem::isIgnoredDir($file)) { - if (!$this->copy($source . '/' . $file, $target . '/' . $file)) { + if (!$this->copy($source . '/' . $file, $target . '/' . $file, $preserveMtime)) { closedir($dir); return false; } } } closedir($dir); + if ($preserveMtime) { + $mtime = $this->filemtime($source); + $this->touch($target, is_int($mtime) ? $mtime : null); + } return true; } else { $sourceStream = $this->fopen($source, 'r'); @@ -246,6 +251,9 @@ public function copy($source, $target) { [, $result] = \OC_Helper::streamCopy($sourceStream, $targetStream); if (!$result) { \OCP\Server::get(LoggerInterface::class)->warning("Failed to write data while copying $source to $target"); + } elseif ($preserveMtime) { + $mtime = $this->filemtime($source); + $this->touch($target, is_int($mtime) ? $mtime : null); } $this->removeCachedFile($target); return $result; @@ -615,21 +623,28 @@ public function getMountOption($name, $default = null) { * @param bool $preserveMtime * @return bool */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, bool $preserveMtime = false): bool { if ($sourceStorage === $this) { - return $this->copy($sourceInternalPath, $targetInternalPath); + return $this->copy($sourceInternalPath, $targetInternalPath, $preserveMtime); } if ($sourceStorage->is_dir($sourceInternalPath)) { $dh = $sourceStorage->opendir($sourceInternalPath); - $result = $this->mkdir($targetInternalPath); if (is_resource($dh)) { - $result = true; - while ($result and ($file = readdir($dh)) !== false) { + if (!$this->is_dir($targetInternalPath)) { + $result = $this->mkdir($targetInternalPath); + } else { + $result = true; + } + while ($result && ($file = readdir($dh)) !== false) { if (!Filesystem::isIgnoredDir($file)) { - $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file); + $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, $preserveMtime); } } + if ($result && $preserveMtime) { + $mtime = $sourceStorage->filemtime($sourceInternalPath); + $this->touch($targetInternalPath, is_int($mtime) ? $mtime : null); + } } } else { $source = $sourceStorage->fopen($sourceInternalPath, 'r'); @@ -655,7 +670,7 @@ public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $t $this->getCache()->remove($targetInternalPath); } } - return (bool)$result; + return $result; } /** diff --git a/lib/private/Files/Storage/DAV.php b/lib/private/Files/Storage/DAV.php index e5bbbb560da72..ffaa70b9fc0d9 100644 --- a/lib/private/Files/Storage/DAV.php +++ b/lib/private/Files/Storage/DAV.php @@ -565,7 +565,7 @@ public function rename($source, $target) { } /** {@inheritdoc} */ - public function copy($source, $target) { + public function copy($source, $target, bool $preserveMtime = false): bool { $this->init(); $source = $this->cleanPath($source); $target = $this->cleanPath($target); diff --git a/lib/private/Files/Storage/FailedStorage.php b/lib/private/Files/Storage/FailedStorage.php index 07b3b21d965d5..9e65d6b5f4301 100644 --- a/lib/private/Files/Storage/FailedStorage.php +++ b/lib/private/Files/Storage/FailedStorage.php @@ -132,7 +132,7 @@ public function rename($source, $target) { throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); } - public function copy($source, $target) { + public function copy($source, $target, bool $preserveMtime = false): bool { throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); } @@ -180,7 +180,7 @@ public function verifyPath($path, $fileName) { return true; } - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, bool $preserveMtime = false): bool { throw new StorageNotAvailableException($this->e->getMessage(), $this->e->getCode(), $this->e); } diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index 0fca853da5987..560d59eb373c4 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -398,21 +398,27 @@ public function rename($source, $target): bool { return $this->copy($source, $target) && $this->unlink($source); } - public function copy($source, $target) { + public function copy($source, $target, bool $preserveMtime = false): bool { if ($this->is_dir($source)) { - return parent::copy($source, $target); + return parent::copy($source, $target, $preserveMtime); } else { $oldMask = umask($this->defUMask); if ($this->unlinkOnTruncate) { $this->unlink($target); } - $result = copy($this->getSourcePath($source), $this->getSourcePath($target)); + $sourceInternalPath = $this->getSourcePath($source); + $targetInternalPath = $this->getSourcePath($target); + $result = copy($sourceInternalPath, $targetInternalPath); umask($oldMask); if ($this->caseInsensitive) { if (mb_strtolower($target) === mb_strtolower($source) && !$this->file_exists($target)) { return false; } } + if ($result && $preserveMtime) { + $mtime = $this->filemtime($sourceInternalPath); + $this->touch($targetInternalPath, is_int($mtime) ? $mtime : null); + } return $result; } } @@ -590,13 +596,10 @@ private function canDoCrossStorageMove(IStorage $sourceStorage) { } /** - * @param IStorage $sourceStorage * @param string $sourceInternalPath * @param string $targetInternalPath - * @param bool $preserveMtime - * @return bool */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, bool $preserveMtime = false): bool { if ($this->canDoCrossStorageMove($sourceStorage)) { if ($sourceStorage->instanceOfStorage(Jail::class)) { /** @@ -608,9 +611,9 @@ public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $t * @var \OC\Files\Storage\Local $sourceStorage */ $rootStorage = new Local(['datadir' => '/']); - return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath)); + return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath), $preserveMtime); } else { - return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); + return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime); } } diff --git a/lib/private/Files/Storage/PolyFill/CopyDirectory.php b/lib/private/Files/Storage/PolyFill/CopyDirectory.php index ff05eecb134ce..170bcffea9e1c 100644 --- a/lib/private/Files/Storage/PolyFill/CopyDirectory.php +++ b/lib/private/Files/Storage/PolyFill/CopyDirectory.php @@ -64,35 +64,37 @@ abstract public function opendir($path); */ abstract public function mkdir($path); - public function copy($source, $target) { + /** + * Copy file or folder + * + * @param string $source + * @param string $target + */ + public function copy($source, $target, bool $preserveMtime = false): bool { if ($this->is_dir($source)) { if ($this->file_exists($target)) { $this->unlink($target); } $this->mkdir($target); - return $this->copyRecursive($source, $target); + return $this->copyRecursive($source, $target, $preserveMtime); } else { - return parent::copy($source, $target); + return parent::copy($source, $target, $preserveMtime); } } /** * For adapters that don't support copying folders natively - * - * @param $source - * @param $target - * @return bool */ - protected function copyRecursive($source, $target) { + protected function copyRecursive(string $source, string $target, bool $preserveMtime = false): bool { $dh = $this->opendir($source); $result = true; while ($file = readdir($dh)) { if (!\OC\Files\Filesystem::isIgnoredDir($file)) { if ($this->is_dir($source . '/' . $file)) { $this->mkdir($target . '/' . $file); - $result = $this->copyRecursive($source . '/' . $file, $target . '/' . $file); + $result = $this->copyRecursive($source . '/' . $file, $target . '/' . $file, $preserveMtime); } else { - $result = parent::copy($source . '/' . $file, $target . '/' . $file); + $result = parent::copy($source . '/' . $file, $target . '/' . $file, $preserveMtime); } if (!$result) { break; diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 7ce4338256f01..39bf9b4e06d2a 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -824,9 +824,9 @@ private function copyBetweenStorage( $result = true; } if (is_resource($dh)) { - while ($result and ($file = readdir($dh)) !== false) { + while ($result && ($file = readdir($dh)) !== false) { if (!Filesystem::isIgnoredDir($file)) { - $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename); + $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, $preserveMtime, $isRename); } } } diff --git a/lib/private/Files/Storage/Wrapper/KnownMtime.php b/lib/private/Files/Storage/Wrapper/KnownMtime.php index dde209c44ab45..645a362eefe80 100644 --- a/lib/private/Files/Storage/Wrapper/KnownMtime.php +++ b/lib/private/Files/Storage/Wrapper/KnownMtime.php @@ -86,7 +86,7 @@ public function unlink($path) { public function rename($source, $target) { $result = parent::rename($source, $target); if ($result) { - $this->knowMtimes->set($target, $this->clock->now()->getTimestamp()); + $this->knowMtimes->set($target, $this->filemtime($source)); $this->knowMtimes->set($source, $this->clock->now()->getTimestamp()); } return $result; @@ -125,9 +125,10 @@ public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $t } public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + $mTime = $sourceStorage->filemtime($sourceInternalPath); $result = parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); if ($result) { - $this->knowMtimes->set($targetInternalPath, $this->clock->now()->getTimestamp()); + $this->knowMtimes->set($targetInternalPath, $mTime); } return $result; } diff --git a/lib/private/Lockdown/Filesystem/NullStorage.php b/lib/private/Lockdown/Filesystem/NullStorage.php index a3976733b1a05..2dcd4bb81cbdb 100644 --- a/lib/private/Lockdown/Filesystem/NullStorage.php +++ b/lib/private/Lockdown/Filesystem/NullStorage.php @@ -117,7 +117,7 @@ public function rename($source, $target) { throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); } - public function copy($source, $target) { + public function copy($source, $target, bool $preserveMtime = false): bool { throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); } @@ -161,7 +161,7 @@ public function getDirectDownload($path) { return false; } - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) { + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, bool $preserveMtime = false): bool { throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); } diff --git a/lib/public/Files/Storage/IMtimePreserving.php b/lib/public/Files/Storage/IMtimePreserving.php new file mode 100644 index 0000000000000..3b64bd6afb59b --- /dev/null +++ b/lib/public/Files/Storage/IMtimePreserving.php @@ -0,0 +1,64 @@ + + * + * @author Côme Chilliet + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +namespace OCP\Files\Storage; + +/** + * @since 29.0.0 + */ +interface IMtimePreserving extends IStorage { + + /** + * see https://www.php.net/manual/en/function.copy.php + * + * @param string $source + * @param string $target + * @return bool + * @since 9.0.0 + * @deprecated 29.0.0 see copyWithMtime + */ + public function copy($source, $target, bool $preserveMtime = false): bool; + + /** + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @since 29.0.0 + */ + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, bool $preserveMtime = false): bool; + + /** + * see https://www.php.net/manual/en/function.copy.php + * + * @since 29.0.0 + */ + // public function copyWithMtime(string $source, string $target, bool $preserveMtime = false): bool; + + /** + * @since 29.0.0 + */ + // public function copyFromStorageWithMtime(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath, bool $preserveMtime = false): bool; +} diff --git a/tests/lib/Files/Storage/CopyDirectoryTest.php b/tests/lib/Files/Storage/CopyDirectoryTest.php index 25bdb016ead94..fb3d411e2ff85 100644 --- a/tests/lib/Files/Storage/CopyDirectoryTest.php +++ b/tests/lib/Files/Storage/CopyDirectoryTest.php @@ -24,11 +24,11 @@ use OC\Files\Storage\Temporary; class StorageNoRecursiveCopy extends Temporary { - public function copy($path1, $path2) { - if ($this->is_dir($path1)) { + public function copy($source, $target, bool $preserveMtime = false): bool { + if ($this->is_dir($source)) { return false; } - return copy($this->getSourcePath($path1), $this->getSourcePath($path2)); + return copy($this->getSourcePath($source), $this->getSourcePath($target)); } } diff --git a/tests/lib/Files/Storage/Storage.php b/tests/lib/Files/Storage/Storage.php index 63b3dfc5a9843..b3821abfe2872 100644 --- a/tests/lib/Files/Storage/Storage.php +++ b/tests/lib/Files/Storage/Storage.php @@ -224,6 +224,15 @@ public function assertSameAsLorem($file) { ); } + protected function getAlteredMtime($path): int { + /* Save mtime, but also change it so that it differs from current timestamp */ + $mTime = $this->instance->filemtime($path); + $mTime -= 100; + $this->instance->touch($path, $mTime); + $this->assertEquals($mTime, $this->instance->filemtime($path), 'Failed to set mtime with touch'); + return $mTime; + } + /** * @dataProvider copyAndMoveProvider */ @@ -243,12 +252,14 @@ public function testCopy($source, $target) { public function testMove($source, $target) { $this->initSourceAndTarget($source); + $mTime = $this->getAlteredMtime($source); $this->instance->rename($source, $target); $this->wait(); $this->assertTrue($this->instance->file_exists($target), $target . ' was not created'); $this->assertFalse($this->instance->file_exists($source), $source . ' still exists'); $this->assertSameAsLorem($target); + $this->assertEquals($mTime, $this->instance->filemtime($target), 'mtime was not preserved by move'); } /** @@ -271,11 +282,13 @@ public function testCopyOverwrite($source, $target) { public function testMoveOverwrite($source, $target) { $this->initSourceAndTarget($source, $target); + $mTime = $this->getAlteredMtime($source); $this->instance->rename($source, $target); $this->assertTrue($this->instance->file_exists($target), $target . ' was not created'); $this->assertFalse($this->instance->file_exists($source), $source . ' still exists'); $this->assertSameAsLorem($target); + $this->assertEquals($mTime, $this->instance->filemtime($target), 'mtime was not preserved by move'); } public function testLocal() { @@ -485,6 +498,10 @@ public function testRenameDirectory() { $this->instance->file_put_contents('source/test2.txt', 'qwerty'); $this->instance->mkdir('source/subfolder'); $this->instance->file_put_contents('source/subfolder/test.txt', 'bar'); + + $mTimeDirectory = $this->getAlteredMtime('source'); + $mTimeTestFile = $this->getAlteredMtime('source/subfolder/test.txt'); + $this->instance->rename('source', 'target'); $this->assertFalse($this->instance->file_exists('source')); @@ -505,6 +522,8 @@ public function testRenameDirectory() { $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); $this->assertEquals('qwerty', $this->instance->file_get_contents('target/test2.txt')); $this->assertEquals('bar', $this->instance->file_get_contents('target/subfolder/test.txt')); + $this->assertEquals($mTimeDirectory, $this->instance->filemtime('target'), 'directory mtime was not preserved by rename'); + $this->assertEquals($mTimeTestFile, $this->instance->filemtime('target/subfolder/test.txt'), 'file mtime was not preserved by rename'); } public function testRenameOverWriteDirectory() { @@ -679,4 +698,49 @@ public function testFseekSize() { $this->assertEquals($size, $pos); } + + public function testMoveFromStorage() { + $source = 'foo.txt'; + $target = 'bar.txt'; + + $instance = $this->createMock(\OCP\Files\Storage\IStorage::class); + $mTime = time() - 400; + + $instance->method('copyFromStorage') + ->willThrowException(new \Exception('copy')); + $instance->expects(static::once()) + ->method('isDeletable') + ->with($source) + ->willReturn(true); + $instance->expects(static::atLeastOnce()) + ->method('is_dir') + ->with($source) + ->willReturn(false); + $instance->expects(static::once()) + ->method('fopen') + ->willReturnCallback(function ($path, $mode) { + $temp = \OCP\Server::get(\OCP\ITempManager::class); + return fopen($temp->getTemporaryFile(), $mode); + }); + $instance->expects(static::once()) + ->method('unlink') + ->with($source) + ->willReturn(true); + $instance->expects(static::atLeastOnce()) + ->method('filemtime') + ->with($source) + ->willReturn($mTime); + $instance->expects(static::any()) + ->method('getId') + ->willReturn('fakeid'); + $cache = $this->createMock(\OCP\Files\Cache\ICache::class); + $instance->expects(static::any()) + ->method('getCache') + ->willReturn($cache); + + $this->assertFalse($this->instance->file_exists($target)); + $this->instance->moveFromStorage($instance, $source, $target); + $this->assertTrue($this->instance->file_exists($target)); + $this->assertEquals($mTime, $this->instance->filemtime($target), 'mtime was not preserved by move'); + } } diff --git a/tests/lib/Files/ViewTest.php b/tests/lib/Files/ViewTest.php index b9dd49d71fe28..3050a8f14b036 100644 --- a/tests/lib/Files/ViewTest.php +++ b/tests/lib/Files/ViewTest.php @@ -39,7 +39,7 @@ public function touch($path, $mtime = null) { } class TemporaryNoCross extends Temporary { - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = null) { + public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, bool $preserveMtime = false): bool { return Common::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime); }