From 58b0931a49da0ae1bd81c5e869f95dab7ee3dab1 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 15 Nov 2023 14:36:41 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E3=83=AA=E3=83=A2=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=81=AE=E7=94=BB=E5=83=8F=E3=81=AEOOM=E5=AF=BE=E7=AD=96?= =?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 --- .../core/service/media/MediaServiceImpl.kt | 106 +++++++----------- .../media/RemoteMediaDownloadService.kt | 7 ++ .../media/RemoteMediaDownloadServiceImpl.kt | 37 ++++++ .../dev/usbharu/hideout/util/TempFileUtil.kt | 4 +- 4 files changed, 88 insertions(+), 66 deletions(-) create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadService.kt create mode 100644 src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadServiceImpl.kt diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt index eb3d0869..c5731bb4 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/MediaServiceImpl.kt @@ -7,29 +7,21 @@ import dev.usbharu.hideout.core.domain.model.media.MediaRepository import dev.usbharu.hideout.core.service.media.converter.MediaProcessService import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest import dev.usbharu.hideout.util.withDelete -import io.ktor.client.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import io.ktor.util.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.nio.file.Files -import java.util.* import javax.imageio.ImageIO import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia @Service @Suppress("TooGenericExceptionCaught") -class MediaServiceImpl( +open class MediaServiceImpl( private val mediaDataStore: MediaDataStore, private val fileTypeDeterminationService: FileTypeDeterminationService, private val mediaBlurhashService: MediaBlurhashService, private val mediaRepository: MediaRepository, private val mediaProcessServices: List, - private val httpClient: HttpClient + private val remoteMediaDownloadService: RemoteMediaDownloadService ) : MediaService { override suspend fun uploadLocalMedia(mediaRequest: MediaRequest): EntityMedia { val fileName = mediaRequest.file.name @@ -102,63 +94,49 @@ class MediaServiceImpl( override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media { logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}") - val httpResponse = httpClient.get(remoteMedia.url) - val bytes = httpResponse.bodyAsChannel().toByteArray() + remoteMediaDownloadService.download(remoteMedia.url).withDelete().use { + val mimeType = fileTypeDeterminationService.fileType(it.path, remoteMedia.name) - val contentType = httpResponse.contentType()?.toString() - val mimeType = - fileTypeDeterminationService.fileType(bytes, remoteMedia.name, contentType) + val process = findMediaProcessor(mimeType).process(mimeType, remoteMedia.name, it.path, null) - if (mimeType.fileType != FileType.Image) { - throw UnsupportedMediaException("FileType: $mimeType isn't supported.") - } - - val processedMedia = mediaProcessServices.first().process( - fileType = mimeType.fileType, - contentType = contentType.orEmpty(), - fileName = remoteMedia.name, - file = bytes, - thumbnail = null - ) - - val mediaSave = MediaSave( - "${UUID.randomUUID()}.${processedMedia.file.extension}", - "", - processedMedia.file.byteArray, - processedMedia.thumbnail?.byteArray - ) - - val save = try { - mediaDataStore.save(mediaSave) - } catch (e: Exception) { - logger.warn("Failed save media", e) - throw MediaSaveException("Failed save media.", e) - } - - if (save.success.not()) { - save as FaildSavedMedia - logger.warn("Failed to save the media. reason: ${save.reason}") - logger.warn(save.description, save.trace) - throw MediaSaveException("Failed to save the media.") - } - save as SuccessSavedMedia - - val blurhash = withContext(Dispatchers.IO) { - mediaBlurhashService.generateBlurhash(ImageIO.read(bytes.inputStream())) - } - - return mediaRepository.save( - EntityMedia( - id = mediaRepository.generateId(), - name = remoteMedia.name, - url = save.url, - remoteUrl = remoteMedia.url, - thumbnailUrl = save.thumbnailUrl, - type = mimeType.fileType, - mimeType = mimeType, - blurHash = blurhash + val mediaSaveRequest = MediaSaveRequest( + process.filePath.fileName.toString(), + "", + process.filePath, + process.thumbnailPath ) - ) + + mediaSaveRequest.filePath.withDelete().use { + mediaSaveRequest.filePath.withDelete().use { + val save = try { + mediaDataStore.save(mediaSaveRequest) + } catch (e: Exception) { + logger.warn("Failed to save the media", e) + throw MediaSaveException("Failed to save the media.", e) + } + + if (save is FaildSavedMedia) { + logger.warn("Failed to save the media. reason: ${save.reason}") + logger.warn(save.description, save.trace) + throw MediaSaveException("Failed to save the media.") + } + save as SuccessSavedMedia + val blurhash = generateBlurhash(process) + return mediaRepository.save( + EntityMedia( + id = mediaRepository.generateId(), + name = remoteMedia.name, + url = save.url, + remoteUrl = remoteMedia.url, + thumbnailUrl = save.thumbnailUrl, + type = process.fileMimeType.fileType, + mimeType = process.fileMimeType, + blurHash = blurhash + ) + ) + } + } + } } protected fun findMediaProcessor(mimeType: MimeType): MediaProcessService { diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadService.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadService.kt new file mode 100644 index 00000000..33b9b071 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadService.kt @@ -0,0 +1,7 @@ +package dev.usbharu.hideout.core.service.media + +import java.nio.file.Path + +interface RemoteMediaDownloadService { + suspend fun download(url: String): Path +} diff --git a/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadServiceImpl.kt b/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadServiceImpl.kt new file mode 100644 index 00000000..6bc9040c --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/core/service/media/RemoteMediaDownloadServiceImpl.kt @@ -0,0 +1,37 @@ +package dev.usbharu.hideout.core.service.media + +import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.utils.io.jvm.javaio.* +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.outputStream + +@Service +class RemoteMediaDownloadServiceImpl(private val httpClient: HttpClient) : RemoteMediaDownloadService { + override suspend fun download(url: String): Path { + logger.info("START Download remote file. url: {}", url) + val httpResponse = httpClient.get(url) + httpResponse.contentLength() + val createTempFile = Files.createTempFile("hideout-remote-download", ".tmp") + + logger.debug("Save to {} url: {} ", createTempFile, url) + + httpResponse.bodyAsChannel().toInputStream().use { inputStream -> + createTempFile.outputStream().use { + inputStream.transferTo(it) + } + } + + logger.info("SUCCESS Download remote file. url: {}", url) + return createTempFile + } + + companion object { + private val logger = LoggerFactory.getLogger(RemoteMediaDownloadServiceImpl::class.java) + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt b/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt index 1a3ab50b..186aa889 100644 --- a/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt +++ b/src/main/kotlin/dev/usbharu/hideout/util/TempFileUtil.kt @@ -3,9 +3,9 @@ package dev.usbharu.hideout.util import java.nio.file.Files import java.nio.file.Path -fun Path?.withDelete(): TempFile = TempFile(this) +fun T.withDelete(): TempFile = TempFile(this) -class TempFile(val path: Path?) : AutoCloseable { +class TempFile(val path: T) : AutoCloseable { override fun close() { path?.let { Files.deleteIfExists(it) } }