diff --git a/apps/testing/composer/composer/autoload_classmap.php b/apps/testing/composer/composer/autoload_classmap.php index 83d1fc771fc88..e7f1ce7446640 100644 --- a/apps/testing/composer/composer/autoload_classmap.php +++ b/apps/testing/composer/composer/autoload_classmap.php @@ -12,6 +12,7 @@ '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', diff --git a/apps/testing/composer/composer/autoload_static.php b/apps/testing/composer/composer/autoload_static.php index 3dc4bfe2fd623..f87a822aaf2fc 100644 --- a/apps/testing/composer/composer/autoload_static.php +++ b/apps/testing/composer/composer/autoload_static.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', diff --git a/apps/testing/lib/AppInfo/Application.php b/apps/testing/lib/AppInfo/Application.php index 3502b78402e2a..faf25b563fd55 100644 --- a/apps/testing/lib/AppInfo/Application.php +++ b/apps/testing/lib/AppInfo/Application.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 @@ public function register(IRegistrationContext $context): void { $context->registerTaskProcessingProvider(FakeTranscribeProvider::class); $context->registerTaskProcessingProvider(FakeContextWriteProvider::class); + $context->registerConversionProvider(ConversionProvider::class); + $context->registerDeclarativeSettings(DeclarativeSettingsForm::class); $context->registerEventListener(DeclarativeSettingsRegisterFormEvent::class, RegisterDeclarativeSettingsListener::class); $context->registerEventListener(DeclarativeSettingsGetValueEvent::class, GetDeclarativeSettingsValueListener::class); diff --git a/apps/testing/lib/Conversion/ConversionProvider.php b/apps/testing/lib/Conversion/ConversionProvider.php new file mode 100644 index 0000000000000..668736e906888 --- /dev/null +++ b/apps/testing/lib/Conversion/ConversionProvider.php @@ -0,0 +1,39 @@ +getContent()); + + imagepalettetotruecolor($image); + + ob_start(); + imagepng($image); + return ob_get_clean(); + } +} diff --git a/core/Controller/ConversionApiController.php b/core/Controller/ConversionApiController.php new file mode 100644 index 0000000000000..df2399798be5c --- /dev/null +++ b/core/Controller/ConversionApiController.php @@ -0,0 +1,76 @@ + + * + * 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: '/convert', root: '/conversion')] + 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(); + } + + try { + if ($destination !== null) { + $destination = $userFolder->getFullpath($destination); + } + + $convertedFile = $this->conversionManager->convert($file, $targetMimeType, $destination); + } catch (\Exception $e) { + throw new OCSException($e->getMessage()); + } + + return new DataResponse([ + 'path' => $convertedFile, + ], Http::STATUS_CREATED); + } +} diff --git a/core/openapi-full.json b/core/openapi-full.json index d6f9837b1c602..24e466a4f9679 100644 --- a/core/openapi-full.json +++ b/core/openapi-full.json @@ -2535,6 +2535,133 @@ } } }, + "/ocs/v2.php/conversion/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": {} + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/hovercard/v1/{userId}": { "get": { "operationId": "hover_card-get-user", diff --git a/core/openapi.json b/core/openapi.json index bf8f2478fbda5..a51e845351c9a 100644 --- a/core/openapi.json +++ b/core/openapi.json @@ -2535,6 +2535,133 @@ } } }, + "/ocs/v2.php/conversion/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": {} + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/hovercard/v1/{userId}": { "get": { "operationId": "hover_card-get-user", diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index fd5d9e62ba63e..48c3e9c70dbc7 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -264,6 +264,9 @@ 'OCP\\Contacts\\ContactsMenu\\IProvider' => $baseDir . '/lib/public/Contacts/ContactsMenu/IProvider.php', 'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => $baseDir . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php', 'OCP\\Contacts\\IManager' => $baseDir . '/lib/public/Contacts/IManager.php', + 'OCP\\Conversion\\ConversionMimeTuple' => $baseDir . '/lib/public/Conversion/ConversionMimeTuple.php', + 'OCP\\Conversion\\IConversionManager' => $baseDir . '/lib/public/Conversion/IConversionManager.php', + 'OCP\\Conversion\\IConversionProvider' => $baseDir . '/lib/public/Conversion/IConversionProvider.php', 'OCP\\DB\\Events\\AddMissingColumnsEvent' => $baseDir . '/lib/public/DB/Events/AddMissingColumnsEvent.php', 'OCP\\DB\\Events\\AddMissingIndicesEvent' => $baseDir . '/lib/public/DB/Events/AddMissingIndicesEvent.php', 'OCP\\DB\\Events\\AddMissingPrimaryKeyEvent' => $baseDir . '/lib/public/DB/Events/AddMissingPrimaryKeyEvent.php', @@ -1166,6 +1169,7 @@ 'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php', 'OC\\Contacts\\ContactsMenu\\Providers\\LocalTimeProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/LocalTimeProvider.php', 'OC\\Contacts\\ContactsMenu\\Providers\\ProfileProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php', + 'OC\\Conversion\\ConversionManager' => $baseDir . '/lib/private/Conversion/ConversionManager.php', 'OC\\Core\\Application' => $baseDir . '/core/Application.php', 'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => $baseDir . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php', 'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => $baseDir . '/core/BackgroundJobs/CheckForUserCertificates.php', @@ -1304,6 +1308,7 @@ 'OC\\Core\\Controller\\ClientFlowLoginV2Controller' => $baseDir . '/core/Controller/ClientFlowLoginV2Controller.php', 'OC\\Core\\Controller\\CollaborationResourcesController' => $baseDir . '/core/Controller/CollaborationResourcesController.php', 'OC\\Core\\Controller\\ContactsMenuController' => $baseDir . '/core/Controller/ContactsMenuController.php', + 'OC\\Core\\Controller\\ConversionApiController' => $baseDir . '/core/Controller/ConversionApiController.php', 'OC\\Core\\Controller\\CssController' => $baseDir . '/core/Controller/CssController.php', 'OC\\Core\\Controller\\ErrorController' => $baseDir . '/core/Controller/ErrorController.php', 'OC\\Core\\Controller\\GuestAvatarController' => $baseDir . '/core/Controller/GuestAvatarController.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index d9af7846bd882..20d16969e37b8 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -313,6 +313,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Contacts\\ContactsMenu\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Contacts/ContactsMenu/IProvider.php', 'OCP\\Contacts\\Events\\ContactInteractedWithEvent' => __DIR__ . '/../../..' . '/lib/public/Contacts/Events/ContactInteractedWithEvent.php', 'OCP\\Contacts\\IManager' => __DIR__ . '/../../..' . '/lib/public/Contacts/IManager.php', + 'OCP\\Conversion\\ConversionMimeTuple' => __DIR__ . '/../../..' . '/lib/public/Conversion/ConversionMimeTuple.php', + 'OCP\\Conversion\\IConversionManager' => __DIR__ . '/../../..' . '/lib/public/Conversion/IConversionManager.php', + 'OCP\\Conversion\\IConversionProvider' => __DIR__ . '/../../..' . '/lib/public/Conversion/IConversionProvider.php', 'OCP\\DB\\Events\\AddMissingColumnsEvent' => __DIR__ . '/../../..' . '/lib/public/DB/Events/AddMissingColumnsEvent.php', 'OCP\\DB\\Events\\AddMissingIndicesEvent' => __DIR__ . '/../../..' . '/lib/public/DB/Events/AddMissingIndicesEvent.php', 'OCP\\DB\\Events\\AddMissingPrimaryKeyEvent' => __DIR__ . '/../../..' . '/lib/public/DB/Events/AddMissingPrimaryKeyEvent.php', @@ -1215,6 +1218,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php', 'OC\\Contacts\\ContactsMenu\\Providers\\LocalTimeProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/LocalTimeProvider.php', 'OC\\Contacts\\ContactsMenu\\Providers\\ProfileProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php', + 'OC\\Conversion\\ConversionManager' => __DIR__ . '/../../..' . '/lib/private/Conversion/ConversionManager.php', 'OC\\Core\\Application' => __DIR__ . '/../../..' . '/core/Application.php', 'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php', 'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CheckForUserCertificates.php', @@ -1353,6 +1357,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Controller\\ClientFlowLoginV2Controller' => __DIR__ . '/../../..' . '/core/Controller/ClientFlowLoginV2Controller.php', 'OC\\Core\\Controller\\CollaborationResourcesController' => __DIR__ . '/../../..' . '/core/Controller/CollaborationResourcesController.php', 'OC\\Core\\Controller\\ContactsMenuController' => __DIR__ . '/../../..' . '/core/Controller/ContactsMenuController.php', + 'OC\\Core\\Controller\\ConversionApiController' => __DIR__ . '/../../..' . '/core/Controller/ConversionApiController.php', 'OC\\Core\\Controller\\CssController' => __DIR__ . '/../../..' . '/core/Controller/CssController.php', 'OC\\Core\\Controller\\ErrorController' => __DIR__ . '/../../..' . '/core/Controller/ErrorController.php', 'OC\\Core\\Controller\\GuestAvatarController' => __DIR__ . '/../../..' . '/core/Controller/GuestAvatarController.php', diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index f3b612edc38bb..8790caf253626 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -153,6 +153,9 @@ class RegistrationContext { /** @var ServiceRegistration<\OCP\TaskProcessing\ITaskType>[] */ private array $taskProcessingTaskTypes = []; + + /** @var ServiceRegistration<\OCP\Conversion\IConversionProvider>[] */ + private array $conversionProviders = []; /** @var ServiceRegistration[] */ private $mailProviders = []; @@ -420,6 +423,13 @@ public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeCla ); } + public function registerConversionProvider(string $conversionProviderClass): void { + $this->context->registerConversionProvider( + $this->appId, + $conversionProviderClass + ); + } + public function registerMailProvider(string $class): void { $this->context->registerMailProvider( $this->appId, @@ -625,6 +635,14 @@ public function registerTaskProcessingProvider(string $appId, string $taskProces public function registerTaskProcessingTaskType(string $appId, string $taskProcessingTaskTypeClass) { $this->taskProcessingTaskTypes[] = new ServiceRegistration($appId, $taskProcessingTaskTypeClass); } + + /** + * @psalm-param class-string<\OCP\Conversion\IConversionProvider> $conversionProviderClass + */ + public function registerConversionProvider(string $appId, string $conversionProviderClass): void { + $this->conversionProviders[] = new ServiceRegistration($appId, $conversionProviderClass); + } + /** * @psalm-param class-string $migratorClass */ @@ -984,6 +1002,13 @@ public function getTaskProcessingTaskTypes(): array { return $this->taskProcessingTaskTypes; } + /** + * @return ServiceRegistration<\OCP\Conversion\IConversionProvider>[] + */ + public function getConversionProviders(): array { + return $this->conversionProviders; + } + /** * @return ServiceRegistration[] */ diff --git a/lib/private/CapabilitiesManager.php b/lib/private/CapabilitiesManager.php index d7bf25f078ac0..07076d9aacd80 100644 --- a/lib/private/CapabilitiesManager.php +++ b/lib/private/CapabilitiesManager.php @@ -32,7 +32,7 @@ public function __construct(LoggerInterface $logger) { } /** - * 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 diff --git a/lib/private/Conversion/ConversionManager.php b/lib/private/Conversion/ConversionManager.php new file mode 100644 index 0000000000000..0dff801a02150 --- /dev/null +++ b/lib/private/Conversion/ConversionManager.php @@ -0,0 +1,115 @@ +coordinator->getRegistrationContext(); + return !empty($context->getConversionProviders()); + } + + public function getMimeTypes(): array { + $mimeTypes = []; + + foreach ($this->getProviders() as $provider) { + $mimeTypes[] = $provider->getSupportedMimeTypes(); + } + + return $mimeTypes; + } + + public function convert(File $file, string $targetMimeType, ?string $destination = null): string { + if (!$this->hasProviders()) { + throw new PreConditionNotMetException('No conversion providers available'); + } + + $fileMimeType = $file->getMimetype(); + foreach ($this->getProviders() as $provider) { + $availableProviderConversions = array_filter( + $provider->getSupportedMimeTypes(), + function (ConversionMimeTuple $mimeTuple) use ($fileMimeType, $targetMimeType) { + ['from' => $from, 'to' => $to] = $mimeTuple->jsonSerialize(); + + return $from === $fileMimeType && in_array($targetMimeType, $to); + } + ); + + if (!empty($availableProviderConversions)) { + $convertedFile = $provider->convertFile($file, $targetMimeType); + + if ($destination !== null) { + $convertedFile = $this->writeToDestination($destination, $convertedFile); + return $convertedFile->getInternalPath(); + } + + $tmp = $this->tempManager->getTemporaryFile(); + file_put_contents($tmp, $convertedFile); + + return $tmp; + } + } + + throw new RuntimeException('Could not convert file'); + } + + public function getProviders(): array { + if ($this->providers !== null) { + return $this->providers; + } + + $context = $this->coordinator->getRegistrationContext(); + $this->providers = []; + + foreach ($context->getConversionProviders() as $providerRegistration) { + $class = $providerRegistration->getService(); + + try { + $this->providers[$class] = $this->serverContainer->get($class); + } catch (NotFoundExceptionInterface|ContainerExceptionInterface|Throwable $e) { + $this->logger->error('Failed to load conversion provider ' . $class, [ + 'exception' => $e, + ]); + } + } + + return $this->providers; + } + + private function writeToDestination(string $destination, mixed $content): File { + return $this->rootFolder->newFile($destination, $content); + } +} diff --git a/lib/private/OCS/CoreCapabilities.php b/lib/private/OCS/CoreCapabilities.php index 3d988a8662e76..f67d853dd5dc1 100644 --- a/lib/private/OCS/CoreCapabilities.php +++ b/lib/private/OCS/CoreCapabilities.php @@ -8,6 +8,7 @@ namespace OC\OCS; use OCP\Capabilities\ICapability; +use OCP\Conversion\IConversionManager; use OCP\IConfig; use OCP\IURLGenerator; @@ -22,6 +23,7 @@ class CoreCapabilities implements ICapability { */ public function __construct( private IConfig $config, + private IConversionManager $conversionManager, ) { } @@ -34,6 +36,7 @@ public function __construct( * webdav-root: string, * reference-api: boolean, * reference-regex: string, + * conversions: array>, * mod-rewrite-working: boolean, * }, * } @@ -45,6 +48,7 @@ public function getCapabilities(): array { 'webdav-root' => $this->config->getSystemValueString('webdav-root', 'remote.php/webdav'), 'reference-api' => true, 'reference-regex' => IURLGenerator::URL_REGEX_NO_MODIFIERS, + 'conversions' => array_merge([], ...$this->conversionManager->getMimeTypes()), 'mod-rewrite-working' => $this->config->getSystemValueBool('htaccess.IgnoreFrontController') || getenv('front_controller_active') === 'true', ], ]; diff --git a/lib/private/Server.php b/lib/private/Server.php index a20c37732a76e..99bfe14b08440 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -34,6 +34,7 @@ use OC\Comments\ManagerFactory as CommentsManagerFactory; use OC\Contacts\ContactsMenu\ActionFactory; use OC\Contacts\ContactsMenu\ContactsStore; +use OC\Conversion\ConversionManager; use OC\DB\Connection; use OC\DB\ConnectionAdapter; use OC\Diagnostics\EventLogger; @@ -143,6 +144,7 @@ use OCP\Comments\ICommentsManager; use OCP\Contacts\ContactsMenu\IActionFactory; use OCP\Contacts\ContactsMenu\IContactsStore; +use OCP\Conversion\IConversionManager; use OCP\Defaults; use OCP\Diagnostics\IEventLogger; use OCP\Diagnostics\IQueryLogger; @@ -1006,7 +1008,10 @@ public function __construct($webRoot, \OC\Config $config) { $this->registerService(CapabilitiesManager::class, function (ContainerInterface $c) { $manager = new CapabilitiesManager($c->get(LoggerInterface::class)); $manager->registerCapability(function () use ($c) { - return new \OC\OCS\CoreCapabilities($c->get(\OCP\IConfig::class)); + return new \OC\OCS\CoreCapabilities( + $c->get(\OCP\IConfig::class), + $c->get(\OCP\Conversion\IConversionManager::class) + ); }); $manager->registerCapability(function () use ($c) { return $c->get(\OC\Security\Bruteforce\Capabilities::class); @@ -1258,6 +1263,8 @@ public function __construct($webRoot, \OC\Config $config) { $this->registerAlias(ITranslationManager::class, TranslationManager::class); + $this->registerAlias(IConversionManager::class, ConversionManager::class); + $this->registerAlias(ISpeechToTextManager::class, SpeechToTextManager::class); $this->registerAlias(IEventSourceFactory::class, EventSourceFactory::class); diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php index 8a18ec8ae9da0..6a3ed93f12a83 100644 --- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php +++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php @@ -414,6 +414,19 @@ public function registerTaskProcessingProvider(string $taskProcessingProviderCla */ public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeClass): void; + /** + * Register an implementation of \OCP\Conversion\IConversionProvider + * that will handle the conversion of files from one MIME type to another + * + * @param string $conversionProviderClass + * @psalm-param class-string<\OCP\Conversion\IConversionProvider> $conversionProviderClass + * + * @return void + * + * @since 31.0.0 + */ + public function registerConversionProvider(string $conversionProviderClass): void; + /** * Register a mail provider * diff --git a/lib/public/Conversion/ConversionMimeTuple.php b/lib/public/Conversion/ConversionMimeTuple.php new file mode 100644 index 0000000000000..59aefc585252d --- /dev/null +++ b/lib/public/Conversion/ConversionMimeTuple.php @@ -0,0 +1,39 @@ + $to The desired MIME type for the file + * + * @since 31.0.0 + */ + public function __construct( + private string $from, + private array $to, + ) { + } + + public function jsonSerialize(): array { + return [ + 'from' => $this->from, + 'to' => $this->to, + ]; + } +} diff --git a/lib/public/Conversion/IConversionManager.php b/lib/public/Conversion/IConversionManager.php new file mode 100644 index 0000000000000..179e33b458277 --- /dev/null +++ b/lib/public/Conversion/IConversionManager.php @@ -0,0 +1,48 @@ + + * + * @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; +} diff --git a/lib/public/Conversion/IConversionProvider.php b/lib/public/Conversion/IConversionProvider.php new file mode 100644 index 0000000000000..6dfba4f5b6cd2 --- /dev/null +++ b/lib/public/Conversion/IConversionProvider.php @@ -0,0 +1,50 @@ + + * + * @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; +} diff --git a/tests/Core/Controller/ConversionApiControllerTest.php b/tests/Core/Controller/ConversionApiControllerTest.php new file mode 100644 index 0000000000000..07b797dc51218 --- /dev/null +++ b/tests/Core/Controller/ConversionApiControllerTest.php @@ -0,0 +1,90 @@ +request = $this->createMock(IRequest::class); + $this->conversionManager = $this->createMock(IConversionManager::class); + $this->file = $this->createMock(File::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( + 'core', + $this->request, + $this->conversionManager, + $this->rootFolder, + $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->conversionManager->method('convert')->willThrowException(new \Exception()); + + $this->expectException(OCSException::class); + $this->conversionApiController->convert(42, 'image/png'); + } + + public function testConvert() { + $this->userFolder->method('getFirstNodeById')->with(42)->willReturn($this->file); + $this->conversionManager->method('convert')->with($this->file, 'image/png')->willReturn('files/test.png'); + + $actual = $this->conversionApiController->convert(42, 'image/png'); + $expected = new DataResponse([ + 'path' => 'files/test.png', + ], Http::STATUS_CREATED); + + $this->assertEquals($expected, $actual); + } +}