(1)
+ init {
+ eventBus.register(this)
+ }
+
@Subscribe(threadMode = ThreadMode.ASYNC)
override fun handleCameraEvent(event: CameraEvent) {
jobs.find { it.camera === event.device }?.handleCameraEvent(event)
@@ -36,7 +38,7 @@ class FlatWizardExecutor(
check(jobs.none { it.camera === camera }) { "${camera.name} Flat Wizard is already in progress" }
with(FlatWizardJob(this, camera, request)) {
- val completable = runAsync(threadPoolTaskExecutor)
+ val completable = runAsync(executorService)
jobs.add(this)
completable.whenComplete { _, _ -> jobs.remove(this) }
}
diff --git a/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardJob.kt b/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardJob.kt
index dd2ee0e53..cbd78bffa 100644
--- a/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardJob.kt
+++ b/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardJob.kt
@@ -11,6 +11,7 @@ import nebulosa.indi.device.camera.FrameType
import nebulosa.job.manager.AbstractJob
import nebulosa.job.manager.Task
import nebulosa.log.loggerFor
+import nebulosa.util.concurrency.cancellation.CancellationSource
import nebulosa.util.concurrency.latch.CountUpDownLatch
import java.nio.file.Path
import java.time.Duration
@@ -51,6 +52,11 @@ data class FlatWizardJob(
cameraExposureTask.handleCameraEvent(event)
}
+ override fun onCancel(source: CancellationSource) {
+ waitToComputeOptimalExposureTime.reset()
+ super.onCancel(source)
+ }
+
override fun accept(event: Any) {
when (event) {
is CameraExposureEvent -> {
@@ -126,6 +132,12 @@ data class FlatWizardJob(
}
override fun afterFinish() {
+ if (status.state == FlatWizardState.EXPOSURING) {
+ status.state = FlatWizardState.IDLE
+ status.capture.state = CameraCaptureState.IDLE
+ status.send()
+ }
+
LOG.debug("Flat Wizard finished. camera={}, request={}, exposureTime={} µs", camera, request, status.exposureTime)
}
diff --git a/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardRequest.kt b/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardRequest.kt
index 92a214d3c..6165225d4 100644
--- a/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardRequest.kt
+++ b/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardRequest.kt
@@ -1,21 +1,31 @@
package nebulosa.api.wizard.flat
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import nebulosa.api.beans.converters.time.DurationUnit
import nebulosa.api.cameras.CameraStartCaptureRequest
-import org.hibernate.validator.constraints.Range
-import org.hibernate.validator.constraints.time.DurationMax
-import org.hibernate.validator.constraints.time.DurationMin
-import org.springframework.boot.convert.DurationUnit
+import nebulosa.api.javalin.Validatable
+import nebulosa.api.javalin.max
+import nebulosa.api.javalin.min
+import nebulosa.api.javalin.range
import java.time.Duration
import java.time.temporal.ChronoUnit
+import java.util.concurrent.TimeUnit
data class FlatWizardRequest(
- @JsonIgnoreProperties("camera", "focuser", "dither") @JvmField val capture: CameraStartCaptureRequest,
- @field:DurationMin(millis = 1) @field:DurationMax(minutes = 1) @field:DurationUnit(ChronoUnit.MILLIS) @JvmField val exposureMin: Duration = MIN_EXPOSURE,
- @field:DurationMin(millis = 1) @field:DurationMax(minutes = 1) @field:DurationUnit(ChronoUnit.MILLIS) @JvmField val exposureMax: Duration = MAX_EXPOSURE,
- @field:Range(min = 0, max = 65535) @JvmField val meanTarget: Int = 32768, // 50% = 32768 (16-bit)
- @field:Range(min = 0, max = 100) @JvmField val meanTolerance: Int = 10, // 10%
-) {
+ @JsonIgnoreProperties("camera", "focuser", "dither") @JvmField val capture: CameraStartCaptureRequest = CameraStartCaptureRequest.EMPTY,
+ @field:DurationUnit(ChronoUnit.MILLIS) @JvmField val exposureMin: Duration = MIN_EXPOSURE,
+ @field:DurationUnit(ChronoUnit.MILLIS) @JvmField val exposureMax: Duration = MAX_EXPOSURE,
+ @JvmField val meanTarget: Int = 32768, // 50% = 32768 (16-bit)
+ @JvmField val meanTolerance: Int = 10, // 10%
+) : Validatable {
+
+ override fun validate() {
+ capture.validate()
+ exposureMin.min(1L, TimeUnit.MILLISECONDS).max(10L, TimeUnit.MINUTES)
+ exposureMax.min(1L, TimeUnit.MILLISECONDS).max(10L, TimeUnit.MINUTES)
+ meanTarget.range(0, 65535)
+ meanTolerance.range(0, 100)
+ }
companion object {
diff --git a/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardService.kt b/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardService.kt
index 4ac313b61..5e67bdad9 100644
--- a/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardService.kt
+++ b/api/src/main/kotlin/nebulosa/api/wizard/flat/FlatWizardService.kt
@@ -1,12 +1,10 @@
package nebulosa.api.wizard.flat
import nebulosa.indi.device.camera.Camera
-import org.springframework.stereotype.Service
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
-@Service
class FlatWizardService(
private val capturesPath: Path,
private val flatWizardExecutor: FlatWizardExecutor,
diff --git a/api/src/test/kotlin/SkyAtlasServiceTest.kt b/api/src/test/kotlin/SkyAtlasServiceTest.kt
index 83b467fd5..5be0c2ac8 100644
--- a/api/src/test/kotlin/SkyAtlasServiceTest.kt
+++ b/api/src/test/kotlin/SkyAtlasServiceTest.kt
@@ -23,10 +23,10 @@ import nebulosa.skycatalog.SkyObjectType
import nebulosa.test.HTTP_CLIENT
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Test
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
+import java.util.concurrent.Executors
class SkyAtlasServiceTest {
@@ -239,7 +239,7 @@ class SkyAtlasServiceTest {
BOX_STORE.close()
}
- @JvmStatic private val THREAD_POOL_TASK_EXECUTOR = ThreadPoolTaskExecutor().also { it.initialize() }
+ @JvmStatic private val THREAD_POOL_TASK_EXECUTOR = Executors.newSingleThreadExecutor()
@JvmStatic private val HORIZONS_SERVICE = HorizonsService(httpClient = HTTP_CLIENT)
@JvmStatic private val HORIZONS_EPHEMERIS_PROVIDER = HorizonsEphemerisProvider(HORIZONS_SERVICE)
@JvmStatic private val BODY_EPHEMERIS_PROVIDER = BodyEphemerisProvider(THREAD_POOL_TASK_EXECUTOR)
@@ -265,6 +265,7 @@ class SkyAtlasServiceTest {
@JvmStatic private val SERVICE = SkyAtlasService(
HORIZONS_EPHEMERIS_PROVIDER, BODY_EPHEMERIS_PROVIDER, SMALL_BODY_DATABASE_SERVICE,
SATELLITE_REPOSITORY, SIMBAD_ENTITY_REPOSITORY, HTTP_CLIENT, OBJECT_MAPPER, MOON_PHASE_FINDER,
+ Executors.newSingleThreadScheduledExecutor(),
)
@JvmStatic private val LOCATION = Location("-19.846616".deg, "-43.96872".deg, 852.0.m, -180)
diff --git a/build.gradle.kts b/build.gradle.kts
index ce42856cd..c59c150de 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,8 +5,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20")
- classpath("org.jetbrains.kotlin:kotlin-allopen:2.0.20")
classpath("io.objectbox:objectbox-gradle-plugin:4.0.2")
+ classpath("com.gradleup.shadow:shadow-gradle-plugin:8.3.3")
}
repositories {
diff --git a/desktop/app/main.ts b/desktop/app/main.ts
index 4f3c7b2ad..f5bd81ae2 100644
--- a/desktop/app/main.ts
+++ b/desktop/app/main.ts
@@ -3,13 +3,10 @@ import * as fs from 'fs'
import type { ChildProcessWithoutNullStreams } from 'node:child_process'
import { spawn } from 'node:child_process'
import { join } from 'path'
-import { WebSocket } from 'ws'
import type { InternalEventType, JsonFile } from '../src/shared/types/app.types'
import { ArgumentParser } from './argument.parser'
import { WindowManager } from './window.manager'
-Object.assign(global, { WebSocket })
-
const argParser = new ArgumentParser()
const parsedArgs = argParser.parse(process.argv.slice(1))
@@ -34,10 +31,10 @@ process.on('beforeExit', () => {
function createApiProcess(port: number = parsedArgs.port) {
const apiJar = join(process.resourcesPath, 'api.jar')
- const apiProcess = spawn('java', ['-jar', apiJar, `--server.port=${port}`])
+ const apiProcess = spawn('java', ['-jar', apiJar, `--port=${port}`])
apiProcess.on('close', (code) => {
- console.warn(`server process exited with code: ${code}`)
+ console.warn(`api process exited with code: ${code}`)
process.exit(code ?? 0)
})
diff --git a/desktop/app/package-lock.json b/desktop/app/package-lock.json
index 11a11b8bf..fd94f2fce 100644
--- a/desktop/app/package-lock.json
+++ b/desktop/app/package-lock.json
@@ -9,16 +9,10 @@
"version": "0.1.0",
"license": "MIT",
"dependencies": {
- "@stomp/stompjs": "7.0.0",
"electron-store": "8.2.0",
"ws": "8.18.0"
}
},
- "node_modules/@stomp/stompjs": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.0.0.tgz",
- "integrity": "sha512-fGdq4wPDnSV/KyOsjq4P+zLc8MFWC3lMmP5FBgLWKPJTYcuCbAIrnRGjB7q2jHZdYCOD5vxLuFoKIYLy5/u8Pw=="
- },
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
diff --git a/desktop/app/package.json b/desktop/app/package.json
index dae08a583..19752d065 100644
--- a/desktop/app/package.json
+++ b/desktop/app/package.json
@@ -11,7 +11,6 @@
"main": "main.js",
"private": true,
"dependencies": {
- "@stomp/stompjs": "7.0.0",
"electron-store": "8.2.0",
"ws": "8.18.0"
}
diff --git a/desktop/app/window.manager.ts b/desktop/app/window.manager.ts
index 777fa5e98..ea7863aad 100644
--- a/desktop/app/window.manager.ts
+++ b/desktop/app/window.manager.ts
@@ -1,9 +1,9 @@
-import { Client } from '@stomp/stompjs'
import type { Rectangle } from 'electron'
import { BrowserWindow, Notification, dialog, screen, shell } from 'electron'
import Store from 'electron-store'
import type { ChildProcessWithoutNullStreams } from 'node:child_process'
import { join } from 'path'
+import { WebSocket } from 'ws'
import type { MessageEvent } from '../src/shared/types/api.types'
import type { CloseWindow, ConfirmationEvent, FullscreenWindow, NotificationEvent, OpenDirectory, OpenFile, OpenWindow, ResizeWindow, WindowCommand } from '../src/shared/types/app.types'
import type { Nullable } from '../src/shared/utils/types'
@@ -21,7 +21,7 @@ export class ApplicationWindow {
public readonly browserWindow: BrowserWindow,
public readonly data: OpenWindow,
public readonly parentWindow?: BrowserWindow,
- public webSocket?: Client,
+ public webSocket?: WebSocket,
public apiProcess?: ChildProcessWithoutNullStreams,
public resolver?: (data: unknown) => void,
) {}
@@ -209,6 +209,44 @@ export class WindowManager {
}
}
+ private createWebSocket(host: string, port: number, connected: (webSocket: WebSocket) => void) {
+ const webSocket = new WebSocket(`ws://${host}:${port}/ws`)
+
+ const reconnect = () => {
+ setTimeout(() => this.createWebSocket(host, port, connected), 2000)
+ }
+
+ webSocket.on('open', () => {
+ console.info('Web Socket connected')
+ connected(webSocket)
+ })
+
+ webSocket.on('message', (data: Buffer) => {
+ const event = JSON.parse(data.toString()) as MessageEvent
+
+ if (isNotificationEvent(event)) {
+ this.showNotification(event)
+ } else if (isConfirmationEvent(event)) {
+ this.showConfirmation(event)
+ } else if (event.eventName) {
+ this.dispatchEvent(event)
+ } else {
+ console.warn('invalid message event', event)
+ }
+ })
+
+ webSocket.on('close', (code, reason) => {
+ console.warn('Web Socket closed', code, reason.toString())
+ reconnect()
+ })
+
+ webSocket.on('error', () => {
+ console.error('Web Socket error')
+ })
+
+ return webSocket
+ }
+
async createMainWindow(apiProcess?: ChildProcessWithoutNullStreams, port: number = this.port, host: string = this.host) {
this.port = port
this.host = host
@@ -216,39 +254,8 @@ export class WindowManager {
const open: OpenWindow = { id: 'home', path: 'home', preference: {} }
const appWindow = await this.createWindow(open)
- const webSocket = new Client({
- brokerURL: `ws://${host}:${port}/ws`,
- onConnect: () => {
- webSocket.subscribe('NEBULOSA.EVENT', (message) => {
- const event = JSON.parse(message.body) as MessageEvent
-
- if (isNotificationEvent(event)) {
- this.showNotification(event)
- } else if (isConfirmationEvent(event)) {
- this.showConfirmation(event)
- } else if (event.eventName) {
- this.dispatchEvent(event)
- } else {
- console.warn('invalid message event', event)
- }
- })
-
- console.info('Web Socket connected')
- },
- onDisconnect: () => {
- console.warn('Web Socket disconnected')
- },
- onWebSocketClose: () => {
- console.warn('Web Socket closed')
- },
- onWebSocketError: () => {
- console.error('Web Socket error')
- },
- })
-
- webSocket.activate()
+ this.createWebSocket(host, port, (webSocket) => (appWindow.webSocket = webSocket))
- appWindow.webSocket = webSocket
appWindow.apiProcess = apiProcess
}
diff --git a/desktop/src/app/filterwheel/filterwheel.component.ts b/desktop/src/app/filterwheel/filterwheel.component.ts
index 1cc4c9bbf..c5866a8d1 100644
--- a/desktop/src/app/filterwheel/filterwheel.component.ts
+++ b/desktop/src/app/filterwheel/filterwheel.component.ts
@@ -247,7 +247,7 @@ export class FilterWheelComponent implements AfterContentInit, OnDestroy, Tickab
const offset = nextFocusOffset - currentFocusOffset
- if (this.focuser && offset !== 0) {
+ if (this.focuser?.connected && offset !== 0) {
if (offset < 0) await this.api.focuserMoveIn(this.focuser, -offset)
else await this.api.focuserMoveOut(this.focuser, offset)
}
diff --git a/desktop/src/app/flat-wizard/flat-wizard.component.ts b/desktop/src/app/flat-wizard/flat-wizard.component.ts
index 0c822aadf..bc32f121e 100644
--- a/desktop/src/app/flat-wizard/flat-wizard.component.ts
+++ b/desktop/src/app/flat-wizard/flat-wizard.component.ts
@@ -60,14 +60,15 @@ export class FlatWizardComponent implements AfterViewInit, OnDestroy, Tickable {
if (event.state === 'EXPOSURING' && event.capture && event.camera.id === this.camera?.id) {
this.running = true
this.cameraExposure.handleCameraCaptureEvent(event.capture, true)
- } else if (event.state === 'CAPTURED') {
+ } else {
this.running = false
this.cameraExposure.reset()
- this.angularService.message('Flat frame captured')
- } else if (event.state === 'FAILED') {
- this.running = false
- this.cameraExposure.reset()
- this.angularService.message('Failed to find an optimal exposure time from given parameters', 'error')
+
+ if (event.state === 'CAPTURED') {
+ this.angularService.message('Flat frame captured')
+ } else if (event.state === 'FAILED') {
+ this.angularService.message('Failed to find an optimal exposure time from given parameters', 'error')
+ }
}
})
})
diff --git a/desktop/src/app/mount/mount.component.html b/desktop/src/app/mount/mount.component.html
index 69e96a25d..7f8cf302a 100644
--- a/desktop/src/app/mount/mount.component.html
+++ b/desktop/src/app/mount/mount.component.html
@@ -476,7 +476,7 @@
severity="danger"
pTooltip="Stop"
tooltipPosition="bottom"
- (onClick)="stopRemoteControl(item.type)" />
+ (onClick)="stopRemoteControl(item.protocol)" />
diff --git a/desktop/src/app/stacker/stacker.component.html b/desktop/src/app/stacker/stacker.component.html
index d44c2ccdc..fc21b4a44 100644
--- a/desktop/src/app/stacker/stacker.component.html
+++ b/desktop/src/app/stacker/stacker.component.html
@@ -92,8 +92,12 @@
GAIN: {{ item.analyzed.gain }}
}
-
-
{{ item.path }}
+
+
+ {{ item.path }}
+
diff --git a/desktop/src/shared/interceptors/confirmation.interceptor.ts b/desktop/src/shared/interceptors/confirmation.interceptor.ts
index ec5be3093..3fe7e2bae 100644
--- a/desktop/src/shared/interceptors/confirmation.interceptor.ts
+++ b/desktop/src/shared/interceptors/confirmation.interceptor.ts
@@ -1,6 +1,6 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'
import { Injectable } from '@angular/core'
-import { Observable, finalize } from 'rxjs'
+import { Observable } from 'rxjs'
import { ConfirmationService } from '../services/confirmation.service'
import { IdempotencyKeyInterceptor } from './idempotency-key.interceptor'
@@ -17,20 +17,8 @@ export class ConfirmationInterceptor implements HttpInterceptor {
if (idempotencyKey) {
this.confirmationService.register(idempotencyKey)
}
-
- const res = next.handle(req)
-
- if (idempotencyKey) {
- return res.pipe(
- finalize(() => {
- this.confirmationService.unregister(idempotencyKey)
- }),
- )
- }
-
- return res
- } else {
- return next.handle(req)
}
+
+ return next.handle(req)
}
}
diff --git a/desktop/src/shared/services/api.service.ts b/desktop/src/shared/services/api.service.ts
index 44ebede84..3f3ab681e 100644
--- a/desktop/src/shared/services/api.service.ts
+++ b/desktop/src/shared/services/api.service.ts
@@ -413,7 +413,7 @@ export class ApiService {
}
guidingDisconnect() {
- return this.http.delete(`guiding/disconnect`)
+ return this.http.put(`guiding/disconnect`)
}
guidingStatus() {
diff --git a/desktop/src/shared/services/confirmation.service.ts b/desktop/src/shared/services/confirmation.service.ts
index 55b4c0b12..509c5a14b 100644
--- a/desktop/src/shared/services/confirmation.service.ts
+++ b/desktop/src/shared/services/confirmation.service.ts
@@ -6,7 +6,7 @@ import { ApiService } from './api.service'
@Injectable({ providedIn: 'root' })
export class ConfirmationService {
- private readonly keys = new Map()
+ private readonly keys = new Set()
constructor(
private readonly angularService: AngularService,
@@ -14,11 +14,7 @@ export class ConfirmationService {
) {}
register(key: string) {
- this.keys.set(key, '')
- }
-
- unregister(key: string) {
- this.keys.delete(key)
+ this.keys.add(key)
}
has(key: string) {
@@ -28,6 +24,5 @@ export class ConfirmationService {
async processConfirmationEvent(event: ConfirmationEvent) {
const response = await this.angularService.confirm(event.message)
await this.api.confirm(event.idempotencyKey, response === ConfirmEventType.ACCEPT)
- this.unregister(event.idempotencyKey)
}
}
diff --git a/nebulosa-json/src/main/kotlin/nebulosa/json/PathModule.kt b/nebulosa-json/src/main/kotlin/nebulosa/json/PathModule.kt
index 23e4a2d13..dbe293038 100644
--- a/nebulosa-json/src/main/kotlin/nebulosa/json/PathModule.kt
+++ b/nebulosa-json/src/main/kotlin/nebulosa/json/PathModule.kt
@@ -3,10 +3,9 @@ package nebulosa.json
import com.fasterxml.jackson.databind.module.SimpleDeserializers
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.module.SimpleSerializers
-import com.fasterxml.jackson.datatype.jsr310.PackageVersion
import java.nio.file.Path
-class PathModule : SimpleModule(PackageVersion.VERSION) {
+class PathModule : SimpleModule() {
override fun setupModule(context: SetupContext) {
super.setupModule(context)
diff --git a/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/AbstractPixInsightScript.kt b/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/AbstractPixInsightScript.kt
index 4a20f1771..dfadc0d7f 100644
--- a/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/AbstractPixInsightScript.kt
+++ b/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/AbstractPixInsightScript.kt
@@ -28,13 +28,13 @@ abstract class AbstractPixInsightScript : PixInsigh
final override fun startCommandLine(commandLine: CommandLine) {
commandLine.whenComplete { exitCode, exception ->
try {
- LOG.info("{} script finished. done={}, exitCode={}", this::class.simpleName, isDone, exitCode, exception)
+ LOG.debug("{} script finished. done={}, exitCode={}", this::class.simpleName, isDone, exitCode, exception)
waitOnComplete()
if (isDone) return@whenComplete
else if (exception != null) completeExceptionally(exception)
- else complete(processOnComplete(exitCode).also { LOG.info("{} script processed. output={}", this::class.simpleName, it) })
+ else complete(processOnComplete(exitCode).also { LOG.debug("{} script processed. output={}", this::class.simpleName, it) })
} catch (e: Throwable) {
LOG.error("{} finished with fatal exception. message={}", this::class.simpleName, e.message)
completeExceptionally(e)
diff --git a/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/PixInsightScriptRunner.kt b/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/PixInsightScriptRunner.kt
index f5db75a5f..229140b15 100644
--- a/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/PixInsightScriptRunner.kt
+++ b/nebulosa-pixinsight/src/main/kotlin/nebulosa/pixinsight/script/PixInsightScriptRunner.kt
@@ -13,7 +13,7 @@ data class PixInsightScriptRunner(private val executablePath: Path) {
DEFAULT_ARGS.forEach(::putArg)
}
- LOG.info("running {} script: {}", script::class.simpleName, commandLine.command)
+ LOG.debug("running {} script: {}", script::class.simpleName, commandLine.command)
script.startCommandLine(commandLine)
}
diff --git a/nebulosa-retrofit/src/main/kotlin/nebulosa/retrofit/RetrofitService.kt b/nebulosa-retrofit/src/main/kotlin/nebulosa/retrofit/RetrofitService.kt
index ab25fa033..cee9c6348 100644
--- a/nebulosa-retrofit/src/main/kotlin/nebulosa/retrofit/RetrofitService.kt
+++ b/nebulosa-retrofit/src/main/kotlin/nebulosa/retrofit/RetrofitService.kt
@@ -4,8 +4,8 @@ import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
+import com.fasterxml.jackson.module.kotlin.jsonMapper
import nebulosa.json.PathModule
import okhttp3.ConnectionPool
import okhttp3.OkHttpClient
@@ -21,7 +21,7 @@ abstract class RetrofitService(
mapper: ObjectMapper? = null,
) {
- protected val objectMapper: ObjectMapper by lazy { mapper ?: DEFAULT_MAPPER.also(::withJsonMapper).build() }
+ protected val objectMapper by lazy { mapper ?: DEFAULT_MAPPER }
protected open val converterFactory: Iterable
get() = emptyList()
@@ -31,8 +31,6 @@ abstract class RetrofitService(
protected open fun withOkHttpClient(builder: OkHttpClient.Builder) = Unit
- protected open fun withJsonMapper(mapper: JsonMapper.Builder) = Unit
-
protected open val retrofit by lazy {
val builder = Retrofit.Builder()
builder.baseUrl(url.trim().let { if (it.endsWith("/")) it else "$it/" })
@@ -63,9 +61,9 @@ abstract class RetrofitService(
.callTimeout(60L, TimeUnit.SECONDS)
.build()
- @JvmStatic private val DEFAULT_MAPPER = JsonMapper.builder().apply {
- addModule(JavaTimeModule()) // Why needs to be registered first? Fix "java.time.LocalDateTime not supported by default"
+ @JvmStatic private val DEFAULT_MAPPER = jsonMapper {
addModule(PathModule())
+ addModule(JavaTimeModule())
enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
serializationInclusion(JsonInclude.Include.NON_NULL)
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2ed6a15eb..8cad64316 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -34,6 +34,9 @@ dependencyResolutionManagement {
library("apache-numbers-complex", "org.apache.commons:commons-numbers-complex:1.2")
library("oshi", "com.github.oshi:oshi-core:6.6.5")
library("jna", "net.java.dev.jna:jna:5.15.0")
+ library("javalin", "io.javalin:javalin:6.3.0")
+ library("koin", "io.insert-koin:koin-core:4.0.0")
+ library("airline", "com.github.rvesse:airline:3.0.0")
library("kotest", "io.kotest:kotest-assertions-core:5.9.1")
library("junit-api", "org.junit.jupiter:junit-jupiter-api:5.11.1")
library("junit-engine", "org.junit.jupiter:junit-jupiter-engine:5.11.1")