Skip to content

Commit ca8d719

Browse files
authored
Merge pull request #43244 from nextcloud/backport/41327/stable28
[stable28] dont reuse etag for folders marked explicitly unscanned
2 parents 6bdee05 + 9616d48 commit ca8d719

File tree

3 files changed

+76
-8
lines changed

3 files changed

+76
-8
lines changed

lib/private/Files/Cache/Scanner.php

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,9 @@ public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData =
203203
$fileId = $cacheData['fileid'];
204204
$data['fileid'] = $fileId;
205205
// only reuse data if the file hasn't explicitly changed
206-
if (isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime']) {
206+
$mtimeUnchanged = isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime'];
207+
// if the folder is marked as unscanned, never reuse etags
208+
if ($mtimeUnchanged && $cacheData['size'] !== -1) {
207209
$data['mtime'] = $cacheData['mtime'];
208210
if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
209211
$data['size'] = $cacheData['size'];
@@ -220,6 +222,11 @@ public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData =
220222

221223
// Only update metadata that has changed
222224
$newData = array_diff_assoc($data, $cacheData->getData());
225+
226+
// make it known to the caller that etag has been changed and needs propagation
227+
if (isset($newData['etag'])) {
228+
$data['etag_changed'] = true;
229+
}
223230
} else {
224231
// we only updated unencrypted_size if it's already set
225232
unset($data['unencrypted_size']);
@@ -388,16 +395,20 @@ protected function getExistingChildren($folderId) {
388395
* @param int|float $oldSize the size of the folder before (re)scanning the children
389396
* @return int|float the size of the scanned folder or -1 if the size is unknown at this stage
390397
*/
391-
protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize) {
398+
protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize, &$etagChanged = false) {
392399
if ($reuse === -1) {
393400
$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
394401
}
395402
$this->emit('\OC\Files\Cache\Scanner', 'scanFolder', [$path, $this->storageId]);
396403
$size = 0;
397-
$childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size);
404+
$childQueue = $this->handleChildren($path, $recursive, $reuse, $folderId, $lock, $size, $etagChanged);
398405

399406
foreach ($childQueue as $child => [$childId, $childSize]) {
400-
$childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock, $childSize);
407+
// "etag changed" propagates up, but not down, so we pass `false` to the children even if we already know that the etag of the current folder changed
408+
$childEtagChanged = false;
409+
$childSize = $this->scanChildren($child, $recursive, $reuse, $childId, $lock, $childSize, $childEtagChanged);
410+
$etagChanged |= $childEtagChanged;
411+
401412
if ($childSize === -1) {
402413
$size = -1;
403414
} elseif ($size !== -1) {
@@ -410,8 +421,17 @@ protected function scanChildren(string $path, $recursive, int $reuse, int $folde
410421
if ($this->storage->instanceOfStorage(Encryption::class)) {
411422
$this->cache->calculateFolderSize($path);
412423
} else {
413-
if ($this->cacheActive && $oldSize !== $size) {
414-
$this->cache->update($folderId, ['size' => $size]);
424+
if ($this->cacheActive) {
425+
$updatedData = [];
426+
if ($oldSize !== $size) {
427+
$updatedData['size'] = $size;
428+
}
429+
if ($etagChanged) {
430+
$updatedData['etag'] = uniqid();
431+
}
432+
if ($updatedData) {
433+
$this->cache->update($folderId, $updatedData);
434+
}
415435
}
416436
}
417437
$this->emit('\OC\Files\Cache\Scanner', 'postScanFolder', [$path, $this->storageId]);
@@ -421,7 +441,7 @@ protected function scanChildren(string $path, $recursive, int $reuse, int $folde
421441
/**
422442
* @param bool|IScanner::SCAN_RECURSIVE_INCOMPLETE $recursive
423443
*/
424-
private function handleChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float &$size): array {
444+
private function handleChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float &$size, bool &$etagChanged): array {
425445
// we put this in it's own function so it cleans up the memory before we start recursing
426446
$existingChildren = $this->getExistingChildren($folderId);
427447
$newChildren = iterator_to_array($this->storage->getDirectoryContent($path));
@@ -469,6 +489,10 @@ private function handleChildren(string $path, $recursive, int $reuse, int $folde
469489
} elseif ($size !== -1) {
470490
$size += $data['size'];
471491
}
492+
493+
if (isset($data['etag_changed']) && $data['etag_changed']) {
494+
$etagChanged = true;
495+
}
472496
}
473497
} catch (Exception $ex) {
474498
// might happen if inserting duplicate while a scanning

lib/private/Files/ObjectStore/ObjectStoreScanner.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $loc
3939
return [];
4040
}
4141

42-
protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize) {
42+
protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize, &$etagChanged = false) {
4343
return 0;
4444
}
4545

tests/lib/Files/Cache/ScannerTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,4 +404,48 @@ public function dataTestIsPartialFile() {
404404
['/sub/folder/foo.txt', false],
405405
];
406406
}
407+
408+
public function testNoETagUnscannedFolder() {
409+
$this->fillTestFolders();
410+
411+
$this->scanner->scan('');
412+
413+
$oldFolderEntry = $this->cache->get('folder');
414+
// create a new file in a folder by keeping the mtime unchanged, but mark the folder as unscanned
415+
$this->storage->file_put_contents('folder/new.txt', 'foo');
416+
$this->storage->touch('folder', $oldFolderEntry->getMTime());
417+
$this->cache->update($oldFolderEntry->getId(), ['size' => -1]);
418+
419+
$this->scanner->scan('');
420+
421+
$this->cache->inCache('folder/new.txt');
422+
423+
$newFolderEntry = $this->cache->get('folder');
424+
$this->assertNotEquals($newFolderEntry->getEtag(), $oldFolderEntry->getEtag());
425+
}
426+
427+
public function testNoETagUnscannedSubFolder() {
428+
$this->fillTestFolders();
429+
$this->storage->mkdir('folder/sub');
430+
431+
$this->scanner->scan('');
432+
433+
$oldFolderEntry1 = $this->cache->get('folder');
434+
$oldFolderEntry2 = $this->cache->get('folder/sub');
435+
// create a new file in a folder by keeping the mtime unchanged, but mark the folder as unscanned
436+
$this->storage->file_put_contents('folder/sub/new.txt', 'foo');
437+
$this->storage->touch('folder/sub', $oldFolderEntry1->getMTime());
438+
439+
// we only mark the direct parent as unscanned, which is the current "notify" behavior
440+
$this->cache->update($oldFolderEntry2->getId(), ['size' => -1]);
441+
442+
$this->scanner->scan('');
443+
444+
$this->cache->inCache('folder/new.txt');
445+
446+
$newFolderEntry1 = $this->cache->get('folder');
447+
$this->assertNotEquals($newFolderEntry1->getEtag(), $oldFolderEntry1->getEtag());
448+
$newFolderEntry2 = $this->cache->get('folder/sub');
449+
$this->assertNotEquals($newFolderEntry2->getEtag(), $oldFolderEntry2->getEtag());
450+
}
407451
}

0 commit comments

Comments
 (0)