feature/18-add-existing-files-cache #26
|
@ -102,7 +102,7 @@ tasks.withType<JavaCompile>() {
|
|||
}
|
||||
tasks.withType<KotlinCompile>().all {
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xopt-in=kotlin.contracts.ExperimentalContracts",
|
||||
"-Xinline-classes"
|
||||
|
|
|
@ -51,7 +51,7 @@ class MaterialTypeInitializer(
|
|||
// Remove old system types
|
||||
oldSystemTypes.forEach {
|
||||
logger.info("Material type '${it.name}' is not a system type anymore")
|
||||
materialTypeService.update(materialType(it, newSystemType = false))
|
||||
materialTypeService.updateSystemType(it.copy(systemType = false))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import org.springframework.context.annotation.Profile
|
|||
import org.springframework.stereotype.Service
|
||||
|
||||
interface MaterialTypeService :
|
||||
ExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository> {
|
||||
ExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository> {
|
||||
/** Checks if a material type with the given [prefix] exists. */
|
||||
fun existsByPrefix(prefix: String): Boolean
|
||||
|
||||
|
@ -19,14 +19,17 @@ interface MaterialTypeService :
|
|||
|
||||
/** Gets all material types who are not a system type. */
|
||||
fun getAllNonSystemType(): Collection<MaterialType>
|
||||
|
||||
/** Allows to update the given system [materialType], should not be exposed to users. */
|
||||
fun updateSystemType(materialType: MaterialType): MaterialType
|
||||
}
|
||||
|
||||
@Service
|
||||
@Profile("!emergency")
|
||||
class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val materialService: MaterialService) :
|
||||
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository>(
|
||||
repository
|
||||
), MaterialTypeService {
|
||||
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialType, MaterialTypeRepository>(
|
||||
repository
|
||||
), MaterialTypeService {
|
||||
override fun idNotFoundException(id: Long) = materialTypeIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = materialIdAlreadyExistsException(id)
|
||||
override fun nameNotFoundException(name: String) = materialTypeNameNotFoundException(name)
|
||||
|
@ -36,7 +39,7 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
|
|||
|
||||
override fun existsByPrefix(prefix: String): Boolean = repository.existsByPrefix(prefix)
|
||||
override fun isUsedByMaterial(materialType: MaterialType): Boolean =
|
||||
materialService.existsByMaterialType(materialType)
|
||||
materialService.existsByMaterialType(materialType)
|
||||
|
||||
override fun getAllSystemTypes(): Collection<MaterialType> = repository.findAllBySystemTypeIs(true)
|
||||
override fun getAllNonSystemType(): Collection<MaterialType> = repository.findAllBySystemTypeIs(false)
|
||||
|
@ -52,16 +55,22 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
|
|||
|
||||
return update(with(entity) {
|
||||
MaterialType(
|
||||
id = id,
|
||||
name = if (isNotNullAndNotBlank(name)) name else persistedMaterialType.name,
|
||||
prefix = if (isNotNullAndNotBlank(prefix)) prefix else persistedMaterialType.prefix,
|
||||
systemType = false
|
||||
id = id,
|
||||
name = if (isNotNullAndNotBlank(name)) name else persistedMaterialType.name,
|
||||
prefix = if (isNotNullAndNotBlank(prefix)) prefix else persistedMaterialType.prefix,
|
||||
systemType = false
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun update(entity: MaterialType): MaterialType {
|
||||
if (repository.existsByIdAndSystemTypeIsTrue(entity.id!!)) {
|
||||
override fun updateSystemType(materialType: MaterialType) =
|
||||
update(materialType, true)
|
||||
|
||||
override fun update(entity: MaterialType) =
|
||||
update(entity, false)
|
||||
|
||||
private fun update(entity: MaterialType, allowSystemTypes: Boolean): MaterialType {
|
||||
if (!allowSystemTypes && repository.existsByIdAndSystemTypeIsTrue(entity.id!!)) {
|
||||
throw cannotUpdateSystemMaterialTypeException(entity)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
package dev.fyloz.colorrecipesexplorer.service.files
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.utils.FilePath
|
||||
import mu.KotlinLogging
|
||||
|
||||
object FileExistCache {
|
||||
private val map = hashMapOf<FilePath, Boolean>()
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val map = hashMapOf<String, Boolean>()
|
||||
|
||||
/** Checks if the given [path] is in the cache. */
|
||||
operator fun contains(path: FilePath) =
|
||||
path in map
|
||||
path.path in map
|
||||
|
||||
/** Checks if the file at the given [path] exists. */
|
||||
fun exists(path: FilePath) =
|
||||
map[path] ?: false
|
||||
map[path.path] ?: false
|
||||
|
||||
/** Sets the file at the given [path] as existing. */
|
||||
fun setExists(path: FilePath) =
|
||||
|
@ -21,7 +23,10 @@ object FileExistCache {
|
|||
fun setDoesNotExists(path: FilePath) =
|
||||
set(path, false)
|
||||
|
||||
private fun set(path: FilePath, exists: Boolean) {
|
||||
map[path] = exists
|
||||
/** Sets if the file at the given [path] [exists]. */
|
||||
fun set(path: FilePath, exists: Boolean) {
|
||||
map[path.path] = exists
|
||||
|
||||
logger.debug("Updated FileExistCache state: ${path.path} -> $exists")
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import dev.fyloz.colorrecipesexplorer.exception.RestException
|
|||
import dev.fyloz.colorrecipesexplorer.utils.File
|
||||
import dev.fyloz.colorrecipesexplorer.utils.FilePath
|
||||
import dev.fyloz.colorrecipesexplorer.utils.withFileAt
|
||||
import mu.KotlinLogging
|
||||
import org.slf4j.Logger
|
||||
import org.springframework.core.io.ByteArrayResource
|
||||
import org.springframework.core.io.Resource
|
||||
|
@ -47,16 +48,19 @@ interface WriteableFileService : FileService {
|
|||
|
||||
@Service
|
||||
class FileServiceImpl(
|
||||
private val creProperties: CreProperties,
|
||||
private val logger: Logger
|
||||
private val creProperties: CreProperties
|
||||
) : WriteableFileService {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
override fun exists(path: String): Boolean {
|
||||
val fullPath = path.fullPath()
|
||||
return if (fullPath in FileExistCache) {
|
||||
FileExistCache.exists(fullPath)
|
||||
} else {
|
||||
withFileAt(fullPath) {
|
||||
this.exists() && this.isFile
|
||||
(this.exists() && this.isFile).also {
|
||||
FileExistCache.set(fullPath, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +83,8 @@ class FileServiceImpl(
|
|||
withFileAt(fullPath) {
|
||||
this.create()
|
||||
FileExistCache.setExists(fullPath)
|
||||
|
||||
logger.info("Created file at '${fullPath.path}'")
|
||||
}
|
||||
} catch (ex: IOException) {
|
||||
FileCreateException(path).logAndThrow(ex, logger)
|
||||
|
@ -88,11 +94,13 @@ class FileServiceImpl(
|
|||
|
||||
override fun write(file: MultipartFile, path: String, overwrite: Boolean) =
|
||||
prepareWrite(path, overwrite) {
|
||||
logWrittenDataSize(file.size)
|
||||
file.transferTo(this.toPath())
|
||||
}
|
||||
|
||||
override fun write(data: ByteArrayResource, path: String, overwrite: Boolean) =
|
||||
prepareWrite(path, overwrite) {
|
||||
logWrittenDataSize(data.contentLength())
|
||||
this.writeBytes(data.byteArray)
|
||||
}
|
||||
|
||||
|
@ -104,6 +112,8 @@ class FileServiceImpl(
|
|||
|
||||
this.delete()
|
||||
FileExistCache.setDoesNotExists(fullPath)
|
||||
|
||||
logger.info("Deleted file at '${fullPath.path}'")
|
||||
}
|
||||
} catch (ex: IOException) {
|
||||
FileDeleteException(path).logAndThrow(ex, logger)
|
||||
|
@ -130,11 +140,17 @@ class FileServiceImpl(
|
|||
try {
|
||||
withFileAt(fullPath) {
|
||||
this.op()
|
||||
|
||||
logger.info("Wrote data to file at '${fullPath.path}'")
|
||||
}
|
||||
} catch (ex: IOException) {
|
||||
FileWriteException(path).logAndThrow(ex, logger)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logWrittenDataSize(size: Long) {
|
||||
logger.debug("Writing $size bytes to file system...")
|
||||
}
|
||||
}
|
||||
|
||||
private const val FILE_IO_EXCEPTION_TITLE = "File IO error"
|
||||
|
|
|
@ -10,29 +10,29 @@ class File(val file: JavaFile) {
|
|||
get() = file.isFile
|
||||
|
||||
fun toPath(): Path =
|
||||
file.toPath()
|
||||
file.toPath()
|
||||
|
||||
fun exists() =
|
||||
file.exists()
|
||||
file.exists()
|
||||
|
||||
fun readBytes() =
|
||||
file.readBytes()
|
||||
file.readBytes()
|
||||
|
||||
fun writeBytes(array: ByteArray) =
|
||||
file.writeBytes(array)
|
||||
file.writeBytes(array)
|
||||
|
||||
fun create() =
|
||||
file.create()
|
||||
file.create()
|
||||
|
||||
fun delete(): Boolean =
|
||||
file.delete()
|
||||
file.delete()
|
||||
|
||||
companion object {
|
||||
fun from(path: String) =
|
||||
File(JavaFile(path))
|
||||
File(JavaFile(path))
|
||||
|
||||
fun from(path: FilePath) =
|
||||
from(path.path)
|
||||
from(path.path)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ class FilePath(val path: String)
|
|||
|
||||
/** Runs the given [block] in the context of a file with the given [fullPath]. */
|
||||
fun <T> withFileAt(fullPath: FilePath, block: File.() -> T) =
|
||||
File.from(fullPath).block()
|
||||
File.from(fullPath).block()
|
||||
|
||||
/** Shortcut to create a file and its parent directories. */
|
||||
fun JavaFile.create() {
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
%green(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{36}): %msg%n%throwable
|
||||
</Pattern>
|
||||
</layout>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter">-->
|
||||
<!-- <level>INFO</level>-->
|
||||
<!-- </filter>-->
|
||||
</appender>
|
||||
|
||||
<appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
|
|
|
@ -22,9 +22,7 @@ private val mockFilePathPath = Path.of(mockFilePath)
|
|||
private val mockFileData = byteArrayOf(0x1, 0x8, 0xa, 0xf)
|
||||
|
||||
class FileServiceTest {
|
||||
private val fileService = spyk(FileServiceImpl(creProperties, mockk {
|
||||
every { error(any(), any<Exception>()) } just Runs
|
||||
}))
|
||||
private val fileService = spyk(FileServiceImpl(creProperties))
|
||||
private val mockFile = mockk<File> {
|
||||
every { file } returns mockk()
|
||||
every { exists() } returns true
|
||||
|
|
Loading…
Reference in New Issue