mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
feat(api): File conversion API
Signed-off-by: Elizabeth Danzberger <lizzy7128@tutanota.de>
This commit is contained in:
parent
6da58974a1
commit
fdfeb7f265
21 changed files with 732 additions and 2 deletions
|
|
@ -42,6 +42,7 @@ return array(
|
|||
'OCA\\Files\\Command\\ScanAppData' => $baseDir . '/../lib/Command/ScanAppData.php',
|
||||
'OCA\\Files\\Command\\TransferOwnership' => $baseDir . '/../lib/Command/TransferOwnership.php',
|
||||
'OCA\\Files\\Controller\\ApiController' => $baseDir . '/../lib/Controller/ApiController.php',
|
||||
'OCA\\Files\\Controller\\ConversionApiController' => $baseDir . '/../lib/Controller/ConversionApiController.php',
|
||||
'OCA\\Files\\Controller\\DirectEditingController' => $baseDir . '/../lib/Controller/DirectEditingController.php',
|
||||
'OCA\\Files\\Controller\\DirectEditingViewController' => $baseDir . '/../lib/Controller/DirectEditingViewController.php',
|
||||
'OCA\\Files\\Controller\\OpenLocalEditorController' => $baseDir . '/../lib/Controller/OpenLocalEditorController.php',
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ class ComposerStaticInitFiles
|
|||
'OCA\\Files\\Command\\ScanAppData' => __DIR__ . '/..' . '/../lib/Command/ScanAppData.php',
|
||||
'OCA\\Files\\Command\\TransferOwnership' => __DIR__ . '/..' . '/../lib/Command/TransferOwnership.php',
|
||||
'OCA\\Files\\Controller\\ApiController' => __DIR__ . '/..' . '/../lib/Controller/ApiController.php',
|
||||
'OCA\\Files\\Controller\\ConversionApiController' => __DIR__ . '/..' . '/../lib/Controller/ConversionApiController.php',
|
||||
'OCA\\Files\\Controller\\DirectEditingController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingController.php',
|
||||
'OCA\\Files\\Controller\\DirectEditingViewController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingViewController.php',
|
||||
'OCA\\Files\\Controller\\OpenLocalEditorController' => __DIR__ . '/..' . '/../lib/Controller/OpenLocalEditorController.php',
|
||||
|
|
|
|||
|
|
@ -10,18 +10,21 @@ 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\IConversionManager;
|
||||
|
||||
class Capabilities implements ICapability {
|
||||
|
||||
public function __construct(
|
||||
protected FilenameValidator $filenameValidator,
|
||||
protected IConversionManager $fileConversionManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}}}
|
||||
* @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}>}>}}
|
||||
*/
|
||||
public function getCapabilities(): array {
|
||||
return [
|
||||
|
|
@ -38,6 +41,10 @@ class Capabilities implements ICapability {
|
|||
'max_size' => ChunkedUploadConfig::getMaxChunkSize(),
|
||||
'max_parallel_count' => ChunkedUploadConfig::getMaxParallelCount(),
|
||||
],
|
||||
|
||||
'file_conversions' => array_map(function (ConversionMimeTuple $mimeTuple) {
|
||||
return $mimeTuple->jsonSerialize();
|
||||
}, $this->fileConversionManager->getMimeTypes()),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
|
|||
95
apps/files/lib/Controller/ConversionApiController.php
Normal file
95
apps/files/lib/Controller/ConversionApiController.php
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Files\Controller;
|
||||
|
||||
use OC\Files\Utils\PathHelper;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\UserRateLimit;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCS\OCSException;
|
||||
use OCP\AppFramework\OCS\OCSForbiddenException;
|
||||
use OCP\AppFramework\OCS\OCSNotFoundException;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\Files\Conversion\IConversionManager;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
|
||||
class ConversionApiController extends OCSController {
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private IConversionManager $fileConversionManager,
|
||||
private IRootFolder $rootFolder,
|
||||
private IL10N $l10n,
|
||||
private ?string $userId,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a file from one MIME type to another
|
||||
*
|
||||
* @param int $fileId ID of the file to be converted
|
||||
* @param string $targetMimeType The MIME type to which you want to convert the file
|
||||
* @param string|null $destination The target path of the converted file. Written to a temporary file if left empty
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_CREATED, array{path: string}, array{}>
|
||||
*
|
||||
* 201: File was converted and written to the destination or temporary file
|
||||
*
|
||||
* @throws OCSException The file was unable to be converted
|
||||
* @throws OCSNotFoundException The file to be converted was not found
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[UserRateLimit(limit: 25, period: 120)]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/v1/convert')]
|
||||
public function convert(int $fileId, string $targetMimeType, ?string $destination = null): DataResponse {
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||
$file = $userFolder->getFirstNodeById($fileId);
|
||||
|
||||
if (!($file instanceof File)) {
|
||||
throw new OCSNotFoundException($this->l10n->t('The file cannot be found'));
|
||||
}
|
||||
|
||||
if ($destination !== null) {
|
||||
$destination = PathHelper::normalizePath($destination);
|
||||
$parentDir = dirname($destination);
|
||||
|
||||
if (!$userFolder->nodeExists($parentDir)) {
|
||||
throw new OCSNotFoundException($this->l10n->t('The destination path does not exist: %1$s', [$parentDir]));
|
||||
}
|
||||
|
||||
if (!$userFolder->get($parentDir)->isCreatable()) {
|
||||
throw new OCSForbiddenException();
|
||||
}
|
||||
|
||||
$destination = $userFolder->getFullPath($destination);
|
||||
}
|
||||
|
||||
try {
|
||||
$convertedFile = $this->fileConversionManager->convert($file, $targetMimeType, $destination);
|
||||
} catch (\Exception $e) {
|
||||
throw new OCSException($e->getMessage());
|
||||
}
|
||||
|
||||
$convertedFileRelativePath = $userFolder->getRelativePath($convertedFile);
|
||||
if ($convertedFileRelativePath === null) {
|
||||
throw new OCSNotFoundException($this->l10n->t('Could not get relative path to converted file'));
|
||||
}
|
||||
|
||||
return new DataResponse([
|
||||
'path' => $convertedFileRelativePath,
|
||||
], Http::STATUS_CREATED);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
"forbidden_filename_characters",
|
||||
"forbidden_filename_extensions",
|
||||
"chunked_upload",
|
||||
"file_conversions",
|
||||
"directEditing"
|
||||
],
|
||||
"properties": {
|
||||
|
|
@ -94,6 +95,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"file_conversions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"from",
|
||||
"to"
|
||||
],
|
||||
"properties": {
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"directEditing": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -2221,6 +2243,133 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/files/api/v1/convert": {
|
||||
"post": {
|
||||
"operationId": "conversion_api-convert",
|
||||
"summary": "Converts a file from one MIME type to another",
|
||||
"tags": [
|
||||
"conversion_api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"fileId",
|
||||
"targetMimeType"
|
||||
],
|
||||
"properties": {
|
||||
"fileId": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "ID of the file to be converted"
|
||||
},
|
||||
"targetMimeType": {
|
||||
"type": "string",
|
||||
"description": "The MIME type to which you want to convert the file"
|
||||
},
|
||||
"destination": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "The target path of the converted file. Written to a temporary file if left empty"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "File was converted and written to the destination or temporary file",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "The file to be converted was not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
|
|
|
|||
91
apps/files/tests/Controller/ConversionApiControllerTest.php
Normal file
91
apps/files/tests/Controller/ConversionApiControllerTest.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Files\Controller;
|
||||
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCS\OCSException;
|
||||
use OCP\AppFramework\OCS\OCSNotFoundException;
|
||||
use OCP\Files\Conversion\IConversionManager;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
/**
|
||||
* Class ConversionApiController
|
||||
*
|
||||
* @package OCA\Files\Controller
|
||||
*/
|
||||
class ConversionApiControllerTest extends TestCase {
|
||||
private string $appName = 'files';
|
||||
private ConversionApiController $conversionApiController;
|
||||
private IRequest&MockObject $request;
|
||||
private IConversionManager&MockObject $fileConversionManager;
|
||||
private IRootFolder&MockObject $rootFolder;
|
||||
private File&MockObject $file;
|
||||
private Folder&MockObject $userFolder;
|
||||
private IL10N&MockObject $l10n;
|
||||
private string $user;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->fileConversionManager = $this->createMock(IConversionManager::class);
|
||||
$this->file = $this->createMock(File::class);
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->user = 'userid';
|
||||
|
||||
$this->userFolder = $this->createMock(Folder::class);
|
||||
|
||||
$this->rootFolder = $this->createMock(IRootFolder::class);
|
||||
$this->rootFolder->method('getUserFolder')->with($this->user)->willReturn($this->userFolder);
|
||||
|
||||
$this->conversionApiController = new ConversionApiController(
|
||||
$this->appName,
|
||||
$this->request,
|
||||
$this->fileConversionManager,
|
||||
$this->rootFolder,
|
||||
$this->l10n,
|
||||
$this->user,
|
||||
);
|
||||
}
|
||||
|
||||
public function testThrowsNotFoundException() {
|
||||
$this->expectException(OCSNotFoundException::class);
|
||||
$this->conversionApiController->convert(42, 'image/png');
|
||||
}
|
||||
|
||||
public function testThrowsOcsException() {
|
||||
$this->userFolder->method('getFirstNodeById')->with(42)->willReturn($this->file);
|
||||
$this->fileConversionManager->method('convert')->willThrowException(new \Exception());
|
||||
|
||||
$this->expectException(OCSException::class);
|
||||
$this->conversionApiController->convert(42, 'image/png');
|
||||
}
|
||||
|
||||
public function testConvert() {
|
||||
$convertedFileAbsolutePath = $this->user . '/files/test.png';
|
||||
|
||||
$this->userFolder->method('getFirstNodeById')->with(42)->willReturn($this->file);
|
||||
$this->userFolder->method('getRelativePath')->with($convertedFileAbsolutePath)->willReturn('/test.png');
|
||||
|
||||
$this->fileConversionManager->method('convert')->with($this->file, 'image/png', null)->willReturn($convertedFileAbsolutePath);
|
||||
|
||||
$actual = $this->conversionApiController->convert(42, 'image/png', null);
|
||||
$expected = new DataResponse([
|
||||
'path' => '/test.png',
|
||||
], Http::STATUS_CREATED);
|
||||
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ return array(
|
|||
'OCA\\Testing\\Controller\\ConfigController' => $baseDir . '/../lib/Controller/ConfigController.php',
|
||||
'OCA\\Testing\\Controller\\LockingController' => $baseDir . '/../lib/Controller/LockingController.php',
|
||||
'OCA\\Testing\\Controller\\RateLimitTestController' => $baseDir . '/../lib/Controller/RateLimitTestController.php',
|
||||
'OCA\\Testing\\Conversion\\ConversionProvider' => $baseDir . '/../lib/Conversion/ConversionProvider.php',
|
||||
'OCA\\Testing\\Listener\\GetDeclarativeSettingsValueListener' => $baseDir . '/../lib/Listener/GetDeclarativeSettingsValueListener.php',
|
||||
'OCA\\Testing\\Listener\\RegisterDeclarativeSettingsListener' => $baseDir . '/../lib/Listener/RegisterDeclarativeSettingsListener.php',
|
||||
'OCA\\Testing\\Listener\\SetDeclarativeSettingsValueListener' => $baseDir . '/../lib/Listener/SetDeclarativeSettingsValueListener.php',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class ComposerStaticInitTesting
|
|||
'OCA\\Testing\\Controller\\ConfigController' => __DIR__ . '/..' . '/../lib/Controller/ConfigController.php',
|
||||
'OCA\\Testing\\Controller\\LockingController' => __DIR__ . '/..' . '/../lib/Controller/LockingController.php',
|
||||
'OCA\\Testing\\Controller\\RateLimitTestController' => __DIR__ . '/..' . '/../lib/Controller/RateLimitTestController.php',
|
||||
'OCA\\Testing\\Conversion\\ConversionProvider' => __DIR__ . '/..' . '/../lib/Conversion/ConversionProvider.php',
|
||||
'OCA\\Testing\\Listener\\GetDeclarativeSettingsValueListener' => __DIR__ . '/..' . '/../lib/Listener/GetDeclarativeSettingsValueListener.php',
|
||||
'OCA\\Testing\\Listener\\RegisterDeclarativeSettingsListener' => __DIR__ . '/..' . '/../lib/Listener/RegisterDeclarativeSettingsListener.php',
|
||||
'OCA\\Testing\\Listener\\SetDeclarativeSettingsValueListener' => __DIR__ . '/..' . '/../lib/Listener/SetDeclarativeSettingsValueListener.php',
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
namespace OCA\Testing\AppInfo;
|
||||
|
||||
use OCA\Testing\AlternativeHomeUserBackend;
|
||||
use OCA\Testing\Conversion\ConversionProvider;
|
||||
use OCA\Testing\Listener\GetDeclarativeSettingsValueListener;
|
||||
use OCA\Testing\Listener\RegisterDeclarativeSettingsListener;
|
||||
use OCA\Testing\Listener\SetDeclarativeSettingsValueListener;
|
||||
|
|
@ -49,6 +50,8 @@ class Application extends App implements IBootstrap {
|
|||
$context->registerTaskProcessingProvider(FakeTranscribeProvider::class);
|
||||
$context->registerTaskProcessingProvider(FakeContextWriteProvider::class);
|
||||
|
||||
$context->registerFileConversionProvider(ConversionProvider::class);
|
||||
|
||||
$context->registerDeclarativeSettings(DeclarativeSettingsForm::class);
|
||||
$context->registerEventListener(DeclarativeSettingsRegisterFormEvent::class, RegisterDeclarativeSettingsListener::class);
|
||||
$context->registerEventListener(DeclarativeSettingsGetValueEvent::class, GetDeclarativeSettingsValueListener::class);
|
||||
|
|
|
|||
40
apps/testing/lib/Conversion/ConversionProvider.php
Normal file
40
apps/testing/lib/Conversion/ConversionProvider.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Testing\Conversion;
|
||||
|
||||
use OCP\Files\Conversion\ConversionMimeTuple;
|
||||
use OCP\Files\Conversion\IConversionProvider;
|
||||
use OCP\Files\File;
|
||||
use OCP\IL10N;
|
||||
|
||||
class ConversionProvider implements IConversionProvider {
|
||||
public function __construct(
|
||||
private IL10N $l10n,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getSupportedMimeTypes(): array {
|
||||
return [
|
||||
new ConversionMimeTuple('image/jpeg', [
|
||||
['mime' => 'image/png', 'name' => $this->l10n->t('Image (.png)')],
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
public function convertFile(File $file, string $targetMimeType): mixed {
|
||||
$image = imagecreatefromstring($file->getContent());
|
||||
|
||||
imagepalettetotruecolor($image);
|
||||
|
||||
ob_start();
|
||||
imagepng($image);
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
|
@ -1428,6 +1428,14 @@ $CONFIG = [
|
|||
*/
|
||||
'metadata_max_filesize' => 256,
|
||||
|
||||
/**
|
||||
* Maximum file size for file conversion.
|
||||
* If a file exceeds this size, the file will not be converted.
|
||||
*
|
||||
* Default: 100 MiB
|
||||
*/
|
||||
'max_file_conversion_filesize' => 100,
|
||||
|
||||
/**
|
||||
* LDAP
|
||||
*
|
||||
|
|
|
|||
|
|
@ -377,6 +377,9 @@ 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\\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',
|
||||
'OCP\\Files\\EmptyFileNameException' => $baseDir . '/lib/public/Files/EmptyFileNameException.php',
|
||||
'OCP\\Files\\EntityTooLargeException' => $baseDir . '/lib/public/Files/EntityTooLargeException.php',
|
||||
|
|
@ -1575,6 +1578,7 @@ return array(
|
|||
'OC\\Files\\Config\\MountProviderCollection' => $baseDir . '/lib/private/Files/Config/MountProviderCollection.php',
|
||||
'OC\\Files\\Config\\UserMountCache' => $baseDir . '/lib/private/Files/Config/UserMountCache.php',
|
||||
'OC\\Files\\Config\\UserMountCacheListener' => $baseDir . '/lib/private/Files/Config/UserMountCacheListener.php',
|
||||
'OC\\Files\\Conversion\\ConversionManager' => $baseDir . '/lib/private/Files/Conversion/ConversionManager.php',
|
||||
'OC\\Files\\FileInfo' => $baseDir . '/lib/private/Files/FileInfo.php',
|
||||
'OC\\Files\\FilenameValidator' => $baseDir . '/lib/private/Files/FilenameValidator.php',
|
||||
'OC\\Files\\Filesystem' => $baseDir . '/lib/private/Files/Filesystem.php',
|
||||
|
|
|
|||
|
|
@ -418,6 +418,9 @@ 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\\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',
|
||||
'OCP\\Files\\EmptyFileNameException' => __DIR__ . '/../../..' . '/lib/public/Files/EmptyFileNameException.php',
|
||||
'OCP\\Files\\EntityTooLargeException' => __DIR__ . '/../../..' . '/lib/public/Files/EntityTooLargeException.php',
|
||||
|
|
@ -1616,6 +1619,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\Files\\Config\\MountProviderCollection' => __DIR__ . '/../../..' . '/lib/private/Files/Config/MountProviderCollection.php',
|
||||
'OC\\Files\\Config\\UserMountCache' => __DIR__ . '/../../..' . '/lib/private/Files/Config/UserMountCache.php',
|
||||
'OC\\Files\\Config\\UserMountCacheListener' => __DIR__ . '/../../..' . '/lib/private/Files/Config/UserMountCacheListener.php',
|
||||
'OC\\Files\\Conversion\\ConversionManager' => __DIR__ . '/../../..' . '/lib/private/Files/Conversion/ConversionManager.php',
|
||||
'OC\\Files\\FileInfo' => __DIR__ . '/../../..' . '/lib/private/Files/FileInfo.php',
|
||||
'OC\\Files\\FilenameValidator' => __DIR__ . '/../../..' . '/lib/private/Files/FilenameValidator.php',
|
||||
'OC\\Files\\Filesystem' => __DIR__ . '/../../..' . '/lib/private/Files/Filesystem.php',
|
||||
|
|
|
|||
|
|
@ -154,6 +154,9 @@ class RegistrationContext {
|
|||
|
||||
/** @var ServiceRegistration<\OCP\TaskProcessing\ITaskType>[] */
|
||||
private array $taskProcessingTaskTypes = [];
|
||||
|
||||
/** @var ServiceRegistration<\OCP\Files\Conversion\IConversionProvider>[] */
|
||||
private array $fileConversionProviders = [];
|
||||
|
||||
/** @var ServiceRegistration<IMailProvider>[] */
|
||||
private $mailProviders = [];
|
||||
|
|
@ -421,6 +424,13 @@ class RegistrationContext {
|
|||
);
|
||||
}
|
||||
|
||||
public function registerFileConversionProvider(string $class): void {
|
||||
$this->context->registerFileConversionProvider(
|
||||
$this->appId,
|
||||
$class
|
||||
);
|
||||
}
|
||||
|
||||
public function registerMailProvider(string $class): void {
|
||||
$this->context->registerMailProvider(
|
||||
$this->appId,
|
||||
|
|
@ -626,6 +636,14 @@ class RegistrationContext {
|
|||
public function registerTaskProcessingTaskType(string $appId, string $taskProcessingTaskTypeClass) {
|
||||
$this->taskProcessingTaskTypes[] = new ServiceRegistration($appId, $taskProcessingTaskTypeClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param class-string<\OCP\Files\Conversion\IConversionProvider> $class
|
||||
*/
|
||||
public function registerFileConversionProvider(string $appId, string $class): void {
|
||||
$this->fileConversionProviders[] = new ServiceRegistration($appId, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param class-string<IMailProvider> $migratorClass
|
||||
*/
|
||||
|
|
@ -985,6 +1003,13 @@ class RegistrationContext {
|
|||
return $this->taskProcessingTaskTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServiceRegistration<\OCP\Files\Conversion\IConversionProvider>[]
|
||||
*/
|
||||
public function getFileConversionProviders(): array {
|
||||
return $this->fileConversionProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServiceRegistration<IMailProvider>[]
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class CapabilitiesManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get an array of al the capabilities that are registered at this manager
|
||||
* Get an array of all the capabilities that are registered at this manager
|
||||
*
|
||||
* @param bool $public get public capabilities only
|
||||
* @throws \InvalidArgumentException
|
||||
|
|
|
|||
152
lib/private/Files/Conversion/ConversionManager.php
Normal file
152
lib/private/Files/Conversion/ConversionManager.php
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OC\Files\Conversion;
|
||||
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\SystemConfig;
|
||||
use OCP\Files\Conversion\ConversionMimeTuple;
|
||||
use OCP\Files\Conversion\IConversionManager;
|
||||
use OCP\Files\Conversion\IConversionProvider;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\GenericFileException;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\ITempManager;
|
||||
use OCP\PreConditionNotMetException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
class ConversionManager implements IConversionManager {
|
||||
/** @var string[] */
|
||||
private array $preferredApps = [
|
||||
'richdocuments',
|
||||
];
|
||||
|
||||
/** @var IConversionProvider[] */
|
||||
private array $preferredProviders = [];
|
||||
|
||||
/** @var IConversionProvider[] */
|
||||
private array $providers = [];
|
||||
|
||||
public function __construct(
|
||||
private Coordinator $coordinator,
|
||||
private ContainerInterface $serverContainer,
|
||||
private IRootFolder $rootFolder,
|
||||
private ITempManager $tempManager,
|
||||
private LoggerInterface $logger,
|
||||
private SystemConfig $config,
|
||||
) {
|
||||
}
|
||||
|
||||
public function hasProviders(): bool {
|
||||
$context = $this->coordinator->getRegistrationContext();
|
||||
return !empty($context->getFileConversionProviders());
|
||||
}
|
||||
|
||||
public function getMimeTypes(): array {
|
||||
$mimeTypes = [];
|
||||
|
||||
foreach ($this->getProviders() as $provider) {
|
||||
$mimeTypes[] = $provider->getSupportedMimetypes();
|
||||
}
|
||||
|
||||
/** @var list<ConversionMimeTuple> */
|
||||
$mimeTypes = array_merge(...$mimeTypes);
|
||||
return $mimeTypes;
|
||||
}
|
||||
|
||||
public function convert(File $file, string $targetMimeType, ?string $destination = null): string {
|
||||
if (!$this->hasProviders()) {
|
||||
throw new PreConditionNotMetException('No file conversion providers available');
|
||||
}
|
||||
|
||||
// Operate in mebibytes
|
||||
$fileSize = $file->getSize() / (1024 * 1024);
|
||||
$threshold = $this->config->getValue('max_file_conversion_filesize', 100);
|
||||
if ($fileSize > $threshold) {
|
||||
throw new GenericFileException('File is too large to convert');
|
||||
}
|
||||
|
||||
$fileMimeType = $file->getMimetype();
|
||||
$validProvider = $this->getValidProvider($fileMimeType, $targetMimeType);
|
||||
|
||||
if ($validProvider !== null) {
|
||||
$convertedFile = $validProvider->convertFile($file, $targetMimeType);
|
||||
|
||||
if ($destination !== null) {
|
||||
$convertedFile = $this->writeToDestination($destination, $convertedFile);
|
||||
return $convertedFile->getPath();
|
||||
}
|
||||
|
||||
$tmp = $this->tempManager->getTemporaryFile();
|
||||
file_put_contents($tmp, $convertedFile);
|
||||
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
throw new RuntimeException('Could not convert file');
|
||||
}
|
||||
|
||||
public function getProviders(): array {
|
||||
if (count($this->providers) > 0) {
|
||||
return $this->providers;
|
||||
}
|
||||
|
||||
$context = $this->coordinator->getRegistrationContext();
|
||||
foreach ($context->getFileConversionProviders() as $providerRegistration) {
|
||||
$class = $providerRegistration->getService();
|
||||
$appId = $providerRegistration->getAppId();
|
||||
|
||||
try {
|
||||
if (in_array($appId, $this->preferredApps)) {
|
||||
$this->preferredProviders[$class] = $this->serverContainer->get($class);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->providers[$class] = $this->serverContainer->get($class);
|
||||
} catch (NotFoundExceptionInterface|ContainerExceptionInterface|Throwable $e) {
|
||||
$this->logger->error('Failed to load file conversion provider ' . $class, [
|
||||
'exception' => $e,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge([], $this->preferredProviders, $this->providers);
|
||||
}
|
||||
|
||||
private function writeToDestination(string $destination, mixed $content): File {
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
if (!empty($suitableMimeTypes)) {
|
||||
$validProvider = $provider;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $validProvider;
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ use OC\Files\Cache\FileAccess;
|
|||
use OC\Files\Config\MountProviderCollection;
|
||||
use OC\Files\Config\UserMountCache;
|
||||
use OC\Files\Config\UserMountCacheListener;
|
||||
use OC\Files\Conversion\ConversionManager;
|
||||
use OC\Files\Lock\LockManager;
|
||||
use OC\Files\Mount\CacheMountProvider;
|
||||
use OC\Files\Mount\LocalHomeMountProvider;
|
||||
|
|
@ -155,6 +156,7 @@ use OCP\Federation\ICloudIdManager;
|
|||
use OCP\Files\Cache\IFileAccess;
|
||||
use OCP\Files\Config\IMountProviderCollection;
|
||||
use OCP\Files\Config\IUserMountCache;
|
||||
use OCP\Files\Conversion\IConversionManager;
|
||||
use OCP\Files\IMimeTypeDetector;
|
||||
use OCP\Files\IMimeTypeLoader;
|
||||
use OCP\Files\IRootFolder;
|
||||
|
|
@ -1258,6 +1260,8 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
|
||||
$this->registerAlias(ITranslationManager::class, TranslationManager::class);
|
||||
|
||||
$this->registerAlias(IConversionManager::class, ConversionManager::class);
|
||||
|
||||
$this->registerAlias(ISpeechToTextManager::class, SpeechToTextManager::class);
|
||||
|
||||
$this->registerAlias(IEventSourceFactory::class, EventSourceFactory::class);
|
||||
|
|
|
|||
|
|
@ -414,6 +414,19 @@ interface IRegistrationContext {
|
|||
*/
|
||||
public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeClass): void;
|
||||
|
||||
/**
|
||||
* Register an implementation of \OCP\Files\Conversion\IConversionProvider
|
||||
* that will handle the conversion of files from one MIME type to another
|
||||
*
|
||||
* @param string $class
|
||||
* @psalm-param class-string<\OCP\Files\Conversion\IConversionProvider> $class
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function registerFileConversionProvider(string $class): void;
|
||||
|
||||
/**
|
||||
* Register a mail provider
|
||||
*
|
||||
|
|
|
|||
44
lib/public/Files/Conversion/ConversionMimeTuple.php
Normal file
44
lib/public/Files/Conversion/ConversionMimeTuple.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?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,
|
||||
];
|
||||
}
|
||||
}
|
||||
46
lib/public/Files/Conversion/IConversionManager.php
Normal file
46
lib/public/Files/Conversion/IConversionManager.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?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 OCP\Files\File;
|
||||
|
||||
/**
|
||||
* @since 31.0.0
|
||||
*/
|
||||
interface IConversionManager {
|
||||
/**
|
||||
* Determines whether or not conversion providers are available
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function hasProviders(): bool;
|
||||
|
||||
/**
|
||||
* Gets all supported MIME type conversions
|
||||
*
|
||||
* @return list<ConversionMimeTuple>
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getMimeTypes(): array;
|
||||
|
||||
/**
|
||||
* Convert a file to a given MIME type
|
||||
*
|
||||
* @param File $file The file to be converted
|
||||
* @param string $targetMimeType The MIME type to convert the file to
|
||||
* @param ?string $destination The destination to save the converted file
|
||||
*
|
||||
* @return string Path to the converted file
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function convert(File $file, string $targetMimeType, ?string $destination = null): string;
|
||||
}
|
||||
41
lib/public/Files/Conversion/IConversionProvider.php
Normal file
41
lib/public/Files/Conversion/IConversionProvider.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?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 OCP\Files\File;
|
||||
|
||||
/**
|
||||
* This interface is implemented by apps that provide
|
||||
* a file conversion provider
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
interface IConversionProvider {
|
||||
/**
|
||||
* Get an array of MIME type tuples this conversion provider supports
|
||||
*
|
||||
* @return list<ConversionMimeTuple>
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function getSupportedMimeTypes(): array;
|
||||
|
||||
/**
|
||||
* Convert a file to a given MIME type
|
||||
*
|
||||
* @param File $file The file to be converted
|
||||
* @param string $targetMimeType The MIME type to convert the file to
|
||||
*
|
||||
* @return resource|string Resource or string content of the file
|
||||
*
|
||||
* @since 31.0.0
|
||||
*/
|
||||
public function convertFile(File $file, string $targetMimeType): mixed;
|
||||
}
|
||||
Loading…
Reference in a new issue