mirror of
https://github.com/nextcloud/server.git
synced 2026-04-22 06:37:56 -04:00
Merge pull request #50208 from nextcloud/feat/conversion-adjusting
fix(files): conversion api simplification and conflict check
This commit is contained in:
commit
35db02c4f9
18 changed files with 358 additions and 126 deletions
1
.github/workflows/integration-sqlite.yml
vendored
1
.github/workflows/integration-sqlite.yml
vendored
|
|
@ -61,6 +61,7 @@ jobs:
|
|||
- 'federation_features'
|
||||
- '--tags ~@large files_features'
|
||||
- 'filesdrop_features'
|
||||
- 'file_conversions'
|
||||
- 'openldap_features'
|
||||
- 'openldap_numerical_features'
|
||||
- 'ldap_features'
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
BIN
build/integration/data/clouds.jpg
Normal file
BIN
build/integration/data/clouds.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 526 KiB |
2
build/integration/data/clouds.jpg.license
Normal file
2
build/integration/data/clouds.jpg.license
Normal file
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
59
build/integration/features/bootstrap/ConversionsContext.php
Normal file
59
build/integration/features/bootstrap/ConversionsContext.php
Normal file
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
101
build/integration/file_conversions/file_conversions.feature
Normal file
101
build/integration/file_conversions/file_conversions.feature
Normal file
|
|
@ -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
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/** @var list<ConversionMimeTuple> */
|
||||
$mimeTypes = array_merge(...$mimeTypes);
|
||||
return $mimeTypes;
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
66
lib/public/Files/Conversion/ConversionMimeProvider.php
Normal file
66
lib/public/Files/Conversion/ConversionMimeProvider.php
Normal file
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue