Skip to content

Commit

Permalink
fix: pdf
Browse files Browse the repository at this point in the history
  • Loading branch information
storytellerF committed Dec 19, 2024
1 parent c0441e6 commit af75e4e
Show file tree
Hide file tree
Showing 29 changed files with 295 additions and 117 deletions.
4 changes: 0 additions & 4 deletions backend/src/main/kotlin/com/storyteller_f/Backend.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.storyteller_f.media.FileSystemMediaService
import com.storyteller_f.media.MediaService
import com.storyteller_f.media.MinIoMediaService
import com.storyteller_f.naming.NameService
import com.storyteller_f.shared.model.MediaInfo
import com.storyteller_f.shared.type.PrimaryKey
import io.github.aakira.napier.Napier
import org.jetbrains.exposed.sql.Query
Expand Down Expand Up @@ -151,6 +150,3 @@ class UnauthorizedException : Exception()
class ForbiddenException(message: String = "Invalid operation") : Exception(message)
class CustomBadRequestException(message: String) : Exception(message)

fun getMediaInfo(iconUrl: String?): MediaInfo? {
return iconUrl?.let { MediaInfo(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import io.github.aakira.napier.Napier
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.future.await
import kotlinx.coroutines.withContext
import org.apache.http.ConnectionClosedException
import org.apache.http.HttpHost
import org.apache.http.auth.AuthScope
import org.apache.http.auth.UsernamePasswordCredentials
Expand Down Expand Up @@ -211,8 +212,11 @@ private suspend fun <T> useElasticClient(
ElasticsearchAsyncClient(transport).block()
}
}
} catch (e: ConnectException) {
throw Exception("elastic service unavailable", e)
} catch (e: Exception) {
if (e is ConnectException || e is ConnectionClosedException)
throw Exception("elastic service unavailable", e)
else
throw e
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.storyteller_f.media

import com.storyteller_f.shared.model.MediaInfo
import com.storyteller_f.shared.model.MediaItem
import java.awt.datatransfer.MimeTypeParseException
import java.io.File
import java.net.URLConnection
import java.nio.file.Files

class FileSystemMediaService(val base: String) : MediaService {
Expand All @@ -13,7 +17,7 @@ class FileSystemMediaService(val base: String) : MediaService {
}
}

override fun upload(bucketName: String, list: List<UploadPack>): Result<Unit> {
override fun upload(bucketName: String, list: List<UploadPack>): Result<List<MediaInfo?>> {
val file = File(root, bucketName)
if (!file.exists()) {
val r = file.mkdir()
Expand All @@ -34,14 +38,22 @@ class FileSystemMediaService(val base: String) : MediaService {
}
Files.copy(uploadPack.path.toPath(), target.toPath())
}
return Result.success(Unit)
return get(bucketName, list.map {
it.name
})
}

override fun get(bucketName: String, objList: List<String?>): Result<List<String?>> {
override fun get(bucketName: String, objList: List<String?>): Result<List<MediaInfo?>> {
return Result.success(objList.map {
when (it) {
null -> null
else -> "${base}amedia/$it"
else -> {
val file = File(root, "$bucketName/$it")
MediaInfo(
"${base}amedia/$it",
MediaItem(it, URLConnection.guessContentTypeFromName(it), file.length())
)
}
}
})
}
Expand All @@ -52,10 +64,10 @@ class FileSystemMediaService(val base: String) : MediaService {
return Result.success(Unit)
}

override fun list(bucketName: String, prefix: String): Result<List<String>> {
val file = File(root, "$bucketName/$prefix")
return Result.success(file.listFiles().orEmpty().map {
it.name
override fun list(bucketName: String, prefix: String): Result<List<MediaInfo?>> {
val file = File(root, "$bucketName$prefix")
return get(bucketName, file.listFiles().orEmpty().map {
"${prefix}${it.name}"
})
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.storyteller_f.media

import com.storyteller_f.shared.model.MediaInfo
import com.storyteller_f.shared.model.MediaItem
import java.io.File

data class UploadPack(val name: String, val path: File)

interface MediaService {
fun upload(bucketName: String, list: List<UploadPack>): Result<Unit>
fun upload(bucketName: String, list: List<UploadPack>): Result<List<MediaInfo?>>

fun get(bucketName: String, objList: List<String?>): Result<List<String?>>
fun get(bucketName: String, objList: List<String?>): Result<List<MediaInfo?>>

fun clean(bucketName: String): Result<Unit>

fun list(bucketName: String, prefix: String): Result<List<String>>
fun list(bucketName: String, prefix: String): Result<List<MediaInfo?>>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.storyteller_f.media

import com.storyteller_f.MinIoConnection
import com.storyteller_f.shared.model.MediaInfo
import com.storyteller_f.shared.model.MediaItem
import io.minio.*
import io.minio.http.Method
import java.util.concurrent.TimeUnit
Expand All @@ -15,36 +17,59 @@ class MinIoMediaService(private val connection: MinIoConnection) : MediaService
}
}

override fun list(bucketName: String, prefix: String): Result<List<String>> {
override fun list(bucketName: String, prefix: String): Result<List<MediaInfo?>> {
return useMinIoClient(connection) {
listObjects(
val names = listObjects(
ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(false).build()
).map {
it.get().objectName()
}
get(bucketName, names).getOrThrow()
}
}

override fun get(bucketName: String, objList: List<String?>): Result<List<String?>> {
private fun MinioClient.stat(
bucketName: String,
objName: String
): MediaItem {
val statObject =
statObject(StatObjectArgs.builder().bucket(bucketName).`object`(objName).build())
return MediaItem(objName, statObject.contentType(), statObject.size())
}

override fun get(bucketName: String, objList: List<String?>): Result<List<MediaInfo?>> {
return useMinIoClient(connection) {
objList.map {
getIconInMioIo(bucketName, it)
if (it == null) {
null
} else {
val url = getIconInMioIo(bucketName, it)
if (url != null) {
val item = stat(bucketName, it)
MediaInfo(url, item)
} else {
null
}
}

}
}
}

override fun upload(bucketName: String, list: List<UploadPack>): Result<Unit> {
override fun upload(bucketName: String, list: List<UploadPack>): Result<List<MediaInfo?>> {
return useMinIoClient(connection) {
if (!bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
makeBucket(MakeBucketArgs.builder().bucket(bucketName).build())
}
list.forEach { (objName, picFullPath) ->
uploadObject(
val names = list.map { (objName, picFullPath) ->
val response = uploadObject(
UploadObjectArgs.builder().bucket(
bucketName
).`object`(objName).filename(picFullPath.absolutePath).build()
)
response.`object`()
}
get(bucketName, names).getOrThrow()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/main/kotlin/com/storyteller_f/tables/Rooms.kt
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ private fun roomsResponse(list: List<Pair<RoomInfo, String?>>, backend: Backend)
it.second
}).map { icons ->
list.mapIndexed { i, roomPair ->
roomPair.first.copy(icon = getMediaInfo(icons[i]))
roomPair.first.copy(icon = icons[i])
}
}
}
8 changes: 2 additions & 6 deletions backend/src/main/kotlin/com/storyteller_f/tables/Users.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@ fun User.toUserInfo(): UserInfo {
fun toFinalUserInfo(p: Pair<UserInfo, String?>, backend: Backend): Result<UserInfo> {
val (userInfo, icon) = p
return backend.mediaService.get("amedia", listOf(icon)).map { value ->
userInfo.copy(avatar = value.firstOrNull()?.let {
MediaInfo(it)
})
userInfo.copy(avatar = value.firstOrNull())
}
}

Expand Down Expand Up @@ -181,9 +179,7 @@ suspend fun searchMembers(
it.second
}).map { value ->
PaginationResult(pairs.mapIndexed { index, pair ->
pair.first.copy(avatar = value[index]?.let {
MediaInfo(it)
})
pair.first.copy(avatar = value[index])
}, count)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ suspend fun HttpClient.getData() = serviceCatching {
}

suspend fun HttpClient.getTopicSnapshot(topicId: PrimaryKey) = serviceCatching {
get("topics/$topicId/snapshot")
get("topics/$topicId/snapshot").body<MediaInfo>()
}

suspend fun HttpClient.searchTopics(
Expand Down
2 changes: 2 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ kotlin {
implementation(libs.highlights)
implementation(libs.richeditor.compose)
implementation(libs.filekit.compose)
implementation(libs.compose.pdf)
implementation(libs.compose.multiplatform.media.player)

implementation(libs.multiplatform.settings)
implementation(libs.multiplatform.settings.no.arg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ private fun PreviewRoom(@PreviewParameter(MessageListProvider::class) topicInfos
ObjectType.ROOM,
"test",
MaterialTheme.colorScheme.tertiaryContainer,
null,
{},
{
}
Expand Down Expand Up @@ -102,6 +103,7 @@ private fun PreviewInputGroup() {
ObjectType.ROOM,
"test",
MaterialTheme.colorScheme.tertiaryContainer,
null,
{},
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@
<string name="refresh">Refresh</string>
<string name="no_content_yet">No content yet</string>
<string name="copy">Copy</string>
<string name="snapshot">Snapshot</string>
</resources>
24 changes: 17 additions & 7 deletions composeApp/src/commonMain/kotlin/com/storyteller_f/a/app/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ data object LoginScreen
data class TopicScreen(val topicId: PrimaryKey)

@Serializable
data class TopicComposeScreen(val objectType: String, val objectId: PrimaryKey, val enableExperimental: Boolean)
data class TopicComposeScreen(val objectType: String, val objectId: PrimaryKey, val enableExperimental: Boolean, val privateRoomId: PrimaryKey?)

@Serializable
data class MemberScreen(val objectType: String, val objectId: PrimaryKey)
Expand Down Expand Up @@ -123,8 +123,8 @@ private fun NavGraphBuilder.buildRootNav(
TopicPage(it.toRoute<TopicScreen>().topicId)
}
composable<TopicComposeScreen> {
val (objectType, objectId, enableExperimental) = it.toRoute<TopicComposeScreen>()
TopicComposePage(ObjectType.valueOf(objectType), objectId, enableExperimental) {
val (objectType, objectId, enableExperimental, privateRoomId) = it.toRoute<TopicComposeScreen>()
TopicComposePage(ObjectType.valueOf(objectType), objectId, enableExperimental, privateRoomId) {
navigator.popBackStack()
}
}
Expand Down Expand Up @@ -158,8 +158,13 @@ private fun newAppNav(navigator: NavHostController) = object : AppNav {
navigator.popBackStack(HomeScreen, false)
}

override fun gotoTopicCompose(objectType: ObjectType, objectId: PrimaryKey, enableExperimental: Boolean) {
navigator.navigate(route = TopicComposeScreen(objectType.name, objectId, enableExperimental))
override fun gotoTopicCompose(
objectType: ObjectType,
objectId: PrimaryKey,
enableExperimental: Boolean,
privateRoomId: PrimaryKey?
) {
navigator.navigate(route = TopicComposeScreen(objectType.name, objectId, enableExperimental, privateRoomId))
}

override fun gotoMemberPage(
Expand Down Expand Up @@ -223,7 +228,7 @@ interface AppNav {

fun gotoHome()

fun gotoTopicCompose(objectType: ObjectType, objectId: PrimaryKey, enableExperimental: Boolean)
fun gotoTopicCompose(objectType: ObjectType, objectId: PrimaryKey, enableExperimental: Boolean, privateRoomId: PrimaryKey?)

fun gotoMemberPage(objectId: PrimaryKey, objectType: ObjectType)

Expand Down Expand Up @@ -262,7 +267,12 @@ interface AppNav {
TODO("Not yet implemented")
}

override fun gotoTopicCompose(objectType: ObjectType, objectId: PrimaryKey, enableExperimental: Boolean) {
override fun gotoTopicCompose(
objectType: ObjectType,
objectId: PrimaryKey,
enableExperimental: Boolean,
privateRoomId: PrimaryKey?
) {
TODO("Not yet implemented")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ abstract class SimpleViewModel<T> : ViewModel() {

@Composable
inline fun <reified VM : ViewModel> viewModel(
keys: List<Comparable<*>>? = null,
keys: List<Comparable<*>?>? = null,
crossinline factory: () -> VM
): VM {
Napier.i {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ private fun CommunityFloatingButton(
val message = stringResource(Res.string.join_community_prompt)
FloatingActionButton(onClick = {
if (community?.isJoined == true) {
appNav.gotoTopicCompose(ObjectType.COMMUNITY, communityId, false)
appNav.gotoTopicCompose(ObjectType.COMMUNITY, communityId, false, null)
} else {
alertDialogState.showMessage(title, message)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,9 @@ fun RoomInputGroup(
val keyData by keysViewModel.handler.data.collectAsState()
val objectId = topicId ?: roomId
val objectType = if (topicId != null) ObjectType.TOPIC else ObjectType.ROOM
InputGroupInternal(objectId, objectType, input, MaterialTheme.colorScheme.tertiaryContainer, {
InputGroupInternal(objectId, objectType, input, MaterialTheme.colorScheme.tertiaryContainer, roomId.takeIf {
roomInfo.isPrivate
}, {
input = it
}, sendButton = {
val p1 = stringResource(Res.string.private_room_pub_key_loading)
Expand Down Expand Up @@ -501,6 +503,7 @@ fun InputGroupInternal(
objectType: ObjectType,
input: String,
backgroundColor: Color,
privateRoomId: PrimaryKey?,
updateInput: (String) -> Unit,
sendButton: @Composable () -> Unit
) {
Expand All @@ -523,9 +526,9 @@ fun InputGroupInternal(
}
val appNav = LocalAppNav.current
Icon(Icons.Default.OpenInFull, "open in full", modifier = Modifier.combinedClickable(onLongClick = {
appNav.gotoTopicCompose(objectType, objectId, true)
appNav.gotoTopicCompose(objectType, objectId, true, privateRoomId)
}) {
appNav.gotoTopicCompose(objectType, objectId, false)
appNav.gotoTopicCompose(objectType, objectId, false, privateRoomId)
})
}
})
Expand Down
Loading

0 comments on commit af75e4e

Please sign in to comment.