Skip to content
Closed
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
15 changes: 15 additions & 0 deletions src/Storage/Device.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ abstract public function getPath(string $filename, string $prefix = null): strin
*/
abstract public function upload(string $source, string $path, int $chunk = 1, int $chunks = 1, array &$metadata = []): int;

/**
* Upload Directory.
*
* Upload a directory and all its contents to the desired destination in the selected disk.
* Returns the number of files uploaded successfully.
*
* @param string $source Source directory path
* @param string $path Destination path
* @param bool $recursive Whether to upload subdirectories recursively (default: true)
* @return int Number of files uploaded successfully
*
* @throws Exception
*/
abstract public function uploadDirectory(string $source, string $path, bool $recursive = true): int;

/**
* Upload Data.
*
Expand Down
60 changes: 60 additions & 0 deletions src/Storage/Device/Local.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,66 @@ public function upload(string $source, string $path, int $chunk = 1, int $chunks
return $chunksReceived;
}

/**
* Upload Directory.
*
* Upload a directory and all its contents to the desired destination in the selected disk.
* Returns the number of files uploaded successfully.
*
* @param string $source Source directory path
* @param string $path Destination path
* @param bool $recursive Whether to upload subdirectories recursively (default: true)
* @return int Number of files uploaded successfully
*
* @throws \Exception
*/
public function uploadDirectory(string $source, string $path, bool $recursive = true): int
{
if (! is_dir($source)) {
throw new Exception("Source path '{$source}' is not a directory");
}

$uploadedCount = 0;
$path = rtrim($path, DIRECTORY_SEPARATOR);
$source = rtrim($source, DIRECTORY_SEPARATOR);

$this->createDirectory($path);

$iterator = $recursive
? new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS))
: new \DirectoryIterator($source);

foreach ($iterator as $file) {
if (! $recursive && $file instanceof \DirectoryIterator && $file->isDot()) {
continue;
}

if ($file->isFile()) {
try {
$relativePath = $recursive
? substr($file->getPathname(), strlen($source) + 1)
: $file->getFilename();

$destinationPath = $path.DIRECTORY_SEPARATOR.$relativePath;

$this->createDirectory(dirname($destinationPath));

if (copy($file->getPathname(), $destinationPath)) {
$uploadedCount++;
} else {
$error = error_get_last();
$errorMessage = isset($error['message']) ? $error['message'] : 'Unknown error';
error_log("Failed to upload file {$file->getPathname()}: ".$errorMessage);
}
} catch (Exception $e) {
error_log("Failed to upload file {$file->getPathname()}: ".$e->getMessage());
}
}
}

return $uploadedCount;
}

/**
* Upload Data.
*
Expand Down
54 changes: 54 additions & 0 deletions src/Storage/Device/S3.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,60 @@ public function upload(string $source, string $path, int $chunk = 1, int $chunks
return $this->uploadData(\file_get_contents($source), $path, \mime_content_type($source), $chunk, $chunks, $metadata);
}

/**
* Upload Directory.
*
* Upload a directory and all its contents to the desired destination in the selected disk.
* Returns the number of files uploaded successfully.
*
* @param string $source Source directory path
* @param string $path Destination path in S3
* @param bool $recursive Whether to upload subdirectories recursively (default: true)
* @return int Number of files uploaded successfully
*
* @throws \Exception
*/
public function uploadDirectory(string $source, string $path, bool $recursive = true): int
{
if (! is_dir($source)) {
throw new Exception("Source path '{$source}' is not a directory");
}

$uploadedCount = 0;
$path = rtrim($path, '/');
$source = rtrim($source, DIRECTORY_SEPARATOR);

$iterator = $recursive
? new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS))
: new \DirectoryIterator($source);

foreach ($iterator as $file) {
if (! $recursive && $file instanceof \DirectoryIterator && $file->isDot()) {
continue;
}

if ($file->isFile()) {
try {
$relativePath = $recursive
? substr($file->getPathname(), strlen($source) + 1)
: $file->getFilename();

$relativePath = str_replace(DIRECTORY_SEPARATOR, '/', $relativePath);
$destinationPath = $this->getPath($path.'/'.$relativePath);

$chunksUploaded = $this->upload($file->getPathname(), $destinationPath);
if ($chunksUploaded > 0) {
$uploadedCount++;
}
} catch (Exception $e) {
error_log("Failed to upload file {$file->getPathname()}: ".$e->getMessage());
}
}
}

return $uploadedCount;
}

/**
* Upload Data.
*
Expand Down
5 changes: 5 additions & 0 deletions src/Storage/Device/Telemetry.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ public function upload(string $source, string $path, int $chunk = 1, int $chunks
return $this->measure(__FUNCTION__, $source, $path, $chunk, $chunks, $metadata);
}

public function uploadDirectory(string $source, string $path, bool $recursive = true): int
{
return $this->measure(__FUNCTION__, $source, $path, $recursive);
}

public function uploadData(string $data, string $path, string $contentType, int $chunk = 1, int $chunks = 1, array &$metadata = []): int
{
return $this->measure(__FUNCTION__, $data, $path, $contentType, $chunk, $chunks, $metadata);
Expand Down
16 changes: 16 additions & 0 deletions tests/Storage/Device/LocalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,20 @@ public function testNestedDeletePath()
$this->assertTrue($this->object->deletePath('nested-delete-path-test'));
$this->assertFalse($this->object->exists($dir));
}

public function testUploadDirectory()
{
$sourceDir = __DIR__.'/../../resources/disk-b';
$destDir = $this->object->getPath('uploaded-directory');

$uploadedCount = $this->object->uploadDirectory($sourceDir, $destDir, true);

$this->assertGreaterThan(0, $uploadedCount);

$this->assertTrue($this->object->exists($destDir.DIRECTORY_SEPARATOR.'appwrite.svg'));
$this->assertTrue($this->object->exists($destDir.DIRECTORY_SEPARATOR.'kitten-1.png'));
$this->assertTrue($this->object->exists($destDir.DIRECTORY_SEPARATOR.'kitten-2.png'));

$this->object->deletePath('uploaded-directory');
}
}
21 changes: 21 additions & 0 deletions tests/Storage/S3Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ abstract protected function init(): void;
*/
abstract protected function getAdapterName(): string;

/**
* @return string
*/
abstract protected function getAdapterType(): string;

/**
* @return string
*/
Expand Down Expand Up @@ -410,4 +415,20 @@ public function testTransferSmall()
$this->object->delete($path);
$device->delete($destination);
}

public function testUploadDirectory()
{
$sourceDir = __DIR__.'/../resources/disk-a';
$destDir = 'uploaded-directory';

$uploadedCount = $this->object->uploadDirectory($sourceDir, $destDir, true);

$this->assertGreaterThan(0, $uploadedCount);

$this->assertTrue($this->object->exists($this->object->getPath($destDir.'/config.xml')));
$this->assertTrue($this->object->exists($this->object->getPath($destDir.'/kitten-1.jpg')));
$this->assertTrue($this->object->exists($this->object->getPath($destDir.'/lorem.txt')));

$this->object->deletePath($destDir);
}
}