diff options
Diffstat (limited to 'lib')
28 files changed, 359 insertions, 134 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 5dbba982b15..b936e6d6968 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -191,6 +191,7 @@ return array( 'OCP\\Cache\\CappedMemoryCache' => $baseDir . '/lib/public/Cache/CappedMemoryCache.php', 'OCP\\Calendar\\BackendTemporarilyUnavailableException' => $baseDir . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php', 'OCP\\Calendar\\CalendarEventStatus' => $baseDir . '/lib/public/Calendar/CalendarEventStatus.php', + 'OCP\\Calendar\\CalendarExportOptions' => $baseDir . '/lib/public/Calendar/CalendarExportOptions.php', 'OCP\\Calendar\\Events\\AbstractCalendarObjectEvent' => $baseDir . '/lib/public/Calendar/Events/AbstractCalendarObjectEvent.php', 'OCP\\Calendar\\Events\\CalendarObjectCreatedEvent' => $baseDir . '/lib/public/Calendar/Events/CalendarObjectCreatedEvent.php', 'OCP\\Calendar\\Events\\CalendarObjectDeletedEvent' => $baseDir . '/lib/public/Calendar/Events/CalendarObjectDeletedEvent.php', @@ -202,6 +203,8 @@ return array( 'OCP\\Calendar\\IAvailabilityResult' => $baseDir . '/lib/public/Calendar/IAvailabilityResult.php', 'OCP\\Calendar\\ICalendar' => $baseDir . '/lib/public/Calendar/ICalendar.php', 'OCP\\Calendar\\ICalendarEventBuilder' => $baseDir . '/lib/public/Calendar/ICalendarEventBuilder.php', + 'OCP\\Calendar\\ICalendarExport' => $baseDir . '/lib/public/Calendar/ICalendarExport.php', + 'OCP\\Calendar\\ICalendarIsEnabled' => $baseDir . '/lib/public/Calendar/ICalendarIsEnabled.php', 'OCP\\Calendar\\ICalendarIsShared' => $baseDir . '/lib/public/Calendar/ICalendarIsShared.php', 'OCP\\Calendar\\ICalendarIsWritable' => $baseDir . '/lib/public/Calendar/ICalendarIsWritable.php', 'OCP\\Calendar\\ICalendarProvider' => $baseDir . '/lib/public/Calendar/ICalendarProvider.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 585059cdd20..02284310e01 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -232,6 +232,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/public/Cache/CappedMemoryCache.php', 'OCP\\Calendar\\BackendTemporarilyUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php', 'OCP\\Calendar\\CalendarEventStatus' => __DIR__ . '/../../..' . '/lib/public/Calendar/CalendarEventStatus.php', + 'OCP\\Calendar\\CalendarExportOptions' => __DIR__ . '/../../..' . '/lib/public/Calendar/CalendarExportOptions.php', 'OCP\\Calendar\\Events\\AbstractCalendarObjectEvent' => __DIR__ . '/../../..' . '/lib/public/Calendar/Events/AbstractCalendarObjectEvent.php', 'OCP\\Calendar\\Events\\CalendarObjectCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Calendar/Events/CalendarObjectCreatedEvent.php', 'OCP\\Calendar\\Events\\CalendarObjectDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Calendar/Events/CalendarObjectDeletedEvent.php', @@ -243,6 +244,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Calendar\\IAvailabilityResult' => __DIR__ . '/../../..' . '/lib/public/Calendar/IAvailabilityResult.php', 'OCP\\Calendar\\ICalendar' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendar.php', 'OCP\\Calendar\\ICalendarEventBuilder' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarEventBuilder.php', + 'OCP\\Calendar\\ICalendarExport' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarExport.php', + 'OCP\\Calendar\\ICalendarIsEnabled' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarIsEnabled.php', 'OCP\\Calendar\\ICalendarIsShared' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarIsShared.php', 'OCP\\Calendar\\ICalendarIsWritable' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarIsWritable.php', 'OCP\\Calendar\\ICalendarProvider' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarProvider.php', diff --git a/lib/l10n/es.js b/lib/l10n/es.js index 94e52dbc724..a3cf61509ec 100644 --- a/lib/l10n/es.js +++ b/lib/l10n/es.js @@ -364,6 +364,11 @@ OC.L10N.register( "How many images to generate" : "Cuántas imágenes se generarán", "Output images" : "Imágenes de salida", "The generated images" : "Las imágenes generadas", + "Generate speech" : "Generar dictado", + "Generate speech from a transcript" : "Generar dictado desde una transcripción", + "Write transcript that you want the assistant to generate speech from" : "Escriba la transcripción desde la que desea que el asistente genere un dictado", + "Output speech" : "Dictado de salida", + "The generated speech" : "El dictado generado", "Free text to text prompt" : "Texto libre a prompt de texto", "Runs an arbitrary prompt through a language model that returns a reply" : "Ejecuta un prompt arbitrario a través de un modelo de lenguaje que retorna una respuesta", "Describe a task that you want the assistant to do or ask a question" : "Describa una tarea que quiere que el asistente realice, o, haga una pregunta", diff --git a/lib/l10n/es.json b/lib/l10n/es.json index 61f519db2a7..34d4db2eec4 100644 --- a/lib/l10n/es.json +++ b/lib/l10n/es.json @@ -362,6 +362,11 @@ "How many images to generate" : "Cuántas imágenes se generarán", "Output images" : "Imágenes de salida", "The generated images" : "Las imágenes generadas", + "Generate speech" : "Generar dictado", + "Generate speech from a transcript" : "Generar dictado desde una transcripción", + "Write transcript that you want the assistant to generate speech from" : "Escriba la transcripción desde la que desea que el asistente genere un dictado", + "Output speech" : "Dictado de salida", + "The generated speech" : "El dictado generado", "Free text to text prompt" : "Texto libre a prompt de texto", "Runs an arbitrary prompt through a language model that returns a reply" : "Ejecuta un prompt arbitrario a través de un modelo de lenguaje que retorna una respuesta", "Describe a task that you want the assistant to do or ask a question" : "Describa una tarea que quiere que el asistente realice, o, haga una pregunta", diff --git a/lib/l10n/et_EE.js b/lib/l10n/et_EE.js index d882e0ee79e..e14744dc6ee 100644 --- a/lib/l10n/et_EE.js +++ b/lib/l10n/et_EE.js @@ -279,6 +279,7 @@ OC.L10N.register( "Ensure there is a file called \"%1$s\" in the root of the data directory. It should have the content: \"%2$s\"" : "Palun taga, et andmete juurkaustas leidub fail „%1$s“, mille sisuks on „%2$s“", "Action \"%s\" not supported or implemented." : "„%s“ tegevus pole toetatud või implementeeritud.", "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Päringu lõpetamiseks on puudu järgmised parameetrid; „%s“", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "„%1$s“ tunnus on juba kasutusel liitpilve serveris „%2$s“", "Cloud Federation Provider with ID: \"%s\" does not exist." : "Liitpilve teenusepakkujat tunnusega „%s“ pole olemas.", "Could not obtain lock type %d on \"%s\"." : "Ei suutnud hankida %d tüüpi lukustust asukohas „%s“.", "Storage unauthorized. %s" : "Andmeruum on autoriseerimata. %s", @@ -286,6 +287,16 @@ OC.L10N.register( "Storage connection error. %s" : "Viga andmeruumi ühenduse loomisel. %s", "Storage is temporarily not available" : "Salvestusruum pole ajutiselt kättesaadav", "Storage connection timeout. %s" : "Aegumine andmeruumi ühenduse loomisel. %s", + "Transcribe audio" : "Kirjuta heli üles", + "Transcribe the things said in an audio" : "Kirjuta üles helifailis kuuldav jutt", + "Audio input" : "Helisisend", + "The audio to transcribe" : "Üleskirjutatav helifail", + "Transcription" : "Üleskirjutus", + "The transcribed text" : "Üleskirjutatud tekst", + "Context write" : "Kontekstuaalne kirjutamine", + "Writes text in a given style based on the provided source material." : "Kirjutab etteantud lähtematerjali lausel teksti üles.", + "Writing style" : "Kirjutamisstiil", + "Source material" : "Lähtematerjal", "Generate image" : "Piltide loomine", "Generate an image from a text prompt" : "Loo tekstisisendist pilt", "Prompt" : "Sisendvorm", @@ -299,6 +310,9 @@ OC.L10N.register( "Write transcript that you want the assistant to generate speech from" : "Kirjuta üles see, mille alusel tahad Abilisel lasta koostada kõne", "Output speech" : "Kõneväljund", "The generated speech" : "Koostatud kõne", + "Chat" : "Vestle", + "Chat with the assistant" : "Vestle Abilisega", + "System prompt" : "Süsteemi viip", "Generate a headline" : "Alapealkirja loomine", "Generates a possible headline for a text." : "Võimaldab luua teksti põhjal kokkuvõtliku alapealkirja.", "Original text" : "Lähtetekst", diff --git a/lib/l10n/et_EE.json b/lib/l10n/et_EE.json index 295c4b77855..f6c46ab05cb 100644 --- a/lib/l10n/et_EE.json +++ b/lib/l10n/et_EE.json @@ -277,6 +277,7 @@ "Ensure there is a file called \"%1$s\" in the root of the data directory. It should have the content: \"%2$s\"" : "Palun taga, et andmete juurkaustas leidub fail „%1$s“, mille sisuks on „%2$s“", "Action \"%s\" not supported or implemented." : "„%s“ tegevus pole toetatud või implementeeritud.", "Parameters missing in order to complete the request. Missing Parameters: \"%s\"" : "Päringu lõpetamiseks on puudu järgmised parameetrid; „%s“", + "ID \"%1$s\" already used by cloud federation provider \"%2$s\"" : "„%1$s“ tunnus on juba kasutusel liitpilve serveris „%2$s“", "Cloud Federation Provider with ID: \"%s\" does not exist." : "Liitpilve teenusepakkujat tunnusega „%s“ pole olemas.", "Could not obtain lock type %d on \"%s\"." : "Ei suutnud hankida %d tüüpi lukustust asukohas „%s“.", "Storage unauthorized. %s" : "Andmeruum on autoriseerimata. %s", @@ -284,6 +285,16 @@ "Storage connection error. %s" : "Viga andmeruumi ühenduse loomisel. %s", "Storage is temporarily not available" : "Salvestusruum pole ajutiselt kättesaadav", "Storage connection timeout. %s" : "Aegumine andmeruumi ühenduse loomisel. %s", + "Transcribe audio" : "Kirjuta heli üles", + "Transcribe the things said in an audio" : "Kirjuta üles helifailis kuuldav jutt", + "Audio input" : "Helisisend", + "The audio to transcribe" : "Üleskirjutatav helifail", + "Transcription" : "Üleskirjutus", + "The transcribed text" : "Üleskirjutatud tekst", + "Context write" : "Kontekstuaalne kirjutamine", + "Writes text in a given style based on the provided source material." : "Kirjutab etteantud lähtematerjali lausel teksti üles.", + "Writing style" : "Kirjutamisstiil", + "Source material" : "Lähtematerjal", "Generate image" : "Piltide loomine", "Generate an image from a text prompt" : "Loo tekstisisendist pilt", "Prompt" : "Sisendvorm", @@ -297,6 +308,9 @@ "Write transcript that you want the assistant to generate speech from" : "Kirjuta üles see, mille alusel tahad Abilisel lasta koostada kõne", "Output speech" : "Kõneväljund", "The generated speech" : "Koostatud kõne", + "Chat" : "Vestle", + "Chat with the assistant" : "Vestle Abilisega", + "System prompt" : "Süsteemi viip", "Generate a headline" : "Alapealkirja loomine", "Generates a possible headline for a text." : "Võimaldab luua teksti põhjal kokkuvõtliku alapealkirja.", "Original text" : "Lähtetekst", diff --git a/lib/l10n/lv.js b/lib/l10n/lv.js index e5b635a2bd3..c6eeeaa756c 100644 --- a/lib/l10n/lv.js +++ b/lib/l10n/lv.js @@ -31,14 +31,14 @@ OC.L10N.register( "View profile" : "Skatīt profilu", "today" : "šodien", "yesterday" : "vakar", - "_%n day ago_::_%n days ago_" : ["%n dienas atpakaļ","%n dienas atpakaļ","%n dienām"], + "_%n day ago_::_%n days ago_" : ["pirms %n dienām","pirms %n dienas","pirms %n dienām"], "last month" : "pagājušajā mēnesī", - "_%n month ago_::_%n months ago_" : ["%n mēneši atpakaļ","%n mēneši atpakaļ","%n mēnešiem"], + "_%n month ago_::_%n months ago_" : ["pirms %n mēnešiem","pirms %n mēneša","pirms %n mēnešiem"], "last year" : "gājušajā gadā", - "_%n year ago_::_%n years ago_" : ["%n gadiem","%n gadiem","%n gadiem"], - "_%n hour ago_::_%n hours ago_" : ["%n stundas atpakaļ","%n stundas atpakaļ","%n stundām"], - "_%n minute ago_::_%n minutes ago_" : ["%n minūtes atpakaļ","%n minūtes atpakaļ","%n minūtēm"], - "seconds ago" : "sekundēm", + "_%n year ago_::_%n years ago_" : ["pirms %n gadiem","pirms %n gada","pirms %n gadiem"], + "_%n hour ago_::_%n hours ago_" : ["pirms %n stundām","pirms %n stundas","pirms %n stundām"], + "_%n minute ago_::_%n minutes ago_" : ["pirms %n minūtēm","pirms %n minūtes","pirms %n minūtēm"], + "seconds ago" : "pirms vairākām sekundēm", "Empty file" : "Tukša datne", "File already exists" : "Datne jau pastāv", "Filename contains at least one invalid character" : "Datnes nosaukums satur vismaz vienu nederīgu rakstzīmi", diff --git a/lib/l10n/lv.json b/lib/l10n/lv.json index f71090c5fd4..865458737df 100644 --- a/lib/l10n/lv.json +++ b/lib/l10n/lv.json @@ -29,14 +29,14 @@ "View profile" : "Skatīt profilu", "today" : "šodien", "yesterday" : "vakar", - "_%n day ago_::_%n days ago_" : ["%n dienas atpakaļ","%n dienas atpakaļ","%n dienām"], + "_%n day ago_::_%n days ago_" : ["pirms %n dienām","pirms %n dienas","pirms %n dienām"], "last month" : "pagājušajā mēnesī", - "_%n month ago_::_%n months ago_" : ["%n mēneši atpakaļ","%n mēneši atpakaļ","%n mēnešiem"], + "_%n month ago_::_%n months ago_" : ["pirms %n mēnešiem","pirms %n mēneša","pirms %n mēnešiem"], "last year" : "gājušajā gadā", - "_%n year ago_::_%n years ago_" : ["%n gadiem","%n gadiem","%n gadiem"], - "_%n hour ago_::_%n hours ago_" : ["%n stundas atpakaļ","%n stundas atpakaļ","%n stundām"], - "_%n minute ago_::_%n minutes ago_" : ["%n minūtes atpakaļ","%n minūtes atpakaļ","%n minūtēm"], - "seconds ago" : "sekundēm", + "_%n year ago_::_%n years ago_" : ["pirms %n gadiem","pirms %n gada","pirms %n gadiem"], + "_%n hour ago_::_%n hours ago_" : ["pirms %n stundām","pirms %n stundas","pirms %n stundām"], + "_%n minute ago_::_%n minutes ago_" : ["pirms %n minūtēm","pirms %n minūtes","pirms %n minūtēm"], + "seconds ago" : "pirms vairākām sekundēm", "Empty file" : "Tukša datne", "File already exists" : "Datne jau pastāv", "Filename contains at least one invalid character" : "Datnes nosaukums satur vismaz vienu nederīgu rakstzīmi", diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index 092d37c3338..a8a6f689ffa 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -488,6 +488,14 @@ class AppConfig implements IAppConfig { * @see VALUE_ARRAY */ public function getValueType(string $app, string $key, ?bool $lazy = null): int { + $type = self::VALUE_MIXED; + $ignorable = $lazy ?? false; + $this->matchAndApplyLexiconDefinition($app, $key, $ignorable, $type); + if ($type !== self::VALUE_MIXED) { + // a modified $type means config key is set in Lexicon + return $type; + } + $this->assertParams($app, $key); $this->loadConfig($app, $lazy); diff --git a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php index 2b3025fccff..b3040673d0f 100644 --- a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php +++ b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php @@ -120,7 +120,7 @@ class PublicShareMiddleware extends Middleware { private function throttle($bruteforceProtectionAction, $token): void { $ip = $this->request->getRemoteAddress(); - $this->throttler->sleepDelay($ip, $bruteforceProtectionAction); + $this->throttler->sleepDelayOrThrowOnMax($ip, $bruteforceProtectionAction); $this->throttler->registerAttempt($bruteforceProtectionAction, $ip, ['token' => $token]); } } diff --git a/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php b/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php index 16f63594f19..982693bcfe8 100644 --- a/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php +++ b/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php @@ -19,6 +19,7 @@ use OCP\Files\NotPermittedException; use OCP\FilesMetadata\AMetadataEvent; use OCP\FilesMetadata\Event\MetadataBackgroundEvent; use OCP\FilesMetadata\Event\MetadataLiveEvent; +use OCP\IPreview; use OCP\Lock\LockedException; /** @@ -27,11 +28,14 @@ use OCP\Lock\LockedException; * @template-implements IEventListener<AMetadataEvent> */ class GenerateBlurhashMetadata implements IEventListener { - private const RESIZE_BOXSIZE = 30; - private const COMPONENTS_X = 4; private const COMPONENTS_Y = 3; + public function __construct( + private IPreview $preview, + ) { + } + /** * @throws NotPermittedException * @throws GenericFileException @@ -64,7 +68,9 @@ class GenerateBlurhashMetadata implements IEventListener { return; } - $image = $this->resizedImageFromFile($file); + $preview = $this->preview->getPreview($file, 64, 64, cacheResult: false); + $image = @imagecreatefromstring($preview->getContent()); + if (!$image) { return; } @@ -74,35 +80,6 @@ class GenerateBlurhashMetadata implements IEventListener { } /** - * @param File $file - * - * @return GdImage|false - * @throws GenericFileException - * @throws NotPermittedException - * @throws LockedException - */ - private function resizedImageFromFile(File $file): GdImage|false { - $image = @imagecreatefromstring($file->getContent()); - if ($image === false) { - return false; - } - - $currX = imagesx($image); - $currY = imagesy($image); - - if ($currX > $currY) { - $newX = self::RESIZE_BOXSIZE; - $newY = intval($currY * $newX / $currX); - } else { - $newY = self::RESIZE_BOXSIZE; - $newX = intval($currX * $newY / $currY); - } - - $newImage = @imagescale($image, $newX, $newY); - return ($newImage !== false) ? $newImage : $image; - } - - /** * @param GdImage $image * * @return string diff --git a/lib/private/Files/Cache/Scanner.php b/lib/private/Files/Cache/Scanner.php index 1fb408a0655..b067f70b8cb 100644 --- a/lib/private/Files/Cache/Scanner.php +++ b/lib/private/Files/Cache/Scanner.php @@ -210,7 +210,7 @@ class Scanner extends BasicEmitter implements IScanner { * @var \OC\Files\Cache\CacheEntry $cacheData */ $newData = $this->array_diff_assoc_multi($data, $cacheData->getData()); - + // make it known to the caller that etag has been changed and needs propagation if (isset($newData['etag'])) { $data['etag_changed'] = true; @@ -351,23 +351,23 @@ class Scanner extends BasicEmitter implements IScanner { * */ protected function array_diff_assoc_multi(array $array1, array $array2) { - + $result = []; foreach ($array1 as $key => $value) { - + // if $array2 doesn't have the same key, that's a result if (!array_key_exists($key, $array2)) { $result[$key] = $value; continue; } - + // if $array2's value for the same key is different, that's a result if ($array2[$key] !== $value && !is_array($value)) { $result[$key] = $value; continue; } - + if (is_array($value)) { $nestedDiff = $this->array_diff_assoc_multi($value, $array2[$key]); if (!empty($nestedDiff)) { diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index 16ef4e7de63..ebe87399ab4 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -22,6 +22,7 @@ use OCP\Files\FileInfo; use OCP\Files\GenericFileException; use OCP\Files\NotFoundException; use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\ObjectStore\IObjectStoreMetaData; use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload; use OCP\Files\Storage\IChunkedFileWrite; use OCP\Files\Storage\IStorage; @@ -479,6 +480,11 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil $mimetypeDetector = \OC::$server->getMimeTypeDetector(); $mimetype = $mimetypeDetector->detectPath($path); + $metadata = [ + 'mimetype' => $mimetype, + 'original-storage' => $this->getId(), + 'original-path' => $path, + ]; $stat['mimetype'] = $mimetype; $stat['etag'] = $this->getETag($path); @@ -507,13 +513,21 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil ]); $size = $writtenSize; }); - $this->objectStore->writeObject($urn, $countStream, $mimetype); + if ($this->objectStore instanceof IObjectStoreMetaData) { + $this->objectStore->writeObjectWithMetaData($urn, $countStream, $metadata); + } else { + $this->objectStore->writeObject($urn, $countStream, $metadata['mimetype']); + } if (is_resource($countStream)) { fclose($countStream); } $stat['size'] = $size; } else { - $this->objectStore->writeObject($urn, $stream, $mimetype); + if ($this->objectStore instanceof IObjectStoreMetaData) { + $this->objectStore->writeObjectWithMetaData($urn, $stream, $metadata); + } else { + $this->objectStore->writeObject($urn, $stream, $metadata['mimetype']); + } if (is_resource($stream)) { fclose($stream); } diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index e970fb6ac14..23c061db174 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -95,6 +95,16 @@ class S3 implements IObjectStore, IObjectStoreMultiPartUpload, IObjectStoreMetaD ]); } + private function parseS3Metadata(array $metadata): array { + $result = []; + foreach ($metadata as $key => $value) { + if (str_starts_with($key, 'x-amz-meta-')) { + $result[substr($key, strlen('x-amz-meta-'))] = $value; + } + } + return $result; + } + public function getObjectMetaData(string $urn): array { $object = $this->getConnection()->headObject([ 'Bucket' => $this->bucket, @@ -104,7 +114,7 @@ class S3 implements IObjectStore, IObjectStoreMultiPartUpload, IObjectStoreMetaD 'mtime' => $object['LastModified'], 'etag' => trim($object['ETag'], '"'), 'size' => (int)($object['Size'] ?? $object['ContentLength']), - ]; + ] + $this->parseS3Metadata($object['Metadata'] ?? []); } public function listObjects(string $prefix = ''): \Iterator { diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php index 9d7cfa644e6..61e8158b863 100644 --- a/lib/private/Files/ObjectStore/S3ObjectTrait.php +++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php @@ -77,22 +77,32 @@ trait S3ObjectTrait { return $fh; } + private function buildS3Metadata(array $metadata): array { + $result = []; + foreach ($metadata as $key => $value) { + $result['x-amz-meta-' . $key] = $value; + } + return $result; + } /** * Single object put helper * * @param string $urn the unified resource name used to identify the object * @param StreamInterface $stream stream with the data to write - * @param string|null $mimetype the mimetype to set for the remove object @since 22.0.0 + * @param array $metaData the metadata to set for the object * @throws \Exception when something goes wrong, message will be logged */ - protected function writeSingle(string $urn, StreamInterface $stream, ?string $mimetype = null): void { + protected function writeSingle(string $urn, StreamInterface $stream, array $metaData): void { + $mimetype = $metaData['mimetype'] ?? null; + unset($metaData['mimetype']); $this->getConnection()->putObject([ 'Bucket' => $this->bucket, 'Key' => $urn, 'Body' => $stream, 'ACL' => 'private', 'ContentType' => $mimetype, + 'Metadata' => $this->buildS3Metadata($metaData), 'StorageClass' => $this->storageClass, ] + $this->getSSECParameters()); } @@ -103,10 +113,12 @@ trait S3ObjectTrait { * * @param string $urn the unified resource name used to identify the object * @param StreamInterface $stream stream with the data to write - * @param string|null $mimetype the mimetype to set for the remove object + * @param array $metaData the metadata to set for the object * @throws \Exception when something goes wrong, message will be logged */ - protected function writeMultiPart(string $urn, StreamInterface $stream, ?string $mimetype = null): void { + protected function writeMultiPart(string $urn, StreamInterface $stream, array $metaData): void { + $mimetype = $metaData['mimetype'] ?? null; + unset($metaData['mimetype']); $uploader = new MultipartUploader($this->getConnection(), $stream, [ 'bucket' => $this->bucket, 'concurrency' => $this->concurrency, @@ -114,6 +126,7 @@ trait S3ObjectTrait { 'part_size' => $this->uploadPartSize, 'params' => [ 'ContentType' => $mimetype, + 'Metadata' => $this->buildS3Metadata($metaData), 'StorageClass' => $this->storageClass, ] + $this->getSSECParameters(), ]); @@ -131,15 +144,15 @@ trait S3ObjectTrait { } } - - /** - * @param string $urn the unified resource name used to identify the object - * @param resource $stream stream with the data to write - * @param string|null $mimetype the mimetype to set for the remove object @since 22.0.0 - * @throws \Exception when something goes wrong, message will be logged - * @since 7.0.0 - */ public function writeObject($urn, $stream, ?string $mimetype = null) { + $metaData = []; + if ($mimetype) { + $metaData['mimetype'] = $mimetype; + } + $this->writeObjectWithMetaData($urn, $stream, $metaData); + } + + public function writeObjectWithMetaData(string $urn, $stream, array $metaData): void { $canSeek = fseek($stream, 0, SEEK_CUR) === 0; $psrStream = Utils::streamFor($stream); @@ -154,16 +167,16 @@ trait S3ObjectTrait { $buffer->seek(0); if ($buffer->getSize() < $this->putSizeLimit) { // buffer is fully seekable, so use it directly for the small upload - $this->writeSingle($urn, $buffer, $mimetype); + $this->writeSingle($urn, $buffer, $metaData); } else { $loadStream = new Psr7\AppendStream([$buffer, $psrStream]); - $this->writeMultiPart($urn, $loadStream, $mimetype); + $this->writeMultiPart($urn, $loadStream, $metaData); } } else { if ($size < $this->putSizeLimit) { - $this->writeSingle($urn, $psrStream, $mimetype); + $this->writeSingle($urn, $psrStream, $metaData); } else { - $this->writeMultiPart($urn, $psrStream, $mimetype); + $this->writeMultiPart($urn, $psrStream, $metaData); } } $psrStream->close(); diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php index 4d94629443f..e9ed351b27b 100644 --- a/lib/private/Files/Utils/Scanner.php +++ b/lib/private/Files/Utils/Scanner.php @@ -29,6 +29,7 @@ use OCP\Files\Storage\IStorage; use OCP\Files\StorageNotAvailableException; use OCP\IDBConnection; use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; use Psr\Log\LoggerInterface; /** @@ -260,7 +261,15 @@ class Scanner extends PublicEmitter { try { $propagator = $storage->getPropagator(); $propagator->beginBatch(); - $scanner->scan($relativePath, $recursive, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE); + try { + $scanner->scan($relativePath, $recursive, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE); + } catch (LockedException $e) { + if (is_string($e->getReadablePath()) && str_starts_with($e->getReadablePath(), 'scanner::')) { + throw new LockedException("scanner::$dir", $e, $e->getExistingLock()); + } else { + throw $e; + } + } $cache = $storage->getCache(); if ($cache instanceof Cache) { // only re-calculate for the root folder we scanned, anything below that is taken care of by the scanner diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index bbad24d3e43..f17ced1611b 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1466,8 +1466,7 @@ class View { public function addSubMounts(FileInfo $info, $extOnly = false): void { $mounts = Filesystem::getMountManager()->findIn($info->getPath()); $info->setSubMounts(array_filter($mounts, function (IMountPoint $mount) use ($extOnly) { - $subStorage = $mount->getStorage(); - return !($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage); + return !($extOnly && $mount instanceof SharedMount); })); } diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php index b95971d0085..00fc3b43d61 100644 --- a/lib/private/Preview/Generator.php +++ b/lib/private/Preview/Generator.php @@ -12,6 +12,7 @@ use OCP\Files\IAppData; use OCP\Files\InvalidPathException; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\InMemoryFile; use OCP\Files\SimpleFS\ISimpleFile; use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IConfig; @@ -43,17 +44,19 @@ class Generator { * The cache is searched first and if nothing usable was found then a preview is * generated by one of the providers * - * @param File $file - * @param int $width - * @param int $height - * @param bool $crop - * @param string $mode - * @param string|null $mimeType * @return ISimpleFile * @throws NotFoundException * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) */ - public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) { + public function getPreview( + File $file, + int $width = -1, + int $height = -1, + bool $crop = false, + string $mode = IPreview::MODE_FILL, + ?string $mimeType = null, + bool $cacheResult = true, + ): ISimpleFile { $specification = [ 'width' => $width, 'height' => $height, @@ -78,23 +81,19 @@ class Generator { 'mode' => $mode, 'mimeType' => $mimeType, ]); - + // since we only ask for one preview, and the generate method return the last one it created, it returns the one we want - return $this->generatePreviews($file, [$specification], $mimeType); + return $this->generatePreviews($file, [$specification], $mimeType, $cacheResult); } /** * Generates previews of a file * - * @param File $file - * @param non-empty-array $specifications - * @param string $mimeType - * @return ISimpleFile the last preview that was generated * @throws NotFoundException * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) */ - public function generatePreviews(File $file, array $specifications, $mimeType = null) { + public function generatePreviews(File $file, array $specifications, ?string $mimeType = null, bool $cacheResult = true): ISimpleFile { //Make sure that we can read the file if (!$file->isReadable()) { $this->logger->warning('Cannot read file: {path}, skipping preview generation.', ['path' => $file->getPath()]); @@ -167,7 +166,7 @@ class Generator { } $this->logger->warning('Cached preview not found for file {path}, generating a new preview.', ['path' => $file->getPath()]); - $preview = $this->generatePreview($previewFolder, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion); + $preview = $this->generatePreview($previewFolder, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion, $cacheResult); // New file, augment our array $previewFiles[] = $preview; } @@ -351,11 +350,10 @@ class Generator { $path = $this->generatePath($preview->width(), $preview->height(), $crop, $max, $preview->dataMimeType(), $prefix); try { - $file = $previewFolder->newFile($path); if ($preview instanceof IStreamImage) { - $file->putContent($preview->resource()); + return $previewFolder->newFile($path, $preview->resource()); } else { - $file->putContent($preview->data()); + return $previewFolder->newFile($path, $preview->data()); } } catch (NotPermittedException $e) { throw new NotFoundException(); @@ -490,19 +488,20 @@ class Generator { } /** - * @param ISimpleFolder $previewFolder - * @param ISimpleFile $maxPreview - * @param int $width - * @param int $height - * @param bool $crop - * @param int $maxWidth - * @param int $maxHeight - * @param string $prefix - * @return ISimpleFile * @throws NotFoundException * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) */ - private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $prefix) { + private function generatePreview( + ISimpleFolder $previewFolder, + IImage $maxPreview, + int $width, + int $height, + bool $crop, + int $maxWidth, + int $maxHeight, + string $prefix, + bool $cacheResult, + ): ISimpleFile { $preview = $maxPreview; if (!$preview->valid()) { throw new \InvalidArgumentException('Failed to generate preview, failed to load image'); @@ -539,12 +538,14 @@ class Generator { $path = $this->generatePath($width, $height, $crop, false, $preview->dataMimeType(), $prefix); try { - $file = $previewFolder->newFile($path); - $file->putContent($preview->data()); + if ($cacheResult) { + return $previewFolder->newFile($path, $preview->data()); + } else { + return new InMemoryFile($path, $preview->data()); + } } catch (NotPermittedException $e) { throw new NotFoundException(); } - return $file; } diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php index bc77dcfe483..1f618dab22d 100644 --- a/lib/private/PreviewManager.php +++ b/lib/private/PreviewManager.php @@ -145,29 +145,20 @@ class PreviewManager implements IPreview { return $this->generator; } - /** - * Returns a preview of a file - * - * The cache is searched first and if nothing usable was found then a preview is - * generated by one of the providers - * - * @param File $file - * @param int $width - * @param int $height - * @param bool $crop - * @param string $mode - * @param string $mimeType - * @return ISimpleFile - * @throws NotFoundException - * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) - * @since 11.0.0 - \InvalidArgumentException was added in 12.0.0 - */ - public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) { + public function getPreview( + File $file, + $width = -1, + $height = -1, + $crop = false, + $mode = IPreview::MODE_FILL, + $mimeType = null, + bool $cacheResult = true, + ): ISimpleFile { $this->throwIfPreviewsDisabled(); $previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all'); $sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency); try { - $preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType); + $preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType, $cacheResult); } finally { Generator::unguardWithSemaphore($sem); } diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php index 21d50848641..065f720ba72 100644 --- a/lib/private/Security/Bruteforce/Throttler.php +++ b/lib/private/Security/Bruteforce/Throttler.php @@ -127,6 +127,13 @@ class Throttler implements IThrottler { */ public function getDelay(string $ip, string $action = ''): int { $attempts = $this->getAttempts($ip, $action); + return $this->calculateDelay($attempts); + } + + /** + * {@inheritDoc} + */ + public function calculateDelay(int $attempts): int { if ($attempts === 0) { return 0; } @@ -199,25 +206,29 @@ class Throttler implements IThrottler { * {@inheritDoc} */ public function sleepDelayOrThrowOnMax(string $ip, string $action = ''): int { - $delay = $this->getDelay($ip, $action); - if (($delay === self::MAX_DELAY_MS) && $this->getAttempts($ip, $action, 0.5) > $this->config->getSystemValueInt('auth.bruteforce.max-attempts', self::MAX_ATTEMPTS)) { - $this->logger->info('IP address blocked because it reached the maximum failed attempts in the last 30 minutes [action: {action}, ip: {ip}]', [ + $attempts = $this->getAttempts($ip, $action, 0.5); + if ($attempts > $this->config->getSystemValueInt('auth.bruteforce.max-attempts', self::MAX_ATTEMPTS)) { + $this->logger->info('IP address blocked because it reached the maximum failed attempts in the last 30 minutes [action: {action}, attempts: {attempts}, ip: {ip}]', [ 'action' => $action, 'ip' => $ip, + 'attempts' => $attempts, ]); // If the ip made too many attempts within the last 30 mins we don't execute anymore throw new MaxDelayReached('Reached maximum delay'); } - if ($delay > 100) { - $this->logger->info('IP address throttled because it reached the attempts limit in the last 30 minutes [action: {action}, delay: {delay}, ip: {ip}]', [ + + $attempts = $this->getAttempts($ip, $action); + if ($attempts > 10) { + $this->logger->info('IP address throttled because it reached the attempts limit in the last 12 hours [action: {action}, attempts: {attempts}, ip: {ip}]', [ 'action' => $action, 'ip' => $ip, - 'delay' => $delay, + 'attempts' => $attempts, ]); } - if (!$this->config->getSystemValueBool('auth.bruteforce.protection.testing')) { - usleep($delay * 1000); + if ($attempts > 0) { + return $this->calculateDelay($attempts); } - return $delay; + + return 0; } } diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 6d3021aff71..959797fb962 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -14,6 +14,7 @@ use Exception; use InvalidArgumentException; use OC\Authentication\Token\PublicKeyTokenProvider; use OC\Authentication\Token\TokenCleanupJob; +use OC\Core\BackgroundJobs\GenerateMetadataJob; use OC\Log\Rotate; use OC\Preview\BackgroundCleanupJob; use OC\TextProcessing\RemoveOldTasksBackgroundJob; @@ -495,6 +496,7 @@ class Setup { $jobList->add(BackgroundCleanupJob::class); $jobList->add(RemoveOldTasksBackgroundJob::class); $jobList->add(CleanupDeletedUsers::class); + $jobList->add(GenerateMetadataJob::class); } /** diff --git a/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php b/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php index c6f26e3aa8b..42d073a024d 100644 --- a/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php +++ b/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php @@ -16,7 +16,7 @@ use OCP\Files\SimpleFS\ISimpleFolder; use Psr\Log\LoggerInterface; class RemoveOldTasksBackgroundJob extends TimedJob { - public const MAX_TASK_AGE_SECONDS = 60 * 60 * 24 * 7 * 4; // 4 weeks + public const MAX_TASK_AGE_SECONDS = 60 * 60 * 24 * 30 * 4; // 4 months private \OCP\Files\IAppData $appData; public function __construct( diff --git a/lib/public/Calendar/CalendarExportOptions.php b/lib/public/Calendar/CalendarExportOptions.php new file mode 100644 index 00000000000..bf21dd85ae4 --- /dev/null +++ b/lib/public/Calendar/CalendarExportOptions.php @@ -0,0 +1,68 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar; + +/** + * Calendar Export Options + * + * @since 32.0.0 + */ +final class CalendarExportOptions { + + /** @var 'ical'|'jcal'|'xcal' */ + private string $format = 'ical'; + private ?string $rangeStart = null; + private ?int $rangeCount = null; + + /** + * Gets the export format + * + * @return 'ical'|'jcal'|'xcal' (defaults to ical) + */ + public function getFormat(): string { + return $this->format; + } + + /** + * Sets the export format + * + * @param 'ical'|'jcal'|'xcal' $format + */ + public function setFormat(string $format): void { + $this->format = $format; + } + + /** + * Gets the start of the range to export + */ + public function getRangeStart(): ?string { + return $this->rangeStart; + } + + /** + * Sets the start of the range to export + */ + public function setRangeStart(?string $rangeStart): void { + $this->rangeStart = $rangeStart; + } + + /** + * Gets the number of objects to export + */ + public function getRangeCount(): ?int { + return $this->rangeCount; + } + + /** + * Sets the number of objects to export + */ + public function setRangeCount(?int $rangeCount): void { + $this->rangeCount = $rangeCount; + } +} diff --git a/lib/public/Calendar/ICalendarExport.php b/lib/public/Calendar/ICalendarExport.php new file mode 100644 index 00000000000..61b286e1668 --- /dev/null +++ b/lib/public/Calendar/ICalendarExport.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar; + +use Generator; + +/** + * ICalendar Interface Extension to export data + * + * @since 32.0.0 + */ +interface ICalendarExport { + + /** + * Export objects + * + * @since 32.0.0 + * + * @param CalendarExportOptions|null $options + * + * @return Generator<\Sabre\VObject\Component\VCalendar> + */ + public function export(?CalendarExportOptions $options): Generator; + +} diff --git a/lib/public/Calendar/ICalendarIsEnabled.php b/lib/public/Calendar/ICalendarIsEnabled.php new file mode 100644 index 00000000000..868159d208f --- /dev/null +++ b/lib/public/Calendar/ICalendarIsEnabled.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar; + +/** + * ICalendar Interface Extension + * + * @since 32.0.0 + */ +interface ICalendarIsEnabled { + + /** + * Indicates whether the calendar is enabled + * + * @since 32.0.0 + */ + public function isEnabled(): bool; + +} diff --git a/lib/public/Files/ObjectStore/IObjectStoreMetaData.php b/lib/public/Files/ObjectStore/IObjectStoreMetaData.php index 8359e83f573..9683873be36 100644 --- a/lib/public/Files/ObjectStore/IObjectStoreMetaData.php +++ b/lib/public/Files/ObjectStore/IObjectStoreMetaData.php @@ -9,7 +9,7 @@ namespace OCP\Files\ObjectStore; /** * Interface IObjectStoreMetaData * - * @psalm-type ObjectMetaData = array{mtime?: \DateTime, etag?: string, size?: int, mimetype?: string, filename?: string} + * @psalm-type ObjectMetaData = array{mtime?: \DateTime, etag?: string, size?: int, mimetype?: string, filename?: string, original-path?: string, original-storage?: string} * * @since 32.0.0 */ @@ -35,4 +35,13 @@ interface IObjectStoreMetaData { * @since 32.0.0 */ public function listObjects(string $prefix = ''): \Iterator; + + /** + * @param string $urn the unified resource name used to identify the object + * @param resource $stream stream with the data to write + * @param ObjectMetaData $metaData the metadata to set for the object + * @throws \Exception when something goes wrong, message will be logged + * @since 32.0.0 + */ + public function writeObjectWithMetaData(string $urn, $stream, array $metaData): void; } diff --git a/lib/public/IPreview.php b/lib/public/IPreview.php index 6ab4af1b7ca..5a2bcde69f1 100644 --- a/lib/public/IPreview.php +++ b/lib/public/IPreview.php @@ -71,12 +71,14 @@ interface IPreview { * @param bool $crop * @param string $mode * @param string $mimeType To force a given mimetype for the file (files_versions needs this) + * @param bool $cacheResult Whether or not to cache the preview on the filesystem. Default to true. Can be useful to set to false to limit the amount of stored previews. * @return ISimpleFile * @throws NotFoundException * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) * @since 11.0.0 - \InvalidArgumentException was added in 12.0.0 + * @since 32.0.0 - getPreview($cacheResult) added the $cacheResult argument to the signature */ - public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null); + public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null, bool $cacheResult = true); /** * Returns true if the passed mime type is supported diff --git a/lib/public/Lock/LockedException.php b/lib/public/Lock/LockedException.php index ce41d2d7242..799886a2dbb 100644 --- a/lib/public/Lock/LockedException.php +++ b/lib/public/Lock/LockedException.php @@ -24,6 +24,8 @@ class LockedException extends \Exception { /** @var string|null */ private $existingLock; + private ?string $readablePath; + /** * LockedException constructor. * @@ -34,6 +36,7 @@ class LockedException extends \Exception { * @since 8.1.0 */ public function __construct(string $path, ?\Exception $previous = null, ?string $existingLock = null, ?string $readablePath = null) { + $this->readablePath = $readablePath; if ($readablePath) { $message = "\"$path\"(\"$readablePath\") is locked"; } else { @@ -62,4 +65,13 @@ class LockedException extends \Exception { public function getExistingLock(): ?string { return $this->existingLock; } + + /** + * @return ?string + * @since 32.0.0 + */ + public function getReadablePath(): ?string { + return $this->readablePath; + } + } |