aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@users.noreply.github.com>2025-01-16 19:31:14 +0100
committerGitHub <noreply@github.com>2025-01-16 19:31:14 +0100
commit35db02c4f92124acbe96bb76d37f0595f87bc924 (patch)
tree62c8a1ef407e80881e0405ddfae98514b150dd78
parentd9a7124592beaab371d7c39e792f3b3e8d0dd6fa (diff)
parent19ce3628965f73f21beac3fc9ee3757e091313c4 (diff)
downloadnextcloud-server-35db02c4f92124acbe96bb76d37f0595f87bc924.tar.gz
nextcloud-server-35db02c4f92124acbe96bb76d37f0595f87bc924.zip
Merge pull request #50208 from nextcloud/feat/conversion-adjusting
fix(files): conversion api simplification and conflict check
-rw-r--r--.github/workflows/integration-sqlite.yml1
-rw-r--r--apps/files/lib/Capabilities.php10
-rw-r--r--apps/files/openapi.json27
-rw-r--r--apps/testing/lib/Conversion/ConversionProvider.php19
-rw-r--r--build/integration/config/behat.yml36
-rw-r--r--build/integration/data/clouds.jpgbin0 -> 538205 bytes
-rw-r--r--build/integration/data/clouds.jpg.license2
-rw-r--r--build/integration/features/bootstrap/BasicStructure.php3
-rw-r--r--build/integration/features/bootstrap/ConversionsContext.php59
-rw-r--r--build/integration/file_conversions/file_conversions.feature101
-rw-r--r--lib/composer/composer/autoload_classmap.php2
-rw-r--r--lib/composer/composer/autoload_psr4.php1
-rw-r--r--lib/composer/composer/autoload_static.php10
-rw-r--r--lib/private/Files/Conversion/ConversionManager.php97
-rw-r--r--lib/public/Files/Conversion/ConversionMimeProvider.php66
-rw-r--r--lib/public/Files/Conversion/ConversionMimeTuple.php44
-rw-r--r--lib/public/Files/Conversion/IConversionManager.php4
-rw-r--r--lib/public/Files/Conversion/IConversionProvider.php2
18 files changed, 358 insertions, 126 deletions
diff --git a/.github/workflows/integration-sqlite.yml b/.github/workflows/integration-sqlite.yml
index b16e64b21da..d19e45600ae 100644
--- a/.github/workflows/integration-sqlite.yml
+++ b/.github/workflows/integration-sqlite.yml
@@ -61,6 +61,7 @@ jobs:
- 'federation_features'
- '--tags ~@large files_features'
- 'filesdrop_features'
+ - 'file_conversions'
- 'openldap_features'
- 'openldap_numerical_features'
- 'ldap_features'
diff --git a/apps/files/lib/Capabilities.php b/apps/files/lib/Capabilities.php
index 88efb4fcaf0..6b50e5807a5 100644
--- a/apps/files/lib/Capabilities.php
+++ b/apps/files/lib/Capabilities.php
@@ -10,7 +10,7 @@ namespace OCA\Files;
use OC\Files\FilenameValidator;
use OCA\Files\Service\ChunkedUploadConfig;
use OCP\Capabilities\ICapability;
-use OCP\Files\Conversion\ConversionMimeTuple;
+use OCP\Files\Conversion\ConversionMimeProvider;
use OCP\Files\Conversion\IConversionManager;
class Capabilities implements ICapability {
@@ -24,7 +24,7 @@ class Capabilities implements ICapability {
/**
* Return this classes capabilities
*
- * @return array{files: array{'$comment': ?string, bigfilechunking: bool, blacklisted_files: list<mixed>, forbidden_filenames: list<string>, forbidden_filename_basenames: list<string>, forbidden_filename_characters: list<string>, forbidden_filename_extensions: list<string>, chunked_upload: array{max_size: int, max_parallel_count: int}, file_conversions: list<array{from: string, to: list<array{mime: string, name: string}>}>}}
+ * @return array{files: array{'$comment': ?string, bigfilechunking: bool, blacklisted_files: list<mixed>, forbidden_filenames: list<string>, forbidden_filename_basenames: list<string>, forbidden_filename_characters: list<string>, forbidden_filename_extensions: list<string>, chunked_upload: array{max_size: int, max_parallel_count: int}, file_conversions: list<array{from: string, to: string, extension: string, displayName: string}>}}
*/
public function getCapabilities(): array {
return [
@@ -42,9 +42,9 @@ class Capabilities implements ICapability {
'max_parallel_count' => ChunkedUploadConfig::getMaxParallelCount(),
],
- 'file_conversions' => array_map(function (ConversionMimeTuple $mimeTuple) {
- return $mimeTuple->jsonSerialize();
- }, $this->fileConversionManager->getMimeTypes()),
+ 'file_conversions' => array_map(function (ConversionMimeProvider $mimeProvider) {
+ return $mimeProvider->jsonSerialize();
+ }, $this->fileConversionManager->getProviders()),
],
];
}
diff --git a/apps/files/openapi.json b/apps/files/openapi.json
index 015d4f42648..ddf3535c55f 100644
--- a/apps/files/openapi.json
+++ b/apps/files/openapi.json
@@ -101,29 +101,22 @@
"type": "object",
"required": [
"from",
- "to"
+ "to",
+ "extension",
+ "displayName"
],
"properties": {
"from": {
"type": "string"
},
"to": {
- "type": "array",
- "items": {
- "type": "object",
- "required": [
- "mime",
- "name"
- ],
- "properties": {
- "mime": {
- "type": "string"
- },
- "name": {
- "type": "string"
- }
- }
- }
+ "type": "string"
+ },
+ "extension": {
+ "type": "string"
+ },
+ "displayName": {
+ "type": "string"
}
}
}
diff --git a/apps/testing/lib/Conversion/ConversionProvider.php b/apps/testing/lib/Conversion/ConversionProvider.php
index 15e468b0756..b8d93428694 100644
--- a/apps/testing/lib/Conversion/ConversionProvider.php
+++ b/apps/testing/lib/Conversion/ConversionProvider.php
@@ -9,7 +9,7 @@ declare(strict_types=1);
namespace OCA\Testing\Conversion;
-use OCP\Files\Conversion\ConversionMimeTuple;
+use OCP\Files\Conversion\ConversionMimeProvider;
use OCP\Files\Conversion\IConversionProvider;
use OCP\Files\File;
use OCP\IL10N;
@@ -22,19 +22,26 @@ class ConversionProvider implements IConversionProvider {
public function getSupportedMimeTypes(): array {
return [
- new ConversionMimeTuple('image/jpeg', [
- ['mime' => 'image/png', 'name' => $this->l10n->t('Image (.png)')],
- ])
+ new ConversionMimeProvider('image/jpeg', 'image/png', 'png', $this->l10n->t('Image (.png)')),
+ new ConversionMimeProvider('image/jpeg', 'image/gif', 'gif', $this->l10n->t('Image (.gif)')),
];
}
public function convertFile(File $file, string $targetMimeType): mixed {
$image = imagecreatefromstring($file->getContent());
-
imagepalettetotruecolor($image);
+ // Start output buffering
ob_start();
- imagepng($image);
+
+ // Convert the image to the target format
+ if ($targetMimeType === 'image/gif') {
+ imagegif($image);
+ } else {
+ imagepng($image);
+ }
+
+ // End and return the output buffer
return ob_get_clean();
}
}
diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml
index 21e0cdb3309..47f8f3c827c 100644
--- a/build/integration/config/behat.yml
+++ b/build/integration/config/behat.yml
@@ -16,7 +16,7 @@ default:
- "%paths.base%/../features"
contexts:
- FeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -39,7 +39,7 @@ default:
- "%paths.base%/../comments_features"
contexts:
- FeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -62,7 +62,7 @@ default:
- "%paths.base%/../dav_features"
contexts:
- DavFeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -85,7 +85,7 @@ default:
- "%paths.base%/../federation_features"
contexts:
- FederationContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -95,7 +95,7 @@ default:
- "%paths.base%/../files_features"
contexts:
- FeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -113,12 +113,22 @@ default:
- CommandLineContext:
baseUrl: http://localhost:8080
ocPath: ../../
+ files_conversion:
+ paths:
+ - "%paths.base%/../file_conversions"
+ contexts:
+ - ConversionsContext:
+ baseUrl: http://localhost:8080
+ admin:
+ - admin
+ - admin
+ regular_user_password: 123456
capabilities:
paths:
- "%paths.base%/../capabilities_features"
contexts:
- CapabilitiesContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -128,7 +138,7 @@ default:
- "%paths.base%/../collaboration_features"
contexts:
- CollaborationContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -138,7 +148,7 @@ default:
- "%paths.base%/../sharees_features"
contexts:
- ShareesContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -148,7 +158,7 @@ default:
- "%paths.base%/../sharing_features"
contexts:
- SharingContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -159,7 +169,7 @@ default:
- "%paths.base%/../videoverification_features"
contexts:
- SharingContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -170,7 +180,7 @@ default:
- "%paths.base%/../setup_features"
contexts:
- SetupContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
@@ -220,10 +230,10 @@ default:
- "%paths.base%/../remoteapi_features"
contexts:
- FeatureContext:
- baseUrl: http://localhost:8080/ocs/
+ baseUrl: http://localhost:8080/ocs/
admin:
- admin
- admin
regular_user_password: 123456
- RemoteContext:
- remote: http://localhost:8080
+ remote: http://localhost:8080
diff --git a/build/integration/data/clouds.jpg b/build/integration/data/clouds.jpg
new file mode 100644
index 00000000000..2433b140766
--- /dev/null
+++ b/build/integration/data/clouds.jpg
Binary files differ
diff --git a/build/integration/data/clouds.jpg.license b/build/integration/data/clouds.jpg.license
new file mode 100644
index 00000000000..d7c54c39d02
--- /dev/null
+++ b/build/integration/data/clouds.jpg.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2019 CHUTTERSNAP <https://unsplash.com/@chuttersnap> <https://unsplash.com/photos/blue-clouds-under-white-sky-9AqIdzEc9pY>"
+SPDX-License-Identifier: LicenseRef-Unsplash
diff --git a/build/integration/features/bootstrap/BasicStructure.php b/build/integration/features/bootstrap/BasicStructure.php
index b51c0a6a34c..60926e65f5c 100644
--- a/build/integration/features/bootstrap/BasicStructure.php
+++ b/build/integration/features/bootstrap/BasicStructure.php
@@ -8,6 +8,7 @@ use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ClientException;
+use GuzzleHttp\Exception\ServerException;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
@@ -170,6 +171,8 @@ trait BasicStructure {
$this->response = $client->request($verb, $fullUrl, $options);
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
+ } catch (ServerException $ex) {
+ $this->response = $ex->getResponse();
}
}
diff --git a/build/integration/features/bootstrap/ConversionsContext.php b/build/integration/features/bootstrap/ConversionsContext.php
new file mode 100644
index 00000000000..099a2263630
--- /dev/null
+++ b/build/integration/features/bootstrap/ConversionsContext.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+require __DIR__ . '/../../vendor/autoload.php';
+
+use Behat\Behat\Context\Context;
+use Behat\Behat\Context\SnippetAcceptingContext;
+use Behat\Gherkin\Node\TableNode;
+
+class ConversionsContext implements Context, SnippetAcceptingContext {
+ use AppConfiguration;
+ use BasicStructure;
+ use WebDav;
+
+ /** @BeforeScenario */
+ public function setUpScenario() {
+ $this->asAn('admin');
+ $this->setStatusTestingApp(true);
+ }
+
+ /** @AfterScenario */
+ public function tearDownScenario() {
+ $this->asAn('admin');
+ $this->setStatusTestingApp(false);
+ }
+
+ protected function resetAppConfigs() {
+ }
+
+ /**
+ * @When /^user "([^"]*)" converts file "([^"]*)" to "([^"]*)"$/
+ */
+ public function userConvertsTheSavedFileId(string $user, string $path, string $mime) {
+ $this->userConvertsTheSavedFileIdTo($user, $path, $mime, null);
+ }
+
+ /**
+ * @When /^user "([^"]*)" converts file "([^"]*)" to "([^"]*)" and saves it to "([^"]*)"$/
+ */
+ public function userConvertsTheSavedFileIdTo(string $user, string $path, string $mime, ?string $destination) {
+ try {
+ $fileId = $this->getFileIdForPath($user, $path);
+ } catch (Exception $e) {
+ // return a fake value to keep going and be able to test the error
+ $fileId = 0;
+ }
+
+ $data = [['fileId', $fileId], ['targetMimeType', $mime]];
+ if ($destination !== null) {
+ $data[] = ['destination', $destination];
+ }
+
+ $this->asAn($user);
+ $this->sendingToWith('post', '/apps/files/api/v1/convert', new TableNode($data));
+ }
+}
diff --git a/build/integration/file_conversions/file_conversions.feature b/build/integration/file_conversions/file_conversions.feature
new file mode 100644
index 00000000000..f56dca0ebb4
--- /dev/null
+++ b/build/integration/file_conversions/file_conversions.feature
@@ -0,0 +1,101 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+Feature: conversions
+ Background:
+ Given using api version "2"
+ Given using new dav path
+ Given user "user0" exists
+
+ Scenario: Converting a file works
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ Then as "user0" the file "/image.jpg" exists
+ When user "user0" converts file "/image.jpg" to "image/png"
+ Then the HTTP status code should be "201"
+ Then the OCS status code should be "201"
+ Then as "user0" the file "/image.png" exists
+
+ Scenario: Converting a file to a given path works
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ And User "user0" created a folder "/folder"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the folder "/folder" exists
+ When user "user0" converts file "/image.jpg" to "image/png" and saves it to "/folder/image.png"
+ Then the HTTP status code should be "201"
+ Then the OCS status code should be "201"
+ Then as "user0" the file "/folder/image.png" exists
+ Then as "user0" the file "/image.png" does not exist
+
+ Scenario: Converting a file path with overwrite
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ And user "user0" uploads file "data/green-square-256.png" to "/image.png"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the file "/image.png" exists
+ When user "user0" converts file "/image.jpg" to "image/png"
+ Then the HTTP status code should be "201"
+ Then the OCS status code should be "201"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the file "/image.png" exists
+ Then as "user0" the file "/image (2).png" exists
+
+ Scenario: Converting a file path with overwrite to a given path
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ And User "user0" created a folder "/folder"
+ And user "user0" uploads file "data/green-square-256.png" to "/folder/image.png"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the folder "/folder" exists
+ Then as "user0" the file "/folder/image.png" exists
+ When user "user0" converts file "/image.jpg" to "image/png" and saves it to "/folder/image.png"
+ Then the HTTP status code should be "201"
+ Then the OCS status code should be "201"
+ Then as "user0" the file "/folder/image.png" exists
+ Then as "user0" the file "/folder/image (2).png" exists
+ Then as "user0" the file "/image.png" does not exist
+ Then as "user0" the file "/image.jpg" exists
+
+ Scenario: Converting a file which does not exist fails
+ When user "user0" converts file "/image.jpg" to "image/png"
+ Then the HTTP status code should be "404"
+ Then the OCS status code should be "404"
+ Then as "user0" the file "/image.jpg" does not exist
+ Then as "user0" the file "/image.png" does not exist
+
+ Scenario: Converting a file to an invalid destination path fails
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ When user "user0" converts file "/image.jpg" to "image/png" and saves it to "/folder/image.png"
+ Then the HTTP status code should be "404"
+ Then the OCS status code should be "404"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the file "/folder/image.png" does not exist
+
+ Scenario: Converting a file to an invalid format fails
+ Given user "user0" uploads file "data/clouds.jpg" to "/image.jpg"
+ When user "user0" converts file "/image.jpg" to "image/invalid"
+ Then the HTTP status code should be "500"
+ Then the OCS status code should be "999"
+ Then as "user0" the file "/image.jpg" exists
+ Then as "user0" the file "/image.png" does not exist
+
+ Scenario: Forbid conversion to a destination without create permission
+ Given user "user1" exists
+ # Share the folder with user1
+ Given User "user0" created a folder "/folder"
+ Then As an "user0"
+ When creating a share with
+ | path | folder |
+ | shareWith | user1 |
+ | shareType | 0 |
+ | permissions | 1 |
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ # Create the folder, upload the image
+ Then As an "user1"
+ Given user "user1" accepts last share
+ Given as "user1" the folder "/folder" exists
+ Given user "user1" uploads file "data/clouds.jpg" to "/image.jpg"
+ Then as "user1" the file "/image.jpg" exists
+ # Try to convert the image to a folder where user1 has no create permission
+ When user "user1" converts file "/image.jpg" to "image/png" and saves it to "/folder/folder.png"
+ Then the OCS status code should be "403"
+ And the HTTP status code should be "403"
+ Then as "user1" the file "/folder/folder.png" does not exist
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index dab71032ccc..d20e3f9f501 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -377,7 +377,7 @@ return array(
'OCP\\Files\\Config\\IRootMountProvider' => $baseDir . '/lib/public/Files/Config/IRootMountProvider.php',
'OCP\\Files\\Config\\IUserMountCache' => $baseDir . '/lib/public/Files/Config/IUserMountCache.php',
'OCP\\Files\\ConnectionLostException' => $baseDir . '/lib/public/Files/ConnectionLostException.php',
- 'OCP\\Files\\Conversion\\ConversionMimeTuple' => $baseDir . '/lib/public/Files/Conversion/ConversionMimeTuple.php',
+ 'OCP\\Files\\Conversion\\ConversionMimeProvider' => $baseDir . '/lib/public/Files/Conversion/ConversionMimeProvider.php',
'OCP\\Files\\Conversion\\IConversionManager' => $baseDir . '/lib/public/Files/Conversion/IConversionManager.php',
'OCP\\Files\\Conversion\\IConversionProvider' => $baseDir . '/lib/public/Files/Conversion/IConversionProvider.php',
'OCP\\Files\\DavUtil' => $baseDir . '/lib/public/Files/DavUtil.php',
diff --git a/lib/composer/composer/autoload_psr4.php b/lib/composer/composer/autoload_psr4.php
index b07b2c0074b..09892528d3a 100644
--- a/lib/composer/composer/autoload_psr4.php
+++ b/lib/composer/composer/autoload_psr4.php
@@ -10,5 +10,6 @@ return array(
'OC\\' => array($baseDir . '/lib/private'),
'OCP\\' => array($baseDir . '/lib/public'),
'NCU\\' => array($baseDir . '/lib/unstable'),
+ 'Bamarni\\Composer\\Bin\\' => array($vendorDir . '/bamarni/composer-bin-plugin/src'),
'' => array($baseDir . '/lib/private/legacy'),
);
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 00d3b2c2b5a..ead08210def 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -21,6 +21,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
array (
'NCU\\' => 4,
),
+ 'B' =>
+ array (
+ 'Bamarni\\Composer\\Bin\\' => 21,
+ ),
);
public static $prefixDirsPsr4 = array (
@@ -40,6 +44,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
array (
0 => __DIR__ . '/../../..' . '/lib/unstable',
),
+ 'Bamarni\\Composer\\Bin\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src',
+ ),
);
public static $fallbackDirsPsr4 = array (
@@ -418,7 +426,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Files\\Config\\IRootMountProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IRootMountProvider.php',
'OCP\\Files\\Config\\IUserMountCache' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IUserMountCache.php',
'OCP\\Files\\ConnectionLostException' => __DIR__ . '/../../..' . '/lib/public/Files/ConnectionLostException.php',
- 'OCP\\Files\\Conversion\\ConversionMimeTuple' => __DIR__ . '/../../..' . '/lib/public/Files/Conversion/ConversionMimeTuple.php',
+ 'OCP\\Files\\Conversion\\ConversionMimeProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Conversion/ConversionMimeProvider.php',
'OCP\\Files\\Conversion\\IConversionManager' => __DIR__ . '/../../..' . '/lib/public/Files/Conversion/IConversionManager.php',
'OCP\\Files\\Conversion\\IConversionProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Conversion/IConversionProvider.php',
'OCP\\Files\\DavUtil' => __DIR__ . '/../../..' . '/lib/public/Files/DavUtil.php',
diff --git a/lib/private/Files/Conversion/ConversionManager.php b/lib/private/Files/Conversion/ConversionManager.php
index 37fc9a6c754..db49f7afed9 100644
--- a/lib/private/Files/Conversion/ConversionManager.php
+++ b/lib/private/Files/Conversion/ConversionManager.php
@@ -11,7 +11,7 @@ namespace OC\Files\Conversion;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\SystemConfig;
-use OCP\Files\Conversion\ConversionMimeTuple;
+use OCP\Files\Conversion\ConversionMimeProvider;
use OCP\Files\Conversion\IConversionManager;
use OCP\Files\Conversion\IConversionProvider;
use OCP\Files\File;
@@ -32,10 +32,10 @@ class ConversionManager implements IConversionManager {
'richdocuments',
];
- /** @var IConversionProvider[] */
+ /** @var list<IConversionProvider> */
private array $preferredProviders = [];
- /** @var IConversionProvider[] */
+ /** @var list<IConversionProvider> */
private array $providers = [];
public function __construct(
@@ -53,16 +53,28 @@ class ConversionManager implements IConversionManager {
return !empty($context->getFileConversionProviders());
}
- public function getMimeTypes(): array {
- $mimeTypes = [];
-
- foreach ($this->getProviders() as $provider) {
- $mimeTypes[] = $provider->getSupportedMimetypes();
+ public function getProviders(): array {
+ $providers = [];
+ foreach ($this->getRegisteredProviders() as $provider) {
+ $providers = array_merge($providers, $provider->getSupportedMimeTypes());
}
+ return $providers;
+ }
+
+ /**
+ * @param string $mime
+ * @return list<ConversionMimeProvider>
+ */
+ private function getProvidersForMime(string $mime): array {
+ $mimeTypes = $this->getProviders();
+ $filtered = array_filter(
+ $mimeTypes,
+ function (ConversionMimeProvider $mimeProvider) use ($mime) {
+ return $mimeProvider->getFrom() === $mime;
+ }
+ );
- /** @var list<ConversionMimeTuple> */
- $mimeTypes = array_merge(...$mimeTypes);
- return $mimeTypes;
+ return array_values($filtered);
}
public function convert(File $file, string $targetMimeType, ?string $destination = null): string {
@@ -80,24 +92,36 @@ class ConversionManager implements IConversionManager {
$fileMimeType = $file->getMimetype();
$validProvider = $this->getValidProvider($fileMimeType, $targetMimeType);
+ $targetExtension = '';
+ foreach ($this->getProvidersForMime($fileMimeType) as $mimeProvider) {
+ if ($mimeProvider->getTo() === $targetMimeType) {
+ $targetExtension = $mimeProvider->getExtension();
+ break;
+ }
+ }
+
if ($validProvider !== null) {
$convertedFile = $validProvider->convertFile($file, $targetMimeType);
- if ($destination !== null) {
- $convertedFile = $this->writeToDestination($destination, $convertedFile);
- return $convertedFile->getPath();
+ // If destination not provided, we use the same path
+ // as the original file, but with the new extension
+ if ($destination === null) {
+ $basename = pathinfo($file->getPath(), PATHINFO_FILENAME);
+ $parent = $file->getParent();
+ $destination = $parent->getFullPath($basename . '.' . $targetExtension);
}
- $tmp = $this->tempManager->getTemporaryFile();
- file_put_contents($tmp, $convertedFile);
-
- return $tmp;
+ $convertedFile = $this->writeToDestination($destination, $convertedFile);
+ return $convertedFile->getPath();
}
throw new RuntimeException('Could not convert file');
}
- public function getProviders(): array {
+ /**
+ * @return list<IConversionProvider>
+ */
+ private function getRegisteredProviders(): array {
if (count($this->providers) > 0) {
return $this->providers;
}
@@ -121,32 +145,33 @@ class ConversionManager implements IConversionManager {
}
}
- return array_merge([], $this->preferredProviders, $this->providers);
+ return array_values(array_merge([], $this->preferredProviders, $this->providers));
}
private function writeToDestination(string $destination, mixed $content): File {
+ if ($this->rootFolder->nodeExists($destination)) {
+ $file = $this->rootFolder->get($destination);
+ $parent = $file->getParent();
+ if (!$parent->isCreatable()) {
+ throw new GenericFileException('Destination is not creatable');
+ }
+
+ $newName = $parent->getNonExistingName(basename($destination));
+ $destination = $parent->getFullPath($newName);
+ }
+
return $this->rootFolder->newFile($destination, $content);
}
private function getValidProvider(string $fileMimeType, string $targetMimeType): ?IConversionProvider {
- $validProvider = null;
- foreach ($this->getProviders() as $provider) {
- $suitableMimeTypes = array_filter(
- $provider->getSupportedMimeTypes(),
- function (ConversionMimeTuple $mimeTuple) use ($fileMimeType, $targetMimeType) {
- ['from' => $from, 'to' => $to] = $mimeTuple->jsonSerialize();
-
- $supportsTargetMimeType = in_array($targetMimeType, array_column($to, 'mime'));
- return ($from === $fileMimeType) && $supportsTargetMimeType;
+ foreach ($this->getRegisteredProviders() as $provider) {
+ foreach ($provider->getSupportedMimeTypes() as $mimeProvider) {
+ if ($mimeProvider->getFrom() === $fileMimeType && $mimeProvider->getTo() === $targetMimeType) {
+ return $provider;
}
- );
-
- if (!empty($suitableMimeTypes)) {
- $validProvider = $provider;
- break;
}
}
-
- return $validProvider;
+
+ return null;
}
}
diff --git a/lib/public/Files/Conversion/ConversionMimeProvider.php b/lib/public/Files/Conversion/ConversionMimeProvider.php
new file mode 100644
index 00000000000..0daf4a10648
--- /dev/null
+++ b/lib/public/Files/Conversion/ConversionMimeProvider.php
@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\Files\Conversion;
+
+use JsonSerializable;
+
+/**
+ * A tuple-like object representing both an original and target
+ * MIME type for a file conversion
+ *
+ * @since 31.0.0
+ */
+class ConversionMimeProvider implements JsonSerializable {
+ /**
+ * @param string $from The source MIME type of a file
+ * @param string $to The target MIME type for the file
+ * @param string $extension The file extension for the target MIME type (e.g. 'png')
+ * @param string $displayName The human-readable name of the target MIME type (e.g. 'Image (.png)')
+ *
+ * @since 31.0.0
+ */
+ public function __construct(
+ private string $from,
+ private string $to,
+ private string $extension,
+ private string $displayName,
+ ) {
+ }
+
+ public function getFrom(): string {
+ return $this->from;
+ }
+
+ public function getTo(): string {
+ return $this->to;
+ }
+
+ public function getExtension(): string {
+ return $this->extension;
+ }
+
+ public function getDisplayName(): string {
+ return $this->displayName;
+ }
+
+ /**
+ * @return array{from: string, to: string, extension: string, displayName: string}
+ *
+ * @since 31.0.0
+ */
+ public function jsonSerialize(): array {
+ return [
+ 'from' => $this->from,
+ 'to' => $this->to,
+ 'extension' => $this->extension,
+ 'displayName' => $this->displayName,
+ ];
+ }
+}
diff --git a/lib/public/Files/Conversion/ConversionMimeTuple.php b/lib/public/Files/Conversion/ConversionMimeTuple.php
deleted file mode 100644
index 0180f3311f3..00000000000
--- a/lib/public/Files/Conversion/ConversionMimeTuple.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-namespace OCP\Files\Conversion;
-
-use JsonSerializable;
-
-/**
- * A tuple-like object representing both an original and target
- * MIME type for a file conversion
- *
- * @since 31.0.0
- */
-class ConversionMimeTuple implements JsonSerializable {
- /**
- * @param string $from The original MIME type of a file
- * @param list<array{mime: string, name: string}> $to The desired MIME type for the file mapped to its translated name
- *
- * @since 31.0.0
- */
- public function __construct(
- private string $from,
- private array $to,
- ) {
- }
-
- /**
- * @return array{from: string, to: list<array{mime: string, name: string}>}
- *
- * @since 31.0.0
- */
- public function jsonSerialize(): array {
- return [
- 'from' => $this->from,
- 'to' => $this->to,
- ];
- }
-}
diff --git a/lib/public/Files/Conversion/IConversionManager.php b/lib/public/Files/Conversion/IConversionManager.php
index 59ff580fdf1..ed418129d3b 100644
--- a/lib/public/Files/Conversion/IConversionManager.php
+++ b/lib/public/Files/Conversion/IConversionManager.php
@@ -25,11 +25,11 @@ interface IConversionManager {
/**
* Gets all supported MIME type conversions
*
- * @return list<ConversionMimeTuple>
+ * @return list<ConversionMimeProvider>
*
* @since 31.0.0
*/
- public function getMimeTypes(): array;
+ public function getProviders(): array;
/**
* Convert a file to a given MIME type
diff --git a/lib/public/Files/Conversion/IConversionProvider.php b/lib/public/Files/Conversion/IConversionProvider.php
index b0a09fc93a4..3b5c5945c99 100644
--- a/lib/public/Files/Conversion/IConversionProvider.php
+++ b/lib/public/Files/Conversion/IConversionProvider.php
@@ -21,7 +21,7 @@ interface IConversionProvider {
/**
* Get an array of MIME type tuples this conversion provider supports
*
- * @return list<ConversionMimeTuple>
+ * @return list<ConversionMimeProvider>
*
* @since 31.0.0
*/