From 82974b5abeb827b78906a7fafc8a10a9b5ba10c7 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 2 Nov 2023 18:25:50 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=E3=83=AA=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=AE=E3=83=A1=E3=83=87=E3=82=A3=E3=82=A2=E3=82=92?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/objects/note/APNoteService.kt | 19 ++++++++++++++++++- .../core/service/media/MediaService.kt | 5 +++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt index c221e9fb..dd2ec6ae 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt @@ -13,6 +13,8 @@ import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.PostRepository import dev.usbharu.hideout.core.domain.model.post.Visibility import dev.usbharu.hideout.core.query.PostQueryService +import dev.usbharu.hideout.core.service.media.MediaService +import dev.usbharu.hideout.core.service.media.RemoteMedia import dev.usbharu.hideout.core.service.post.PostService import io.ktor.client.plugins.* import kotlinx.coroutines.CoroutineScope @@ -52,7 +54,8 @@ class APNoteServiceImpl( private val postService: PostService, private val apResourceResolveService: APResourceResolveService, private val postBuilder: Post.PostBuilder, - private val noteQueryService: NoteQueryService + private val noteQueryService: NoteQueryService, + private val mediaService: MediaService ) : APNoteService { @@ -123,6 +126,19 @@ class APNoteServiceImpl( postQueryService.findByUrl(it) } + + val mediaList = note.attachment + .filter { it.url != null } + .map { + mediaService.uploadRemoteMedia( + RemoteMedia( + (it.name ?: it.url)!!, + it.url!!, it.mediaType ?: "application/octet-stream" + ) + ) + } + .map { it.id } + // TODO: リモートのメディア処理を追加 postService.createRemote( postBuilder.of( @@ -135,6 +151,7 @@ class APNoteServiceImpl( replyId = reply?.id, sensitive = note.sensitive, apId = note.id ?: url, + mediaIds = mediaList ) ) return note diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaService.kt index 75d928e8..b85c497a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaService.kt @@ -1,8 +1,9 @@ package dev.usbharu.hideout.core.service.media +import dev.usbharu.hideout.core.domain.model.media.Media import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest interface MediaService { - suspend fun uploadLocalMedia(mediaRequest: MediaRequest): dev.usbharu.hideout.core.domain.model.media.Media - suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia) + suspend fun uploadLocalMedia(mediaRequest: MediaRequest): Media + suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media } From 1e322fb8ae0e249920798ac4967c8cc5817ee8f6 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 2 Nov 2023 18:46:48 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=E3=83=AA=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=AE=E3=83=A1=E3=83=87=E3=82=A3=E3=82=A2=E3=82=92?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E3=81=97=E3=81=9F=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=8B=95=E3=81=8B=E3=81=99=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/service/media/MediaServiceImpl.kt | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt index ee507aed..d439099d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt @@ -3,9 +3,11 @@ package dev.usbharu.hideout.core.service.media import dev.usbharu.hideout.core.domain.exception.media.MediaFileSizeIsZeroException import dev.usbharu.hideout.core.domain.exception.media.MediaSaveException import dev.usbharu.hideout.core.domain.exception.media.UnsupportedMediaException +import dev.usbharu.hideout.core.domain.model.media.Media import dev.usbharu.hideout.core.domain.model.media.MediaRepository import dev.usbharu.hideout.core.service.media.converter.MediaProcessService import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest +import io.ktor.client.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory @@ -21,7 +23,8 @@ class MediaServiceImpl( private val fileTypeDeterminationService: FileTypeDeterminationService, private val mediaBlurhashService: MediaBlurhashService, private val mediaRepository: MediaRepository, - private val mediaProcessService: MediaProcessService + private val mediaProcessService: MediaProcessService, + private val httpClient: HttpClient ) : MediaService { override suspend fun uploadLocalMedia(mediaRequest: MediaRequest): EntityMedia { logger.info( @@ -88,7 +91,24 @@ class MediaServiceImpl( ) } - override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia) = Unit + + // TODO: 仮の処理として保存したように動かす + override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media { + logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}") + + return mediaRepository.save( + EntityMedia( + id = mediaRepository.generateId(), + name = remoteMedia.name, + url = remoteMedia.url, + remoteUrl = remoteMedia.url, + thumbnailUrl = remoteMedia.url, + type = FileType.Image, + blurHash = null + ) + ) + } + companion object { private val logger = LoggerFactory.getLogger(MediaServiceImpl::class.java) From c02b7efc83ba976228301c3aa255e25bcb7997b8 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 2 Nov 2023 19:34:07 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=E3=83=AA=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=AE=E3=83=A1=E3=83=87=E3=82=A3=E3=82=A2=E3=82=92?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=82=AB=E3=83=AB=E3=81=AB=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/service/media/MediaServiceImpl.kt | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt index d439099d..aefbc05e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt @@ -8,6 +8,10 @@ import dev.usbharu.hideout.core.domain.model.media.MediaRepository import dev.usbharu.hideout.core.service.media.converter.MediaProcessService import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory @@ -96,15 +100,61 @@ class MediaServiceImpl( override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media { logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}") + val httpResponse = httpClient.get(remoteMedia.url) + val bytes = httpResponse.bodyAsChannel().toByteArray() + + val contentType = httpResponse.contentType()?.toString() + val fileType = + fileTypeDeterminationService.fileType(bytes, remoteMedia.name, contentType) + + if (fileType != FileType.Image) { + throw UnsupportedMediaException("FileType: $fileType is not supported.") + } + + val processedMedia = mediaProcessService.process( + fileType = fileType, + contentType = contentType.orEmpty(), + fileName = remoteMedia.name, + file = bytes, + thumbnail = null + ) + + val mediaSave = MediaSave( + "${UUID.randomUUID()}.${processedMedia.file.extension}", + "", + processedMedia.file.byteArray, + processedMedia.thumbnail?.byteArray + ) + + val save = try { + mediaDataStore.save(mediaSave) + } catch (e: Exception) { + logger.warn("Failed save media", e) + throw MediaSaveException("Failed save media.", e) + } + + if (save.success.not()) { + save as FaildSavedMedia + logger.warn("Failed save media. reason: ${save.reason}") + logger.warn(save.description, save.trace) + throw MediaSaveException("Failed save media.") + } + save as SuccessSavedMedia + + val blurhash = withContext(Dispatchers.IO) { + mediaBlurhashService.generateBlurhash(ImageIO.read(bytes.inputStream())) + } + + return mediaRepository.save( EntityMedia( id = mediaRepository.generateId(), name = remoteMedia.name, - url = remoteMedia.url, + url = save.url, remoteUrl = remoteMedia.url, - thumbnailUrl = remoteMedia.url, - type = FileType.Image, - blurHash = null + thumbnailUrl = save.thumbnailUrl, + type = fileType, + blurHash = blurhash ) ) } From f8c54e3d9ffa934707fdfc9e1a57bd49e7693b03 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 2 Nov 2023 19:41:36 +0900 Subject: [PATCH 4/7] =?UTF-8?q?chore:=20webp=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index 62de2087..927b4ed4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -141,6 +141,7 @@ dependencies { implementation("dev.usbharu:http-signature:1.0.0") implementation("org.postgresql:postgresql:42.6.0") + implementation("com.twelvemonkeys.imageio:imageio-webp:3.10.0") implementation("io.ktor:ktor-client-logging-jvm:$ktor_version") From b1a7d8adf9a7c3f0bbdef81c6d18f1f9c38b1a45 Mon Sep 17 00:00:00 2001 From: usbharu Date: Thu, 2 Nov 2023 19:56:06 +0900 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../hideout/activitypub/service/objects/note/APNoteService.kt | 4 ++-- .../usbharu/hideout/core/service/media/MediaServiceImpl.kt | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt index dd2ec6ae..0fdee80b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteService.kt @@ -126,14 +126,14 @@ class APNoteServiceImpl( postQueryService.findByUrl(it) } - val mediaList = note.attachment .filter { it.url != null } .map { mediaService.uploadRemoteMedia( RemoteMedia( (it.name ?: it.url)!!, - it.url!!, it.mediaType ?: "application/octet-stream" + it.url!!, + it.mediaType ?: "application/octet-stream" ) ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt index aefbc05e..ca0f1387 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt @@ -95,7 +95,6 @@ class MediaServiceImpl( ) } - // TODO: 仮の処理として保存したように動かす override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media { logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}") @@ -145,7 +144,6 @@ class MediaServiceImpl( mediaBlurhashService.generateBlurhash(ImageIO.read(bytes.inputStream())) } - return mediaRepository.save( EntityMedia( id = mediaRepository.generateId(), @@ -159,7 +157,6 @@ class MediaServiceImpl( ) } - companion object { private val logger = LoggerFactory.getLogger(MediaServiceImpl::class.java) } From 67d284ffcc3c5fa50781c5143eb386005f2cac36 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 2 Nov 2023 19:58:37 +0900 Subject: [PATCH 6/7] =?UTF-8?q?test:=20=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/objects/note/APNoteServiceImplTest.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt index 5c0d6363..c7e047ed 100644 --- a/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/activitypub/service/objects/note/APNoteServiceImplTest.kt @@ -78,7 +78,8 @@ class APNoteServiceImplTest { postService = mock(), apResourceResolveService = mock(), postBuilder = Post.PostBuilder(CharacterLimit()), - noteQueryService = noteQueryService + noteQueryService = noteQueryService, + mock() ) val actual = apNoteServiceImpl.fetchNote(url) @@ -155,7 +156,8 @@ class APNoteServiceImplTest { postService = mock(), apResourceResolveService = apResourceResolveService, postBuilder = Post.PostBuilder(CharacterLimit()), - noteQueryService = noteQueryService + noteQueryService = noteQueryService, + mock() ) val actual = apNoteServiceImpl.fetchNote(url) @@ -223,7 +225,8 @@ class APNoteServiceImplTest { postService = mock(), apResourceResolveService = apResourceResolveService, postBuilder = Post.PostBuilder(CharacterLimit()), - noteQueryService = noteQueryService + noteQueryService = noteQueryService, + mock() ) assertThrows { apNoteServiceImpl.fetchNote(url) } @@ -275,7 +278,8 @@ class APNoteServiceImplTest { postService = postService, apResourceResolveService = mock(), postBuilder = postBuilder, - noteQueryService = noteQueryService + noteQueryService = noteQueryService, + mock() ) val note = Note( @@ -333,7 +337,8 @@ class APNoteServiceImplTest { postService = mock(), apResourceResolveService = mock(), postBuilder = postBuilder, - noteQueryService = noteQueryService + noteQueryService = noteQueryService, + mock() ) From 8264d798b8e83b75f41e131a44a6d59750acc5a5 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 2 Nov 2023 20:09:45 +0900 Subject: [PATCH 7/7] style: fix lint --- .../service/common/APRequestServiceImpl.kt | 1 + .../exposedquery/StatusQueryServiceImpl.kt | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt index 6e8edef9..6e87d402 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/common/APRequestServiceImpl.kt @@ -96,6 +96,7 @@ class APRequestServiceImpl( return objectMapper.readValue(bodyAsText, responseClass) } + @Suppress("LongMethod") override suspend fun apPost(url: String, body: T?, signer: User?): String { logger.debug("START ActivityPub Request POST url: {}, signer: {}", url, signer?.url) val requestBody = if (body != null) { diff --git a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt index 7589fcb6..c300375a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/StatusQueryServiceImpl.kt @@ -48,12 +48,12 @@ class StatusQueryServiceImpl : StatusQueryService { } } - return statusQueries.mapNotNull { - postMap[it.postId]?.copy( - inReplyToId = it.replyId?.toString(), - inReplyToAccountId = postMap[it.replyId]?.account?.id, - reblog = postMap[it.repostId], - mediaAttachments = it.mediaIds.mapNotNull { mediaMap[it] } + return statusQueries.mapNotNull { statusQuery -> + postMap[statusQuery.postId]?.copy( + inReplyToId = statusQuery.replyId?.toString(), + inReplyToAccountId = postMap[statusQuery.replyId]?.account?.id, + reblog = postMap[statusQuery.repostId], + mediaAttachments = statusQuery.mediaIds.mapNotNull { mediaMap[it] } ) } }