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.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<MediaProcessService>,
|
||||
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 {
|
||||
|
|
|
@ -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.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() {
|
||||
path?.let { Files.deleteIfExists(it) }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue