mirror of https://github.com/usbharu/Hideout.git
feat: リモートの画像のOOM対策を追加
This commit is contained in:
parent
5f50f4fb28
commit
58b0931a49
|
@ -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.core.service.media.converter.MediaProcessService
|
||||||
import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest
|
import dev.usbharu.hideout.mastodon.interfaces.api.media.MediaRequest
|
||||||
import dev.usbharu.hideout.util.withDelete
|
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.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.util.*
|
|
||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia
|
import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Suppress("TooGenericExceptionCaught")
|
@Suppress("TooGenericExceptionCaught")
|
||||||
class MediaServiceImpl(
|
open class MediaServiceImpl(
|
||||||
private val mediaDataStore: MediaDataStore,
|
private val mediaDataStore: MediaDataStore,
|
||||||
private val fileTypeDeterminationService: FileTypeDeterminationService,
|
private val fileTypeDeterminationService: FileTypeDeterminationService,
|
||||||
private val mediaBlurhashService: MediaBlurhashService,
|
private val mediaBlurhashService: MediaBlurhashService,
|
||||||
private val mediaRepository: MediaRepository,
|
private val mediaRepository: MediaRepository,
|
||||||
private val mediaProcessServices: List<MediaProcessService>,
|
private val mediaProcessServices: List<MediaProcessService>,
|
||||||
private val httpClient: HttpClient
|
private val remoteMediaDownloadService: RemoteMediaDownloadService
|
||||||
) : MediaService {
|
) : MediaService {
|
||||||
override suspend fun uploadLocalMedia(mediaRequest: MediaRequest): EntityMedia {
|
override suspend fun uploadLocalMedia(mediaRequest: MediaRequest): EntityMedia {
|
||||||
val fileName = mediaRequest.file.name
|
val fileName = mediaRequest.file.name
|
||||||
|
@ -102,51 +94,34 @@ class MediaServiceImpl(
|
||||||
override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media {
|
override suspend fun uploadRemoteMedia(remoteMedia: RemoteMedia): Media {
|
||||||
logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}")
|
logger.info("MEDIA Remote media. filename:${remoteMedia.name} url:${remoteMedia.url}")
|
||||||
|
|
||||||
val httpResponse = httpClient.get(remoteMedia.url)
|
remoteMediaDownloadService.download(remoteMedia.url).withDelete().use {
|
||||||
val bytes = httpResponse.bodyAsChannel().toByteArray()
|
val mimeType = fileTypeDeterminationService.fileType(it.path, remoteMedia.name)
|
||||||
|
|
||||||
val contentType = httpResponse.contentType()?.toString()
|
val process = findMediaProcessor(mimeType).process(mimeType, remoteMedia.name, it.path, null)
|
||||||
val mimeType =
|
|
||||||
fileTypeDeterminationService.fileType(bytes, remoteMedia.name, contentType)
|
|
||||||
|
|
||||||
if (mimeType.fileType != FileType.Image) {
|
val mediaSaveRequest = MediaSaveRequest(
|
||||||
throw UnsupportedMediaException("FileType: $mimeType isn't supported.")
|
process.filePath.fileName.toString(),
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
process.filePath,
|
||||||
processedMedia.thumbnail?.byteArray
|
process.thumbnailPath
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mediaSaveRequest.filePath.withDelete().use {
|
||||||
|
mediaSaveRequest.filePath.withDelete().use {
|
||||||
val save = try {
|
val save = try {
|
||||||
mediaDataStore.save(mediaSave)
|
mediaDataStore.save(mediaSaveRequest)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.warn("Failed save media", e)
|
logger.warn("Failed to save the media", e)
|
||||||
throw MediaSaveException("Failed save media.", e)
|
throw MediaSaveException("Failed to save the media.", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (save.success.not()) {
|
if (save is FaildSavedMedia) {
|
||||||
save as FaildSavedMedia
|
|
||||||
logger.warn("Failed to save the media. reason: ${save.reason}")
|
logger.warn("Failed to save the media. reason: ${save.reason}")
|
||||||
logger.warn(save.description, save.trace)
|
logger.warn(save.description, save.trace)
|
||||||
throw MediaSaveException("Failed to save the media.")
|
throw MediaSaveException("Failed to save the media.")
|
||||||
}
|
}
|
||||||
save as SuccessSavedMedia
|
save as SuccessSavedMedia
|
||||||
|
val blurhash = generateBlurhash(process)
|
||||||
val blurhash = withContext(Dispatchers.IO) {
|
|
||||||
mediaBlurhashService.generateBlurhash(ImageIO.read(bytes.inputStream()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return mediaRepository.save(
|
return mediaRepository.save(
|
||||||
EntityMedia(
|
EntityMedia(
|
||||||
id = mediaRepository.generateId(),
|
id = mediaRepository.generateId(),
|
||||||
|
@ -154,12 +129,15 @@ class MediaServiceImpl(
|
||||||
url = save.url,
|
url = save.url,
|
||||||
remoteUrl = remoteMedia.url,
|
remoteUrl = remoteMedia.url,
|
||||||
thumbnailUrl = save.thumbnailUrl,
|
thumbnailUrl = save.thumbnailUrl,
|
||||||
type = mimeType.fileType,
|
type = process.fileMimeType.fileType,
|
||||||
mimeType = mimeType,
|
mimeType = process.fileMimeType,
|
||||||
blurHash = blurhash
|
blurHash = blurhash
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected fun findMediaProcessor(mimeType: MimeType): MediaProcessService {
|
protected fun findMediaProcessor(mimeType: MimeType): MediaProcessService {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.usbharu.hideout.core.service.media
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
interface RemoteMediaDownloadService {
|
||||||
|
suspend fun download(url: String): Path
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,9 +3,9 @@ package dev.usbharu.hideout.util
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
fun Path?.withDelete(): TempFile = TempFile(this)
|
fun <T : Path?> T.withDelete(): TempFile<T> = TempFile(this)
|
||||||
|
|
||||||
class TempFile(val path: Path?) : AutoCloseable {
|
class TempFile<T : Path?>(val path: T) : AutoCloseable {
|
||||||
override fun close() {
|
override fun close() {
|
||||||
path?.let { Files.deleteIfExists(it) }
|
path?.let { Files.deleteIfExists(it) }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue