Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagohm committed Nov 10, 2024
2 parents f04d6b7 + e9aa070 commit a167bd0
Show file tree
Hide file tree
Showing 369 changed files with 4,941 additions and 120,386 deletions.
9 changes: 8 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
* text eol=lf
* text=auto

# Linux start script should use lf
/gradlew text eol=lf

# These are Windows script files and should use crlf
*.bat text eol=crlf

# Ensure shell scripts and other executable text files retain their executable flag
*.sh eol=lf

# Disable diffing for certain file types
*.lock -diff

*.dat binary
*.bsp binary
*.bpc binary
Expand All @@ -16,6 +22,7 @@
*.ttf binary
*.xcf binary
*.png binary
*.icns binary
*.db binary
*.gz binary
*.dll binary
Expand Down
3 changes: 3 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ updates:
apache:
patterns:
- "org.apache*"
ktor:
patterns:
- "io.ktor*"
junit:
patterns:
- "org.junit*"
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:

- name: Install Linux Build Dependencies
if: ${{ matrix.os == 'ubuntu-latest' }}
run: sudo apt-get install -y rpm
run: sudo apt-get install -y rpm libarchive-tools

- name: Build
run: npm run electron:build
Expand All @@ -78,6 +78,8 @@ jobs:
desktop/release/nebulosa*.AppImage
desktop/release/nebulosa*.deb
desktop/release/nebulosa*.rpm
desktop/release/nebulosa*.pacman
desktop/release/nebulosa*.exe
desktop/release/nebulosa*.msi
desktop/release/nebulosa*.dmg
retention-days: 3
retention-days: 1
1 change: 1 addition & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [ main ]
paths:
- 'docs/**'
- 'desktop/*.png'
workflow_dispatch:

permissions:
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ The complete integrated solution for all of your astronomical imaging needs.
#### On Linux

4. `npm run electron:build:deb` to build `.deb` package.
5. `npm run electron:build:app` to build `AppImage`.
5. `npm run electron:build:app` to build `AppImage` package.
6. `npm run electron:build:rpm` to build `RPM` package.
7. `npm run electron:build:pacman` to build `.pacman` package.

Before build a `RPM` package, run `sudo apt install rpm`.
> Before build a `RPM` package, run `sudo apt install rpm`.
> Before build a `pacman` package on Ubuntu, run `sudo apt install libarchive-tools`.
#### On Windows

4. `npm run electron:build` to build the `.exe`.
4. `npm run electron:build:portable` to build the portable `.exe`.
5. `npm run electron:build:msi` to build the `.msi` Installer.

#### On Linux ARM (Raspberry PI)

Expand All @@ -41,3 +45,5 @@ run these commands before:
4. `USE_SYSTEM_FPM=true npm run electron:build:deb` to build `.deb` package.

> Look at `release` subdirectory for the generated build.
> Alternatively, you can run `npm run electron:build` for build all package formats.
11 changes: 8 additions & 3 deletions api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
kotlin("jvm")
id("com.gradleup.shadow")
application
}

dependencies {
Expand Down Expand Up @@ -35,22 +36,26 @@ dependencies {
implementation(libs.eventbus)
implementation(libs.okhttp)
implementation(libs.oshi)
implementation(libs.javalin)
implementation(libs.koin)
implementation(libs.airline)
implementation(libs.h2)
implementation(libs.bundles.exposed)
implementation(libs.flyway)
implementation(libs.bundles.ktor)
testImplementation(project(":nebulosa-astrobin-api"))
testImplementation(project(":nebulosa-skycatalog-stellarium"))
testImplementation(project(":nebulosa-test"))
}

application {
mainClass = "nebulosa.api.MainKt"
}

tasks.withType<ShadowJar> {
isZip64 = true

archiveFileName.set("api.jar")
destinationDirectory.set(file("../desktop"))
archiveFileName = "api.jar"
destinationDirectory = file("../desktop")

manifest {
attributes["Main-Class"] = "nebulosa.api.MainKt"
Expand Down
142 changes: 96 additions & 46 deletions api/src/main/kotlin/nebulosa/api/Nebulosa.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,104 +5,154 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.jsonMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.github.rvesse.airline.annotations.Command
import com.github.rvesse.airline.annotations.Option
import io.javalin.Javalin
import io.javalin.http.Context
import io.javalin.http.HttpStatus.BAD_REQUEST
import io.javalin.json.JavalinJackson
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import kotlinx.coroutines.runBlocking
import nebulosa.api.converters.DeviceModule
import nebulosa.api.core.ErrorResponse
import nebulosa.api.database.MainDatabaseMigrator
import nebulosa.api.database.SkyDatabaseMigrator
import nebulosa.api.core.FileLocker
import nebulosa.api.database.migration.MainDatabaseMigrator
import nebulosa.api.database.migration.SkyDatabaseMigrator
import nebulosa.api.inject.*
import nebulosa.api.ktor.configureHTTP
import nebulosa.api.ktor.configureMonitoring
import nebulosa.api.ktor.configureRouting
import nebulosa.api.ktor.configureSerialization
import nebulosa.api.ktor.configureSockets
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.Logger
import org.slf4j.LoggerFactory
import java.net.ConnectException
import java.util.concurrent.ExecutionException
import java.net.HttpURLConnection
import java.net.URL
import java.nio.file.Path
import java.util.*
import java.util.concurrent.ExecutorService
import kotlin.io.path.Path
import kotlin.io.path.exists
import kotlin.io.path.fileSize
import kotlin.io.path.inputStream
import kotlin.io.path.isRegularFile
import kotlin.system.exitProcess
import ch.qos.logback.classic.Logger as LogbackLogger

@Command(name = "nebulosa")
class Nebulosa : Runnable, AutoCloseable {
class Nebulosa : Runnable {

private val properties = Properties(3)

init {
Path(System.getProperty(APP_DIR_KEY), PROPERTIES_FILENAME)
.takeIf { it.exists() && it.isRegularFile() }
?.also { it.inputStream().use(properties::load) }
}

@Option(name = ["-h", "--host"])
private var host = "0.0.0.0"
private var host = properties.getProperty("host")?.ifBlank { null } ?: DEFAULT_HOST

@Option(name = ["-p", "--port"])
private var port = 0
private var port = properties.getProperty("port")?.ifBlank { null }?.toIntOrNull() ?: DEFAULT_PORT

@Option(name = ["-d", "--debug"])
private var debug = false
private var debug = properties.getProperty("debug")?.toBoolean() == true

private lateinit var app: Javalin
@Option(name = ["-t", "--trace"])
private var trace = properties.getProperty("trace")?.toBoolean() == true

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

override fun run() {
if (debug) {
with(LoggerFactory.getLogger("nebulosa") as ch.qos.logback.classic.Logger) {
with(LoggerFactory.getLogger("nebulosa") as LogbackLogger) {
level = Level.DEBUG
}
}

// Run the server.
app = Javalin.create { config ->
config.showJavalinBanner = false
// JACKSON
config.jsonMapper(JavalinJackson(OBJECT_MAPPER))
// CORS
config.bundledPlugins.enableCors { cors ->
cors.addRule {
it.anyHost()
it.exposeHeader("X-Image-Info")
if (trace) {
with(LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) as LogbackLogger) {
level = Level.TRACE
}
}
}.start(host, port)
}

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

koinApp.modules(appModule(app))
exitProcess(129)
}

// Run the server.
// https://start.ktor.io/settings?name=ktor-sample&website=example.com&artifact=com.example.ktor-sample&kotlinVersion=2.0.21&ktorVersion=3.0.1&buildSystem=GRADLE_KTS&engine=NETTY&configurationIn=CODE&addSampleCode=true&plugins=routing%2Cktor-websockets%2Ccontent-negotiation%2Cktor-jackson%2Ccall-logging%2Ccors%2Ccompression%2Cstatic-content%2Cresources
val server = embeddedServer(Netty, port = port, host = host) {
configureHTTP()
configureSerialization(OBJECT_MAPPER)
configureSockets()
configureRouting()
configureMonitoring(debug)
}.start(false)

// app.exception(Exception::class.java, ::handleException)

koinApp.modules(serverModule(server))
koinApp.modules(objectMapperModule(OBJECT_MAPPER))
koinApp.modules(servicesModule())
koinApp.modules(controllersModule())
startKoin(koinApp)

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

with(koinApp.koin) {
val executor = get<ExecutorService>()
executor.submit(get<MainDatabaseMigrator>())
executor.submit(get<SkyDatabaseMigrator>())
}
}

private fun handleException(ex: Exception, ctx: Context) {
val message = when (ex) {
is ConnectException -> "connection refused"
is NumberFormatException -> "invalid number: ${ex.message}"
is ExecutionException -> ex.cause!!.message!!
else -> ex.message!!
}

ctx.status(BAD_REQUEST).json(ErrorResponse.error(message.lowercase()))
Thread.currentThread().join()
}

override fun close() {
app.stop()
private fun requestToOpenImagesOnDesktop(paths: Iterable<Path>): Boolean {
val port = FileLocker.read().toIntOrNull() ?: return false
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()
return true
}

companion object {

@JvmStatic internal val LOG = loggerFor<Nebulosa>()
internal val LOG = loggerFor<Nebulosa>()

@JvmStatic private val OBJECT_MAPPER = jsonMapper {
const val PROPERTIES_FILENAME = "nebulosa.properties"
const val DEFAULT_HOST = "0.0.0.0"
const val DEFAULT_PORT = 0

private val OBJECT_MAPPER = jsonMapper {
addModule(JavaTimeModule())
addModule(PathModule())
addModule(DeviceModule())
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
}
}.registerKotlinModule()
}
}
Loading

0 comments on commit a167bd0

Please sign in to comment.