diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 52e52ffac5f6ec850a16e2673b05709c8bce785c..8c272bef56d344559053762e98a227a9a7630432 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ appdirs = "1.2.0" kstore = "0.9.1" kmpIo = "0.1.5" kotlinSemver = "2.0.0" +jansi = "2.4.1" temerity = "[0.1.0-dev0z+41180a5,0.1.0]" kotlinx-dataframe = "0.14.2" @@ -89,7 +90,7 @@ kotlinx-coroutines-debug = { module = "org.jetbrains.kotlinx:kotlinx-coroutines- kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" } kotlinx-coroutines-slf4j = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-slf4j", version.ref = "coroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } -kotlinx-io = { module = "org.jetbrains.kotlinx:kotlinx-io", version.ref = "kotlinx-io" } +kotlinx-io = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinx-io" } ktorfit-lib = { module = "de.jensklingenberg.ktorfit:ktorfit-lib-light", version.ref = "ktorfit" } @@ -102,6 +103,7 @@ kstore = { module = "io.github.xxfast:kstore", version.ref = "kstore" } kstore-file = { module = "io.github.xxfast:kstore-file", version.ref = "kstore" } kmpIo = { module = "io.github.skolson:kmp-io", version.ref = "kmpIo" } kotlinSemver = { module = "io.github.z4kn4fein:semver", version.ref = "kotlinSemver" } +jansi = { module = "org.fusesource.jansi:jansi", version.ref = "jansi" } temerity = { module = "edu.ucsc.its:temerity", version.ref = "temerity" } sandwich = { module = "com.github.skydoves:sandwich", version.ref = "sandwich" } diff --git a/temerity/build.gradle.kts b/temerity/build.gradle.kts index 399af7dc7a0ac163b8357e687b3a69feb61f6a8d..0deccaa3ec015bfdcd317699b9291df1d6666df1 100644 --- a/temerity/build.gradle.kts +++ b/temerity/build.gradle.kts @@ -104,11 +104,13 @@ kotlin { dependencies { implementation(libs.ktor.client.java) implementation(libs.slf4j.api) + implementation(libs.jansi) } } val androidMain by getting { dependencies { implementation(libs.ktor.client.android) + // TODO: Add slf4f-api } } val jvmTest by getting { diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/JsonFactory.kt b/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.android.kt similarity index 72% rename from temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/JsonFactory.kt rename to temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.android.kt index 02f61f54d0c020a81d018f20a3fb4642f3659449..32d677f1394f211c1431f9a53e52d769553bb1d8 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/JsonFactory.kt +++ b/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.android.kt @@ -17,15 +17,13 @@ */ package edu.ucsc.its.temerity.core -import kotlinx.serialization.json.Json +import co.touchlab.kermit.Logger +import edu.ucsc.its.temerity.TemClientConfig -/** - * Factory for creating a Ktor [Json] object. - * Adapted from the tmdb-api project: https://github.com/MoviebaseApp/tmdb-kotlin/raw/refs/heads/main/tmdb-api/src/commonMain/kotlin/app/moviebase/tmdb/core/JsonFactory.kt - */ -internal object JsonFactory { - - fun buildJson(): Json = Json { - coerceInputValues = true - } +internal actual fun createLoggerCommon( + tag: String?, + config: TemClientConfig?, + supportKtxNotebook: Boolean, +): Logger { + TODO("Not yet implemented") } diff --git a/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/Actual.kt b/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/di/PlatformModule.android.kt similarity index 93% rename from temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/Actual.kt rename to temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/di/PlatformModule.android.kt index a15413e1183d5d6c9a4185c071c5fa179f022f89..4c5f5da5d897af76a30dc3872693c611e554948e 100644 --- a/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/Actual.kt +++ b/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/di/PlatformModule.android.kt @@ -15,9 +15,9 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package edu.ucsc.its.temerity +package edu.ucsc.its.temerity.di -import edu.ucsc.its.temerity.core.DispatcherFactory.createDispatcher +import edu.ucsc.its.temerity.extensions.coroutines.createDispatcher import io.ktor.client.engine.android.Android import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers diff --git a/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/extensions/log/Filesystem.android.kt b/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/extensions/log/Filesystem.android.kt new file mode 100644 index 0000000000000000000000000000000000000000..094b3d7530deffde589ca3ccadd23ad63130c62f --- /dev/null +++ b/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/extensions/log/Filesystem.android.kt @@ -0,0 +1,23 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package edu.ucsc.its.temerity.extensions.log + +import kotlinx.io.files.FileSystem +import kotlinx.io.files.SystemFileSystem + +internal actual fun fileSystem(): FileSystem = SystemFileSystem diff --git a/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/extensions/log/FilesystemActual.kt b/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/extensions/log/FilesystemActual.kt deleted file mode 100644 index 4cba6149f39b8faeefff8e9c0c915242c8fc07a6..0000000000000000000000000000000000000000 --- a/temerity/src/androidMain/kotlin/edu/ucsc/its/temerity/extensions/log/FilesystemActual.kt +++ /dev/null @@ -1,6 +0,0 @@ -package edu.ucsc.its.temerity.extensions.log - -import kotlinx.io.files.FileSystem -import kotlinx.io.files.SystemFileSystem - -internal actual fun fileSystem() : FileSystem = SystemFileSystem diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemerityApi.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemerityApi.kt index b3908d6ea163ba77148fc0dbdf9125828e1d6f69..93adda8dca388bae8e967d6cc332868bf3aaf126 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemerityApi.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/TemerityApi.kt @@ -17,6 +17,7 @@ */ package edu.ucsc.its.temerity +import edu.ucsc.its.temerity.extensions.datetime.DateTimeExt.currentDate import edu.ucsc.its.temerity.model.AuditLogEntry import edu.ucsc.its.temerity.model.Course import edu.ucsc.its.temerity.model.Device @@ -52,7 +53,7 @@ public interface TemerityApi { * @return The version of the client library as a semantic versioning formatted string. */ @Suppress("PropertyName") - public val VERSION: String + public val version: String /** * Fetches the list of users from the platform. diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Util.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Util.kt deleted file mode 100644 index d4c36dc88ca2946ce633d7c28e90060a7ad32344..0000000000000000000000000000000000000000 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Util.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) - * Copyright 2022-2024 The Regents of the University of California. All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ -package edu.ucsc.its.temerity - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob -import kotlinx.datetime.Clock -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalTime -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime -import kotlinx.datetime.todayIn -import kotlin.coroutines.CoroutineContext - -internal fun currentTz(): TimeZone = TimeZone.currentSystemDefault() - -internal fun thisInstant() = Clock.System.now() - -public fun currentDate(): LocalDate = Clock.System.todayIn(currentTz()) - -internal fun currentTime(): LocalTime = thisInstant().toLocalDateTime(currentTz()).time - -internal fun createJobScope(coroutineContext: CoroutineContext, allowIndependentFailure: Boolean = false): CoroutineScope { - val parentJob = if (allowIndependentFailure) SupervisorJob() else Job() - return CoroutineScope(coroutineContext + parentJob) -} diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/DispatcherFactory.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/DispatcherFactory.kt deleted file mode 100644 index 576562b997e701fb8ed6c78cde804b9cd9102812..0000000000000000000000000000000000000000 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/DispatcherFactory.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) - * Copyright 2022-2024 The Regents of the University of California. All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ -package edu.ucsc.its.temerity.core - -import edu.ucsc.its.temerity.core.Temerity.Companion.DEFAULT_MINIMUM_THREAD_COUNT -import edu.ucsc.its.temerity.createJobScope -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers - -internal object DispatcherFactory { - - internal fun createDispatcher(dispatcherThreadPool: CoroutineDispatcher, threadCount: Int, dispatcherName: String): CoroutineDispatcher = dispatcherThreadPool.limitedParallelism(threadCount, dispatcherName) - - internal fun createLibraryScope(dispatcher: CoroutineDispatcher): CoroutineScope = createJobScope(dispatcher, allowIndependentFailure = true) - - private fun availableThreads() = Runtime.getRuntime().availableProcessors().minus(1) - - internal fun calculateMaxThreads(defaultMinimumThreadCount: Int) = availableThreads().coerceAtLeast(defaultMinimumThreadCount) - - internal fun setThreadCount(maximumThreadCount: Int? = null): Int = when (maximumThreadCount) { - null -> calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT) - else -> { - if (maximumThreadCount > availableThreads()) { - // TODO: Log warning that the configured thread count is higher than the number of available threads - calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT) - } - if (maximumThreadCount < DEFAULT_MINIMUM_THREAD_COUNT) { - // TODO: Log warning that the configured thread count is lower than the default minimum thread pool size - calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT) - } - maxOf(maximumThreadCount, calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT)) - } - } -} diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.kt index 1a3902befd259c65dcc2f55ec4d3ed22007c55bc..1dfefa8148eac95f8e868928369ec90cf27ea0a5 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.kt @@ -1,46 +1,27 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ package edu.ucsc.its.temerity.core -import co.touchlab.kermit.CommonWriter import co.touchlab.kermit.Logger -import co.touchlab.kermit.NoTagFormatter -import co.touchlab.kermit.Severity -import co.touchlab.kermit.loggerConfigInit -import co.touchlab.kermit.platformLogWriter import edu.ucsc.its.temerity.TemClientConfig internal object LoggerFactory { - internal fun createLogger(tag: String?, config: TemClientConfig? = null, supportKtxNotebook: Boolean = false): Logger = - when (config){ - null -> { - Logger( - config = loggerConfigInit( - if (supportKtxNotebook) CommonWriter(NoTagFormatter) else platformLogWriter(NoTagFormatter), - minSeverity = Severity.Debug, - ), - tag = tag ?: "TemerityLib", - ) - } - else -> { - when (config.optDebugEnabled) { - // If debug logging is enabled, create a logger which color prints to the console - true -> { - Logger( - config = loggerConfigInit( - if (supportKtxNotebook) CommonWriter(NoTagFormatter) else platformLogWriter(NoTagFormatter), - minSeverity = Severity.Debug, - ), - tag = tag ?: "TemerityLib", - ) - } - // If debug logging is disabled, create a logger which prints to a log file - false -> { - Logger( - config = loggerConfigInit( - - ) - ) - } - } - } - } + internal fun createLogger(tag: String?, config: TemClientConfig? = null, supportKtxNotebook: Boolean = false) = createLoggerCommon(tag = tag, config = config, supportKtxNotebook = supportKtxNotebook) } + +internal expect fun createLoggerCommon(tag: String?, config: TemClientConfig? = null, supportKtxNotebook: Boolean = false): Logger diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt index 0139a0a052296ce7e756278e7330a3584d72d95d..9083faa08333c07d28683ec0f38cf3f4a0f41c64 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/core/Temerity.kt @@ -20,22 +20,24 @@ package edu.ucsc.its.temerity.core import co.touchlab.stately.collections.ConcurrentMutableMap import com.skydoves.sandwich.ApiResponse import com.skydoves.sandwich.StatusCode +import com.skydoves.sandwich.StatusCode.NoContent import com.skydoves.sandwich.getOrThrow import com.skydoves.sandwich.ktor.executeApiResponse import com.skydoves.sandwich.ktor.statusCode -import com.skydoves.sandwich.ktorfit.ApiResponseConverterFactory import com.skydoves.sandwich.onSuccess -import de.jensklingenberg.ktorfit.ktorfit import edu.ucsc.its.temerity.AuditLogSortOrder +import edu.ucsc.its.temerity.AuditLogSortOrder.NEW_FIRST import edu.ucsc.its.temerity.BuildConfig import edu.ucsc.its.temerity.TemClientConfig import edu.ucsc.its.temerity.TemerityApi import edu.ucsc.its.temerity.api.PlatformApi -import edu.ucsc.its.temerity.api.createPlatformApi -import edu.ucsc.its.temerity.core.DispatcherFactory.createLibraryScope -import edu.ucsc.its.temerity.core.JsonFactory.buildJson import edu.ucsc.its.temerity.core.Temerity.Companion.DEFAULT_WEB_TIMEOUT -import edu.ucsc.its.temerity.createJobScope +import edu.ucsc.its.temerity.di.libModule +import edu.ucsc.its.temerity.di.platformModule +import edu.ucsc.its.temerity.extensions.coroutines.calculateMaxThreads +import edu.ucsc.its.temerity.extensions.coroutines.createJobScope +import edu.ucsc.its.temerity.extensions.coroutines.setThreadCount +import edu.ucsc.its.temerity.extensions.datetime.DateTimeExt import edu.ucsc.its.temerity.extensions.time.applyAuditLogFormat import edu.ucsc.its.temerity.extensions.time.applyScheduledSessionDateFormat import edu.ucsc.its.temerity.model.AuditLogEntry @@ -44,13 +46,13 @@ import edu.ucsc.its.temerity.model.Device import edu.ucsc.its.temerity.model.DeviceRecordingSession import edu.ucsc.its.temerity.model.EventType import edu.ucsc.its.temerity.model.FolderPermissions +import edu.ucsc.its.temerity.model.FolderPermissions.Action import edu.ucsc.its.temerity.model.Group import edu.ucsc.its.temerity.model.NewUser import edu.ucsc.its.temerity.model.User import edu.ucsc.its.temerity.model.UserGroup import edu.ucsc.its.temerity.model.UserRecordingSession import edu.ucsc.its.temerity.model.UserUpdate -import edu.ucsc.its.temerity.platformModule import edu.ucsc.its.temerity.sortByCreationDate import io.ktor.client.HttpClient import io.ktor.client.HttpClientConfig @@ -58,18 +60,21 @@ import io.ktor.client.engine.HttpClientEngine import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.defaultRequest import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.LogLevel.ALL import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.header import io.ktor.client.statement.HttpResponse import io.ktor.http.URLProtocol import io.ktor.util.cio.toByteArray import io.ktor.utils.io.ByteReadChannel +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalTime import kotlinx.datetime.minus import kotlinx.serialization.SerializationException import kotlinx.serialization.encodeToString @@ -84,15 +89,15 @@ import org.koin.core.logger.Level.ERROR import org.koin.core.logger.Level.INFO import org.koin.core.logger.Level.NONE import org.koin.core.logger.Level.WARNING +import org.koin.core.logger.Logger import org.koin.core.logger.MESSAGE import org.koin.core.parameter.parametersOf import org.koin.dsl.koinApplication -import org.koin.dsl.module import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes import kotlin.time.DurationUnit -import kotlinx.coroutines.CoroutineDispatcher -import org.koin.core.logger.Logger +import kotlin.time.DurationUnit.MILLISECONDS +import kotlinx.datetime.DateTimeUnit.Companion import co.touchlab.kermit.Logger as KermitLogger import io.ktor.client.plugins.logging.Logger as KtorLogger @@ -114,32 +119,21 @@ public class Temerity internal constructor( // TODO: Use this as cache4k expiration time public val DEFAULT_CACHE_EXPIRATION: Duration = 15.minutes internal const val DEFAULT_MINIMUM_THREAD_COUNT: Int = 2 - } + + @JvmStatic + internal fun createLogger(): KermitLogger = LoggerFactory.createLogger( + tag = TODO(), + config = TODO (), + supportKtxNotebook = TODO() + ) - /** - * This function provides the Koin module for the Temerity library. - * It includes the platform module and provides factories for [HttpClient] and [PlatformApi]. - */ - internal val libModule = module { - factory { (config: TemClientConfig, kermit: co.touchlab.kermit.Logger) -> - buildHttpClient( - httpClientEngine = get( parameters = { parametersOf(libraryCoroutineDispatcher) }), - config = config, - logger = kermit, - ) - } - factory { (config: TemClientConfig) -> - val client: HttpClient = get { parametersOf(config, createLogger(tag = "Temerity Library Web Request engine", config = config)) } - val ktorfit = ktorfit { - config.serviceUrl?.let { baseUrl(it) } - httpClient(client) - converterFactories(ApiResponseConverterFactory.create()) - } - ktorfit.createPlatformApi() - } - single { (dispatcher: CoroutineDispatcher) -> - createLibraryScope(dispatcher) - } + @JvmStatic + @Suppress("MemberVisibilityCanBePrivate") + public fun currentDate(): LocalDate = DateTimeExt.currentDate() + + @JvmStatic + @Suppress("MemberVisibilityCanBePrivate") + public fun currentTime(): LocalTime = DateTimeExt.currentTime() } /** @@ -148,7 +142,7 @@ public class Temerity internal constructor( * via factories defined as part of the lib Module. */ private fun createKoinApp() = object : TemerityKoinContext() { - override val logger = createLogger(tag = "TemerityLib Koin DI Context") + override val logger = createLoggerCommon(tag = "TemerityLib Koin DI Context") override val koinApp = koinApplication { logger(object : Logger() { override fun display(level: Level, msg: MESSAGE) { @@ -162,8 +156,8 @@ public class Temerity internal constructor( } }) modules( - libModule, - platformModule() + libModule(), + platformModule(), ) } override val koin: Koin = koinApp.koin @@ -175,10 +169,12 @@ public class Temerity internal constructor( abstract val logger: KermitLogger } - @Suppress("PropertyName") - override val VERSION: String = BuildConfig.LIB_VERSION + override val version: String = BuildConfig.LIB_VERSION - private val json: Json = buildJson() + // Kotlinx-serialization [Json] encoder/decoder object used for serializing/deserializing JSON object responses + private val json: Json = Json { + coerceInputValues = true + } internal val koinContext = createKoinApp() override fun getKoin(): Koin = koinContext.koin @@ -199,8 +195,8 @@ public class Temerity internal constructor( libraryCoroutineDispatcher = get<CoroutineDispatcher> { val dispatcherName = "Temerity Library Dispatcher" when (val optThreadCount = config.threadCount) { - null -> parametersOf(DispatcherFactory.calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT), dispatcherName) - else -> parametersOf(DispatcherFactory.setThreadCount(optThreadCount), dispatcherName) + null -> parametersOf(calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT), dispatcherName) + else -> parametersOf(setThreadCount(optThreadCount), dispatcherName) } } libraryCoroutineScope = get<CoroutineScope> { @@ -230,7 +226,7 @@ public class Temerity internal constructor( // If the API returns a 204 No Content status code, we should throw a BreakException to stop the page iteration loop // As of Cashew release, this is only used in getGroups() to stop the loop when no more groups are returned // May be used in the future for other paginated endpoints (such as getAuditLogEntries) - StatusCode.NoContent -> throw BreakException(page - 1) + NoContent -> throw BreakException(page - 1) else -> Unit } @@ -246,7 +242,7 @@ public class Temerity internal constructor( when (exception) { is SerializationException -> { // TODO: Implement platform API version checking - get<KermitLogger>{ parametersOf("TemerityLib") }.d { "Returned JSON object: ${exception.message}" } + get<KermitLogger> { parametersOf("TemerityLib") }.d { "Returned JSON object: ${exception.message}" } error("Encountered error decoding response from platform API. You likely need to choose a Temerity release that supports the API version implemented by your instance. \nPlatform API response data : $apiResponsePayload") } @@ -309,11 +305,11 @@ public class Temerity internal constructor( TODO("Not yet implemented") } - public override suspend fun addFolderPermission(perms: FolderPermissions.Action): HttpResponse { + public override suspend fun addFolderPermission(perms: Action): HttpResponse { TODO("Not yet implemented") } - public override suspend fun removeFolderPermission(perms: FolderPermissions.Action) { + public override suspend fun removeFolderPermission(perms: Action) { TODO("Not yet implemented") } @@ -346,13 +342,15 @@ public class Temerity internal constructor( } return returnedGroups } + else -> { return emptyList() } } } - public override suspend fun getCourse(courseCode: String): Course = decodeResponseCatching(platformApi.getCourse(courseCode).executeApiResponse<String>()) + public override suspend fun getCourse(courseCode: String): Course = + decodeResponseCatching(platformApi.getCourse(courseCode).executeApiResponse<String>()) public override suspend fun getCourses(paginated: Boolean): List<Course> { return when (paginated) { @@ -411,7 +409,7 @@ public class Temerity internal constructor( results.forEach { returnedEntries.addAll(it) } // Apply passed sortOrder, or default to NEW_FIRST TODO: Read from user settings - returnedEntries.applyOrDefault(sortOrder ?: AuditLogSortOrder.NEW_FIRST) + returnedEntries.applyOrDefault(sortOrder ?: NEW_FIRST) return returnedEntries } @@ -430,10 +428,10 @@ public class Temerity internal constructor( when (paginated) { true -> { val returnedEntries = ArrayList<AuditLogEntry>() - var windowStart = if (endTime.minus(1, DateTimeUnit.Companion.MONTH) < startTime) { + var windowStart = if (endTime.minus(1, DateTimeUnit.MONTH) < startTime) { startTime } else { - endTime.minus(1, DateTimeUnit.Companion.MONTH) + endTime.minus(1, DateTimeUnit.MONTH) } var windowEnd = endTime while (windowStart < windowEnd) { @@ -445,7 +443,8 @@ public class Temerity internal constructor( entryOffset = x * 75, eventType = eventType.value, ) - val logEventEntries: List<AuditLogEntry> = decodeResponseCatching(endpointRequest.executeApiResponse<String>(), page = x) + val logEventEntries: List<AuditLogEntry> = + decodeResponseCatching(endpointRequest.executeApiResponse<String>(), page = x) returnedEntries.addAll(logEventEntries) } } catch (e: Exception) { @@ -461,29 +460,31 @@ public class Temerity internal constructor( } } windowEnd = windowStart - val backStep = windowStart.minus(1, DateTimeUnit.Companion.MONTH) + val backStep = windowStart.minus(1, DateTimeUnit.MONTH) windowStart = if (backStep < startTime) startTime else backStep } // Apply passed sortOrder, or default to NEW_FIRST TODO: Read from user settings - returnedEntries.applyOrDefault(sortOrder ?: AuditLogSortOrder.NEW_FIRST) + returnedEntries.applyOrDefault(sortOrder ?: NEW_FIRST) return returnedEntries } + false -> { TODO("Implement non-paginated audit log entry retrieval") } } } - private fun ArrayList<AuditLogEntry>.applyOrDefault(sortOrder: AuditLogSortOrder?): ArrayList<AuditLogEntry> = when (sortOrder) { - null -> { - // Currently reads from prefs, falling back to BuildConfig constant. Can be overridden by passing a sort order as a parameter. - // TODO: Implement a way to pass a clientDefaultSortOrder to Builder - // TODO: Persist user-defined default sort order for subsequent runs - apply { sortByCreationDate(AuditLogSortOrder.NEW_FIRST) } - } + private fun ArrayList<AuditLogEntry>.applyOrDefault(sortOrder: AuditLogSortOrder?): ArrayList<AuditLogEntry> = + when (sortOrder) { + null -> { + // Currently reads from prefs, falling back to BuildConfig constant. Can be overridden by passing a sort order as a parameter. + // TODO: Implement a way to pass a clientDefaultSortOrder to Builder + // TODO: Persist user-defined default sort order for subsequent runs + apply { sortByCreationDate(NEW_FIRST) } + } - else -> apply { sortByCreationDate(sortOrder) } - } + else -> apply { sortByCreationDate(sortOrder) } + } // TODO: Implement setUser() function @@ -520,7 +521,8 @@ public class Temerity internal constructor( ) public override suspend fun getStorageAnalyticsReport(groupId: Long): ByteArray = - platformApi.getStorageAnalyticsReport(groupId).executeApiResponse<ByteReadChannel>().getOrThrow().toByteArray(limit = 2000000000) // Limit file downloads to 2 GB + platformApi.getStorageAnalyticsReport(groupId).executeApiResponse<ByteReadChannel>().getOrThrow() + .toByteArray(limit = 2000000000) // Limit file downloads to 2 GB } /** @@ -563,7 +565,7 @@ internal fun buildHttpClient( logger.d(message) } } - level = LogLevel.ALL + level = ALL sanitizeHeader { headerKey -> headerKey == "authToken" } @@ -580,8 +582,8 @@ internal fun buildHttpClient( expectSuccess = config.expectSuccess if (config.useWebTimeout) { - val defaultWebTimeout = DEFAULT_WEB_TIMEOUT.toLong(DurationUnit.MILLISECONDS) - val configuredWebTimeout = config.webTimeout?.toLong(DurationUnit.MILLISECONDS) + val defaultWebTimeout = DEFAULT_WEB_TIMEOUT.toLong(MILLISECONDS) + val configuredWebTimeout = config.webTimeout?.toLong(MILLISECONDS) install(HttpTimeout) { connectTimeoutMillis = configuredWebTimeout ?: defaultWebTimeout requestTimeoutMillis = configuredWebTimeout ?: defaultWebTimeout diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/LibModule.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/LibModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..f823724a757def6756911d2b8119e53444ac9b78 --- /dev/null +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/LibModule.kt @@ -0,0 +1,59 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package edu.ucsc.its.temerity.di + +import com.skydoves.sandwich.ktorfit.ApiResponseConverterFactory +import de.jensklingenberg.ktorfit.ktorfit +import edu.ucsc.its.temerity.TemClientConfig +import edu.ucsc.its.temerity.api.PlatformApi +import edu.ucsc.its.temerity.api.createPlatformApi +import edu.ucsc.its.temerity.core.buildHttpClient +import edu.ucsc.its.temerity.core.createLoggerCommon +import edu.ucsc.its.temerity.extensions.coroutines.createLibraryScope +import io.ktor.client.HttpClient +import kotlinx.coroutines.CoroutineDispatcher +import org.koin.core.parameter.parametersOf +import org.koin.core.qualifier.named +import org.koin.dsl.module + +/** + * This function provides the Koin module for the Temerity library. + * It includes the platform module and provides factories for [HttpClient] and [PlatformApi]. + */ +internal fun libModule() = module { + factory { (config: TemClientConfig, kermit: co.touchlab.kermit.Logger) -> + buildHttpClient( + httpClientEngine = get(parameters = { parametersOf(get<CoroutineDispatcher>(named("libraryCoroutineDispatcher"))) }), + config = config, + logger = kermit, + ) + } + factory { (config: TemClientConfig) -> + val client: HttpClient = + get { parametersOf(config, createLoggerCommon(tag = "Temerity Library Web Request engine", config = config)) } + val ktorfit = ktorfit { + config.serviceUrl?.let { baseUrl(it) } + httpClient(client) + converterFactories(ApiResponseConverterFactory.create()) + } + ktorfit.createPlatformApi() + } + single { (dispatcher: CoroutineDispatcher) -> + createLibraryScope(dispatcher) + } +} diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Expect.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/PlatformModule.kt similarity index 97% rename from temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Expect.kt rename to temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/PlatformModule.kt index 13146c7d67077590e2b5109a6f3f1c7e369ee3ee..06086bdd73c13f50739d89aafbd5708313d113ee 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/Expect.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/di/PlatformModule.kt @@ -15,7 +15,7 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package edu.ucsc.its.temerity +package edu.ucsc.its.temerity.di import org.koin.core.module.Module diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/coroutines/CoroutinesExt.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/coroutines/CoroutinesExt.kt new file mode 100644 index 0000000000000000000000000000000000000000..90e834b2b2707be5926024ddb8620d1df20b2a56 --- /dev/null +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/coroutines/CoroutinesExt.kt @@ -0,0 +1,54 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package edu.ucsc.its.temerity.extensions.coroutines + +import edu.ucsc.its.temerity.core.Temerity.Companion.DEFAULT_MINIMUM_THREAD_COUNT +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlin.coroutines.CoroutineContext + +internal fun createJobScope(coroutineContext: CoroutineContext, allowIndependentFailure: Boolean = false): CoroutineScope { + val parentJob = if (allowIndependentFailure) SupervisorJob() else Job() + return CoroutineScope(coroutineContext + parentJob) +} + +// This function is used to create a dispatcher with a limited number of threads from a platform-specific thread pool +internal fun createDispatcher(dispatcherThreadPool: CoroutineDispatcher, threadCount: Int, dispatcherName: String): CoroutineDispatcher = dispatcherThreadPool.limitedParallelism(threadCount, dispatcherName) + +internal fun createLibraryScope(dispatcher: CoroutineDispatcher): CoroutineScope = createJobScope(dispatcher, allowIndependentFailure = true) + +private fun availableThreads() = Runtime.getRuntime().availableProcessors().minus(1) + +internal fun calculateMaxThreads(defaultMinimumThreadCount: Int) = availableThreads().coerceAtLeast(defaultMinimumThreadCount) + +internal fun setThreadCount(maximumThreadCount: Int? = null): Int = when (maximumThreadCount) { + null -> calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT) + else -> { + if (maximumThreadCount > availableThreads()) { + // TODO: Log warning that the configured thread count is higher than the number of available threads + calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT) + } + if (maximumThreadCount < DEFAULT_MINIMUM_THREAD_COUNT) { + // TODO: Log warning that the configured thread count is lower than the default minimum thread pool size + calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT) + } + maxOf(maximumThreadCount, calculateMaxThreads(DEFAULT_MINIMUM_THREAD_COUNT)) + } +} diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/datetime/DateTimeExt.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/datetime/DateTimeExt.kt new file mode 100644 index 0000000000000000000000000000000000000000..04c295361d81e270b0325b9d5ddfbbc9afd02286 --- /dev/null +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/datetime/DateTimeExt.kt @@ -0,0 +1,35 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package edu.ucsc.its.temerity.extensions.datetime + +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import kotlinx.datetime.todayIn + +internal object DateTimeExt { + internal fun currentTz(): TimeZone = TimeZone.currentSystemDefault() + + internal fun thisInstant() = Clock.System.now() + + internal fun currentDate(): LocalDate = Clock.System.todayIn(currentTz()) + + internal fun currentTime(): LocalTime = thisInstant().toLocalDateTime(currentTz()).time +} diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/log/FilesystemLogWriter.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/log/FilesystemLogWriter.kt index 9c4f5388dded3be5f714a86c4635ab43c3e48a80..baf4c7d887f311954ef2b7cd7fe29e6472e57d47 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/log/FilesystemLogWriter.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/log/FilesystemLogWriter.kt @@ -1,3 +1,20 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ package edu.ucsc.its.temerity.extensions.log import co.touchlab.kermit.DefaultFormatter @@ -14,67 +31,67 @@ import kotlinx.io.writeString internal expect fun fileSystem(): FileSystem internal class FilesystemLogWriter internal constructor( - private val logPath: String, - private val logRoller: LogRoller? = null, - private val formatter: MessageStringFormatter = DefaultFormatter + private val logPath: String, + private val logRoller: LogRoller? = null, + private val formatter: MessageStringFormatter = DefaultFormatter, ) : LogWriter() { - // Not called since we are "Context Aware" - override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) { - val fileSystem = fileSystem() - val kotlinxIoPath = Path(logPath) + // Not called since we are "Context Aware" + override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) { + val fileSystem = fileSystem() + val kotlinxIoPath = Path(logPath) - logRoller?.rollLogs(kotlinxIoPath, fileSystem) + logRoller?.rollLogs(kotlinxIoPath, fileSystem) - val sink = fileSystem.sink(kotlinxIoPath, append = true).buffered() + val sink = fileSystem.sink(kotlinxIoPath, append = true).buffered() - with(sink) { - writeString(formatter.formatMessage(severity, Tag(tag), Message(message))) - writeString("\n") - throwable?.let { - writeString(it.stackTraceToString()) - } - flush() - } + with(sink) { + writeString(formatter.formatMessage(severity, Tag(tag), Message(message))) + writeString("\n") + throwable?.let { + writeString(it.stackTraceToString()) + } + flush() } + } - companion object { - operator fun invoke(block: Builder.() -> Unit) = - with(FilesystemLogWriterBuilder()) { - block(this) - build() - } - } + companion object { + operator fun invoke(block: Builder.() -> Unit) = + with(FilesystemLogWriterBuilder()) { + block(this) + build() + } + } - interface Builder { - fun rollLogAtSize(size: Long): Builder - fun logPath(path: String): Builder - fun build(): FilesystemLogWriter - } + interface Builder { + fun rollLogAtSize(size: Long): Builder + fun logPath(path: String): Builder + fun build(): FilesystemLogWriter + } - class FilesystemLogWriterBuilder() : Builder { - private var maxFileSize: Long? = null - private var logPath: String? = null + class FilesystemLogWriterBuilder : Builder { + private var maxFileSize: Long? = null + private var logPath: String? = null - override fun rollLogAtSize(size: Long): Builder { - maxFileSize = size - return this - } + override fun rollLogAtSize(size: Long): Builder { + maxFileSize = size + return this + } - override fun logPath(path: String): Builder { - logPath = path - return this - } + override fun logPath(path: String): Builder { + logPath = path + return this + } - override fun build(): FilesystemLogWriter { - if (logPath == null) throw NullPointerException("Invalid / missing log path") + override fun build(): FilesystemLogWriter { + if (logPath == null) throw NullPointerException("Invalid / missing log path") - // Can you resist the urge to Rick-roll the logs? - val rick = maxFileSize?.let { - FileSizeLogRoller(logPath!!, maxFileSize!!) - } + // Can you resist the urge to Rick-roll the logs? + val rick = maxFileSize?.let { + FileSizeLogRoller(logPath!!, maxFileSize!!) + } - return FilesystemLogWriter(logPath!!, rick) - } + return FilesystemLogWriter(logPath!!, rick) } + } } diff --git a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/log/LogRoller.kt b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/log/LogRoller.kt index 68b050cc5c46c2f2df730508a6e3265158bc2202..69171fedb96f80f28c3987d4ae125c66336884ab 100644 --- a/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/log/LogRoller.kt +++ b/temerity/src/commonMain/kotlin/edu/ucsc/its/temerity/extensions/log/LogRoller.kt @@ -1,29 +1,46 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ package edu.ucsc.its.temerity.extensions.log import kotlinx.io.files.FileSystem import kotlinx.io.files.Path internal interface LogRoller { - fun rollLogs(kotlinxIoPath: Path, fileSystem: FileSystem) + fun rollLogs(kotlinxIoPath: Path, fileSystem: FileSystem) } internal class FileSizeLogRoller( - private val logPath: String, - private val maxFileSize: Long + private val logPath: String, + private val maxFileSize: Long, ) : LogRoller { - override fun rollLogs(kotlinxIoPath: Path, fileSystem: FileSystem) { - val metadata = fileSystem.metadataOrNull(kotlinxIoPath) - metadata?.let { - it.size.let { size -> - if (size >= maxFileSize) { - val filename = kotlinxIoPath.name - val count = fileSystem.list(kotlinxIoPath.parent!!).count { p: Path -> - p.name.contains(filename) - } - val to = Path("$logPath.$count") - fileSystem.atomicMove(kotlinxIoPath, to) - } - } + override fun rollLogs(kotlinxIoPath: Path, fileSystem: FileSystem) { + val metadata = fileSystem.metadataOrNull(kotlinxIoPath) + metadata?.let { + it.size.let { size -> + if (size >= maxFileSize) { + val filename = kotlinxIoPath.name + val count = fileSystem.list(kotlinxIoPath.parent!!).count { p: Path -> + p.name.contains(filename) + } + val to = Path("$logPath.$count") + fileSystem.atomicMove(kotlinxIoPath, to) } + } } + } } diff --git a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.jvm.kt b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.jvm.kt new file mode 100644 index 0000000000000000000000000000000000000000..e6e70b71c5cb8e6be0817e8343a88d54e039af68 --- /dev/null +++ b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/core/LoggerFactory.jvm.kt @@ -0,0 +1,58 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package edu.ucsc.its.temerity.core + +import co.touchlab.kermit.CommonWriter +import co.touchlab.kermit.Logger +import co.touchlab.kermit.NoTagFormatter +import co.touchlab.kermit.Severity.Debug +import co.touchlab.kermit.loggerConfigInit +import co.touchlab.kermit.platformLogWriter +import edu.ucsc.its.temerity.TemClientConfig + +internal actual fun createLoggerCommon(tag: String?, config: TemClientConfig?, supportKtxNotebook: Boolean): Logger = when (config) { + null -> { + Logger( + config = loggerConfigInit( + if (supportKtxNotebook) CommonWriter(NoTagFormatter) else platformLogWriter(NoTagFormatter), + minSeverity = Debug, + ), + tag = tag ?: "TemerityLib", + ) + } + else -> { + when (config.optDebugEnabled) { + // If debug logging is enabled, create a logger which color prints to the console + true -> { + Logger( + config = loggerConfigInit( + if (supportKtxNotebook) CommonWriter(NoTagFormatter) else platformLogWriter(NoTagFormatter), + minSeverity = Debug, + ), + tag = tag ?: "TemerityLib", + ) + } + // If debug logging is disabled, create a logger which prints to a log file + false -> { + Logger( + config = loggerConfigInit(), + ) + } + } + } +} diff --git a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/Actual.kt b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/di/PlatformModule.jvm.kt similarity index 85% rename from temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/Actual.kt rename to temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/di/PlatformModule.jvm.kt index cbc6a9d34e6893c582470916f64043e4006b5910..395475859059a991d03abf620caa4e789158bdba 100644 --- a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/Actual.kt +++ b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/di/PlatformModule.jvm.kt @@ -15,16 +15,17 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -package edu.ucsc.its.temerity +package edu.ucsc.its.temerity.di -import edu.ucsc.its.temerity.core.DispatcherFactory.createDispatcher +import edu.ucsc.its.temerity.extensions.coroutines.createDispatcher import io.ktor.client.engine.java.Java import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers +import org.koin.core.qualifier.named import org.koin.dsl.module internal actual fun platformModule() = module { - single { (threadCount: Int, dispatcherName: String) -> + single(named("libraryCoroutineDispatcher")) { (threadCount: Int, dispatcherName: String) -> createDispatcher(Dispatchers.IO, threadCount, dispatcherName) } factory { (libraryCoroutineDispatcher: CoroutineDispatcher) -> diff --git a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/ColorFormatter.kt b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/ColorFormatter.kt new file mode 100644 index 0000000000000000000000000000000000000000..d11bb8f44f4a9c7949808ddded9343f09f56e290 --- /dev/null +++ b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/ColorFormatter.kt @@ -0,0 +1,65 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +@file:Suppress("unused") + +package edu.ucsc.its.temerity.extensions.log + +import co.touchlab.kermit.DefaultFormatter +import co.touchlab.kermit.Message +import co.touchlab.kermit.MessageStringFormatter +import co.touchlab.kermit.Severity +import co.touchlab.kermit.Tag +import org.fusesource.jansi.Ansi + +private fun withColor(messageStringFormatter: MessageStringFormatter = DefaultFormatter): MessageStringFormatter = ColorFormatter(messageStringFormatter) + +private fun withBrightColor(messageStringFormatter: MessageStringFormatter = DefaultFormatter): MessageStringFormatter = BrightColorFormatter(messageStringFormatter) + +internal class ColorFormatter( + messageStringFormatter: MessageStringFormatter = DefaultFormatter, +) : WrappingFormatter(messageStringFormatter) { + override fun prefix(severity: Severity?, tag: Tag?, message: Message) = + severity?.toAnsiColor() ?: "" + + override fun suffix(severity: Severity?, tag: Tag?, message: Message) = + resetColor() +} + +internal class BrightColorFormatter( + messageStringFormatter: MessageStringFormatter = DefaultFormatter, +) : WrappingFormatter(messageStringFormatter) { + override fun prefix(severity: Severity?, tag: Tag?, message: Message) = + severity?.toBrightAnsiColor() ?: "" + + override fun suffix(severity: Severity?, tag: Tag?, message: Message) = resetColor() +} + +internal fun Severity.toBrightAnsiColor() = "${Ansi.ansi().fgBright(this.asColor())}" + +internal fun Severity.toAnsiColor() = "${Ansi.ansi().fg(this.asColor())}" + +internal fun resetColor() = "${Ansi.ansi().a(Ansi.Attribute.RESET)}" + +internal fun Severity.asColor(): Ansi.Color = when (this) { + Severity.Verbose -> Ansi.Color.WHITE + Severity.Debug -> Ansi.Color.CYAN + Severity.Info -> Ansi.Color.GREEN + Severity.Warn -> Ansi.Color.YELLOW + Severity.Error -> Ansi.Color.RED + Severity.Assert -> Ansi.Color.MAGENTA +} diff --git a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/Filesystem.jvm.kt b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/Filesystem.jvm.kt new file mode 100644 index 0000000000000000000000000000000000000000..094b3d7530deffde589ca3ccadd23ad63130c62f --- /dev/null +++ b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/Filesystem.jvm.kt @@ -0,0 +1,23 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package edu.ucsc.its.temerity.extensions.log + +import kotlinx.io.files.FileSystem +import kotlinx.io.files.SystemFileSystem + +internal actual fun fileSystem(): FileSystem = SystemFileSystem diff --git a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/KermitServiceProvider.kt b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/KermitServiceProvider.kt index 0010197ea7ae91755baa602f0d1feb199a88f1ce..2f4e0b8a4687d191296792ed2c4617062eefa755 100644 --- a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/KermitServiceProvider.kt +++ b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/KermitServiceProvider.kt @@ -1,3 +1,20 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ @file:Suppress("unused") package edu.ucsc.its.temerity.extensions.log @@ -6,50 +23,46 @@ import co.touchlab.kermit.CommonWriter import co.touchlab.kermit.LogWriter import co.touchlab.kermit.Severity import co.touchlab.kermit.StaticConfig -import kotlinx.io.files.FileSystem -import kotlinx.io.files.SystemFileSystem import org.slf4j.ILoggerFactory import org.slf4j.helpers.BasicMarkerFactory import org.slf4j.helpers.NOPMDCAdapter -internal actual fun fileSystem() : FileSystem = SystemFileSystem - internal class KermitServiceProvider : org.slf4j.spi.SLF4JServiceProvider { - private val markerFactory = BasicMarkerFactory() - private val mdcAdapter = NOPMDCAdapter() - override fun getLoggerFactory() = ILoggerFactory { - Slf4jKermitLogger(it, config) - } + private val markerFactory = BasicMarkerFactory() + private val mdcAdapter = NOPMDCAdapter() + override fun getLoggerFactory() = ILoggerFactory { + Slf4jKermitLogger(it, config) + } - override fun getMarkerFactory() = markerFactory + override fun getMarkerFactory() = markerFactory - override fun getMDCAdapter() = mdcAdapter + override fun getMDCAdapter() = mdcAdapter - override fun getRequestedApiVersion() = "2.0.99" + override fun getRequestedApiVersion() = "2.0.99" - override fun initialize() = Unit + override fun initialize() = Unit - companion object { - private val writers = mutableListOf<LogWriter>().apply { - add(CommonWriter()) - } - var config: StaticConfig = StaticConfig(logWriterList = writers) + companion object { + private val writers = mutableListOf<LogWriter>().apply { + add(CommonWriter()) + } + var config: StaticConfig = StaticConfig(logWriterList = writers) - var minSeverity: Severity - get() = config.minSeverity - set(value) { - config = config.copy(minSeverity = value) - } + var minSeverity: Severity + get() = config.minSeverity + set(value) { + config = config.copy(minSeverity = value) + } - fun addWriter(writer: LogWriter) { - writers.add(writer) - config = config.copy(logWriterList = writers) - } + fun addWriter(writer: LogWriter) { + writers.add(writer) + config = config.copy(logWriterList = writers) + } - fun setWriters(vararg writer: LogWriter) { - writers.clear() - writers.addAll(writer) - config = config.copy(logWriterList = writers) - } + fun setWriters(vararg writer: LogWriter) { + writers.clear() + writers.addAll(writer) + config = config.copy(logWriterList = writers) } + } } diff --git a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/Slf4jKermitLogger.kt b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/Slf4jKermitLogger.kt index 4ffbc2053e5345de72ee129959198bb3d6c884cd..d5ea3eb24c621c3610145a9519817e34535d526a 100644 --- a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/Slf4jKermitLogger.kt +++ b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/Slf4jKermitLogger.kt @@ -1,3 +1,20 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ @file:Suppress("unused") package edu.ucsc.its.temerity.extensions.log @@ -7,54 +24,55 @@ import co.touchlab.kermit.LoggerConfig import co.touchlab.kermit.Severity import org.slf4j.Marker import org.slf4j.event.Level -import org.slf4j.event.Level.* import org.slf4j.helpers.AbstractLogger internal class Slf4jKermitLogger(private val name: String, config: LoggerConfig) : AbstractLogger() { - private val logger = BaseLogger(config) - override fun getName(): String = "slf4j-over-kermit" - - //region Is Logging enabled at various levels - override fun isTraceEnabled() = logger.config.minSeverity <= Severity.Verbose - override fun isTraceEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Verbose - override fun isDebugEnabled() = logger.config.minSeverity <= Severity.Debug - override fun isDebugEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Debug - override fun isInfoEnabled() = logger.config.minSeverity <= Severity.Info - override fun isInfoEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Info - override fun isWarnEnabled() = logger.config.minSeverity <= Severity.Warn - override fun isWarnEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Warn - override fun isErrorEnabled() = logger.config.minSeverity <= Severity.Error - override fun isErrorEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Error - //endregion - - override fun getFullyQualifiedCallerName(): String? = null - - override fun handleNormalizedLoggingCall( - level: Level?, - marker: Marker?, - messagePattern: String?, - arguments: Array<out Any>?, - throwable: Throwable? - ) { - val severity = when (level) { - ERROR -> Severity.Error - WARN -> Severity.Warn - INFO -> Severity.Info - DEBUG -> Severity.Debug - else -> Severity.Verbose - } - - val formatted = if (messagePattern != null && arguments != null) { - String.format(messagePattern, *(arguments.toList().toTypedArray())) - } else null - - messagePattern.let { - logger.log( - severity, - marker?.toString() ?: name, - throwable, - formatted ?: (messagePattern ?: "") - ) - } + private val logger = BaseLogger(config) + override fun getName(): String = "slf4j-over-kermit" + + //region Is Logging enabled at various levels + override fun isTraceEnabled() = logger.config.minSeverity <= Severity.Verbose + override fun isTraceEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Verbose + override fun isDebugEnabled() = logger.config.minSeverity <= Severity.Debug + override fun isDebugEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Debug + override fun isInfoEnabled() = logger.config.minSeverity <= Severity.Info + override fun isInfoEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Info + override fun isWarnEnabled() = logger.config.minSeverity <= Severity.Warn + override fun isWarnEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Warn + override fun isErrorEnabled() = logger.config.minSeverity <= Severity.Error + override fun isErrorEnabled(marker: Marker?) = logger.config.minSeverity <= Severity.Error + //endregion + + override fun getFullyQualifiedCallerName(): String? = null + + override fun handleNormalizedLoggingCall( + level: Level?, + marker: Marker?, + messagePattern: String?, + arguments: Array<out Any>?, + throwable: Throwable?, + ) { + val severity = when (level) { + Level.ERROR -> Severity.Error + Level.WARN -> Severity.Warn + Level.INFO -> Severity.Info + Level.DEBUG -> Severity.Debug + else -> Severity.Verbose + } + + val formatted = if (messagePattern != null && arguments != null) { + String.format(messagePattern, *(arguments.toList().toTypedArray())) + } else { + null + } + + messagePattern.let { + logger.log( + severity, + marker?.toString() ?: name, + throwable, + formatted ?: (messagePattern ?: ""), + ) } + } } diff --git a/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/WrappingFormatter.kt b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/WrappingFormatter.kt new file mode 100644 index 0000000000000000000000000000000000000000..26703f81aca62012f948c33e137c017af0a24728 --- /dev/null +++ b/temerity/src/jvmMain/kotlin/edu/ucsc/its/temerity/extensions/log/WrappingFormatter.kt @@ -0,0 +1,37 @@ +/* + * Designed and developed in 2022-2024 by William Walker (wnwalker@ucsc.edu) + * Copyright 2022-2024 The Regents of the University of California. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package edu.ucsc.its.temerity.extensions.log + +import co.touchlab.kermit.Message +import co.touchlab.kermit.MessageStringFormatter +import co.touchlab.kermit.Severity +import co.touchlab.kermit.Tag + +internal abstract class WrappingFormatter( + private val messageStringFormatter: MessageStringFormatter, +) : MessageStringFormatter { + override fun formatMessage(severity: Severity?, tag: Tag?, message: Message): String { + val prefix = prefix(severity, tag, message) + val content = messageStringFormatter.formatMessage(severity, tag, message) + val suffix = suffix(severity, tag, message) + return "${prefix}$content$suffix" + } + + open fun prefix(severity: Severity?, tag: Tag?, message: Message): String = "" + open fun suffix(severity: Severity?, tag: Tag?, message: Message): String = "" +} diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevDeviceApiTests.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevDeviceApiTests.kt index a629715af9f553b9f07284e8bcc20299c40b58a0..058672e8279d74875a658994c3975424e65c6dbb 100644 --- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevDeviceApiTests.kt +++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevDeviceApiTests.kt @@ -20,11 +20,10 @@ package edu.ucsc.its.temerity.test import edu.ucsc.its.temerity.core.Temerity import io.kotest.core.spec.style.FunSpec import kotlinx.coroutines.runBlocking -import org.dotenv.vault.dotenvVault class DevDeviceApiTests : FunSpec({ - val dotenv = dotenvVault() + val dotenv = dotenvVaultJvm() lateinit var temerityTest: Temerity diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevGroupApiTests.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevGroupApiTests.kt index 7457d64fed42fe280fe04e6f2764a30733fee869..3658f2465eaef2f5dc443af31816e9b5143d2296 100644 --- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevGroupApiTests.kt +++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevGroupApiTests.kt @@ -20,12 +20,11 @@ package edu.ucsc.its.temerity.test import edu.ucsc.its.temerity.core.Temerity import io.kotest.core.spec.style.FunSpec import kotlinx.coroutines.runBlocking -import org.dotenv.vault.dotenvVault class DevGroupApiTests : FunSpec({ - val dotenv = dotenvVault() + val dotenv = dotenvVaultJvm() lateinit var testTemerity: Temerity diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUserApiTests.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUserApiTests.kt index 6c385a94cadaffe14fc106e14d1bda14d5092a96..e4c44452ea0700c2cddc58f8e5da79fb847bf1c5 100644 --- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUserApiTests.kt +++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUserApiTests.kt @@ -28,12 +28,11 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.ktor.http.HttpStatusCode import kotlinx.coroutines.runBlocking -import org.dotenv.vault.dotenvVault class DevUserApiTests : FunSpec({ - val dotenv = dotenvVault() + val dotenv = dotenvVaultJvm() val kermit = createLogger("DevUserApiTests") lateinit var testTemerity: Temerity diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUtilityTests.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUtilityTests.kt index 4dbaee47333853244df68ab3a2b2afe5be12fa9d..be3c3b3850f6f14c1ea5ed6342c44ee1b90cb981 100644 --- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUtilityTests.kt +++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/DevUtilityTests.kt @@ -20,7 +20,6 @@ package edu.ucsc.its.temerity.test import edu.ucsc.its.temerity.core.Temerity import edu.ucsc.its.temerity.core.Temerity.Companion.createLogger import io.kotest.core.spec.style.FunSpec -import org.dotenv.vault.dotenvVault import org.koin.test.KoinTest import kotlin.time.Duration.Companion.minutes @@ -33,7 +32,7 @@ class DevUtilityTests : init { coroutineDebugProbes = true - val dotenv = dotenvVault() + val dotenv = dotenvVaultJvm() lateinit var testTemerity: Temerity diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdReportTests.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdReportTests.kt index 00f46e94e7185ea562e81f324f89c8f33329c901..5eecca73e13bcdac68c5d652fd09ab6cbeff93fd 100644 --- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdReportTests.kt +++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdReportTests.kt @@ -19,7 +19,6 @@ package edu.ucsc.its.temerity.test import edu.ucsc.its.temerity.AuditLogSortOrder.NEW_FIRST import edu.ucsc.its.temerity.core.Temerity import edu.ucsc.its.temerity.core.Temerity.Companion.createLogger -import edu.ucsc.its.temerity.currentDate import edu.ucsc.its.temerity.model.EventType.AUTOMATED_SESSION_FAILED_TO_START import edu.ucsc.its.temerity.model.EventType.AUTOMATED_SESSION_MONITOR import edu.ucsc.its.temerity.model.EventType.CAPTURE_ERROR @@ -39,7 +38,6 @@ import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.TimeZone import kotlinx.datetime.minus import kotlinx.datetime.todayIn -import org.dotenv.vault.dotenvVault import org.jetbrains.kotlinx.dataframe.api.toDataFrame import org.jetbrains.kotlinx.dataframe.io.writeCSV import org.koin.test.KoinTest @@ -59,10 +57,10 @@ class ProdReportTests : init { coroutineDebugProbes = true - val dotenv = dotenvVault() + val dotenv = dotenvVaultJvm() lateinit var testTemerity: Temerity - val today = currentDate() + val today = Temerity.currentDate() beforeTest { testTemerity = Temerity { diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdUtilityTests.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdUtilityTests.kt index 222d15af6cb85c07520c1e5e43616b508cf93197..af9e10d6fe64095548c9e886d25a856d2a858e42 100644 --- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdUtilityTests.kt +++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/ProdUtilityTests.kt @@ -19,7 +19,6 @@ package edu.ucsc.its.temerity.test import edu.ucsc.its.temerity.core.Temerity import io.kotest.core.spec.style.FunSpec -import org.dotenv.vault.dotenvVault import org.koin.test.KoinTest import kotlin.time.Duration.Companion.minutes @@ -30,7 +29,7 @@ class ProdUtilityTests : init { coroutineDebugProbes = true - val dotenv = dotenvVault() + val dotenv = dotenvVaultJvm() lateinit var testTemerity: Temerity diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/TemerityDevTest.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/TemerityDevTest.kt index b98aea8a3321c7370ca9f24a920123efbeab46bd..51fd825ba227df0925ac95ba04aa83696f9dbf26 100644 --- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/TemerityDevTest.kt +++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/TemerityDevTest.kt @@ -22,7 +22,7 @@ import com.skydoves.sandwich.StatusCode import com.skydoves.sandwich.ktor.getStatusCode import edu.ucsc.its.temerity.AuditLogSortOrder.NEW_FIRST import edu.ucsc.its.temerity.core.Temerity -import edu.ucsc.its.temerity.currentDate +import edu.ucsc.its.temerity.core.Temerity.Companion.currentDate import edu.ucsc.its.temerity.model.EventType.NEW_LOG_IN import edu.ucsc.its.temerity.model.NewUser import io.github.z4kn4fein.semver.Version @@ -34,7 +34,6 @@ import kotlinx.coroutines.runBlocking import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.minus import kotlinx.datetime.plus -import org.dotenv.vault.dotenvVault import org.jetbrains.kotlinx.dataframe.api.toDataFrame import org.jetbrains.kotlinx.dataframe.io.writeCSV import java.io.File @@ -48,7 +47,7 @@ class TemerityDevTest : FunSpec() { private lateinit var kermit: Logger init { - val dotenv = dotenvVault() + val dotenv = dotenvVaultJvm() lateinit var testTemerity: Temerity @@ -60,9 +59,9 @@ class TemerityDevTest : FunSpec() { } test("Temerity client returns a correctly-formatted version String") { - val returnedVersion = testTemerity.VERSION.toVersion() + val returnedVersion = testTemerity.version.toVersion() kermit.d { "Returned client version: $returnedVersion" } - assert(testTemerity.VERSION.isNotEmpty()) + assert(testTemerity.version.isNotEmpty()) assert(returnedVersion < Version(0, 1, 0)) assert(returnedVersion.isPreRelease) } diff --git a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/Util.kt b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/Util.kt index 5952412206b4a0b89719df0efa9dd5ac1c40d52a..1f0e87313894ddac9d6c2ceb8b4a9bad9e8166cc 100644 --- a/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/Util.kt +++ b/temerity/src/jvmTest/kotlin/edu/ucsc/its/temerity/test/Util.kt @@ -18,7 +18,9 @@ package edu.ucsc.its.temerity.test import edu.ucsc.its.temerity.TemClientConfig +import io.github.cdimascio.dotenv.Configuration import io.github.cdimascio.dotenv.Dotenv +import io.github.cdimascio.dotenv.dotenv import io.kotest.core.extensions.TestCaseExtension import io.kotest.core.test.TestCase import io.kotest.core.test.TestResult @@ -85,3 +87,10 @@ internal fun TemClientConfig.configureProdEnvironment(dotenv: Dotenv) { serviceToken = dotenv["YUJAPROD_TOKEN"] optDebugEnabled = true } + +fun dotenvVaultJvm(block: (Configuration.() -> Unit)? = null): Dotenv = when (block) { + null -> dotenv() + else -> dotenv { + block() + } +}