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
7 changed files with 109 additions and 31 deletions
Showing only changes of commit eae3aecb31 - Show all commits

View File

@ -50,7 +50,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-devtools:${springBootVersion}")
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
testImplementation("io.mockk:mockk:1.12.0")
testImplementation("io.mockk:mockk:1.12.1")
testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}")
testImplementation("org.mockito:mockito-inline:3.11.2")
testImplementation("org.springframework:spring-test:5.3.13")
@ -68,8 +68,8 @@ springBoot {
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
sourceSets {
@ -83,15 +83,18 @@ sourceSets {
}
tasks.test {
useJUnitPlatform()
jvmArgs("-XX:+ShowCodeDetailsInExceptionMessages")
testLogging {
events("skipped", "failed")
setExceptionFormat("full")
}
reports {
junitXml.required.set(true)
html.required.set(false)
}
useJUnitPlatform()
testLogging {
events("skipped", "failed")
}
}
tasks.withType<JavaCompile>() {

View File

@ -0,0 +1,23 @@
package dev.fyloz.colorrecipesexplorer.service.files
import dev.fyloz.colorrecipesexplorer.utils.FilePath
class FileExistCache {
private val map = hashMapOf<FilePath, Boolean>()
operator fun contains(path: FilePath) =
path in map
fun exists(path: FilePath) =
map[path] ?: false
fun set(path: FilePath, exists: Boolean) {
map[path] = exists
}
fun setExists(path: FilePath) =
set(path, true)
fun setDoesNotExists(path: FilePath) =
set(path, false)
}

View File

@ -2,6 +2,9 @@ package dev.fyloz.colorrecipesexplorer.service.files
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
import dev.fyloz.colorrecipesexplorer.exception.RestException
import dev.fyloz.colorrecipesexplorer.utils.FilePath
import dev.fyloz.colorrecipesexplorer.utils.WrappedFile
import dev.fyloz.colorrecipesexplorer.utils.withFileAt
import org.slf4j.Logger
import org.springframework.core.io.ByteArrayResource
import org.springframework.core.io.Resource
@ -49,8 +52,17 @@ class FileServiceImpl(
private val creProperties: CreProperties,
private val logger: Logger
) : WriteableFileService {
override fun exists(path: String) = withFileAt(path.fullPath()) {
this.exists() && this.isFile
private val existsCache = FileExistCache()
override fun exists(path: String): Boolean {
val fullPath = path.fullPath()
return if (fullPath in existsCache) {
existsCache.exists(fullPath)
} else {
withFileAt(fullPath) {
this.exists() && this.isFile
}
}
}
override fun read(path: String) = ByteArrayResource(
@ -70,6 +82,7 @@ class FileServiceImpl(
try {
withFileAt(fullPath) {
this.create()
existsCache.setExists(fullPath)
}
} catch (ex: IOException) {
FileCreateException(path).logAndThrow(ex, logger)
@ -89,9 +102,12 @@ class FileServiceImpl(
override fun delete(path: String) {
try {
withFileAt(path.fullPath()) {
val fullPath = path.fullPath()
withFileAt(fullPath) {
if (!exists(path)) throw FileNotFoundException(path)
!this.delete()
this.delete()
existsCache.setDoesNotExists(fullPath)
}
} catch (ex: IOException) {
FileDeleteException(path).logAndThrow(ex, logger)
@ -106,7 +122,7 @@ class FileServiceImpl(
return FilePath("${creProperties.dataDirectory}/$this")
}
private fun prepareWrite(path: String, overwrite: Boolean, op: File.() -> Unit) {
private fun prepareWrite(path: String, overwrite: Boolean, op: WrappedFile.() -> Unit) {
val fullPath = path.fullPath()
if (exists(path)) {
@ -123,15 +139,6 @@ class FileServiceImpl(
FileWriteException(path).logAndThrow(ex, logger)
}
}
/** Runs the given [block] in the context of a file with the given [fullPath]. */
private fun <T> withFileAt(fullPath: FilePath, block: File.() -> T) =
fullPath.file.block()
}
data class FilePath(val path: String) {
val file: File
get() = File(path)
}
/** Shortcut to create a file and its parent directories. */

View File

@ -1,5 +1,6 @@
package dev.fyloz.colorrecipesexplorer.service.files
import dev.fyloz.colorrecipesexplorer.utils.FilePath
import org.springframework.core.io.Resource
import org.springframework.core.io.ResourceLoader
import org.springframework.stereotype.Service

View File

@ -0,0 +1,44 @@
package dev.fyloz.colorrecipesexplorer.utils
import dev.fyloz.colorrecipesexplorer.service.files.create
import java.io.File
import java.nio.file.Path
/** Mockable file wrapper, to prevent issues when mocking [java.io.File]. */
class WrappedFile(val file: File) {
val isFile: Boolean
get() = file.isFile
fun toPath(): Path =
file.toPath()
fun exists() =
file.exists()
fun readBytes() =
file.readBytes()
fun writeBytes(array: ByteArray) =
file.writeBytes(array)
fun create() =
file.create()
fun delete(): Boolean =
file.delete()
companion object {
fun from(path: String) =
WrappedFile(File(path))
fun from(path: FilePath) =
from(path.path)
}
}
@JvmInline
value 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: WrappedFile.() -> T) =
WrappedFile.from(fullPath).block()

View File

@ -1,6 +1,7 @@
package dev.fyloz.colorrecipesexplorer.service.files
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
import dev.fyloz.colorrecipesexplorer.utils.WrappedFile
import io.mockk.*
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
@ -24,20 +25,18 @@ private class FileServiceTestContext {
val fileService = spyk(FileServiceImpl(creProperties, mockk {
every { error(any(), any<Exception>()) } just Runs
}))
val mockFile = mockk<File> {
every { path } returns mockFilePath
val mockFile = mockk<WrappedFile> {
every { file } returns mockk()
every { exists() } returns true
every { isFile } returns true
every { toPath() } returns mockFilePathPath
}
val mockFileFullPath = spyk(FilePath("${creProperties.dataDirectory}/$mockFilePath")) {
every { file } returns mockFile
with(fileService) {
every { mockFilePath.fullPath() } returns this@spyk
}
}
val mockMultipartFile = spyk(MockMultipartFile(mockFilePath, mockFileData))
init {
mockkObject(WrappedFile.Companion)
every { WrappedFile.from(any<String>()) } returns mockFile
}
}
class FileServiceTest {

View File

@ -1,5 +1,6 @@
package dev.fyloz.colorrecipesexplorer.service.files
import dev.fyloz.colorrecipesexplorer.utils.FilePath
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk