112 lines
4.1 KiB
Kotlin
112 lines
4.1 KiB
Kotlin
package com.android.trisolarisserver.component
|
|
|
|
import org.springframework.beans.factory.annotation.Value
|
|
import org.springframework.stereotype.Component
|
|
import org.springframework.web.multipart.MultipartFile
|
|
import java.awt.image.BufferedImage
|
|
import java.io.ByteArrayInputStream
|
|
import java.nio.file.Files
|
|
import java.nio.file.Paths
|
|
import java.time.OffsetDateTime
|
|
import java.util.UUID
|
|
import javax.imageio.ImageIO
|
|
|
|
@Component
|
|
class RoomImageStorage(
|
|
@Value("\${storage.rooms.root:/home/androidlover5842/docs/rooms}")
|
|
private val rootPath: String
|
|
) {
|
|
init {
|
|
val root = Paths.get(rootPath)
|
|
try {
|
|
Files.createDirectories(root)
|
|
} catch (ex: Exception) {
|
|
throw IllegalStateException("Room image root not writable: $rootPath", ex)
|
|
}
|
|
if (!Files.isWritable(root)) {
|
|
throw IllegalStateException("Room image root not writable: $rootPath")
|
|
}
|
|
}
|
|
|
|
fun store(propertyId: UUID, roomId: UUID, file: MultipartFile): StoredRoomImage {
|
|
val contentType = file.contentType ?: ""
|
|
if (!contentType.startsWith("image/")) {
|
|
throw IllegalArgumentException("Only image files are allowed")
|
|
}
|
|
val bytes = file.bytes
|
|
val originalName = file.originalFilename ?: UUID.randomUUID().toString()
|
|
val ext = extensionFor(contentType, originalName)
|
|
|
|
val dir = Paths.get(rootPath, propertyId.toString(), roomId.toString())
|
|
try {
|
|
Files.createDirectories(dir)
|
|
} catch (ex: Exception) {
|
|
throw IllegalStateException("Failed to create room image directory: $dir", ex)
|
|
}
|
|
val base = UUID.randomUUID().toString() + "_" + OffsetDateTime.now().toEpochSecond()
|
|
val originalPath = dir.resolve("$base.$ext")
|
|
val originalTmp = dir.resolve("$base.$ext.tmp")
|
|
Files.write(originalTmp, bytes)
|
|
atomicMove(originalTmp, originalPath)
|
|
|
|
val image = readImage(bytes)
|
|
?: throw IllegalArgumentException("Unsupported image")
|
|
val thumb = resize(image, 320)
|
|
val thumbExt = if (ext.lowercase() == "jpg") "jpg" else "png"
|
|
val thumbPath = dir.resolve("${base}_thumb.$thumbExt")
|
|
val thumbTmp = dir.resolve("${base}_thumb.$thumbExt.tmp")
|
|
ByteArrayInputStream(render(thumb, thumbExt)).use { input ->
|
|
Files.copy(input, thumbTmp)
|
|
}
|
|
atomicMove(thumbTmp, thumbPath)
|
|
|
|
return StoredRoomImage(
|
|
originalPath = originalPath.toString(),
|
|
thumbnailPath = thumbPath.toString(),
|
|
contentType = contentType,
|
|
sizeBytes = bytes.size.toLong()
|
|
)
|
|
}
|
|
|
|
private fun readImage(bytes: ByteArray): BufferedImage? {
|
|
return ByteArrayInputStream(bytes).use { input -> ImageIO.read(input) }
|
|
}
|
|
|
|
private fun resize(input: BufferedImage, maxSize: Int): BufferedImage {
|
|
val width = input.width
|
|
val height = input.height
|
|
if (width <= maxSize && height <= maxSize) return input
|
|
val scale = if (width > height) maxSize.toDouble() / width else maxSize.toDouble() / height
|
|
val newW = (width * scale).toInt()
|
|
val newH = (height * scale).toInt()
|
|
val output = BufferedImage(newW, newH, BufferedImage.TYPE_INT_RGB)
|
|
val g = output.createGraphics()
|
|
g.drawImage(input, 0, 0, newW, newH, null)
|
|
g.dispose()
|
|
return output
|
|
}
|
|
|
|
private fun render(image: BufferedImage, format: String): ByteArray {
|
|
val out = java.io.ByteArrayOutputStream()
|
|
ImageIO.write(image, format, out)
|
|
return out.toByteArray()
|
|
}
|
|
|
|
private fun extensionFor(contentType: String, filename: String): String {
|
|
return when {
|
|
contentType.contains("png", true) -> "png"
|
|
contentType.contains("jpeg", true) || contentType.contains("jpg", true) -> "jpg"
|
|
filename.contains(".") -> filename.substringAfterLast('.').lowercase()
|
|
else -> "png"
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
data class StoredRoomImage(
|
|
val originalPath: String,
|
|
val thumbnailPath: String,
|
|
val contentType: String,
|
|
val sizeBytes: Long
|
|
)
|