From 94cc09511aa4d7d9031771f5cb2c3757c12a6a87 Mon Sep 17 00:00:00 2001 From: Kirill <> Date: Wed, 20 Nov 2024 04:38:18 +0100 Subject: [PATCH 1/5] Applying copying routine that is not as neat but at least works; --- .../client/media/opfs/OpfsMediaStore.kt | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt b/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt index 760eb4878..a58f7e629 100644 --- a/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt +++ b/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt @@ -1,15 +1,12 @@ package net.folivo.trixnity.client.media.opfs import js.objects.jso -import js.typedarrays.Uint8Array import net.folivo.trixnity.client.media.MediaStore import net.folivo.trixnity.utils.ByteArrayFlow import net.folivo.trixnity.utils.KeyedMutex import net.folivo.trixnity.utils.byteArrayFlowFromReadableStream -import net.folivo.trixnity.utils.writeTo import okio.ByteString.Companion.toByteString import web.fs.FileSystemDirectoryHandle -import web.streams.WritableStream class OpfsMediaStore(private val basePath: FileSystemDirectoryHandle) : MediaStore { @@ -28,13 +25,17 @@ class OpfsMediaStore(private val basePath: FileSystemDirectoryHandle) : MediaSto getFileHandle(fileSystemSafe(url), jso { this.create = create }) override suspend fun addMedia(url: String, content: ByteArrayFlow) = basePathLock.withLock(url) { - @Suppress("UNCHECKED_CAST") - val writableFileStream = basePath.resolveUrl(url, true).createWritable() as WritableStream + val handle = basePath.resolveUrl(url, true) + val writable = handle.createWritable() try { - content.writeTo(writableFileStream) + content.collect { + writable.write(it) + } } catch (throwable: Throwable) { basePath.removeEntry(fileSystemSafe(url)) throw throwable + } finally { + writable.close() } } @@ -52,16 +53,21 @@ class OpfsMediaStore(private val basePath: FileSystemDirectoryHandle) : MediaSto } override suspend fun changeMediaUrl(oldUrl: String, newUrl: String) = basePathLock.withLock(oldUrl) { - val source = basePath.resolveUrl(oldUrl).getFile() basePathLock.withLock(newUrl) { - val writableFileStream = basePath.resolveUrl(newUrl, true).createWritable() + val handle = basePath.resolveUrl(newUrl, true) + val writable = handle.createWritable() + val content = byteArrayFlowFromReadableStream { basePath.resolveUrl(oldUrl).getFile().stream() } try { - writableFileStream.write(source) - } catch (throwable: Throwable) { + content.collect { + writable.write(it) + } + basePath.removeEntry(fileSystemSafe(oldUrl)) + } catch (e: Exception) { + // Revert copying operation. basePath.removeEntry(fileSystemSafe(newUrl)) - throw throwable + } finally { + writable.close() } } - basePath.removeEntry(fileSystemSafe(oldUrl)) } } \ No newline at end of file -- GitLab From d8bce785b87f549de03571df569674a5b60990cf Mon Sep 17 00:00:00 2001 From: Kirill <> Date: Wed, 20 Nov 2024 04:47:15 +0100 Subject: [PATCH 2/5] Random code touch-ups; --- .../net/folivo/trixnity/client/media/MediaService.kt | 12 +++++++----- .../client/room/OutboxMessageEventHandler.kt | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/trixnity-client/src/commonMain/kotlin/net/folivo/trixnity/client/media/MediaService.kt b/trixnity-client/src/commonMain/kotlin/net/folivo/trixnity/client/media/MediaService.kt index 114404a82..f7237fd39 100644 --- a/trixnity-client/src/commonMain/kotlin/net/folivo/trixnity/client/media/MediaService.kt +++ b/trixnity-client/src/commonMain/kotlin/net/folivo/trixnity/client/media/MediaService.kt @@ -76,7 +76,7 @@ class MediaServiceImpl( private const val MATRIX_SPEC_1_11 = "v1.11" const val UPLOAD_MEDIA_CACHE_URI_PREFIX = "upload://" const val UPLOAD_MEDIA_MXC_URI_PREFIX = "mxc://" - const val maxFileSizeForThumbnail = 1024 * 50_000 // = 50MB + const val MAX_FILE_SIZE_FOR_THUMBNAIL = 1024 * 50_000 // = 50MB } private suspend fun Media.saveMedia( @@ -164,7 +164,9 @@ class MediaServiceImpl( media.decryptAes256Ctr( initialisationVector = encryptedFile.initialisationVector.decodeUnpaddedBase64Bytes(), // url-safe base64 is given - key = encryptedFile.key.key.replace("-", "+").replace("_", "/") + key = encryptedFile.key.key + .replace("-", "+") + .replace("_", "/") .decodeUnpaddedBase64Bytes() ) } @@ -186,7 +188,7 @@ class MediaServiceImpl( height, method, progress = progress, - downloadHandler = downloadHandler + downloadHandler = downloadHandler, ) } @@ -226,7 +228,7 @@ class MediaServiceImpl( ): Pair? { val thumbnail = if (contentType?.contentType == "image") try { - createThumbnail(content.takeBytes(maxFileSizeForThumbnail).toByteArray(), 600, 600) + createThumbnail(content.takeBytes(MAX_FILE_SIZE_FOR_THUMBNAIL).toByteArray(), 600, 600) } catch (e: Exception) { log.warn(e) { "could not create thumbnail from file with content type $contentType" } return null @@ -268,7 +270,7 @@ class MediaServiceImpl( ): Pair? { val thumbnail = if (contentType?.contentType == "image") try { - createThumbnail(content.takeBytes(maxFileSizeForThumbnail).toByteArray(), 600, 600) + createThumbnail(content.takeBytes(MAX_FILE_SIZE_FOR_THUMBNAIL).toByteArray(), 600, 600) } catch (e: Exception) { log.debug { "could not create thumbnail from file with content type $contentType" } return null diff --git a/trixnity-client/src/commonMain/kotlin/net/folivo/trixnity/client/room/OutboxMessageEventHandler.kt b/trixnity-client/src/commonMain/kotlin/net/folivo/trixnity/client/room/OutboxMessageEventHandler.kt index a82f28238..5c97ded0c 100644 --- a/trixnity-client/src/commonMain/kotlin/net/folivo/trixnity/client/room/OutboxMessageEventHandler.kt +++ b/trixnity-client/src/commonMain/kotlin/net/folivo/trixnity/client/room/OutboxMessageEventHandler.kt @@ -79,7 +79,8 @@ class OutboxMessageEventHandler( val alreadyProcessedOutboxMessages = mutableSetOf() outboxMessages .map { outbox -> - // we need to filterKeys twice, because input and output of flattenNotNull are not in sync, and we do not want to flatten unnecessary + // We need to filterKeys twice, because input and output of flattenNotNull are not in sync, + // and we do not want to flatten unnecessary. outbox.filterKeys { !alreadyProcessedOutboxMessages.contains(it) } } .flattenNotNull() -- GitLab From 5bf54009696d2196541a5800b509e2901adc971a Mon Sep 17 00:00:00 2001 From: Kirill <> Date: Wed, 20 Nov 2024 05:19:26 +0100 Subject: [PATCH 3/5] Fixed exception not being forwarded; --- .../net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt b/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt index a58f7e629..11a32785f 100644 --- a/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt +++ b/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt @@ -32,6 +32,7 @@ class OpfsMediaStore(private val basePath: FileSystemDirectoryHandle) : MediaSto writable.write(it) } } catch (throwable: Throwable) { + // Revert adding operation. basePath.removeEntry(fileSystemSafe(url)) throw throwable } finally { @@ -62,9 +63,10 @@ class OpfsMediaStore(private val basePath: FileSystemDirectoryHandle) : MediaSto writable.write(it) } basePath.removeEntry(fileSystemSafe(oldUrl)) - } catch (e: Exception) { + } catch (throwable: Throwable) { // Revert copying operation. basePath.removeEntry(fileSystemSafe(newUrl)) + throw throwable } finally { writable.close() } -- GitLab From cbc563034089834173323aa37a74dfc96564451b Mon Sep 17 00:00:00 2001 From: Kirill <> Date: Wed, 20 Nov 2024 10:06:25 +0100 Subject: [PATCH 4/5] Changelog; --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bce4fb090..30d8e91f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed +- Fixed OPFS-Media-Store. ### Deprecated -- GitLab From bb2d443c4b7f01d81f72a42513828789f0161bb1 Mon Sep 17 00:00:00 2001 From: Kirill <> Date: Fri, 22 Nov 2024 17:28:42 +0100 Subject: [PATCH 5/5] OPFS test fix and wip OPFS fs fix; --- .../client/media/opfs/OpfsMediaStore.kt | 63 +++++++++++++------ .../client/media/opfs/OpfsMediaStoreTest.kt | 31 ++++++--- 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt b/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt index 11a32785f..7bdf23255 100644 --- a/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt +++ b/trixnity-client/trixnity-client-media-opfs/src/jsMain/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStore.kt @@ -7,19 +7,13 @@ import net.folivo.trixnity.utils.KeyedMutex import net.folivo.trixnity.utils.byteArrayFlowFromReadableStream import okio.ByteString.Companion.toByteString import web.fs.FileSystemDirectoryHandle +import web.fs.WriteCommandType +import web.fs.WriteParams class OpfsMediaStore(private val basePath: FileSystemDirectoryHandle) : MediaStore { private val basePathLock = KeyedMutex() - override suspend fun clearCache() = deleteAll() - - override suspend fun deleteAll() { - for (entry in basePath.values()) { - basePath.removeEntry(entry.name) - } - } - private fun fileSystemSafe(url: String) = url.encodeToByteArray().toByteString().sha256().base64Url() private suspend fun FileSystemDirectoryHandle.resolveUrl(url: String, create: Boolean = false) = getFileHandle(fileSystemSafe(url), jso { this.create = create }) @@ -50,26 +44,59 @@ class OpfsMediaStore(private val basePath: FileSystemDirectoryHandle) : MediaSto } override suspend fun deleteMedia(url: String) = basePathLock.withLock(url) { - basePath.removeEntry(fileSystemSafe(url)) + try { + basePath.removeEntry(fileSystemSafe(url)) + } catch (throwable: Throwable) { + // TODO: log + } } override suspend fun changeMediaUrl(oldUrl: String, newUrl: String) = basePathLock.withLock(oldUrl) { + val writable = basePath.resolveUrl(oldUrl).getFile() basePathLock.withLock(newUrl) { - val handle = basePath.resolveUrl(newUrl, true) - val writable = handle.createWritable() - val content = byteArrayFlowFromReadableStream { basePath.resolveUrl(oldUrl).getFile().stream() } + val writableFileStream = basePath.resolveUrl(newUrl, true).createWritable() + val params: WriteParams = jso { + type = WriteCommandType.write + data = writable + position = 0.0 + size = writable.size + } try { - content.collect { - writable.write(it) - } + writableFileStream.write(writable) /* BufferSource | Blob | string | WriteParams */ basePath.removeEntry(fileSystemSafe(oldUrl)) } catch (throwable: Throwable) { // Revert copying operation. basePath.removeEntry(fileSystemSafe(newUrl)) throw throwable - } finally { - writable.close() } } } -} \ No newline at end of file + +// override suspend fun changeMediaUrl(oldUrl: String, newUrl: String) = basePathLock.withLock(oldUrl) { +// basePathLock.withLock(newUrl) { +// val handle = basePath.resolveUrl(newUrl, true) +// val writable = handle.createWritable() +// val content = byteArrayFlowFromReadableStream { basePath.resolveUrl(oldUrl).getFile().stream() } +// try { +// content.collect { +// writable.write(it) +// } +// basePath.removeEntry(fileSystemSafe(oldUrl)) +// } catch (throwable: Throwable) { +// // Revert copying operation. +// basePath.removeEntry(fileSystemSafe(newUrl)) +// throw throwable +// } finally { +// writable.close() +// } +// } +// } + + override suspend fun clearCache() = deleteAll() + + override suspend fun deleteAll() { + for (entry in basePath.values()) { + basePath.removeEntry(entry.name) + } + } +} diff --git a/trixnity-client/trixnity-client-media-opfs/src/jsTest/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStoreTest.kt b/trixnity-client/trixnity-client-media-opfs/src/jsTest/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStoreTest.kt index ff4ea508d..af6771507 100644 --- a/trixnity-client/trixnity-client-media-opfs/src/jsTest/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStoreTest.kt +++ b/trixnity-client/trixnity-client-media-opfs/src/jsTest/kotlin/net/folivo/trixnity/client/media/opfs/OpfsMediaStoreTest.kt @@ -28,6 +28,7 @@ class OpfsMediaStoreTest { testBody() } catch (throwable: Throwable) { throwable.printStackTrace() + throw throwable } finally { for (entry in basePath.values()) { basePath.removeEntry(entry.name) @@ -70,9 +71,12 @@ class OpfsMediaStoreTest { @Test fun shouldGetMedia() = runThisTest { cut.init() - basePath.getFileHandle("K5pAaUF5iDoN1BsrFr4kJ0bP8ayM_Q_ftEtyeb_FY2I=").createWritable().apply { - write("hi".encodeToByteArray().asInt8Array()) - } + + basePath.getFileHandle("K5pAaUF5iDoN1BsrFr4kJ0bP8ayM_Q_ftEtyeb_FY2I=", jso { create = true }) + .createWritable().apply { + write("hi".encodeToByteArray().asInt8Array()) + }.close() + cut.getMedia("url1")?.toByteArray()?.decodeToString() shouldBe "hi" } @@ -85,9 +89,13 @@ class OpfsMediaStoreTest { @Test fun shouldDeleteMedia() = runThisTest { cut.init() - basePath.getFileHandle("K5pAaUF5iDoN1BsrFr4kJ0bP8ayM_Q_ftEtyeb_FY2I=").createWritable().apply { - write("hi".encodeToByteArray().asInt8Array()) - } + + basePath.getFileHandle("K5pAaUF5iDoN1BsrFr4kJ0bP8ayM_Q_ftEtyeb_FY2I=", jso { create = true }) + .createWritable().apply { + write("hi".encodeToByteArray().asInt8Array()) + }.close() + + basePath.values().toList().size shouldBe 1 cut.deleteMedia("url1") basePath.values().toList().size shouldBe 0 } @@ -102,13 +110,18 @@ class OpfsMediaStoreTest { @Test fun shouldChangeMediaUrl() = runThisTest { cut.init() - basePath.getFileHandle("K5pAaUF5iDoN1BsrFr4kJ0bP8ayM_Q_ftEtyeb_FY2I=").createWritable().apply { - write("hi".encodeToByteArray().asInt8Array()) - } + + basePath.getFileHandle("K5pAaUF5iDoN1BsrFr4kJ0bP8ayM_Q_ftEtyeb_FY2I=", jso { create = true }) + .createWritable().apply { + write("hi".encodeToByteArray().asInt8Array()) + }.close() + cut.changeMediaUrl("url1", "url2") Uint8Array( basePath.getFileHandle("hnKdljIEgbx_eKM0uMgfIWYx_slrDvGQQFN8QUQ4QGg=").getFile().arrayBuffer() ).toByteArray().decodeToString() shouldBe "hi" + + cut.getMedia("url1")?.toByteArray()?.decodeToString() shouldBe null } private suspend fun AsyncIterableIterator.toList(): List { -- GitLab