Skip to content

Commit bd71b45

Browse files
[13.x] Move and copy files that only exist on a read-through disk's fallback (#61272)
* Move and copy files that only exist on a read-through disk's fallback * Note the fallback read in the copy docblock
1 parent 9ecdb19 commit bd71b45

2 files changed

Lines changed: 128 additions & 3 deletions

File tree

src/Illuminate/Filesystem/ReadThroughFilesystemAdapter.php

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use League\Flysystem\FilesystemAdapter;
88
use League\Flysystem\FilesystemException;
99
use League\Flysystem\FilesystemOperator;
10+
use League\Flysystem\UnableToCopyFile;
1011
use League\Flysystem\UnableToMoveFile;
1112
use League\Flysystem\UnableToReadFile;
1213

@@ -216,7 +217,7 @@ public function listContents(string $path, bool $deep): iterable
216217
*/
217218
public function move(string $source, string $destination, Config $config): void
218219
{
219-
$this->primary->move($source, $destination, $config->toArray());
220+
$this->moveOnPrimary($source, $destination, $config);
220221

221222
try {
222223
if ($this->fallback->fileExists($source)) {
@@ -228,11 +229,55 @@ public function move(string $source, string $destination, Config $config): void
228229
}
229230

230231
/**
231-
* Copy a file on the primary filesystem.
232+
* Copy a file on the primary filesystem, reading it from the fallback filesystem when necessary.
232233
*/
233234
public function copy(string $source, string $destination, Config $config): void
234235
{
235-
$this->primary->copy($source, $destination, $config->toArray());
236+
if ($this->primary->fileExists($source)) {
237+
$this->primary->copy($source, $destination, $config->toArray());
238+
239+
return;
240+
}
241+
242+
try {
243+
$this->copyFromFallback($source, $destination, $config);
244+
} catch (FilesystemException $exception) {
245+
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
246+
}
247+
}
248+
249+
/**
250+
* Move a file to its destination on the primary filesystem.
251+
*/
252+
protected function moveOnPrimary(string $source, string $destination, Config $config): void
253+
{
254+
if ($this->primary->fileExists($source)) {
255+
$this->primary->move($source, $destination, $config->toArray());
256+
257+
return;
258+
}
259+
260+
try {
261+
$this->copyFromFallback($source, $destination, $config);
262+
} catch (FilesystemException $exception) {
263+
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
264+
}
265+
}
266+
267+
/**
268+
* Copy a file from the fallback filesystem to the primary filesystem.
269+
*/
270+
protected function copyFromFallback(string $source, string $destination, Config $config): void
271+
{
272+
$stream = $this->fallback->readStream($source);
273+
274+
try {
275+
$this->primary->writeStream($destination, $stream, $config->toArray());
276+
} finally {
277+
if (is_resource($stream)) {
278+
fclose($stream);
279+
}
280+
}
236281
}
237282

238283
/**

tests/Filesystem/FilesystemManagerTest.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,86 @@ public function testReadThroughDisksDoNotResurrectMovedFiles()
347347
$this->assertTrue($readThrough->missing('source.txt'));
348348
}
349349

350+
public function testReadThroughDisksMoveFilesThatOnlyExistOnTheFallbackDisk()
351+
{
352+
$filesystem = $this->readThroughFilesystemManager();
353+
$primary = $filesystem->disk('primary');
354+
$fallback = $filesystem->disk('fallback');
355+
$readThrough = $filesystem->disk('read-through');
356+
357+
$fallback->put('source.txt', 'contents');
358+
359+
$this->assertTrue($readThrough->move('source.txt', 'destination.txt'));
360+
$this->assertSame('contents', $primary->get('destination.txt'));
361+
$this->assertTrue($fallback->missing('source.txt'));
362+
$this->assertTrue($readThrough->missing('source.txt'));
363+
}
364+
365+
public function testReadThroughDisksCopyFilesThatOnlyExistOnTheFallbackDisk()
366+
{
367+
$filesystem = $this->readThroughFilesystemManager();
368+
$primary = $filesystem->disk('primary');
369+
$fallback = $filesystem->disk('fallback');
370+
$readThrough = $filesystem->disk('read-through');
371+
372+
$fallback->put('source.txt', 'contents');
373+
374+
$this->assertTrue($readThrough->copy('source.txt', 'destination.txt'));
375+
$this->assertSame('contents', $primary->get('destination.txt'));
376+
$this->assertSame('contents', $fallback->get('source.txt'));
377+
$this->assertSame('contents', $readThrough->get('source.txt'));
378+
}
379+
380+
public function testReadThroughDisksCopyFromTheFallbackDiskWithoutPromotingTheSourceWhenDisabled()
381+
{
382+
$filesystem = $this->readThroughFilesystemManager([
383+
'copy' => false,
384+
]);
385+
$primary = $filesystem->disk('primary');
386+
$fallback = $filesystem->disk('fallback');
387+
$readThrough = $filesystem->disk('read-through');
388+
389+
$fallback->put('source.txt', 'contents');
390+
391+
$this->assertTrue($readThrough->copy('source.txt', 'destination.txt'));
392+
$this->assertSame('contents', $primary->get('destination.txt'));
393+
$this->assertTrue($primary->missing('source.txt'));
394+
$this->assertSame('contents', $fallback->get('source.txt'));
395+
396+
$this->assertTrue($readThrough->move('source.txt', 'moved.txt'));
397+
$this->assertSame('contents', $primary->get('moved.txt'));
398+
$this->assertTrue($primary->missing('source.txt'));
399+
$this->assertTrue($fallback->missing('source.txt'));
400+
}
401+
402+
public function testReadThroughDisksFailToMoveOrCopyMissingFiles()
403+
{
404+
$filesystem = $this->readThroughFilesystemManager();
405+
$readThrough = $filesystem->disk('read-through');
406+
407+
$this->assertFalse($readThrough->move('missing.txt', 'destination.txt'));
408+
$this->assertFalse($readThrough->copy('missing.txt', 'destination.txt'));
409+
}
410+
411+
public function testReadThroughDisksFailToMoveOrCopyWhenThePrimaryDiskIsUnwritable()
412+
{
413+
$filesystem = $this->readThroughFilesystemManager([
414+
'primary' => [
415+
'driver' => 'local',
416+
'root' => $this->temporaryDirectory('primary'),
417+
'read-only' => true,
418+
],
419+
]);
420+
$fallback = $filesystem->disk('fallback');
421+
$readThrough = $filesystem->disk('read-through');
422+
423+
$fallback->put('source.txt', 'contents');
424+
425+
$this->assertFalse($readThrough->move('source.txt', 'destination.txt'));
426+
$this->assertFalse($readThrough->copy('source.txt', 'destination.txt'));
427+
$this->assertSame('contents', $fallback->get('source.txt'));
428+
}
429+
350430
public function testReadThroughDiskPromotionFailuresAreBestEffortByDefault()
351431
{
352432
$filesystem = $this->readThroughFilesystemManager([

0 commit comments

Comments
 (0)