Skip to content

Commit

Permalink
[api][desktop]: Open images always into running process
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm committed Nov 1, 2024
1 parent 4fbf0b0 commit 85b0be1
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 26 deletions.
10 changes: 1 addition & 9 deletions api/src/main/kotlin/nebulosa/api/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package nebulosa.api

import com.github.rvesse.airline.SingleCommand
import com.sun.jna.Platform
import nebulosa.api.core.FileLocker
import java.nio.file.Path
import java.util.*
import javax.swing.filechooser.FileSystemView
import kotlin.io.path.Path
import kotlin.io.path.createDirectories
import kotlin.system.exitProcess

const val APP_DIR_KEY = "app.dir"

Expand All @@ -25,13 +23,7 @@ fun initAppDirectory(): Path {
}

fun main(args: Array<String>) {
with(initAppDirectory()) {
val locker = FileLocker(this)

if (!locker.tryLock()) {
exitProcess(1)
}
}
initAppDirectory()

// Sets default locale to en_US.
Locale.setDefault(Locale.ENGLISH)
Expand Down
44 changes: 42 additions & 2 deletions api/src/main/kotlin/nebulosa/api/Nebulosa.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,27 @@ import io.javalin.http.Context
import io.javalin.http.HttpStatus.BAD_REQUEST
import io.javalin.json.JavalinJackson
import nebulosa.api.converters.DeviceModule
import nebulosa.api.core.FileLocker
import nebulosa.api.database.migration.MainDatabaseMigrator
import nebulosa.api.database.migration.SkyDatabaseMigrator
import nebulosa.api.http.responses.ApiMessageResponse
import nebulosa.api.inject.*
import nebulosa.json.PathModule
import nebulosa.log.i
import nebulosa.log.di
import nebulosa.log.loggerFor
import org.koin.core.context.startKoin
import org.slf4j.LoggerFactory
import java.net.ConnectException
import java.net.HttpURLConnection
import java.net.URL
import java.nio.file.Path
import java.util.*
import java.util.concurrent.ExecutionException
import java.util.concurrent.ExecutorService
import kotlin.io.path.exists
import kotlin.io.path.fileSize
import kotlin.io.path.isRegularFile
import kotlin.system.exitProcess

@Command(name = "nebulosa")
class Nebulosa : Runnable, AutoCloseable {
Expand All @@ -37,6 +46,9 @@ class Nebulosa : Runnable, AutoCloseable {
@Option(name = ["-d", "--debug"])
private var debug = false

@Option(name = ["-f", "--files"])
private val files = mutableListOf<String>()

private lateinit var app: Javalin

override fun run() {
Expand All @@ -46,6 +58,20 @@ class Nebulosa : Runnable, AutoCloseable {
}
}

// is running simultaneously!
if (!FileLocker.tryLock()) {
try {
files.map(Path::of)
.filter { it.exists() && it.isRegularFile() && it.fileSize() > 0L }
.takeIf { it.isNotEmpty() }
?.also(::requestToOpenImagesOnDesktop)
} catch (e: Throwable) {
LOG.error("failed to request to open images on desktop", e)
} finally {
exitProcess(1)
}
}

// Run the server.
app = Javalin.create { config ->
config.showJavalinBanner = false
Expand All @@ -68,7 +94,10 @@ class Nebulosa : Runnable, AutoCloseable {
koinApp.modules(controllersModule())
startKoin(koinApp)

LOG.i("server is started at port: {}", app.port())
with(app.port()) {
println("server is started at port: $this")
FileLocker.write("$this")
}

with(koinApp.koin) {
val executor = get<ExecutorService>()
Expand All @@ -92,6 +121,17 @@ class Nebulosa : Runnable, AutoCloseable {
app.stop()
}

private fun requestToOpenImagesOnDesktop(paths: Iterable<Path>) {
val port = FileLocker.read().toIntOrNull() ?: return
LOG.di("requesting to open images on desktop. port={}, paths={}", port, paths)
val query = paths.map { "$it".encodeToByteArray() }.joinToString("&") { "path=${Base64.getUrlEncoder().encodeToString(it)}" }
val url = URL("http://localhost:$port/image/open-on-desktop?$query")
val connection = url.openConnection() as HttpURLConnection
connection.setRequestMethod("POST")
LOG.di("response from opening images on desktop. url={}, code={}", url, connection.responseCode)
connection.disconnect()
}

companion object {

internal val LOG = loggerFor<Nebulosa>()
Expand Down
16 changes: 11 additions & 5 deletions api/src/main/kotlin/nebulosa/api/core/FileLocker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ package nebulosa.api.core
import nebulosa.log.e
import nebulosa.log.i
import nebulosa.log.loggerFor
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.nio.file.Path
import java.nio.file.Files
import java.nio.file.StandardOpenOption
import kotlin.io.path.Path
import kotlin.io.path.deleteIfExists

data class FileLocker(private val appDir: Path) {
object FileLocker {

private val lockPath = Path("$appDir", "nebulosa.lock")
private val lockPath = Path(System.getProperty("java.io.tmpdir"), "nebulosa.lock")

@Volatile private var lock: FileLock? = null

Expand Down Expand Up @@ -52,8 +53,13 @@ data class FileLocker(private val appDir: Path) {
}
}

companion object {
fun write(text: String) {
lock?.channel()?.write(ByteBuffer.wrap(text.encodeToByteArray()), 0)
}

private val LOG = loggerFor<FileLocker>()
fun read(): String {
return Files.readString(lockPath)
}

private val LOG = loggerFor<FileLocker>()
}
8 changes: 8 additions & 0 deletions api/src/main/kotlin/nebulosa/api/image/ImageController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import nebulosa.api.validators.notNull
import nebulosa.api.validators.path
import nebulosa.image.format.ImageChannel
import java.io.ByteArrayInputStream
import java.nio.file.Path
import java.util.*

class ImageController(
override val app: Javalin,
Expand All @@ -21,6 +23,7 @@ class ImageController(

init {
app.post("image", ::openImage)
app.post("image/open-on-desktop", ::openImagesOnDesktop)
app.delete("image", ::closeImage)
app.put("image/save-as", ::saveImageAs)
app.put("image/analyze", ::analyze)
Expand All @@ -38,6 +41,11 @@ class ImageController(
imageService.openImage(path, camera, transformation, ctx.res())
}

private fun openImagesOnDesktop(ctx: Context) {
val paths = ctx.queryParams("path").map { Base64.getUrlDecoder().decode(it).decodeToString() }.map(Path::of)
imageService.openImageOnDesktop(paths)
}

private fun closeImage(ctx: Context) {
val path = ctx.queryParam("path").notNull().path()
return imageService.closeImage(path)
Expand Down
6 changes: 6 additions & 0 deletions api/src/main/kotlin/nebulosa/api/image/ImageService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import nebulosa.api.calibration.CalibrationFrameService
import nebulosa.api.connection.ConnectionService
import nebulosa.api.framing.FramingService
import nebulosa.api.image.ImageAnnotation.StarDSO
import nebulosa.api.message.MessageService
import nebulosa.fits.*
import nebulosa.image.Image
import nebulosa.image.algorithms.computation.Statistics
Expand Down Expand Up @@ -57,6 +58,7 @@ class ImageService(
private val imageBucket: ImageBucket,
private val executorService: ExecutorService,
private val connectionService: ConnectionService,
private val messageService: MessageService,
) {

private enum class ImageOperation {
Expand Down Expand Up @@ -166,6 +168,10 @@ class ImageService(
return TransformedImage(transformedImage, stretchParams, instrument)
}

fun openImageOnDesktop(paths: Iterable<Path>) {
paths.forEach { messageService.sendMessage(OpenImageEvent(it)) }
}

@Synchronized
fun closeImage(path: Path) {
imageBucket.remove(path)
Expand Down
9 changes: 9 additions & 0 deletions api/src/main/kotlin/nebulosa/api/image/OpenImageEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package nebulosa.api.image

import nebulosa.api.message.MessageEvent
import java.nio.file.Path

data class OpenImageEvent(@JvmField val path: Path) : MessageEvent {

override val eventName = "IMAGE.OPEN"
}
2 changes: 1 addition & 1 deletion api/src/main/kotlin/nebulosa/api/inject/Inject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ fun servicesModule() = module {
single { ImageBucket(get()) }
single { CalibrationFrameService(get()) }
single { FramingService(get(), get()) }
single { ImageService(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
single { ImageService(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
single { PlateSolverService(get(), get()) }
single { FlatWizardExecutor(get(), get(), get()) }
single { FlatWizardService(get(Named.capturesDir), get()) }
Expand Down
2 changes: 1 addition & 1 deletion desktop/app/argument.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class ParsedArgument {
readonly mode: ApplicationMode,
readonly host: string,
readonly port: number,
readonly paths: string[],
readonly files: string[],
) {}

get uiMode() {
Expand Down
8 changes: 4 additions & 4 deletions desktop/app/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ function createApiProcess(port: number = parsedArgs.port) {
const apiJar = join(process.resourcesPath, 'api.jar')

try {
const apiProcess = spawn('java', ['-jar', apiJar, `--port=${port}`])
const files = parsedArgs.files.map((e) => ['-f', e]).flat()
const apiProcess = spawn('java', ['-jar', apiJar, `-p`, `${port}`, ...files])

apiProcess.on('close', (code) => {
console.warn(`api process exited with code: ${code}`)
Expand Down Expand Up @@ -70,13 +71,12 @@ async function startApp() {

apiProcess = createApiProcess()

const regex = /server is started at port: (\d+)/i

apiProcess.stdout.on('data', (data: Buffer) => {
const text = data.toString('utf-8')

console.info(text)

if (text) {
const regex = /server is started at port: (\d+)/i
const match = regex.exec(text)

if (match) {
Expand Down
2 changes: 1 addition & 1 deletion desktop/app/window.manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ export class WindowManager {
appWindow.apiProcess = apiProcess

if (app.isPackaged) {
for (const path of this.args.paths) {
for (const path of this.args.files) {
if (path !== '.' && existsSync(path) && statSync(path).isFile()) {
console.info('opening image at', path)
appWindow.openImage(path)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package nebulosa.indi.client

import nebulosa.indi.client.connection.INDIProccessConnection
import nebulosa.indi.client.connection.INDIProcessConnection
import nebulosa.indi.client.connection.INDISocketConnection
import nebulosa.indi.client.device.DriverInfo
import nebulosa.indi.client.device.INDIDeviceProtocolHandler
Expand Down Expand Up @@ -43,7 +43,7 @@ data class INDIClient(val connection: INDIConnection) : INDIDeviceProtocolHandle

constructor(
process: Process,
) : this(INDIProccessConnection(process))
) : this(INDIProcessConnection(process))

override val id = UUID.randomUUID().toString()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package nebulosa.indi.client.connection
import nebulosa.indi.client.io.INDIProtocolFactory
import nebulosa.indi.protocol.io.INDIConnection

data class INDIProccessConnection(private val process: Process) : INDIConnection {
data class INDIProcessConnection(private val process: Process) : INDIConnection {

override val input = INDIProtocolFactory.createInputStream(process.inputStream)

Expand Down

0 comments on commit 85b0be1

Please sign in to comment.