feature/18-add-existing-files-cache #26

Merged
william merged 11 commits from feature/18-add-existing-files-cache into develop 2022-02-12 15:21:39 -05:00
8 changed files with 64 additions and 36 deletions
Showing only changes of commit 26d696d66b - Show all commits

View File

@ -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"

View File

@ -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))
}
}
}

View File

@ -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)
}

View File

@ -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")
}
}

View File

@ -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"

View File

@ -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() {

View File

@ -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">

View File

@ -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