Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
- Lukas Reschke <lukas@statuscode.ch>
- Lukas Stabe <lukas@stabe.de>
- Luke Policinski <lpolicinski@gmail.com>
- Madison Steiner <me@madisonishopeful.com>
- Magnus Walbeck <mw@mwalbeck.org>
- Maksim Sukharev <antreesy.web@gmail.com>
- Marc Hefter <marchefter@gmail.com>
Expand Down
1 change: 0 additions & 1 deletion build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3851,7 +3851,6 @@
<file src="lib/private/Files/Storage/Common.php">
<InvalidOperand>
<code><![CDATA[!$permissions]]></code>
<code><![CDATA[$this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file)]]></code>
</InvalidOperand>
</file>
<file src="lib/private/Files/Storage/DAV.php">
Expand Down
6 changes: 5 additions & 1 deletion lib/private/Files/Storage/Common.php
Original file line number Diff line number Diff line change
Expand Up @@ -552,9 +552,13 @@ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalP
$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');
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Files/Storage/Local.php
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalP
$rootStorage = new Local(['datadir' => '/']);
return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
} else {
return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime);
}
}

Expand Down
6 changes: 4 additions & 2 deletions lib/private/Files/Storage/Wrapper/KnownMtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,10 @@ public function unlink(string $path): bool {

#[\Override]
public function rename(string $source, string $target): bool {
$mtime = $this->filemtime($source);
$result = parent::rename($source, $target);
if ($result) {
$this->knowMtimes->set($target, $this->clock->now()->getTimestamp());
$this->knowMtimes->set($target, is_int($mtime) ? $mtime : $this->clock->now()->getTimestamp());
$this->knowMtimes->set($source, $this->clock->now()->getTimestamp());
}
return $result;
Expand Down Expand Up @@ -143,9 +144,10 @@ public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalP

#[\Override]
public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath): bool {
$mtime = $sourceStorage->filemtime($sourceInternalPath);
$result = parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
if ($result) {
$this->knowMtimes->set($targetInternalPath, $this->clock->now()->getTimestamp());
$this->knowMtimes->set($targetInternalPath, is_int($mtime) ? $mtime : $this->clock->now()->getTimestamp());
}
return $result;
}
Expand Down
33 changes: 32 additions & 1 deletion lib/private/Files/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,33 @@ protected function copyUpdate(Storage $sourceStorage, Storage $targetStorage, st
}
}

private function preserveCopyMtime(Storage $sourceStorage, Storage $targetStorage, string $sourceInternalPath, string $targetInternalPath): void {
if ($sourceStorage->is_dir($sourceInternalPath)) {
$directory = $sourceStorage->opendir($sourceInternalPath);
if (is_resource($directory)) {
try {
while (($file = readdir($directory)) !== false) {
if (!Filesystem::isIgnoredDir($file)) {
$this->preserveCopyMtime(
$sourceStorage,
$targetStorage,
$sourceInternalPath . '/' . $file,
$targetInternalPath . '/' . $file,
);
}
}
} finally {
closedir($directory);
}
}
}

$mtime = $sourceStorage->filemtime($sourceInternalPath);
if (is_int($mtime)) {
$targetStorage->touch($targetInternalPath, $mtime);
}
}

/**
* @param string $path
* @return bool|mixed
Expand Down Expand Up @@ -938,7 +965,7 @@ private function validateMountMove(array $mounts, IMountPoint $sourceMount, IMou
*
* @return bool|mixed
*/
public function copy($source, $target, $preserveMtime = false) {
public function copy($source, $target, $preserveMtime = true) {
$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($source));
$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($target));
$result = false;
Expand Down Expand Up @@ -995,6 +1022,10 @@ public function copy($source, $target, $preserveMtime = false) {
$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
}

if ($result && $preserveMtime) {
$this->preserveCopyMtime($storage1, $storage2, $internalPath1, $internalPath2);
}

if ($result) {
$this->copyUpdate($storage1, $storage2, $internalPath1, $internalPath2);
}
Expand Down
17 changes: 17 additions & 0 deletions tests/lib/Files/Storage/CommonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,21 @@ public function testMoveFromStorageNestedJail(): void {
$instance->moveFromStorage($source, 'foo.txt', 'bar.txt');
$this->assertTrue($instance->file_exists('bar.txt'));
}

public function testMoveFromStoragePreservesMtimeRecursively(): void {
$sourceDir = $this->tmpDir . '/source';
mkdir($sourceDir);
$source = new \OC\Files\Storage\CommonTest(['datadir' => $sourceDir]);
$source->mkdir('folder');
$source->file_put_contents('folder/file.txt', 'content');

$fileMtime = time() - 3600;
$folderMtime = $fileMtime - 3600;
$source->touch('folder/file.txt', $fileMtime);
$source->touch('folder', $folderMtime);

$this->assertTrue($this->instance->moveFromStorage($source, 'folder', 'moved'));
$this->assertSame($fileMtime, $this->instance->filemtime('moved/file.txt'));
$this->assertSame($folderMtime, $this->instance->filemtime('moved'));
}
}
17 changes: 17 additions & 0 deletions tests/lib/Files/Storage/LocalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,21 @@ public function testMoveNestedJail(): void {
$jail3->moveFromStorage($jail2, 'file.txt', 'file.txt');
$this->assertTrue($this->instance->file_exists('target/file.txt'));
}

public function testMoveFromNonLocalStoragePreservesMtimeRecursively(): void {
$sourceDir = $this->tmpDir . '/source';
mkdir($sourceDir);
$source = new \OC\Files\Storage\CommonTest(['datadir' => $sourceDir]);
$source->mkdir('folder');
$source->file_put_contents('folder/file.txt', 'content');

$fileMtime = time() - 3600;
$folderMtime = $fileMtime - 3600;
$source->touch('folder/file.txt', $fileMtime);
$source->touch('folder', $folderMtime);

$this->assertTrue($this->instance->moveFromStorage($source, 'folder', 'moved'));
$this->assertSame($fileMtime, $this->instance->filemtime('moved/file.txt'));
$this->assertSame($folderMtime, $this->instance->filemtime('moved'));
}
}
25 changes: 25 additions & 0 deletions tests/lib/Files/Storage/Wrapper/KnownMtimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,29 @@ public function testNewerKnownMtime(): void {
$this->assertEquals($future, $this->instance->stat('foo.txt')['mtime']);
$this->assertEquals($future, $this->instance->getMetaData('foo.txt')['mtime']);
}

public function testMoveFromStoragePreservesKnownMtime(): void {
$source = new Temporary([]);
try {
$source->file_put_contents('source.txt', 'content');
$mtime = time() - 3600;
$source->touch('source.txt', $mtime);
$this->fakeTime = time() + 3600;

$this->assertTrue($this->instance->moveFromStorage($source, 'source.txt', 'target.txt'));
$this->assertSame($mtime, $this->instance->filemtime('target.txt'));
} finally {
$source->cleanUp();
}
}

public function testRenamePreservesKnownMtime(): void {
$this->instance->file_put_contents('source.txt', 'content');
$mtime = time() - 3600;
$this->instance->touch('source.txt', $mtime);
$this->fakeTime = time() + 3600;

$this->assertTrue($this->instance->rename('source.txt', 'target.txt'));
$this->assertSame($mtime, $this->instance->filemtime('target.txt'));
}
}
42 changes: 42 additions & 0 deletions tests/lib/Files/ViewTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,48 @@ public function testCopyBetweenStorageCrossNonLocal(): void {
$this->copyBetweenStorages($storage1, $storage2);
}

public static function copyPreservesMtimeProvider(): array {
return [
'same storage' => [false],
'cross storage' => [true],
];
}

#[\PHPUnit\Framework\Attributes\DataProvider('copyPreservesMtimeProvider')]
public function testCopyPreservesMtimeRecursivelyByDefault(bool $crossStorage): void {
$sourceStorage = $this->getTestStorage(true, TemporaryNoCross::class);
Filesystem::mount($sourceStorage, [], '/test/');

$targetPath = 'copied';
if ($crossStorage) {
$targetStorage = $this->getTestStorage(true, TemporaryNoCross::class);
Filesystem::mount($targetStorage, [], '/test/target');
$targetPath = 'target/copied';
}

$view = new View('/test');
$fileMtime = time() - 3600;
$folderMtime = $fileMtime - 3600;
$view->touch('folder/bar.txt', $fileMtime);
$view->touch('folder', $folderMtime);

$this->assertTrue($view->copy('folder', $targetPath));
$this->assertSame($fileMtime, $view->filemtime($targetPath . '/bar.txt'));
$this->assertSame($folderMtime, $view->filemtime($targetPath));
}

public function testCopyDoesNotPreserveMtimeWhenDisabled(): void {
$storage = $this->getTestStorage();
Filesystem::mount($storage, [], '/test/');

$view = new View('/test');
$sourceMtime = time() - 3600;
$view->touch('foo.txt', $sourceMtime);

$this->assertTrue($view->copy('foo.txt', 'copied.txt', false));
$this->assertNotSame($sourceMtime, $view->filemtime('copied.txt'));
}

public function copyBetweenStorages($storage1, $storage2) {
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/substorage');
Expand Down