diff options
Diffstat (limited to 'lib')
41 files changed, 483 insertions, 155 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 6264126b028..f15d4e2d55f 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', @@ -318,6 +321,7 @@ return array( 'OCP\\DirectEditing\\IToken' => $baseDir . '/lib/public/DirectEditing/IToken.php', 'OCP\\DirectEditing\\RegisterDirectEditorEvent' => $baseDir . '/lib/public/DirectEditing/RegisterDirectEditorEvent.php', 'OCP\\Encryption\\Exceptions\\GenericEncryptionException' => $baseDir . '/lib/public/Encryption/Exceptions/GenericEncryptionException.php', + 'OCP\\Encryption\\Exceptions\\InvalidHeaderException' => $baseDir . '/lib/public/Encryption/Exceptions/InvalidHeaderException.php', 'OCP\\Encryption\\IEncryptionModule' => $baseDir . '/lib/public/Encryption/IEncryptionModule.php', 'OCP\\Encryption\\IFile' => $baseDir . '/lib/public/Encryption/IFile.php', 'OCP\\Encryption\\IManager' => $baseDir . '/lib/public/Encryption/IManager.php', @@ -778,6 +782,7 @@ return array( 'OCP\\Share\\IShareHelper' => $baseDir . '/lib/public/Share/IShareHelper.php', 'OCP\\Share\\IShareProvider' => $baseDir . '/lib/public/Share/IShareProvider.php', 'OCP\\Share\\IShareProviderSupportsAccept' => $baseDir . '/lib/public/Share/IShareProviderSupportsAccept.php', + 'OCP\\Share\\IShareProviderSupportsAllSharesInFolder' => $baseDir . '/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php', 'OCP\\Share\\IShareProviderWithNotification' => $baseDir . '/lib/public/Share/IShareProviderWithNotification.php', 'OCP\\Share_Backend' => $baseDir . '/lib/public/Share_Backend.php', 'OCP\\Share_Backend_Collection' => $baseDir . '/lib/public/Share_Backend_Collection.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 5771a621afe..dfb238acbf3 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', @@ -359,6 +362,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\DirectEditing\\IToken' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/IToken.php', 'OCP\\DirectEditing\\RegisterDirectEditorEvent' => __DIR__ . '/../../..' . '/lib/public/DirectEditing/RegisterDirectEditorEvent.php', 'OCP\\Encryption\\Exceptions\\GenericEncryptionException' => __DIR__ . '/../../..' . '/lib/public/Encryption/Exceptions/GenericEncryptionException.php', + 'OCP\\Encryption\\Exceptions\\InvalidHeaderException' => __DIR__ . '/../../..' . '/lib/public/Encryption/Exceptions/InvalidHeaderException.php', 'OCP\\Encryption\\IEncryptionModule' => __DIR__ . '/../../..' . '/lib/public/Encryption/IEncryptionModule.php', 'OCP\\Encryption\\IFile' => __DIR__ . '/../../..' . '/lib/public/Encryption/IFile.php', 'OCP\\Encryption\\IManager' => __DIR__ . '/../../..' . '/lib/public/Encryption/IManager.php', @@ -819,6 +823,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Share\\IShareHelper' => __DIR__ . '/../../..' . '/lib/public/Share/IShareHelper.php', 'OCP\\Share\\IShareProvider' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProvider.php', 'OCP\\Share\\IShareProviderSupportsAccept' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProviderSupportsAccept.php', + 'OCP\\Share\\IShareProviderSupportsAllSharesInFolder' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php', 'OCP\\Share\\IShareProviderWithNotification' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProviderWithNotification.php', 'OCP\\Share_Backend' => __DIR__ . '/../../..' . '/lib/public/Share_Backend.php', 'OCP\\Share_Backend_Collection' => __DIR__ . '/../../..' . '/lib/public/Share_Backend_Collection.php', diff --git a/lib/l10n/de_DE.js b/lib/l10n/de_DE.js index 2d101052cf5..8b00460854c 100644 --- a/lib/l10n/de_DE.js +++ b/lib/l10n/de_DE.js @@ -133,7 +133,7 @@ OC.L10N.register( "View %s on the fediverse" : "Zeige %s auf dem Fediverse", "Phone" : "Telefon", "Call %s" : "%s anrufen", - "Twitter" : "Twitter", + "Twitter" : "X", "View %s on Twitter" : "%s auf Twitter anzeigen", "Website" : "Webseite", "Visit %s" : "%s besuchen", diff --git a/lib/l10n/de_DE.json b/lib/l10n/de_DE.json index ea316cb1060..602206cda8d 100644 --- a/lib/l10n/de_DE.json +++ b/lib/l10n/de_DE.json @@ -131,7 +131,7 @@ "View %s on the fediverse" : "Zeige %s auf dem Fediverse", "Phone" : "Telefon", "Call %s" : "%s anrufen", - "Twitter" : "Twitter", + "Twitter" : "X", "View %s on Twitter" : "%s auf Twitter anzeigen", "Website" : "Webseite", "Visit %s" : "%s besuchen", diff --git a/lib/l10n/en_GB.js b/lib/l10n/en_GB.js index 93782960c94..2494ab4aca4 100644 --- a/lib/l10n/en_GB.js +++ b/lib/l10n/en_GB.js @@ -201,6 +201,7 @@ OC.L10N.register( "Path is already shared with this group" : "Path is already shared with this group", "Link sharing is not allowed" : "Link sharing is not allowed", "Public upload is not allowed" : "Public upload is not allowed", + "You cannot share a folder that contains other shares" : "You cannot share a folder that contains other shares", "Sharing is disabled" : "Sharing is disabled", "Sharing is disabled for you" : "Sharing is disabled for you", "Cannot share with the share owner" : "Cannot share with the share owner", @@ -272,6 +273,7 @@ OC.L10N.register( "A valid Login must be provided" : "A valid Login must be provided", "Login contains whitespace at the beginning or at the end" : "Login contains whitespace at the beginning or at the end", "Login must not consist of dots only" : "Login must not consist of dots only", + "Login is too long" : "Login is too long", "Login is invalid because files already exist for this user" : "Login is invalid because files already exist for this user", "Account disabled" : "Account disabled", "Login canceled by app" : "Login cancelled by app", @@ -362,6 +364,11 @@ OC.L10N.register( "How many images to generate" : "How many images to generate", "Output images" : "Output images", "The generated images" : "The generated images", + "Generate speech" : "Generate speech", + "Generate speech from a transcript" : "Generate speech from a transcript", + "Write transcript that you want the assistant to generate speech from" : "Write transcript that you want the assistant to generate speech from", + "Output speech" : "Output speech", + "The generated speech" : "The generated speech", "Free text to text prompt" : "Free text to text prompt", "Runs an arbitrary prompt through a language model that returns a reply" : "Runs an arbitrary prompt through a language model that returns a reply", "Describe a task that you want the assistant to do or ask a question" : "Describe a task that you want the assistant to do or ask a question", diff --git a/lib/l10n/en_GB.json b/lib/l10n/en_GB.json index 40402118ddf..08e84f39af1 100644 --- a/lib/l10n/en_GB.json +++ b/lib/l10n/en_GB.json @@ -199,6 +199,7 @@ "Path is already shared with this group" : "Path is already shared with this group", "Link sharing is not allowed" : "Link sharing is not allowed", "Public upload is not allowed" : "Public upload is not allowed", + "You cannot share a folder that contains other shares" : "You cannot share a folder that contains other shares", "Sharing is disabled" : "Sharing is disabled", "Sharing is disabled for you" : "Sharing is disabled for you", "Cannot share with the share owner" : "Cannot share with the share owner", @@ -270,6 +271,7 @@ "A valid Login must be provided" : "A valid Login must be provided", "Login contains whitespace at the beginning or at the end" : "Login contains whitespace at the beginning or at the end", "Login must not consist of dots only" : "Login must not consist of dots only", + "Login is too long" : "Login is too long", "Login is invalid because files already exist for this user" : "Login is invalid because files already exist for this user", "Account disabled" : "Account disabled", "Login canceled by app" : "Login cancelled by app", @@ -360,6 +362,11 @@ "How many images to generate" : "How many images to generate", "Output images" : "Output images", "The generated images" : "The generated images", + "Generate speech" : "Generate speech", + "Generate speech from a transcript" : "Generate speech from a transcript", + "Write transcript that you want the assistant to generate speech from" : "Write transcript that you want the assistant to generate speech from", + "Output speech" : "Output speech", + "The generated speech" : "The generated speech", "Free text to text prompt" : "Free text to text prompt", "Runs an arbitrary prompt through a language model that returns a reply" : "Runs an arbitrary prompt through a language model that returns a reply", "Describe a task that you want the assistant to do or ask a question" : "Describe a task that you want the assistant to do or ask a question", 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/l10n/pl.js b/lib/l10n/pl.js index 213b78d5c28..0d9fd0cb98a 100644 --- a/lib/l10n/pl.js +++ b/lib/l10n/pl.js @@ -307,6 +307,7 @@ OC.L10N.register( "Chat" : "Rozmowa", "Chat history" : "Historia rozmów", "Generates a possible headline for a text." : "Generuje możliwy nagłówek tekstu.", + "Original text" : "Tekst oryginalny", "Text" : "Tekst", "Summarize" : "Podsumuj", "Summary" : "Podsumowanie", diff --git a/lib/l10n/pl.json b/lib/l10n/pl.json index 6837f941bb7..c97634e8eff 100644 --- a/lib/l10n/pl.json +++ b/lib/l10n/pl.json @@ -305,6 +305,7 @@ "Chat" : "Rozmowa", "Chat history" : "Historia rozmów", "Generates a possible headline for a text." : "Generuje możliwy nagłówek tekstu.", + "Original text" : "Tekst oryginalny", "Text" : "Tekst", "Summarize" : "Podsumuj", "Summary" : "Podsumowanie", diff --git a/lib/l10n/zh_HK.js b/lib/l10n/zh_HK.js index 7a98e4b4905..a85c29e400c 100644 --- a/lib/l10n/zh_HK.js +++ b/lib/l10n/zh_HK.js @@ -201,6 +201,7 @@ OC.L10N.register( "Path is already shared with this group" : "已與此群組分享了路徑", "Link sharing is not allowed" : "不允許連結分享", "Public upload is not allowed" : "不允許公開上傳", + "You cannot share a folder that contains other shares" : "您無法分享包含其他分享的資料夾", "Sharing is disabled" : "已停用分享", "Sharing is disabled for you" : "您已停用分享", "Cannot share with the share owner" : "無法與分享擁有者分享", @@ -272,6 +273,7 @@ OC.L10N.register( "A valid Login must be provided" : "必須提供有效帳戶", "Login contains whitespace at the beginning or at the end" : "帳戶的開頭或結尾有空白", "Login must not consist of dots only" : "帳戶不能只包含小數點", + "Login is too long" : "帳號太長了", "Login is invalid because files already exist for this user" : "帳戶無效,因為此用戶的檔案已經存在", "Account disabled" : "帳戶已停用", "Login canceled by app" : "登入已被應用程式取消", @@ -362,6 +364,11 @@ OC.L10N.register( "How many images to generate" : "要產生多少圖像", "Output images" : "輸出圖像", "The generated images" : "產生的圖像", + "Generate speech" : "產生語音", + "Generate speech from a transcript" : "從文字稿産生語音", + "Write transcript that you want the assistant to generate speech from" : "寫下您想要小幫手產生語音的文字稿", + "Output speech" : "輸出語音", + "The generated speech" : "產生的語音", "Free text to text prompt" : "文字提示的自由文字", "Runs an arbitrary prompt through a language model that returns a reply" : "透過回傳回覆的語言模型執行任意提示", "Describe a task that you want the assistant to do or ask a question" : "描述您希望助理執行的任務或提出問題", diff --git a/lib/l10n/zh_HK.json b/lib/l10n/zh_HK.json index 94658c90974..b58db1e510c 100644 --- a/lib/l10n/zh_HK.json +++ b/lib/l10n/zh_HK.json @@ -199,6 +199,7 @@ "Path is already shared with this group" : "已與此群組分享了路徑", "Link sharing is not allowed" : "不允許連結分享", "Public upload is not allowed" : "不允許公開上傳", + "You cannot share a folder that contains other shares" : "您無法分享包含其他分享的資料夾", "Sharing is disabled" : "已停用分享", "Sharing is disabled for you" : "您已停用分享", "Cannot share with the share owner" : "無法與分享擁有者分享", @@ -270,6 +271,7 @@ "A valid Login must be provided" : "必須提供有效帳戶", "Login contains whitespace at the beginning or at the end" : "帳戶的開頭或結尾有空白", "Login must not consist of dots only" : "帳戶不能只包含小數點", + "Login is too long" : "帳號太長了", "Login is invalid because files already exist for this user" : "帳戶無效,因為此用戶的檔案已經存在", "Account disabled" : "帳戶已停用", "Login canceled by app" : "登入已被應用程式取消", @@ -360,6 +362,11 @@ "How many images to generate" : "要產生多少圖像", "Output images" : "輸出圖像", "The generated images" : "產生的圖像", + "Generate speech" : "產生語音", + "Generate speech from a transcript" : "從文字稿産生語音", + "Write transcript that you want the assistant to generate speech from" : "寫下您想要小幫手產生語音的文字稿", + "Output speech" : "輸出語音", + "The generated speech" : "產生的語音", "Free text to text prompt" : "文字提示的自由文字", "Runs an arbitrary prompt through a language model that returns a reply" : "透過回傳回覆的語言模型執行任意提示", "Describe a task that you want the assistant to do or ask a question" : "描述您希望助理執行的任務或提出問題", 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/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index ba23f3c43ec..0de009f0894 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -18,6 +18,7 @@ use OC\Files\Storage\Common; use OC\Files\Storage\LocalTempFileTrait; use OC\Memcache\ArrayCache; use OCP\Cache\CappedMemoryCache; +use OCP\Encryption\Exceptions\InvalidHeaderException; use OCP\Encryption\IFile; use OCP\Encryption\IManager; use OCP\Encryption\Keys\IStorage; @@ -344,6 +345,16 @@ class Encryption extends Wrapper { if ($shouldEncrypt === true && $encryptionModule !== null) { $this->encryptedPaths->set($this->util->stripPartialFileExtension($path), true); $headerSize = $this->getHeaderSize($path); + if ($mode === 'r' && $headerSize === 0) { + $firstBlock = $this->readFirstBlock($path); + if (!$firstBlock) { + throw new InvalidHeaderException("Unable to get header block for $path"); + } elseif (!str_starts_with($firstBlock, Util::HEADER_START)) { + throw new InvalidHeaderException("Unable to get header size for $path, file doesn't start with encryption header"); + } else { + throw new InvalidHeaderException("Unable to get header size for $path, even though file does start with encryption header"); + } + } $source = $this->storage->fopen($path, $mode); if (!is_resource($source)) { return false; 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/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index a257bc4f7b5..e1eebe1e450 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -5,6 +5,7 @@ * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-only */ + namespace OC\Share20; use OC\Files\Cache\Cache; @@ -31,6 +32,7 @@ use OCP\Share\IAttributes; use OCP\Share\IManager; use OCP\Share\IShare; use OCP\Share\IShareProviderSupportsAccept; +use OCP\Share\IShareProviderSupportsAllSharesInFolder; use OCP\Share\IShareProviderWithNotification; use Psr\Log\LoggerInterface; use function str_starts_with; @@ -40,7 +42,7 @@ use function str_starts_with; * * @package OC\Share20 */ -class DefaultShareProvider implements IShareProviderWithNotification, IShareProviderSupportsAccept { +class DefaultShareProvider implements IShareProviderWithNotification, IShareProviderSupportsAccept, IShareProviderSupportsAllSharesInFolder { // Special share type for user modified group shares public const SHARE_TYPE_USERGROUP = 2; @@ -603,6 +605,17 @@ class DefaultShareProvider implements IShareProviderWithNotification, IShareProv throw new \Exception('non-shallow getSharesInFolder is no longer supported'); } + return $this->getSharesInFolderInternal($userId, $node, $reshares); + } + + public function getAllSharesInFolder(Folder $node): array { + return $this->getSharesInFolderInternal(null, $node, null); + } + + /** + * @return array<int, list<IShare>> + */ + private function getSharesInFolderInternal(?string $userId, Folder $node, ?bool $reshares): array { $qb = $this->dbConn->getQueryBuilder(); $qb->select('s.*', 'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash', @@ -613,18 +626,20 @@ class DefaultShareProvider implements IShareProviderWithNotification, IShareProv $qb->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK], IQueryBuilder::PARAM_INT_ARRAY))); - /** - * Reshares for this user are shares where they are the owner. - */ - if ($reshares === false) { - $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))); - } else { - $qb->andWhere( - $qb->expr()->orX( - $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), - $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)) - ) - ); + if ($userId !== null) { + /** + * Reshares for this user are shares where they are the owner. + */ + if ($reshares !== true) { + $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))); + } else { + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)), + $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)) + ) + ); + } } // todo? maybe get these from the oc_mounts table @@ -656,7 +671,6 @@ class DefaultShareProvider implements IShareProviderWithNotification, IShareProv foreach ($chunks as $chunk) { $qb->setParameter('chunk', $chunk, IQueryBuilder::PARAM_INT_ARRAY); - $a = $qb->getSQL(); $cursor = $qb->executeQuery(); while ($data = $cursor->fetch()) { $shares[$data['fileid']][] = $this->createShare($data); diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 3b247475afa..2104c07593a 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -51,6 +51,7 @@ use OCP\Share\IProviderFactory; use OCP\Share\IShare; use OCP\Share\IShareProvider; use OCP\Share\IShareProviderSupportsAccept; +use OCP\Share\IShareProviderSupportsAllSharesInFolder; use OCP\Share\IShareProviderWithNotification; use Psr\Log\LoggerInterface; @@ -1213,11 +1214,13 @@ class Manager implements IManager { $shares = []; foreach ($providers as $provider) { if ($isOwnerless) { - foreach ($node->getDirectoryListing() as $childNode) { - $data = $provider->getSharesByPath($childNode); - $fid = $childNode->getId(); - $shares[$fid] ??= []; - $shares[$fid] = array_merge($shares[$fid], $data); + // If the provider does not implement the additional interface, + // we lack a performant way of querying all shares and therefore ignore the provider. + if ($provider instanceof IShareProviderSupportsAllSharesInFolder) { + foreach ($provider->getAllSharesInFolder($node) as $fid => $data) { + $shares[$fid] ??= []; + $shares[$fid] = array_merge($shares[$fid], $data); + } } } else { foreach ($provider->getSharesInFolder($userId, $node, $reshares) as $fid => $data) { 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/Encryption/Exceptions/InvalidHeaderException.php b/lib/public/Encryption/Exceptions/InvalidHeaderException.php new file mode 100644 index 00000000000..f7213577fb6 --- /dev/null +++ b/lib/public/Encryption/Exceptions/InvalidHeaderException.php @@ -0,0 +1,17 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\Encryption\Exceptions; + +use OCP\HintException; + +/** + * Class InvalidHeaderException + * + * @since 32.0.0 + */ +class InvalidHeaderException extends HintException { +} 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; + } + } diff --git a/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php b/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php new file mode 100644 index 00000000000..e27da7682ce --- /dev/null +++ b/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php @@ -0,0 +1,24 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Share; + +use OCP\Files\Folder; + +/** + * Allows defining a IShareProvider with support for the getAllSharesInFolder method. + * + * @since 32.0.0 + */ +interface IShareProviderSupportsAllSharesInFolder extends IShareProvider { + /** + * Get all shares in a folder. + * + * @return array<int, list<IShare>> + * @since 32.0.0 + */ + public function getAllSharesInFolder(Folder $node): array; +} |