Skip to content

Commit

Permalink
Merge pull request #1721 from thephpleague/feature/copy-without-visib…
Browse files Browse the repository at this point in the history
…ility

Allow copy without retaining visibility for adapters and implementations that required fetching visibility for copy and move.
  • Loading branch information
frankdejonge authored Nov 6, 2023
2 parents 015633a + 88beda9 commit 702e2b4
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 33 deletions.
9 changes: 6 additions & 3 deletions src/AsyncAwsS3/AsyncAwsS3Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -315,14 +315,17 @@ public function move(string $source, string $destination, Config $config): void
public function copy(string $source, string $destination, Config $config): void
{
try {
/** @var string $visibility */
$visibility = $config->get(Config::OPTION_VISIBILITY) ?: $this->visibility($source)->visibility();
$visibility = $config->get(Config::OPTION_VISIBILITY);

if ($visibility === null && $config->get('retain_visibility', true)) {
$visibility = $this->visibility($source)->visibility();
}
} catch (Throwable $exception) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}

$arguments = [
'ACL' => $this->visibility->visibilityToAcl($visibility),
'ACL' => $this->visibility->visibilityToAcl($visibility ?: 'private'),
'Bucket' => $this->bucket,
'Key' => $this->prefixer->prefixPath($destination),
'CopySource' => $this->bucket . '/' . $this->prefixer->prefixPath($source),
Expand Down
9 changes: 6 additions & 3 deletions src/AwsS3V3/AwsS3V3Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,11 @@ public function move(string $source, string $destination, Config $config): void
public function copy(string $source, string $destination, Config $config): void
{
try {
/** @var string $visibility */
$visibility = $config->get(Config::OPTION_VISIBILITY) ?: $this->visibility($source)->visibility();
$visibility = $config->get(Config::OPTION_VISIBILITY);

if ($visibility === null && $config->get('retain_visibility', true)) {
$visibility = $this->visibility($source)->visibility();
}
} catch (Throwable $exception) {
throw UnableToCopyFile::fromLocationTo(
$source,
Expand All @@ -431,7 +434,7 @@ public function copy(string $source, string $destination, Config $config): void
$this->prefixer->prefixPath($source),
$this->bucket,
$this->prefixer->prefixPath($destination),
$this->visibility->visibilityToAcl($visibility),
$this->visibility->visibilityToAcl($visibility ?: 'private'),
$this->createOptionsFromConfig($config)['params']
);
} catch (Throwable $exception) {
Expand Down
10 changes: 10 additions & 0 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,14 @@ public function withDefaults(array $defaults): Config
{
return new Config($this->options + $defaults);
}

public function toArray(): array
{
return $this->options;
}

public function withSetting(string $property, mixed $setting): Config
{
return $this->extend([$property => $setting]);
}
}
16 changes: 12 additions & 4 deletions src/Ftp/FtpAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -547,16 +547,21 @@ public function move(string $source, string $destination, Config $config): void
$connection = $this->connection();

if ( ! @ftp_rename($connection, $sourceLocation, $destinationLocation)) {
throw UnableToMoveFile::fromLocationTo($source, $destination);
throw UnableToMoveFile::because(error_get_last()['message'] ?? 'reason unknown', $source, $destination);
}
}

public function copy(string $source, string $destination, Config $config): void
{
try {
$readStream = $this->readStream($source);
$visibility = $this->visibility($source)->visibility();
$this->writeStream($destination, $readStream, new Config(compact('visibility')));
$visibility = $config->get(Config::OPTION_VISIBILITY);

if ($visibility === null && $config->get('retain_visibility', true)) {
$config = $config->withSetting(Config::OPTION_VISIBILITY, $this->visibility($source)->visibility());
}

$this->writeStream($destination, $readStream, $config);
} catch (Throwable $exception) {
if (isset($readStream) && is_resource($readStream)) {
@fclose($readStream);
Expand Down Expand Up @@ -604,7 +609,10 @@ private function ensureDirectoryExists(string $dirname, ?string $visibility): vo
}

if ($mode !== false && @ftp_chmod($connection, $mode, $location) === false) {
throw UnableToCreateDirectory::atLocation($dirPath, 'unable to chmod the directory');
throw UnableToCreateDirectory::atLocation(
$dirPath,
'unable to chmod the directory: ' . (error_get_last()['message'] ?? 'reason unknown'),
);
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions src/GoogleCloudStorage/GoogleCloudStorageAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,17 @@ public function move(string $source, string $destination, Config $config): void
public function copy(string $source, string $destination, Config $config): void
{
try {
/** @var string $visibility */
$visibility = $this->visibility($source)->visibility();
$visibility = $config->get(Config::OPTION_VISIBILITY);

if ($visibility === null && $config->get('retain_visibility', true)) {
$visibility = $this->visibility($source)->visibility();
}

$prefixedSource = $this->prefixer->prefixPath($source);
$options = ['name' => $this->prefixer->prefixPath($destination)];
$predefinedAcl = $this->visibilityHandler->visibilityToPredefinedAcl($visibility);
$predefinedAcl = $this->visibilityHandler->visibilityToPredefinedAcl(
$visibility ?: PortableVisibilityHandler::NO_PREDEFINED_VISIBILITY
);

if ($predefinedAcl !== PortableVisibilityHandler::NO_PREDEFINED_VISIBILITY) {
$options['predefinedAcl'] = $predefinedAcl;
Expand Down
53 changes: 35 additions & 18 deletions src/MountManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@ class MountManager implements FilesystemOperator
*/
private $filesystems = [];

/**
* @var Config
*/
private $config;

/**
* MountManager constructor.
*
* @param array<string,FilesystemOperator> $filesystems
*/
public function __construct(array $filesystems = [])
public function __construct(array $filesystems = [], array $config = [])
{
$this->mountFilesystems($filesystems);
$this->config = new Config($config);
}

public function fileExists(string $location): bool
Expand Down Expand Up @@ -156,7 +162,7 @@ public function write(string $location, string $contents, array $config = []): v
[$filesystem, $path] = $this->determineFilesystemAndPath($location);

try {
$filesystem->write($path, $contents, $config);
$filesystem->write($path, $contents, $this->config->extend($config)->toArray());
} catch (UnableToWriteFile $exception) {
throw UnableToWriteFile::atLocation($location, $exception->reason(), $exception);
}
Expand All @@ -166,7 +172,7 @@ public function writeStream(string $location, $contents, array $config = []): vo
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
$filesystem->writeStream($path, $contents, $config);
$filesystem->writeStream($path, $contents, $this->config->extend($config)->toArray());
}

public function setVisibility(string $path, string $visibility): void
Expand Down Expand Up @@ -206,7 +212,7 @@ public function createDirectory(string $location, array $config = []): void
[$filesystem, $path] = $this->determineFilesystemAndPath($location);

try {
$filesystem->createDirectory($path, $config);
$filesystem->createDirectory($path, $this->config->extend($config)->toArray());
} catch (UnableToCreateDirectory $exception) {
throw UnableToCreateDirectory::dueToFailure($location, $exception);
}
Expand All @@ -224,7 +230,8 @@ public function move(string $source, string $destination, array $config = []): v
$sourcePath,
$destinationPath,
$source,
$destination
$destination,
$config,
) : $this->moveAcrossFilesystems($source, $destination, $config);
}

Expand All @@ -240,15 +247,16 @@ public function copy(string $source, string $destination, array $config = []): v
$sourcePath,
$destinationPath,
$source,
$destination
$destination,
$config,
) : $this->copyAcrossFilesystem(
$config['visibility'] ?? null,
$sourceFilesystem,
$sourcePath,
$destinationFilesystem,
$destinationPath,
$source,
$destination
$destination,
$config,
);
}

Expand All @@ -273,7 +281,7 @@ public function temporaryUrl(string $path, DateTimeInterface $expiresAt, array $
throw new UnableToGenerateTemporaryUrl(sprintf('%s does not support generating public urls.', $filesystem::class), $path);
}

return $filesystem->temporaryUrl($path, $expiresAt, $config);
return $filesystem->temporaryUrl($path, $expiresAt, $this->config->extend($config)->toArray());
}

public function checksum(string $path, array $config = []): string
Expand All @@ -285,7 +293,7 @@ public function checksum(string $path, array $config = []): string
throw new UnableToProvideChecksum(sprintf('%s does not support providing checksums.', $filesystem::class), $path);
}

return $filesystem->checksum($path, $config);
return $filesystem->checksum($path, $this->config->extend($config)->toArray());
}

private function mountFilesystems(array $filesystems): void
Expand Down Expand Up @@ -345,28 +353,36 @@ private function copyInSameFilesystem(
string $sourcePath,
string $destinationPath,
string $source,
string $destination
string $destination,
array $config,
): void {
try {
$sourceFilesystem->copy($sourcePath, $destinationPath);
$sourceFilesystem->copy($sourcePath, $destinationPath, $this->config->extend($config)->toArray());
} catch (UnableToCopyFile $exception) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}
}

private function copyAcrossFilesystem(
?string $visibility,
FilesystemOperator $sourceFilesystem,
string $sourcePath,
FilesystemOperator $destinationFilesystem,
string $destinationPath,
string $source,
string $destination
string $destination,
array $config,
): void {
$config = $this->config->extend($config);
$retainVisibility = (bool) $config->get('retain_visibility', true);
$visibility = $config->get('visibility');

try {
$visibility = $visibility ?? $sourceFilesystem->visibility($sourcePath);
if ($visibility == null && $retainVisibility) {
$visibility = $sourceFilesystem->visibility($sourcePath);
}

$stream = $sourceFilesystem->readStream($sourcePath);
$destinationFilesystem->writeStream($destinationPath, $stream, compact('visibility'));
$destinationFilesystem->writeStream($destinationPath, $stream, $visibility ? compact('visibility') : []);
} catch (UnableToRetrieveMetadata | UnableToReadFile | UnableToWriteFile $exception) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}
Expand All @@ -377,10 +393,11 @@ private function moveInTheSameFilesystem(
string $sourcePath,
string $destinationPath,
string $source,
string $destination
string $destination,
array $config,
): void {
try {
$sourceFilesystem->move($sourcePath, $destinationPath);
$sourceFilesystem->move($sourcePath, $destinationPath, $this->config->extend($config)->toArray());
} catch (UnableToMoveFile $exception) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
}
Expand Down
44 changes: 44 additions & 0 deletions src/MountManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,50 @@ protected function setUp(): void
]);
}

/**
* @test
*/
public function copying_without_retaining_visibility(): void
{
// arrange
$firstFilesystemAdapter = new InMemoryFilesystemAdapter();
$secondFilesystemAdapter = new InMemoryFilesystemAdapter();
$mountManager = new MountManager([
'first' => new Filesystem($firstFilesystemAdapter, ['visibility' => 'public']),
'second' => new Filesystem($secondFilesystemAdapter, ['visibility' => 'private']),
], ['retain_visibility' => false]);

// act
$mountManager->write('first://file.txt', 'contents');
$mountManager->copy('first://file.txt', 'second://file.txt');

// assert
$visibility = $mountManager->visibility('second://file.txt');
self::assertEquals('private', $visibility);
}

/**
* @test
*/
public function copying_while_retaining_visibility(): void
{
// arrange
$firstFilesystemAdapter = new InMemoryFilesystemAdapter();
$secondFilesystemAdapter = new InMemoryFilesystemAdapter();
$mountManager = new MountManager([
'first' => new Filesystem($firstFilesystemAdapter, ['visibility' => 'public']),
'second' => new Filesystem($secondFilesystemAdapter, ['visibility' => 'private']),
], ['retain_visibility' => true]);

// act
$mountManager->write('first://file.txt', 'contents');
$mountManager->copy('first://file.txt', 'second://file.txt');

// assert
$visibility = $mountManager->visibility('second://file.txt');
self::assertEquals('public', $visibility);
}

/**
* @test
*/
Expand Down
9 changes: 7 additions & 2 deletions src/PhpseclibV3/SftpAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,13 @@ public function copy(string $source, string $destination, Config $config): void
{
try {
$readStream = $this->readStream($source);
$visibility = $this->visibility($source)->visibility();
$this->writeStream($destination, $readStream, new Config(compact('visibility')));
$visibility = $config->get(Config::OPTION_VISIBILITY);

if ($visibility === null && $config->get('retain_visibility', true)) {
$config = $config->withSetting(Config::OPTION_VISIBILITY, $this->visibility($source)->visibility());
}

$this->writeStream($destination, $readStream, $config);
} catch (Throwable $exception) {
if (isset($readStream) && is_resource($readStream)) {
@fclose($readStream);
Expand Down

0 comments on commit 702e2b4

Please sign in to comment.