Skip to content

Commit

Permalink
Make sure that base folder exists (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
airxnoor authored May 28, 2024
1 parent ca1fed1 commit e914737
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
0.2.11 - May 27, 2024
• Fix a bug when a log file is stored in a non-existing folder.

0.2.6 - April 11, 2024
• Fix a bug when extracting the date from a log file name.

Expand Down
5 changes: 5 additions & 0 deletions src/commonMain/kotlin/com/airthings/lib/logging/TypeAlias.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ internal typealias LoggerFacilitiesMap = Map<String, LoggerFacility>
* An alias for a [MutableMap] of [LoggerFacility] instances associated by their unique names.
*/
internal typealias LoggerFacilitiesMutableMap = MutableMap<String, LoggerFacility>

/**
* An alias for a function that handles a missing folder incident.
*/
internal typealias LogFolderMissingHandler = (String) -> Unit
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.airthings.lib.logging.LogMessage
import com.airthings.lib.logging.LoggerFacility
import com.airthings.lib.logging.dateStamp
import com.airthings.lib.logging.datetimeStampPrefix
import com.airthings.lib.logging.platform.DelegateFileInputOutput
import com.airthings.lib.logging.platform.PlatformDirectoryListing
import com.airthings.lib.logging.platform.PlatformFileInputOutput
import com.airthings.lib.logging.platform.PlatformFileInputOutputImpl
Expand Down Expand Up @@ -128,19 +129,15 @@ class FileLoggerFacility(
notifier = null,
)

private val io: PlatformFileInputOutput = PlatformFileInputOutputImpl()
private val io: PlatformFileInputOutput = DelegateFileInputOutput(
folder = baseFolder,
io = PlatformFileInputOutputImpl(),
onFolderMissing = {
notifier?.onLogFolderInvalid(it) ?: DelegateFileInputOutput.reportMissingFolder(it)
},
)
private val currentLogFile = AtomicReference<String?>(null)

init {
coroutineScope.launch {
// Please note: The call to `io.mkdirs()` returns true if the directory exists, which may be
// different from the platform's implementation.
if (!io.mkdirs(baseFolder)) {
throw IllegalArgumentException("Base log folder is invalid: $baseFolder")
}
}
}

/**
* Returns the platform-dependent [PlatformDirectoryListing] instance.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.airthings.lib.logging.LogMessage
import com.airthings.lib.logging.LoggerFacility
import com.airthings.lib.logging.dateStamp
import com.airthings.lib.logging.datetimeStamp
import com.airthings.lib.logging.platform.DelegateFileInputOutput
import com.airthings.lib.logging.platform.PlatformDirectoryListing
import com.airthings.lib.logging.platform.PlatformFileInputOutput
import com.airthings.lib.logging.platform.PlatformFileInputOutputImpl
Expand Down Expand Up @@ -128,19 +129,15 @@ class JsonLoggerFacility(
notifier = null,
)

private val io: PlatformFileInputOutput = PlatformFileInputOutputImpl()
private val io: PlatformFileInputOutput = DelegateFileInputOutput(
folder = baseFolder,
io = PlatformFileInputOutputImpl(),
onFolderMissing = {
notifier?.onLogFolderInvalid(it) ?: DelegateFileInputOutput.reportMissingFolder(it)
},
)
private val currentLogFile = AtomicReference<String?>(null)

init {
coroutineScope.launch {
// Please note: The call to `io.mkdirs()` returns true if the directory exists, which may be
// different from the platform's implementation.
if (!io.mkdirs(baseFolder)) {
throw IllegalArgumentException("Base log folder is invalid: $baseFolder")
}
}
}

/**
* Returns the platform-dependent [PlatformDirectoryListing] instance.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2024 Airthings ASA. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the “Software”), to deal in the Software without
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package com.airthings.lib.logging.platform

import com.airthings.lib.logging.LogDate
import com.airthings.lib.logging.LogFolderMissingHandler

internal class DelegateFileInputOutput(
private val folder: String,
private val io: PlatformFileInputOutput,
private val onFolderMissing: LogFolderMissingHandler? = null,
) : PlatformFileInputOutput {
override val pathSeparator: Char = io.pathSeparator

override suspend fun mkdirs(path: String): Boolean = ensureFolder {
mkdirs(path)
}

override suspend fun size(path: String): Long = ensureFolder {
size(path)
}

override suspend fun write(
path: String,
position: Long,
contents: String,
) = ensureFolder {
write(
path = path,
position = position,
contents = contents,
)
}

override suspend fun append(
path: String,
contents: String,
) = ensureFolder {
append(
path = path,
contents = contents,
)
}

override suspend fun ensure(path: String) = ensureFolder {
ensure(path)
}

override suspend fun delete(path: String) = ensureFolder {
delete(path)
}

override suspend fun of(path: String): Collection<String> = ensureFolder {
of(path)
}

override suspend fun of(
path: String,
date: LogDate,
): Collection<String> = ensureFolder {
of(
path = path,
date = date,
)
}

private suspend fun <T> ensureFolder(
action: suspend PlatformFileInputOutput.() -> T,
): T = with(io) {
val isDirectoryExists = mkdirs(folder)
try {
io.action()
} finally {
if (!isDirectoryExists) {
onFolderMissing?.invoke(folder)
}
}
}

companion object {
/**
* Default handler when a folder is invalid.
*
* @param folder The invalid folder's path.
*/
fun reportMissingFolder(folder: String) {
throw IllegalStateException("Log folder is invalid: $folder")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
package com.airthings.lib.logging.platform

/**
* Defines a contract that notifies about opening and closing log files.
* Defines a contract that notifies about opening and closing log files and folders.
*/
interface PlatformFileInputOutputNotifier {
/**
Expand All @@ -36,4 +36,11 @@ interface PlatformFileInputOutputNotifier {
* @param path The location of the log file.
*/
fun onLogFileClosed(path: String)

/**
* Invoked when the log folder cannot be prepared and is effectively invalid.
*
* @param path The location of the log folder.
*/
fun onLogFolderInvalid(path: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,12 @@ internal actual class PlatformFileInputOutputImpl : PlatformFileInputOutput {
}

override suspend fun ensure(path: String) {
synchronized(writeLock) {
File(path).createNewFile()
val parentPath = File(path).parentFile.canonicalPath

if (mkdirs(parentPath)) {
synchronized(writeLock) {
File(path).createNewFile()
}
}
}

Expand Down

0 comments on commit e914737

Please sign in to comment.