From 9e570dc1bfffde4ea1a9065d169a5c522ecbc033 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 4 Oct 2023 01:45:55 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20Media=E3=82=A2=E3=83=83=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=89=E3=81=AE=E3=82=A8=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=83=9D=E3=82=A4=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 + .../mastodon/MastodonMediaApiController.kt | 19 +++++++++ src/main/resources/openapi/mastodon.yaml | 41 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt diff --git a/build.gradle.kts b/build.gradle.kts index f88635b8..4b91b4f7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,6 +60,8 @@ tasks.create("openApiGenerateMastodonCompatibleApi", GenerateTask: configOptions.put("interfaceOnly", "true") configOptions.put("useSpringBoot3", "true") additionalProperties.put("useTags", "true") + importMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile") + typeMappings.put("org.springframework.core.io.Resource", "org.springframework.web.multipart.MultipartFile") } repositories { diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt new file mode 100644 index 00000000..e1951898 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt @@ -0,0 +1,19 @@ +package dev.usbharu.hideout.controller.mastodon + +import dev.usbharu.hideout.controller.mastodon.generated.MediaApi +import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller +import org.springframework.web.multipart.MultipartFile + +@Controller +class MastodonMediaApiController : MediaApi { + override fun apiV1MediaPost( + file: MultipartFile, + thumbnail: MultipartFile?, + description: String?, + focus: String? + ): ResponseEntity { + + } +} diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml index 5464a28a..6f93fa36 100644 --- a/src/main/resources/openapi/mastodon.yaml +++ b/src/main/resources/openapi/mastodon.yaml @@ -17,6 +17,8 @@ tags: description: instance - name: timeline description: timeline + - name: media + description: media paths: /api/v2/instance: @@ -291,9 +293,48 @@ paths: type: array items: $ref: "#/components/schemas/Status" + /api/v1/media: + post: + tags: + - media + security: + - OAuth2: + - "write:media" + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/V1MediaRequest" + encoding: + file: + contentType: image/jpeg, image/png + responses: + 200: + description: 成功 + content: + application/json: + schema: + $ref: "#/components/schemas/MediaAttachment" components: schemas: + V1MediaRequest: + type: object + properties: + file: + type: string + format: binary + thumbnail: + type: string + format: binary + description: + type: string + focus: + type: string + required: + - file + AccountsCreateRequest: type: object properties: From 7fa2e9736865883b65c7eaaa090d98245ac0c541 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:48:12 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=E3=83=A1=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=82=A2=E9=96=A2=E4=BF=82=E3=81=AE=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mastodon/MastodonMediaApiController.kt | 15 +++- .../usbharu/hideout/domain/model/MediaSave.kt | 10 +++ .../domain/model/hideout/entity/Media.kt | 13 +++ .../domain/model/hideout/form/Media.kt | 10 +++ .../hideout/exception/media/MediaException.kt | 14 ++++ .../exception/media/MediaUploadException.kt | 14 ++++ .../hideout/repository/MediaRepository.kt | 10 +++ .../hideout/repository/MediaRepositoryImpl.kt | 80 +++++++++++++++++++ .../service/api/mastodon/MediaApiService.kt | 10 +++ .../api/mastodon/MediaApiServiceImpl.kt | 11 +++ .../media/FileTypeDeterminationService.kt | 12 +++ .../media/FileTypeDeterminationServiceImpl.kt | 26 ++++++ .../service/media/MediaBlurhashService.kt | 7 ++ .../hideout/service/media/MediaDataStore.kt | 8 ++ .../hideout/service/media/MediaService.kt | 7 ++ .../hideout/service/media/MediaServiceImpl.kt | 56 +++++++++++++ .../hideout/service/media/SavedMedia.kt | 18 +++++ 17 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Media.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/form/Media.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/media/MediaException.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/media/MediaUploadException.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/repository/MediaRepository.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationServiceImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/MediaBlurhashService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/MediaService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/SavedMedia.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt index e1951898..f2d19aab 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt @@ -2,18 +2,29 @@ package dev.usbharu.hideout.controller.mastodon import dev.usbharu.hideout.controller.mastodon.generated.MediaApi import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment +import dev.usbharu.hideout.domain.model.hideout.form.Media +import dev.usbharu.hideout.service.api.mastodon.MediaApiService import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller import org.springframework.web.multipart.MultipartFile @Controller -class MastodonMediaApiController : MediaApi { +class MastodonMediaApiController(private val mediaApiService: MediaApiService) : MediaApi { override fun apiV1MediaPost( file: MultipartFile, thumbnail: MultipartFile?, description: String?, focus: String? ): ResponseEntity { - + return ResponseEntity.ok( + mediaApiService.postMedia( + Media( + file, + thumbnail, + description, + focus + ) + ) + ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt new file mode 100644 index 00000000..f4086904 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.domain.model + +import java.io.InputStream + +data class MediaSave( + val name: String, + val prefix: String, + val fileInputStream: InputStream, + val thumbnailInputStream: InputStream +) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Media.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Media.kt new file mode 100644 index 00000000..6f5f0e78 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Media.kt @@ -0,0 +1,13 @@ +package dev.usbharu.hideout.domain.model.hideout.entity + +import dev.usbharu.hideout.service.media.FileTypeDeterminationService + +data class Media( + val id: Long, + val name: String, + val url: String, + val remoteUrl: String?, + val thumbnailUrl: String?, + val type: FileTypeDeterminationService.FileType, + val blurHash: String? +) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/form/Media.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/form/Media.kt new file mode 100644 index 00000000..978eaa56 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/form/Media.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.domain.model.hideout.form + +import org.springframework.web.multipart.MultipartFile + +data class Media( + val file: MultipartFile, + val thumbnail: MultipartFile?, + val description: String?, + val focus: String? +) diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaException.kt new file mode 100644 index 00000000..922a42a8 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaException.kt @@ -0,0 +1,14 @@ +package dev.usbharu.hideout.exception.media + +abstract class MediaException : RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaUploadException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaUploadException.kt new file mode 100644 index 00000000..36afc8b7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaUploadException.kt @@ -0,0 +1,14 @@ +package dev.usbharu.hideout.exception.media + +open class MediaUploadException : MediaException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepository.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepository.kt new file mode 100644 index 00000000..be55bb86 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepository.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.repository + +import dev.usbharu.hideout.domain.model.hideout.entity.Media + +interface MediaRepository { + suspend fun generateId(): Long + suspend fun save(media: Media): Media + suspend fun findById(id: Long): Media + suspend fun delete(id: Long) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt new file mode 100644 index 00000000..ba5724d0 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt @@ -0,0 +1,80 @@ +package dev.usbharu.hideout.repository + + +import dev.usbharu.hideout.exception.FailedToGetResourcesException +import dev.usbharu.hideout.service.core.IdGenerateService +import dev.usbharu.hideout.service.media.FileTypeDeterminationService +import dev.usbharu.hideout.util.singleOr +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.springframework.stereotype.Repository +import dev.usbharu.hideout.domain.model.hideout.entity.Media as EntityMedia + +@Repository +class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : MediaRepository { + override suspend fun generateId(): Long = idGenerateService.generateId() + + override suspend fun save(media: EntityMedia): EntityMedia { + if (Media.select { + Media.id eq media.id + }.singleOrNull() != null) { + Media.update({ Media.id eq media.id }) { + it[Media.name] = media.name + it[Media.url] = media.url + it[Media.remoteUrl] = media.remoteUrl + it[Media.thumbnailUrl] = media.thumbnailUrl + it[Media.type] = media.type.ordinal + it[Media.blurhash] = media.blurHash + } + } else { + Media.insert { + it[Media.id] = media.id + it[Media.name] = media.name + it[Media.url] = media.url + it[Media.remoteUrl] = media.remoteUrl + it[Media.thumbnailUrl] = media.thumbnailUrl + it[Media.type] = media.type.ordinal + it[Media.blurhash] = media.blurHash + } + } + return media + } + + override suspend fun findById(id: Long): EntityMedia { + return Media + .select { + Media.id eq id + } + .singleOr { + FailedToGetResourcesException("id: $id was not found.") + }.toMedia() + } + + override suspend fun delete(id: Long) { + Media.deleteWhere { + Media.id eq id + } + } + + fun ResultRow.toMedia(): EntityMedia { + return EntityMedia( + this[Media.id], + this[Media.name], + this[Media.url], + this[Media.remoteUrl], + this[Media.thumbnailUrl], + FileTypeDeterminationService.FileType.values().first { it.ordinal == this[Media.type] }, + this[Media.blurhash], + ) + } +} + +object Media : Table("media") { + val id = long("id") + val name = varchar("name", 255) + val url = varchar("url", 255) + val remoteUrl = varchar("remote_url", 255).nullable() + val thumbnailUrl = varchar("thumbnail_url", 255).nullable() + val type = integer("type") + val blurhash = varchar("blurhash", 255).nullable() +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiService.kt new file mode 100644 index 00000000..8b1da1b9 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiService.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.service.api.mastodon + +import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment +import dev.usbharu.hideout.domain.model.hideout.form.Media +import org.springframework.stereotype.Service + +@Service +interface MediaApiService { + suspend fun postMedia(media: Media): MediaAttachment +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt new file mode 100644 index 00000000..230e749e --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt @@ -0,0 +1,11 @@ +package dev.usbharu.hideout.service.api.mastodon + +import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment +import dev.usbharu.hideout.domain.model.hideout.form.Media +import dev.usbharu.hideout.service.media.MediaService + +class MediaApiServiceImpl(private val mediaService: MediaService) : MediaApiService { + override suspend fun postMedia(media: Media): MediaAttachment { + mediaService.uploadLocalMedia(media) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt new file mode 100644 index 00000000..c30762de --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt @@ -0,0 +1,12 @@ +package dev.usbharu.hideout.service.media + +interface FileTypeDeterminationService { + fun fileType(byteArray: ByteArray, filename: String, contentType: String?): FileType + + enum class FileType { + Image, + Video, + Audio, + Unknown + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationServiceImpl.kt new file mode 100644 index 00000000..5ab2df2e --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationServiceImpl.kt @@ -0,0 +1,26 @@ +package dev.usbharu.hideout.service.media + +import org.springframework.stereotype.Component + +@Component +class FileTypeDeterminationServiceImpl : FileTypeDeterminationService { + override fun fileType( + byteArray: ByteArray, + filename: String, + contentType: String? + ): FileTypeDeterminationService.FileType { + if (contentType == null) { + return FileTypeDeterminationService.FileType.Unknown + } + if (contentType.startsWith("image")) { + return FileTypeDeterminationService.FileType.Image + } + if (contentType.startsWith("video")) { + return FileTypeDeterminationService.FileType.Video + } + if (contentType.startsWith("audio")) { + return FileTypeDeterminationService.FileType.Audio + } + return FileTypeDeterminationService.FileType.Unknown + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaBlurhashService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaBlurhashService.kt new file mode 100644 index 00000000..490a2ffe --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaBlurhashService.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.service.media + +import java.awt.image.BufferedImage + +interface MediaBlurhashService { + fun generateBlurhash(bufferedImage: BufferedImage): String +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt new file mode 100644 index 00000000..bb452f0d --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.service.media + +import dev.usbharu.hideout.domain.model.MediaSave + +interface MediaDataStore { + suspend fun save(dataMediaSave: MediaSave) + suspend fun delete(id: Long) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaService.kt new file mode 100644 index 00000000..47446a3b --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaService.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.service.media + +import dev.usbharu.hideout.domain.model.hideout.form.Media + +interface MediaService { + suspend fun uploadLocalMedia(media: Media): SavedMedia +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt new file mode 100644 index 00000000..799e0dc4 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt @@ -0,0 +1,56 @@ +package dev.usbharu.hideout.service.media + +import dev.usbharu.hideout.domain.model.MediaSave +import dev.usbharu.hideout.domain.model.hideout.form.Media +import dev.usbharu.hideout.exception.media.MediaException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.springframework.stereotype.Service +import javax.imageio.ImageIO + +@Service +class MediaServiceImpl( + private val mediaDataStore: MediaDataStore, + private val fileTypeDeterminationService: FileTypeDeterminationService, + private val mediaBlurhashService: MediaBlurhashService +) : MediaService { + override suspend fun uploadLocalMedia(media: Media): SavedMedia { + if (media.file.size == 0L) { + return FaildSavedMedia( + "File size is 0.", + "Cannot upload a file with a file size of 0." + ) + } + + val fileType = fileTypeDeterminationService.fileType(media.file.bytes, media.file.name, media.file.contentType) + if (fileType != FileTypeDeterminationService.FileType.Image) { + return FaildSavedMedia("Unsupported file type.", "FileType: $fileType is not supported.") + } + + try { + mediaDataStore.save( + MediaSave( + media.file.name, + "", + media.file.inputStream, + media.thumbnail.inputStream + ) + ) + } catch (e: MediaException) { + return FaildSavedMedia( + "Faild to upload.", + e.localizedMessage, + e + ) + } + + val withContext = withContext(Dispatchers.IO) { + mediaBlurhashService.generateBlurhash(ImageIO.read(media.file.inputStream)) + } + + return SuccessSavedMedia( + media.file.name, "", "", + withContext + ) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/SavedMedia.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/SavedMedia.kt new file mode 100644 index 00000000..ffd94ef8 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/SavedMedia.kt @@ -0,0 +1,18 @@ +package dev.usbharu.hideout.service.media + +sealed class SavedMedia(val success: Boolean) + +class SuccessSavedMedia( + val name: String, + val url: String, + val thumbnailUrl: String, + val blurhash: String +) : + SavedMedia(true) + + +class FaildSavedMedia( + val reason: String, + val description: String, + val trace: Throwable? = null +) : SavedMedia(false) From bbf3249c4518089f20ef170961b84a51d3cf55f0 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:41:36 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=E3=83=AD=E3=83=BC=E3=82=AB?= =?UTF-8?q?=E3=83=AB=E3=81=AE=E3=82=A2=E3=83=83=E3=83=97=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=AE=E5=87=A6=E7=90=86=E3=81=AE=E4=B8=80=E9=83=A8?= =?UTF-8?q?=E3=81=8C=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usbharu/hideout/config/SpringConfig.kt | 14 +++ .../usbharu/hideout/domain/model/MediaSave.kt | 2 +- .../domain/model/hideout/dto/FileType.kt | 8 ++ .../domain/model/hideout/dto/RemoteMedia.kt | 7 ++ .../model/hideout/dto}/SavedMedia.kt | 2 +- .../domain/model/hideout/entity/Media.kt | 4 +- .../exception/media/MediaConvertException.kt | 14 +++ .../exception/media/MediaFileSizeException.kt | 14 +++ .../media/MediaFileSizeIsZeroException.kt | 14 +++ ...loadException.kt => MediaSaveException.kt} | 2 +- .../media/UnsupportedMediaException.kt | 14 +++ .../hideout/repository/MediaRepositoryImpl.kt | 4 +- .../api/mastodon/MediaApiServiceImpl.kt | 5 +- .../media/FileTypeDeterminationService.kt | 8 +- .../media/FileTypeDeterminationServiceImpl.kt | 13 +-- .../hideout/service/media/MediaDataStore.kt | 3 +- .../hideout/service/media/MediaService.kt | 4 +- .../hideout/service/media/MediaServiceImpl.kt | 95 ++++++++++++------- .../service/media/ThumbnailGenerateService.kt | 7 ++ .../service/media/converter/MediaConverter.kt | 9 ++ .../media/converter/MediaConverterRoot.kt | 8 ++ .../media/converter/MediaProcessService.kt | 8 ++ .../converter/MediaProcessServiceImpl.kt | 41 ++++++++ src/main/resources/application.yml | 12 +++ 24 files changed, 258 insertions(+), 54 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/FileType.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteMedia.kt rename src/main/kotlin/dev/usbharu/hideout/{service/media => domain/model/hideout/dto}/SavedMedia.kt (86%) create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/media/MediaConvertException.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/media/MediaFileSizeException.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/media/MediaFileSizeIsZeroException.kt rename src/main/kotlin/dev/usbharu/hideout/exception/media/{MediaUploadException.kt => MediaSaveException.kt} (90%) create mode 100644 src/main/kotlin/dev/usbharu/hideout/exception/media/UnsupportedMediaException.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/config/SpringConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/SpringConfig.kt index 05d0c019..25ef84ed 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/SpringConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/SpringConfig.kt @@ -10,9 +10,23 @@ class SpringConfig { @Autowired lateinit var config: ApplicationConfig + + @Autowired + lateinit var storageConfig: StorageConfig } @ConfigurationProperties("hideout") data class ApplicationConfig( val url: URL ) + +@ConfigurationProperties("hideout.storage") +data class StorageConfig( + val useS3: Boolean, + val endpoint: String, + val publicUrl: String, + val bucket: String, + val region: String, + val accessKey: String, + val secretKey: String +) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt index f4086904..cf415fff 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt @@ -6,5 +6,5 @@ data class MediaSave( val name: String, val prefix: String, val fileInputStream: InputStream, - val thumbnailInputStream: InputStream + val thumbnailInputStream: InputStream? ) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/FileType.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/FileType.kt new file mode 100644 index 00000000..a72ac82a --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/FileType.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.domain.model.hideout.dto + +enum class FileType { + Image, + Video, + Audio, + Unknown +} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteMedia.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteMedia.kt new file mode 100644 index 00000000..d4428ad1 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/RemoteMedia.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.domain.model.hideout.dto + +data class RemoteMedia( + val name: String, + val url: String, + val mediaType: String +) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/SavedMedia.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt similarity index 86% rename from src/main/kotlin/dev/usbharu/hideout/service/media/SavedMedia.kt rename to src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt index ffd94ef8..18dd9e9d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/SavedMedia.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt @@ -1,4 +1,4 @@ -package dev.usbharu.hideout.service.media +package dev.usbharu.hideout.domain.model.hideout.dto sealed class SavedMedia(val success: Boolean) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Media.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Media.kt index 6f5f0e78..042388b7 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Media.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/entity/Media.kt @@ -1,6 +1,6 @@ package dev.usbharu.hideout.domain.model.hideout.entity -import dev.usbharu.hideout.service.media.FileTypeDeterminationService +import dev.usbharu.hideout.domain.model.hideout.dto.FileType data class Media( val id: Long, @@ -8,6 +8,6 @@ data class Media( val url: String, val remoteUrl: String?, val thumbnailUrl: String?, - val type: FileTypeDeterminationService.FileType, + val type: FileType, val blurHash: String? ) diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaConvertException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaConvertException.kt new file mode 100644 index 00000000..1082f080 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaConvertException.kt @@ -0,0 +1,14 @@ +package dev.usbharu.hideout.exception.media + +open class MediaConvertException : MediaException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaFileSizeException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaFileSizeException.kt new file mode 100644 index 00000000..f7bd6536 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaFileSizeException.kt @@ -0,0 +1,14 @@ +package dev.usbharu.hideout.exception.media + +open class MediaFileSizeException : MediaException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaFileSizeIsZeroException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaFileSizeIsZeroException.kt new file mode 100644 index 00000000..523f94b0 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaFileSizeIsZeroException.kt @@ -0,0 +1,14 @@ +package dev.usbharu.hideout.exception.media + +class MediaFileSizeIsZeroException : MediaFileSizeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaUploadException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaSaveException.kt similarity index 90% rename from src/main/kotlin/dev/usbharu/hideout/exception/media/MediaUploadException.kt rename to src/main/kotlin/dev/usbharu/hideout/exception/media/MediaSaveException.kt index 36afc8b7..8887a3fe 100644 --- a/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaUploadException.kt +++ b/src/main/kotlin/dev/usbharu/hideout/exception/media/MediaSaveException.kt @@ -1,6 +1,6 @@ package dev.usbharu.hideout.exception.media -open class MediaUploadException : MediaException { +open class MediaSaveException : MediaException { constructor() : super() constructor(message: String?) : super(message) constructor(message: String?, cause: Throwable?) : super(message, cause) diff --git a/src/main/kotlin/dev/usbharu/hideout/exception/media/UnsupportedMediaException.kt b/src/main/kotlin/dev/usbharu/hideout/exception/media/UnsupportedMediaException.kt new file mode 100644 index 00000000..2fd8fc23 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/exception/media/UnsupportedMediaException.kt @@ -0,0 +1,14 @@ +package dev.usbharu.hideout.exception.media + +class UnsupportedMediaException : MediaException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super( + message, + cause, + enableSuppression, + writableStackTrace + ) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt index ba5724d0..f2ffb720 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt @@ -1,9 +1,9 @@ package dev.usbharu.hideout.repository +import dev.usbharu.hideout.domain.model.hideout.dto.FileType import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.service.core.IdGenerateService -import dev.usbharu.hideout.service.media.FileTypeDeterminationService import dev.usbharu.hideout.util.singleOr import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq @@ -63,7 +63,7 @@ class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : Me this[Media.url], this[Media.remoteUrl], this[Media.thumbnailUrl], - FileTypeDeterminationService.FileType.values().first { it.ordinal == this[Media.type] }, + FileType.values().first { it.ordinal == this[Media.type] }, this[Media.blurhash], ) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt index 230e749e..1fdb5e60 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt @@ -6,6 +6,9 @@ import dev.usbharu.hideout.service.media.MediaService class MediaApiServiceImpl(private val mediaService: MediaService) : MediaApiService { override suspend fun postMedia(media: Media): MediaAttachment { - mediaService.uploadLocalMedia(media) + val uploadLocalMedia = mediaService.uploadLocalMedia(media) + return MediaAttachment( + + ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt index c30762de..19a7feb6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt @@ -1,12 +1,8 @@ package dev.usbharu.hideout.service.media +import dev.usbharu.hideout.domain.model.hideout.dto.FileType + interface FileTypeDeterminationService { fun fileType(byteArray: ByteArray, filename: String, contentType: String?): FileType - enum class FileType { - Image, - Video, - Audio, - Unknown - } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationServiceImpl.kt index 5ab2df2e..a13ebf66 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationServiceImpl.kt @@ -1,5 +1,6 @@ package dev.usbharu.hideout.service.media +import dev.usbharu.hideout.domain.model.hideout.dto.FileType import org.springframework.stereotype.Component @Component @@ -8,19 +9,19 @@ class FileTypeDeterminationServiceImpl : FileTypeDeterminationService { byteArray: ByteArray, filename: String, contentType: String? - ): FileTypeDeterminationService.FileType { + ): FileType { if (contentType == null) { - return FileTypeDeterminationService.FileType.Unknown + return FileType.Unknown } if (contentType.startsWith("image")) { - return FileTypeDeterminationService.FileType.Image + return FileType.Image } if (contentType.startsWith("video")) { - return FileTypeDeterminationService.FileType.Video + return FileType.Video } if (contentType.startsWith("audio")) { - return FileTypeDeterminationService.FileType.Audio + return FileType.Audio } - return FileTypeDeterminationService.FileType.Unknown + return FileType.Unknown } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt index bb452f0d..dd21b519 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt @@ -1,8 +1,9 @@ package dev.usbharu.hideout.service.media import dev.usbharu.hideout.domain.model.MediaSave +import dev.usbharu.hideout.domain.model.hideout.dto.SavedMedia interface MediaDataStore { - suspend fun save(dataMediaSave: MediaSave) + suspend fun save(dataMediaSave: MediaSave): SavedMedia suspend fun delete(id: Long) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaService.kt index 47446a3b..025b22ad 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaService.kt @@ -1,7 +1,9 @@ package dev.usbharu.hideout.service.media +import dev.usbharu.hideout.domain.model.hideout.dto.RemoteMedia import dev.usbharu.hideout.domain.model.hideout.form.Media interface MediaService { - suspend fun uploadLocalMedia(media: Media): SavedMedia + suspend fun uploadLocalMedia(media: Media): dev.usbharu.hideout.domain.model.hideout.entity.Media + suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt index 799e0dc4..041deb57 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt @@ -1,56 +1,87 @@ package dev.usbharu.hideout.service.media import dev.usbharu.hideout.domain.model.MediaSave +import dev.usbharu.hideout.domain.model.hideout.dto.FaildSavedMedia +import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import dev.usbharu.hideout.domain.model.hideout.dto.RemoteMedia +import dev.usbharu.hideout.domain.model.hideout.dto.SuccessSavedMedia import dev.usbharu.hideout.domain.model.hideout.form.Media -import dev.usbharu.hideout.exception.media.MediaException +import dev.usbharu.hideout.exception.media.MediaFileSizeIsZeroException +import dev.usbharu.hideout.exception.media.MediaSaveException +import dev.usbharu.hideout.exception.media.UnsupportedMediaException +import dev.usbharu.hideout.repository.MediaRepository +import dev.usbharu.hideout.service.media.converter.MediaProcessService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import java.util.* import javax.imageio.ImageIO +import dev.usbharu.hideout.domain.model.hideout.entity.Media as EntityMedia @Service class MediaServiceImpl( private val mediaDataStore: MediaDataStore, private val fileTypeDeterminationService: FileTypeDeterminationService, - private val mediaBlurhashService: MediaBlurhashService + private val mediaBlurhashService: MediaBlurhashService, + private val mediaRepository: MediaRepository, + private val mediaProcessService: MediaProcessService ) : MediaService { - override suspend fun uploadLocalMedia(media: Media): SavedMedia { + override suspend fun uploadLocalMedia(media: Media): EntityMedia { if (media.file.size == 0L) { - return FaildSavedMedia( - "File size is 0.", - "Cannot upload a file with a file size of 0." - ) + throw MediaFileSizeIsZeroException("Media file size is zero.") } val fileType = fileTypeDeterminationService.fileType(media.file.bytes, media.file.name, media.file.contentType) - if (fileType != FileTypeDeterminationService.FileType.Image) { - return FaildSavedMedia("Unsupported file type.", "FileType: $fileType is not supported.") + if (fileType != FileType.Image) { + throw UnsupportedMediaException("FileType: $fileType is not supported.") } - try { - mediaDataStore.save( - MediaSave( - media.file.name, - "", - media.file.inputStream, - media.thumbnail.inputStream - ) + val process = mediaProcessService.process(fileType, media.file.inputStream, media.thumbnail?.inputStream) + + val dataMediaSave = MediaSave( + UUID.randomUUID().toString(), + "", + process.first, + process.second + ) + val save = try { + mediaDataStore.save(dataMediaSave) + } 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(media.file.bytes.inputStream())) + } + + return mediaRepository.save( + EntityMedia( + id = mediaRepository.generateId(), + name = media.file.name, + url = save.url, + remoteUrl = null, + thumbnailUrl = save.thumbnailUrl, + type = fileType, + blurHash = blurHash ) - } catch (e: MediaException) { - return FaildSavedMedia( - "Faild to upload.", - e.localizedMessage, - e - ) - } - - val withContext = withContext(Dispatchers.IO) { - mediaBlurhashService.generateBlurhash(ImageIO.read(media.file.inputStream)) - } - - return SuccessSavedMedia( - media.file.name, "", "", - withContext ) } + + override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia) { + + } + + companion object { + private val logger = LoggerFactory.getLogger(MediaServiceImpl::class.java) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt new file mode 100644 index 00000000..15db6ee7 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.service.media + +import java.io.InputStream + +interface ThumbnailGenerateService { + fun generate(bufferedImage: InputStream, width: Int, height: Int): InputStream +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt new file mode 100644 index 00000000..6771c73d --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt @@ -0,0 +1,9 @@ +package dev.usbharu.hideout.service.media.converter + +import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import java.io.InputStream + +interface MediaConverter { + fun isSupport(fileType: FileType): Boolean + fun convert(inputStream: InputStream): InputStream +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt new file mode 100644 index 00000000..a032a3f1 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.service.media.converter + +import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import java.io.InputStream + +interface MediaConverterRoot { + suspend fun convert(fileType: FileType, inputStream: InputStream): InputStream +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt new file mode 100644 index 00000000..107647f5 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt @@ -0,0 +1,8 @@ +package dev.usbharu.hideout.service.media.converter + +import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import java.io.InputStream + +interface MediaProcessService { + suspend fun process(fileType: FileType, file: InputStream, thumbnail: InputStream?): Pair +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt new file mode 100644 index 00000000..b7fc6fd8 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt @@ -0,0 +1,41 @@ +package dev.usbharu.hideout.service.media.converter + +import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import dev.usbharu.hideout.exception.media.MediaConvertException +import dev.usbharu.hideout.service.media.ThumbnailGenerateService +import org.slf4j.LoggerFactory +import java.io.InputStream + +class MediaProcessServiceImpl( + private val mediaConverterRoot: MediaConverterRoot, + private val thumbnailGenerateService: ThumbnailGenerateService +) : MediaProcessService { + override suspend fun process( + fileType: FileType, + file: InputStream, + thumbnail: InputStream? + ): Pair { + + val fileInputStream = try { + mediaConverterRoot.convert(fileType, file) + } catch (e: Exception) { + logger.warn("Failed convert media.", e) + throw MediaConvertException("Failed convert media.", e) + } + val thumbnailInputStream = try { + thumbnail?.let { mediaConverterRoot.convert(fileType, it) } + } catch (e: Exception) { + logger.warn("Failed convert thumbnail media.", e) + null + } + return fileInputStream to (thumbnailInputStream ?: thumbnailGenerateService.generate( + fileInputStream, + 2048, + 2048 + )) + } + + companion object { + private val logger = LoggerFactory.getLogger(MediaProcessServiceImpl::class.java) + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0c4a32a1..9c9975d0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,6 +7,18 @@ hideout: key-id: a private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==" public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" + storage: + use-s3: true + endpoint: "" + public-url: "" + bucket: "" + region: "" + access-key: "" + secret-key: "" + + + + spring: jackson: serialization: From 0fd620d16ebe0d9160c5386084493ebce29f2165 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 4 Oct 2023 23:07:55 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=E3=83=A1=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=82=A2=E3=81=AE=E5=A4=89=E6=8F=9B=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + .../usbharu/hideout/domain/model/MediaSave.kt | 6 ++-- .../service/media/MediaBlurhashServiceImpl.kt | 10 +++++++ .../service/media/ThumbnailGenerateService.kt | 5 +++- .../media/ThumbnailGenerateServiceImpl.kt | 28 +++++++++++++++++++ .../service/media/converter/MediaConverter.kt | 3 +- .../media/converter/MediaConverterRoot.kt | 3 +- .../media/converter/MediaConverterRootImpl.kt | 20 +++++++++++++ .../media/converter/MediaProcessService.kt | 7 ++++- .../converter/MediaProcessServiceImpl.kt | 11 +++++--- 10 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/MediaBlurhashServiceImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateServiceImpl.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4b91b4f7..0b2a4472 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -118,6 +118,7 @@ dependencies { implementation("org.springframework.security:spring-security-oauth2-jose") implementation("org.springframework.boot:spring-boot-starter-data-mongodb") implementation("org.jetbrains.exposed:exposed-spring-boot-starter:0.44.0") + implementation("io.trbl:blurhash:1.0.0") implementation("io.ktor:ktor-client-logging-jvm:$ktor_version") diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt index cf415fff..49dc04e1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt @@ -1,10 +1,10 @@ package dev.usbharu.hideout.domain.model -import java.io.InputStream +import java.io.OutputStream data class MediaSave( val name: String, val prefix: String, - val fileInputStream: InputStream, - val thumbnailInputStream: InputStream? + val fileInputStream: OutputStream, + val thumbnailInputStream: OutputStream? ) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaBlurhashServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaBlurhashServiceImpl.kt new file mode 100644 index 00000000..d2156796 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaBlurhashServiceImpl.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.service.media + +import io.trbl.blurhash.BlurHash +import org.springframework.stereotype.Service +import java.awt.image.BufferedImage + +@Service +class MediaBlurhashServiceImpl : MediaBlurhashService { + override fun generateBlurhash(bufferedImage: BufferedImage): String = BlurHash.encode(bufferedImage) +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt index 15db6ee7..0d53fe3c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt @@ -1,7 +1,10 @@ package dev.usbharu.hideout.service.media +import java.io.ByteArrayOutputStream import java.io.InputStream +import java.io.OutputStream interface ThumbnailGenerateService { - fun generate(bufferedImage: InputStream, width: Int, height: Int): InputStream + fun generate(bufferedImage: InputStream, width: Int, height: Int): ByteArrayOutputStream + fun generate(outputStream: OutputStream, width: Int, height: Int): ByteArrayOutputStream } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateServiceImpl.kt new file mode 100644 index 00000000..5e6d416f --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateServiceImpl.kt @@ -0,0 +1,28 @@ +package dev.usbharu.hideout.service.media + +import org.springframework.stereotype.Service +import java.awt.image.BufferedImage +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream +import javax.imageio.ImageIO +import javax.imageio.stream.MemoryCacheImageOutputStream + +@Service +class ThumbnailGenerateServiceImpl : ThumbnailGenerateService { + override fun generate(bufferedImage: InputStream, width: Int, height: Int): ByteArrayOutputStream { + val image = ImageIO.read(bufferedImage) + return internalGenerate(image) + } + + override fun generate(outputStream: OutputStream, width: Int, height: Int): ByteArrayOutputStream { + val image = ImageIO.read(MemoryCacheImageOutputStream(outputStream)) + return internalGenerate(image) + } + + private fun internalGenerate(image: BufferedImage): ByteArrayOutputStream { + val byteArrayOutputStream = ByteArrayOutputStream() + ImageIO.write(image, "webp", byteArrayOutputStream) + return byteArrayOutputStream + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt index 6771c73d..b57c7a50 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt @@ -2,8 +2,9 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType import java.io.InputStream +import java.io.OutputStream interface MediaConverter { fun isSupport(fileType: FileType): Boolean - fun convert(inputStream: InputStream): InputStream + fun convert(inputStream: InputStream): OutputStream } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt index a032a3f1..77c99a8d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt @@ -2,7 +2,8 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType import java.io.InputStream +import java.io.OutputStream interface MediaConverterRoot { - suspend fun convert(fileType: FileType, inputStream: InputStream): InputStream + suspend fun convert(fileType: FileType, inputStream: InputStream): OutputStream } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt new file mode 100644 index 00000000..9c005458 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt @@ -0,0 +1,20 @@ +package dev.usbharu.hideout.service.media.converter + +import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import org.springframework.stereotype.Service +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream + +@Service +class MediaConverterRootImpl(private val converters: List) : MediaConverterRoot { + override suspend fun convert(fileType: FileType, inputStream: InputStream): OutputStream { + return converters.find { + it.isSupport(fileType) + }?.convert(inputStream) ?: inputStream.let { + val byteArrayOutputStream = ByteArrayOutputStream() + it.transferTo(byteArrayOutputStream) + byteArrayOutputStream + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt index 107647f5..c31ecd66 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt @@ -2,7 +2,12 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType import java.io.InputStream +import java.io.OutputStream interface MediaProcessService { - suspend fun process(fileType: FileType, file: InputStream, thumbnail: InputStream?): Pair + suspend fun process( + fileType: FileType, + file: InputStream, + thumbnail: InputStream? + ): Pair } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt index b7fc6fd8..bbab8626 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt @@ -4,8 +4,11 @@ import dev.usbharu.hideout.domain.model.hideout.dto.FileType import dev.usbharu.hideout.exception.media.MediaConvertException import dev.usbharu.hideout.service.media.ThumbnailGenerateService import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service import java.io.InputStream +import java.io.OutputStream +@Service class MediaProcessServiceImpl( private val mediaConverterRoot: MediaConverterRoot, private val thumbnailGenerateService: ThumbnailGenerateService @@ -14,7 +17,7 @@ class MediaProcessServiceImpl( fileType: FileType, file: InputStream, thumbnail: InputStream? - ): Pair { + ): Pair { val fileInputStream = try { mediaConverterRoot.convert(fileType, file) @@ -28,11 +31,11 @@ class MediaProcessServiceImpl( logger.warn("Failed convert thumbnail media.", e) null } - return fileInputStream to (thumbnailInputStream ?: thumbnailGenerateService.generate( - fileInputStream, + return fileInputStream to thumbnailGenerateService.generate( + thumbnailInputStream ?: fileInputStream, 2048, 2048 - )) + ) } companion object { From 60242693f9e817bd64b36a824276c5c316049179 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 5 Oct 2023 00:16:56 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20S3=E3=82=A2=E3=83=83=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=89=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 + .../dev/usbharu/hideout/config/AwsConfig.kt | 16 +++++ .../usbharu/hideout/domain/model/MediaSave.kt | 6 +- .../domain/model/hideout/dto/SavedMedia.kt | 1 - .../hideout/service/media/MediaServiceImpl.kt | 2 +- .../hideout/service/media/S3MediaDataStore.kt | 59 +++++++++++++++++++ .../service/media/ThumbnailGenerateService.kt | 6 +- .../service/media/converter/MediaConverter.kt | 3 +- .../media/converter/MediaConverterRoot.kt | 3 +- .../media/converter/MediaConverterRootImpl.kt | 12 ++-- .../media/converter/MediaProcessService.kt | 8 +-- .../converter/MediaProcessServiceImpl.kt | 12 ++-- 12 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt diff --git a/build.gradle.kts b/build.gradle.kts index 0b2a4472..0b8f61b2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -119,6 +119,8 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-data-mongodb") implementation("org.jetbrains.exposed:exposed-spring-boot-starter:0.44.0") implementation("io.trbl:blurhash:1.0.0") + implementation("software.amazon.awssdk:s3:2.20.157") + implementation("io.ktor:ktor-client-logging-jvm:$ktor_version") diff --git a/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt new file mode 100644 index 00000000..48b4d4bb --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt @@ -0,0 +1,16 @@ +package dev.usbharu.hideout.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.services.s3.S3Client + +@Configuration +class AwsConfig { + @Bean + fun s3(awsConfig: StorageConfig): S3Client { + return S3Client.builder() + .credentialsProvider { AwsBasicCredentials.create(awsConfig.accessKey, awsConfig.secretKey) } + .build() + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt index 49dc04e1..0a32a8e3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/MediaSave.kt @@ -1,10 +1,8 @@ package dev.usbharu.hideout.domain.model -import java.io.OutputStream - data class MediaSave( val name: String, val prefix: String, - val fileInputStream: OutputStream, - val thumbnailInputStream: OutputStream? + val fileInputStream: ByteArray, + val thumbnailInputStream: ByteArray? ) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt index 18dd9e9d..60ea05b8 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt @@ -6,7 +6,6 @@ class SuccessSavedMedia( val name: String, val url: String, val thumbnailUrl: String, - val blurhash: String ) : SavedMedia(true) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt index 041deb57..b9afd85d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt @@ -37,7 +37,7 @@ class MediaServiceImpl( throw UnsupportedMediaException("FileType: $fileType is not supported.") } - val process = mediaProcessService.process(fileType, media.file.inputStream, media.thumbnail?.inputStream) + val process = mediaProcessService.process(fileType, media.file.bytes, media.thumbnail?.bytes) val dataMediaSave = MediaSave( UUID.randomUUID().toString(), diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt new file mode 100644 index 00000000..cc7d459f --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt @@ -0,0 +1,59 @@ +package dev.usbharu.hideout.service.media + +import dev.usbharu.hideout.config.StorageConfig +import dev.usbharu.hideout.domain.model.MediaSave +import dev.usbharu.hideout.domain.model.hideout.dto.SavedMedia +import dev.usbharu.hideout.domain.model.hideout.dto.SuccessSavedMedia +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext +import org.springframework.stereotype.Service +import software.amazon.awssdk.core.sync.RequestBody +import software.amazon.awssdk.services.s3.S3Client +import software.amazon.awssdk.services.s3.model.GetUrlRequest +import software.amazon.awssdk.services.s3.model.PutObjectRequest + +@Service +class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig: StorageConfig) : MediaDataStore { + override suspend fun save(dataMediaSave: MediaSave): SavedMedia { + val fileUploadRequest = PutObjectRequest.builder() + .bucket(storageConfig.bucket) + .key(dataMediaSave.name) + .build() + + val thumbnailKey = "thumbnail-${dataMediaSave.name}" + val thumbnailUploadRequest = PutObjectRequest.builder() + .bucket(storageConfig.bucket) + .key(thumbnailKey) + .build() + + val pairList = withContext(Dispatchers.IO) { + awaitAll( + async { + if (dataMediaSave.thumbnailInputStream != null) { + s3Client.putObject(fileUploadRequest, RequestBody.fromBytes(dataMediaSave.thumbnailInputStream)) + "thumbnail" to s3Client.utilities() + .getUrl(GetUrlRequest.builder().bucket(storageConfig.bucket).key(thumbnailKey).build()) + } else { + "thumbnail" to null + } + }, + async { + s3Client.putObject(thumbnailUploadRequest, RequestBody.fromBytes(dataMediaSave.fileInputStream)) + "file" to s3Client.utilities() + .getUrl(GetUrlRequest.builder().bucket(storageConfig.bucket).key(dataMediaSave.name).build()) + } + ) + }.toMap() + return SuccessSavedMedia( + dataMediaSave.name, + pairList.getValue("file").toString(), + pairList.getValue("thumbnail").toString() + ) + } + + override suspend fun delete(id: Long) { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt index 0d53fe3c..9be5c865 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt @@ -1,10 +1,8 @@ package dev.usbharu.hideout.service.media -import java.io.ByteArrayOutputStream import java.io.InputStream -import java.io.OutputStream interface ThumbnailGenerateService { - fun generate(bufferedImage: InputStream, width: Int, height: Int): ByteArrayOutputStream - fun generate(outputStream: OutputStream, width: Int, height: Int): ByteArrayOutputStream + fun generate(bufferedImage: InputStream, width: Int, height: Int): ByteArray + fun generate(outputStream: ByteArray, width: Int, height: Int): ByteArray } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt index b57c7a50..06b6c624 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt @@ -2,9 +2,8 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType import java.io.InputStream -import java.io.OutputStream interface MediaConverter { fun isSupport(fileType: FileType): Boolean - fun convert(inputStream: InputStream): OutputStream + fun convert(inputStream: InputStream): ByteArray } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt index 77c99a8d..fd788277 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt @@ -2,8 +2,7 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType import java.io.InputStream -import java.io.OutputStream interface MediaConverterRoot { - suspend fun convert(fileType: FileType, inputStream: InputStream): OutputStream + suspend fun convert(fileType: FileType, inputStream: InputStream): ByteArray } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt index 9c005458..404f3f34 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt @@ -1,20 +1,18 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.springframework.stereotype.Service -import java.io.ByteArrayOutputStream import java.io.InputStream -import java.io.OutputStream @Service class MediaConverterRootImpl(private val converters: List) : MediaConverterRoot { - override suspend fun convert(fileType: FileType, inputStream: InputStream): OutputStream { + override suspend fun convert(fileType: FileType, inputStream: InputStream): ByteArray { return converters.find { it.isSupport(fileType) - }?.convert(inputStream) ?: inputStream.let { - val byteArrayOutputStream = ByteArrayOutputStream() - it.transferTo(byteArrayOutputStream) - byteArrayOutputStream + }?.convert(inputStream) ?: withContext(Dispatchers.IO) { + inputStream.readAllBytes() } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt index c31ecd66..06f752ff 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt @@ -1,13 +1,11 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType -import java.io.InputStream -import java.io.OutputStream interface MediaProcessService { suspend fun process( fileType: FileType, - file: InputStream, - thumbnail: InputStream? - ): Pair + file: ByteArray, + thumbnail: ByteArray? + ): Pair } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt index bbab8626..470e0bcf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt @@ -5,8 +5,6 @@ import dev.usbharu.hideout.exception.media.MediaConvertException import dev.usbharu.hideout.service.media.ThumbnailGenerateService import org.slf4j.LoggerFactory import org.springframework.stereotype.Service -import java.io.InputStream -import java.io.OutputStream @Service class MediaProcessServiceImpl( @@ -15,18 +13,18 @@ class MediaProcessServiceImpl( ) : MediaProcessService { override suspend fun process( fileType: FileType, - file: InputStream, - thumbnail: InputStream? - ): Pair { + file: ByteArray, + thumbnail: ByteArray? + ): Pair { val fileInputStream = try { - mediaConverterRoot.convert(fileType, file) + mediaConverterRoot.convert(fileType, file.inputStream().buffered()) } catch (e: Exception) { logger.warn("Failed convert media.", e) throw MediaConvertException("Failed convert media.", e) } val thumbnailInputStream = try { - thumbnail?.let { mediaConverterRoot.convert(fileType, it) } + thumbnail?.let { mediaConverterRoot.convert(fileType, it.inputStream().buffered()) } } catch (e: Exception) { logger.warn("Failed convert thumbnail media.", e) null From 97cf5eac65c7b067c39e953ccd2ada7608dc9603 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 5 Oct 2023 00:20:42 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=81=AE=E5=89=8A=E9=99=A4=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/usbharu/hideout/service/media/MediaDataStore.kt | 2 +- .../usbharu/hideout/service/media/S3MediaDataStore.kt | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt index dd21b519..bd03f704 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaDataStore.kt @@ -5,5 +5,5 @@ import dev.usbharu.hideout.domain.model.hideout.dto.SavedMedia interface MediaDataStore { suspend fun save(dataMediaSave: MediaSave): SavedMedia - suspend fun delete(id: Long) + suspend fun delete(id: String) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt index cc7d459f..91e4bcff 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.withContext import org.springframework.stereotype.Service import software.amazon.awssdk.core.sync.RequestBody import software.amazon.awssdk.services.s3.S3Client +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest import software.amazon.awssdk.services.s3.model.GetUrlRequest import software.amazon.awssdk.services.s3.model.PutObjectRequest @@ -53,7 +54,11 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig ) } - override suspend fun delete(id: Long) { - TODO("Not yet implemented") + override suspend fun delete(id: String) { + val fileDeleteRequest = DeleteObjectRequest.builder().bucket(storageConfig.bucket).key(id).build() + val thumbnailDeleteRequest = + DeleteObjectRequest.builder().bucket(storageConfig.bucket).key("thumbnail-$id").build() + s3Client.deleteObject(fileDeleteRequest) + s3Client.deleteObject(thumbnailDeleteRequest) } } From e4ac37740444aec8f4ec86ca7c153af6e92c690b Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:54:27 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20S3=E3=81=AB=E3=82=A2=E3=83=83?= =?UTF-8?q?=E3=83=97=E3=83=AD=E3=83=BC=E3=83=89=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/usbharu/hideout/config/AwsConfig.kt | 4 ++++ .../mastodon/MastodonMediaApiController.kt | 5 +++-- .../domain/model/hideout/dto/ProcessedFile.kt | 6 +++++ .../model/hideout/dto/ProcessedMedia.kt | 6 +++++ .../api/mastodon/MediaApiServiceImpl.kt | 16 ++++++++++---- .../hideout/service/media/MediaServiceImpl.kt | 16 ++++++++++---- .../hideout/service/media/S3MediaDataStore.kt | 7 ++++-- .../service/media/ThumbnailGenerateService.kt | 5 +++-- .../media/ThumbnailGenerateServiceImpl.kt | 15 ++++++------- .../service/media/converter/MediaConverter.kt | 3 ++- .../media/converter/MediaConverterRoot.kt | 8 ++++++- .../media/converter/MediaConverterRootImpl.kt | 22 +++++++++++++++---- .../media/converter/MediaProcessService.kt | 5 ++++- .../converter/MediaProcessServiceImpl.kt | 19 ++++++++++------ src/main/resources/application.yml | 9 +------- 15 files changed, 102 insertions(+), 44 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/ProcessedFile.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/ProcessedMedia.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt index 48b4d4bb..ce9ad5a3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt @@ -3,13 +3,17 @@ package dev.usbharu.hideout.config import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.s3.S3Client +import java.net.URI @Configuration class AwsConfig { @Bean fun s3(awsConfig: StorageConfig): S3Client { return S3Client.builder() + .endpointOverride(URI.create(awsConfig.endpoint)) + .region(Region.of(awsConfig.region)) .credentialsProvider { AwsBasicCredentials.create(awsConfig.accessKey, awsConfig.secretKey) } .build() } diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt index f2d19aab..e357e2cc 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonMediaApiController.kt @@ -4,6 +4,7 @@ import dev.usbharu.hideout.controller.mastodon.generated.MediaApi import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment import dev.usbharu.hideout.domain.model.hideout.form.Media import dev.usbharu.hideout.service.api.mastodon.MediaApiService +import kotlinx.coroutines.runBlocking import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller import org.springframework.web.multipart.MultipartFile @@ -15,8 +16,8 @@ class MastodonMediaApiController(private val mediaApiService: MediaApiService) : thumbnail: MultipartFile?, description: String?, focus: String? - ): ResponseEntity { - return ResponseEntity.ok( + ): ResponseEntity = runBlocking { + ResponseEntity.ok( mediaApiService.postMedia( Media( file, diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/ProcessedFile.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/ProcessedFile.kt new file mode 100644 index 00000000..1bf60d6c --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/ProcessedFile.kt @@ -0,0 +1,6 @@ +package dev.usbharu.hideout.domain.model.hideout.dto + +data class ProcessedFile( + val byteArray: ByteArray, + val extension: String +) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/ProcessedMedia.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/ProcessedMedia.kt new file mode 100644 index 00000000..b11416e8 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/ProcessedMedia.kt @@ -0,0 +1,6 @@ +package dev.usbharu.hideout.domain.model.hideout.dto + +data class ProcessedMedia( + val file: ProcessedFile, + val thumbnail: ProcessedFile? +) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt index 1fdb5e60..0ea14e71 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt @@ -2,13 +2,21 @@ package dev.usbharu.hideout.service.api.mastodon import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment import dev.usbharu.hideout.domain.model.hideout.form.Media +import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.media.MediaService +import org.springframework.stereotype.Service + +@Service +class MediaApiServiceImpl(private val mediaService: MediaService, private val transaction: Transaction) : + MediaApiService { -class MediaApiServiceImpl(private val mediaService: MediaService) : MediaApiService { override suspend fun postMedia(media: Media): MediaAttachment { - val uploadLocalMedia = mediaService.uploadLocalMedia(media) - return MediaAttachment( + return transaction.transaction { - ) + val uploadLocalMedia = mediaService.uploadLocalMedia(media) + return@transaction MediaAttachment( + + ) + } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt index b9afd85d..2ed7d274 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt @@ -28,6 +28,8 @@ class MediaServiceImpl( private val mediaProcessService: MediaProcessService ) : MediaService { override suspend fun uploadLocalMedia(media: Media): EntityMedia { + logger.info("Media upload. filename:${media.file.name} size:${media.file.size} contentType:${media.file.contentType}") + if (media.file.size == 0L) { throw MediaFileSizeIsZeroException("Media file size is zero.") } @@ -37,13 +39,19 @@ class MediaServiceImpl( throw UnsupportedMediaException("FileType: $fileType is not supported.") } - val process = mediaProcessService.process(fileType, media.file.bytes, media.thumbnail?.bytes) + val process = mediaProcessService.process( + fileType, + media.file.contentType.orEmpty(), + media.file.name, + media.file.bytes, + media.thumbnail?.bytes + ) val dataMediaSave = MediaSave( - UUID.randomUUID().toString(), + "${UUID.randomUUID()}.${process.file.extension}", "", - process.first, - process.second + process.file.byteArray, + process.thumbnail?.byteArray ) val save = try { mediaDataStore.save(dataMediaSave) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt index 91e4bcff..a10482f5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/S3MediaDataStore.kt @@ -33,7 +33,10 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig awaitAll( async { if (dataMediaSave.thumbnailInputStream != null) { - s3Client.putObject(fileUploadRequest, RequestBody.fromBytes(dataMediaSave.thumbnailInputStream)) + s3Client.putObject( + thumbnailUploadRequest, + RequestBody.fromBytes(dataMediaSave.thumbnailInputStream) + ) "thumbnail" to s3Client.utilities() .getUrl(GetUrlRequest.builder().bucket(storageConfig.bucket).key(thumbnailKey).build()) } else { @@ -41,7 +44,7 @@ class S3MediaDataStore(private val s3Client: S3Client, private val storageConfig } }, async { - s3Client.putObject(thumbnailUploadRequest, RequestBody.fromBytes(dataMediaSave.fileInputStream)) + s3Client.putObject(fileUploadRequest, RequestBody.fromBytes(dataMediaSave.fileInputStream)) "file" to s3Client.utilities() .getUrl(GetUrlRequest.builder().bucket(storageConfig.bucket).key(dataMediaSave.name).build()) } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt index 9be5c865..cb8438d6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateService.kt @@ -1,8 +1,9 @@ package dev.usbharu.hideout.service.media +import dev.usbharu.hideout.domain.model.hideout.dto.ProcessedFile import java.io.InputStream interface ThumbnailGenerateService { - fun generate(bufferedImage: InputStream, width: Int, height: Int): ByteArray - fun generate(outputStream: ByteArray, width: Int, height: Int): ByteArray + fun generate(bufferedImage: InputStream, width: Int, height: Int): ProcessedFile? + fun generate(outputStream: ByteArray, width: Int, height: Int): ProcessedFile? } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateServiceImpl.kt index 5e6d416f..7f736de5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/ThumbnailGenerateServiceImpl.kt @@ -1,28 +1,27 @@ package dev.usbharu.hideout.service.media +import dev.usbharu.hideout.domain.model.hideout.dto.ProcessedFile import org.springframework.stereotype.Service import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import java.io.InputStream -import java.io.OutputStream import javax.imageio.ImageIO -import javax.imageio.stream.MemoryCacheImageOutputStream @Service class ThumbnailGenerateServiceImpl : ThumbnailGenerateService { - override fun generate(bufferedImage: InputStream, width: Int, height: Int): ByteArrayOutputStream { + override fun generate(bufferedImage: InputStream, width: Int, height: Int): ProcessedFile? { val image = ImageIO.read(bufferedImage) return internalGenerate(image) } - override fun generate(outputStream: OutputStream, width: Int, height: Int): ByteArrayOutputStream { - val image = ImageIO.read(MemoryCacheImageOutputStream(outputStream)) + override fun generate(outputStream: ByteArray, width: Int, height: Int): ProcessedFile? { + val image = ImageIO.read(outputStream.inputStream()) return internalGenerate(image) } - private fun internalGenerate(image: BufferedImage): ByteArrayOutputStream { + private fun internalGenerate(image: BufferedImage): ProcessedFile { val byteArrayOutputStream = ByteArrayOutputStream() - ImageIO.write(image, "webp", byteArrayOutputStream) - return byteArrayOutputStream + ImageIO.write(image, "jpeg", byteArrayOutputStream) + return ProcessedFile(byteArrayOutputStream.toByteArray(), "jpg") } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt index 06b6c624..0829ac46 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverter.kt @@ -1,9 +1,10 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import dev.usbharu.hideout.domain.model.hideout.dto.ProcessedFile import java.io.InputStream interface MediaConverter { fun isSupport(fileType: FileType): Boolean - fun convert(inputStream: InputStream): ByteArray + fun convert(inputStream: InputStream): ProcessedFile } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt index fd788277..4965da2a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRoot.kt @@ -1,8 +1,14 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import dev.usbharu.hideout.domain.model.hideout.dto.ProcessedFile import java.io.InputStream interface MediaConverterRoot { - suspend fun convert(fileType: FileType, inputStream: InputStream): ByteArray + suspend fun convert( + fileType: FileType, + contentType: String, + filename: String, + inputStream: InputStream + ): ProcessedFile } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt index 404f3f34..4d340bef 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaConverterRootImpl.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import dev.usbharu.hideout.domain.model.hideout.dto.ProcessedFile import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.springframework.stereotype.Service @@ -8,11 +9,24 @@ import java.io.InputStream @Service class MediaConverterRootImpl(private val converters: List) : MediaConverterRoot { - override suspend fun convert(fileType: FileType, inputStream: InputStream): ByteArray { - return converters.find { + override suspend fun convert( + fileType: FileType, + contentType: String, + filename: String, + inputStream: InputStream + ): ProcessedFile { + val convert = converters.find { it.isSupport(fileType) - }?.convert(inputStream) ?: withContext(Dispatchers.IO) { - inputStream.readAllBytes() + }?.convert(inputStream) + if (convert != null) { + return convert + } + return withContext(Dispatchers.IO) { + if (filename.contains('.')) { + ProcessedFile(inputStream.readAllBytes(), filename.substringAfterLast(".")) + } else { + ProcessedFile(inputStream.readAllBytes(), contentType.substringAfterLast("/")) + } } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt index 06f752ff..5df2c16a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessService.kt @@ -1,11 +1,14 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import dev.usbharu.hideout.domain.model.hideout.dto.ProcessedMedia interface MediaProcessService { suspend fun process( fileType: FileType, + contentType: String, + fileName: String, file: ByteArray, thumbnail: ByteArray? - ): Pair + ): ProcessedMedia } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt index 470e0bcf..6ae5d834 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.service.media.converter import dev.usbharu.hideout.domain.model.hideout.dto.FileType +import dev.usbharu.hideout.domain.model.hideout.dto.ProcessedMedia import dev.usbharu.hideout.exception.media.MediaConvertException import dev.usbharu.hideout.service.media.ThumbnailGenerateService import org.slf4j.LoggerFactory @@ -13,26 +14,30 @@ class MediaProcessServiceImpl( ) : MediaProcessService { override suspend fun process( fileType: FileType, + contentType: String, + filename: String, file: ByteArray, thumbnail: ByteArray? - ): Pair { + ): ProcessedMedia { val fileInputStream = try { - mediaConverterRoot.convert(fileType, file.inputStream().buffered()) + mediaConverterRoot.convert(fileType, contentType, filename, file.inputStream().buffered()) } catch (e: Exception) { logger.warn("Failed convert media.", e) throw MediaConvertException("Failed convert media.", e) } val thumbnailInputStream = try { - thumbnail?.let { mediaConverterRoot.convert(fileType, it.inputStream().buffered()) } + thumbnail?.let { mediaConverterRoot.convert(fileType, contentType, filename, it.inputStream().buffered()) } } catch (e: Exception) { logger.warn("Failed convert thumbnail media.", e) null } - return fileInputStream to thumbnailGenerateService.generate( - thumbnailInputStream ?: fileInputStream, - 2048, - 2048 + return ProcessedMedia( + fileInputStream, thumbnailGenerateService.generate( + thumbnailInputStream?.byteArray ?: file, + 2048, + 2048 + ) ) } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9c9975d0..143486fb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,14 +7,7 @@ hideout: key-id: a private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==" public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" - storage: - use-s3: true - endpoint: "" - public-url: "" - bucket: "" - region: "" - access-key: "" - secret-key: "" + From 7c66ceac00b0404086e2a0efdcbed7f0a14671a9 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:58:37 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=E3=83=A1=E3=83=87=E3=82=A3?= =?UTF-8?q?=E3=82=A2=E3=81=AE=E3=82=A2=E3=83=83=E3=83=97=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=89=E5=87=A6=E7=90=86=E3=81=AE=E7=B5=90=E6=9E=9C=E3=82=92?= =?UTF-8?q?=E8=BF=94=E5=8D=B4=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/api/mastodon/MediaApiServiceImpl.kt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt index 0ea14e71..ae4c444e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.service.api.mastodon import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment +import dev.usbharu.hideout.domain.model.hideout.dto.FileType import dev.usbharu.hideout.domain.model.hideout.form.Media import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.media.MediaService @@ -14,8 +15,21 @@ class MediaApiServiceImpl(private val mediaService: MediaService, private val tr return transaction.transaction { val uploadLocalMedia = mediaService.uploadLocalMedia(media) + val type = when (uploadLocalMedia.type) { + FileType.Image -> MediaAttachment.Type.image + FileType.Video -> MediaAttachment.Type.video + FileType.Audio -> MediaAttachment.Type.audio + FileType.Unknown -> MediaAttachment.Type.unknown + } return@transaction MediaAttachment( - + uploadLocalMedia.id.toString(), + type, + uploadLocalMedia.url, + uploadLocalMedia.thumbnailUrl, + null, + media.description, + uploadLocalMedia.blurHash, + uploadLocalMedia.url ) } } From d19a6029e21883fb60b8c8b88e95d12fe4e36059 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:23:11 +0900 Subject: [PATCH 9/9] =?UTF-8?q?style:=20=E3=82=B9=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=82=92=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- detekt.yml | 1 + .../dev/usbharu/hideout/config/AwsConfig.kt | 2 +- .../domain/model/hideout/dto/SavedMedia.kt | 1 - .../hideout/repository/MediaRepositoryImpl.kt | 18 +++++++++--------- .../repository/ReactionRepositoryImpl.kt | 1 - .../api/mastodon/MediaApiServiceImpl.kt | 17 ++++++++--------- .../media/FileTypeDeterminationService.kt | 1 - .../hideout/service/media/MediaServiceImpl.kt | 8 ++++---- .../media/converter/MediaProcessServiceImpl.kt | 4 ++-- 9 files changed, 25 insertions(+), 28 deletions(-) diff --git a/detekt.yml b/detekt.yml index 26af6de1..658f1b3b 100644 --- a/detekt.yml +++ b/detekt.yml @@ -3,6 +3,7 @@ build: weights: Indentation: 0 MagicNumber: 0 + InjectDispatcher: 0 style: ClassOrdering: diff --git a/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt index ce9ad5a3..b697bb61 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/AwsConfig.kt @@ -10,7 +10,7 @@ import java.net.URI @Configuration class AwsConfig { @Bean - fun s3(awsConfig: StorageConfig): S3Client { + fun s3Client(awsConfig: StorageConfig): S3Client { return S3Client.builder() .endpointOverride(URI.create(awsConfig.endpoint)) .region(Region.of(awsConfig.region)) diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt index 60ea05b8..b8ab7490 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/SavedMedia.kt @@ -9,7 +9,6 @@ class SuccessSavedMedia( ) : SavedMedia(true) - class FaildSavedMedia( val reason: String, val description: String, diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt index f2ffb720..610d3e5a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/MediaRepositoryImpl.kt @@ -1,6 +1,5 @@ package dev.usbharu.hideout.repository - import dev.usbharu.hideout.domain.model.hideout.dto.FileType import dev.usbharu.hideout.exception.FailedToGetResourcesException import dev.usbharu.hideout.service.core.IdGenerateService @@ -17,7 +16,8 @@ class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : Me override suspend fun save(media: EntityMedia): EntityMedia { if (Media.select { Media.id eq media.id - }.singleOrNull() != null) { + }.singleOrNull() != null + ) { Media.update({ Media.id eq media.id }) { it[Media.name] = media.name it[Media.url] = media.url @@ -58,13 +58,13 @@ class MediaRepositoryImpl(private val idGenerateService: IdGenerateService) : Me fun ResultRow.toMedia(): EntityMedia { return EntityMedia( - this[Media.id], - this[Media.name], - this[Media.url], - this[Media.remoteUrl], - this[Media.thumbnailUrl], - FileType.values().first { it.ordinal == this[Media.type] }, - this[Media.blurhash], + id = this[Media.id], + name = this[Media.name], + url = this[Media.url], + remoteUrl = this[Media.remoteUrl], + thumbnailUrl = this[Media.thumbnailUrl], + type = FileType.values().first { it.ordinal == this[Media.type] }, + blurHash = this[Media.blurhash], ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt index 866feaf3..34789884 100644 --- a/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/repository/ReactionRepositoryImpl.kt @@ -12,7 +12,6 @@ class ReactionRepositoryImpl( private val idGenerateService: IdGenerateService ) : ReactionRepository { - override suspend fun generateId(): Long = idGenerateService.generateId() override suspend fun save(reaction: Reaction): Reaction { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt index ae4c444e..ab4d07de 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/MediaApiServiceImpl.kt @@ -13,7 +13,6 @@ class MediaApiServiceImpl(private val mediaService: MediaService, private val tr override suspend fun postMedia(media: Media): MediaAttachment { return transaction.transaction { - val uploadLocalMedia = mediaService.uploadLocalMedia(media) val type = when (uploadLocalMedia.type) { FileType.Image -> MediaAttachment.Type.image @@ -22,14 +21,14 @@ class MediaApiServiceImpl(private val mediaService: MediaService, private val tr FileType.Unknown -> MediaAttachment.Type.unknown } return@transaction MediaAttachment( - uploadLocalMedia.id.toString(), - type, - uploadLocalMedia.url, - uploadLocalMedia.thumbnailUrl, - null, - media.description, - uploadLocalMedia.blurHash, - uploadLocalMedia.url + id = uploadLocalMedia.id.toString(), + type = type, + url = uploadLocalMedia.url, + previewUrl = uploadLocalMedia.thumbnailUrl, + remoteUrl = null, + description = media.description, + blurhash = uploadLocalMedia.blurHash, + textUrl = uploadLocalMedia.url ) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt index 19a7feb6..5849feea 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/FileTypeDeterminationService.kt @@ -4,5 +4,4 @@ import dev.usbharu.hideout.domain.model.hideout.dto.FileType interface FileTypeDeterminationService { fun fileType(byteArray: ByteArray, filename: String, contentType: String?): FileType - } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt index 2ed7d274..313bb8e1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/MediaServiceImpl.kt @@ -28,7 +28,9 @@ class MediaServiceImpl( private val mediaProcessService: MediaProcessService ) : MediaService { override suspend fun uploadLocalMedia(media: Media): EntityMedia { - logger.info("Media upload. filename:${media.file.name} size:${media.file.size} contentType:${media.file.contentType}") + logger.info( + "Media upload. filename:${media.file.name} size:${media.file.size} contentType:${media.file.contentType}" + ) if (media.file.size == 0L) { throw MediaFileSizeIsZeroException("Media file size is zero.") @@ -85,9 +87,7 @@ class MediaServiceImpl( ) } - override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia) { - - } + override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia) = Unit companion object { private val logger = LoggerFactory.getLogger(MediaServiceImpl::class.java) diff --git a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt index 6ae5d834..c32688d5 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/media/converter/MediaProcessServiceImpl.kt @@ -19,7 +19,6 @@ class MediaProcessServiceImpl( file: ByteArray, thumbnail: ByteArray? ): ProcessedMedia { - val fileInputStream = try { mediaConverterRoot.convert(fileType, contentType, filename, file.inputStream().buffered()) } catch (e: Exception) { @@ -33,7 +32,8 @@ class MediaProcessServiceImpl( null } return ProcessedMedia( - fileInputStream, thumbnailGenerateService.generate( + fileInputStream, + thumbnailGenerateService.generate( thumbnailInputStream?.byteArray ?: file, 2048, 2048