Skip to content
This repository was archived by the owner on Apr 29, 2026. It is now read-only.

Commit 0c45fd5

Browse files
author
Kevin Ludwig
committed
Add version to about, use config model (for rollback), ignore invalid data, add k8scp, sftp progress, and delete operation, add local delete operation, don not save default configuration on start-up, set worker thread to daemon
1 parent cf0a6b6 commit 0c45fd5

19 files changed

Lines changed: 205 additions & 124 deletions

build.gradle.kts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,15 @@ tasks {
6969

7070
withType<Test> { useJUnitPlatform() }
7171

72-
shadowJar { archiveName = "blit.jar" }
72+
jar {
73+
manifest {
74+
attributes(
75+
"Implementation-Title" to "Blit",
76+
"Implementation-Version" to project.version,
77+
"Implementation-Vendor" to "Valaphee"
78+
)
79+
}
80+
}
7381
}
7482

7583
application { mainClass.set("com.valaphee.blit.MainKt") }
@@ -78,14 +86,11 @@ javafx { modules("javafx.controls", "javafx.graphics") }
7886

7987
launch4j {
8088
mainClassName = "com.valaphee.blit.MainKt"
81-
jarTask = tasks.shadowJar.get()
8289
icon = "${projectDir}/app.ico"
8390
copyright = "Copyright (c) 2021, Valaphee"
8491
jvmOptions = setOf("--add-opens=java.base/java.nio=ALL-UNNAMED", "--add-opens java.base/jdk.internal.misc=ALL-UNNAMED", "-Dio.netty.tryReflectionSetAccessible=true")
8592
companyName = "Valaphee"
8693
productName = "Blit"
87-
/*splashFileName = "${projectDir}/splash.bmp"*/
88-
copyConfigurable = emptyArray<Any>()
8994
}
9095

9196
signing { useGpgCmd() }

src/main/kotlin/com/valaphee/blit/AboutView.kt

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,35 @@
1616

1717
package com.valaphee.blit
1818

19+
import javafx.geometry.Pos
20+
import javafx.scene.image.Image
1921
import javafx.scene.text.TextAlignment
2022
import jfxtras.styles.jmetro.JMetro
2123
import jfxtras.styles.jmetro.JMetroStyleClass
2224
import jfxtras.styles.jmetro.Style
2325
import tornadofx.View
24-
import tornadofx.borderpane
25-
import tornadofx.center
26+
import tornadofx.hbox
27+
import tornadofx.imageview
2628
import tornadofx.label
2729

2830
/**
2931
* @author Kevin Ludwig
3032
*/
3133
class AboutView : View("About Blit") {
32-
override val root = borderpane {
33-
prefWidth = 300.0
34-
prefHeight = 100.0
35-
34+
override val root = hbox {
3635
JMetro(this, Style.DARK)
3736
styleClass.add(JMetroStyleClass.BACKGROUND)
3837

39-
center {
40-
label(
41-
"""
42-
Blit
43-
Copyright (c) 2021, Valaphee.
44-
""".trimIndent()
45-
) { textAlignment = TextAlignment.CENTER }
46-
}
38+
prefWidth = 300.0
39+
prefHeight = 100.0
40+
alignment = Pos.CENTER
41+
42+
imageview(Image(AboutView::class.java.getResourceAsStream("/app.png")))
43+
label(
44+
"""
45+
Blit${AboutView::class.java.`package`.implementationVersion?.let { " $it" } ?: ""}
46+
Copyright (c) 2021, Valaphee.
47+
""".trimIndent()
48+
) { textAlignment = TextAlignment.CENTER }
4749
}
4850
}

src/main/kotlin/com/valaphee/blit/util/ComUtil.kt renamed to src/main/kotlin/com/valaphee/blit/ComUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.valaphee.blit.util
17+
package com.valaphee.blit
1818

1919
import com.sun.javafx.tk.TKStage
2020
import javafx.stage.Stage

src/main/kotlin/com/valaphee/blit/Main.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,18 @@
1616

1717
package com.valaphee.blit
1818

19-
import com.fasterxml.jackson.databind.ObjectMapper
2019
import com.google.inject.AbstractModule
2120
import com.google.inject.Guice
2221
import com.google.inject.Provides
2322
import com.google.inject.Singleton
2423
import com.valaphee.blit.data.DataModule
25-
import com.valaphee.blit.data.config.Config
2624
import com.valaphee.blit.data.locale.Locale
2725
import de.codecentric.centerdevice.javafxsvg.SvgImageLoaderFactory
2826
import javafx.scene.image.Image
2927
import tornadofx.App
3028
import tornadofx.DIContainer
3129
import tornadofx.FX
3230
import tornadofx.launch
33-
import java.io.File
3431
import kotlin.reflect.KClass
3532

3633
/**
@@ -44,12 +41,9 @@ fun main(arguments: Array<String>) {
4441
val injector = Guice.createInjector(DataModule(), object : AbstractModule() {
4542
@Singleton
4643
@Provides
47-
fun i18n(locales: Map<String, @JvmSuppressWildcards Locale>) = locales["en_us"]
44+
fun locale(locales: Map<String, @JvmSuppressWildcards Locale>) = locales["en_us"]
4845
})
4946

50-
val configFile = File(File("data").also(File::mkdir), "config.json")
51-
if (!configFile.exists()) injector.getInstance(ObjectMapper::class.java).writeValue(configFile, injector.getInstance(Config::class.java)) // TODO
52-
5347
FX.dicontainer = object : DIContainer {
5448
override fun <T : Any> getInstance(type: KClass<T>) = injector.getInstance(type.java)
5549
}

src/main/kotlin/com/valaphee/blit/MainView.kt

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@
1616

1717
package com.valaphee.blit
1818

19-
import com.valaphee.blit.data.config.Config
19+
import com.valaphee.blit.data.config.ConfigModel
2020
import com.valaphee.blit.data.config.ConfigView
2121
import com.valaphee.blit.data.locale.Locale
2222
import com.valaphee.blit.data.manifest.IconManifest
2323
import com.valaphee.blit.source.Entry
2424
import com.valaphee.blit.source.Source
25-
import com.valaphee.blit.util.Work
26-
import com.valaphee.blit.util.comExecutor
27-
import com.valaphee.blit.util.hWnd
2825
import javafx.beans.property.SimpleObjectProperty
2926
import javafx.beans.property.SimpleStringProperty
3027
import javafx.collections.ObservableList
@@ -87,9 +84,9 @@ import java.util.concurrent.CompletableFuture
8784
class MainView : View("Blit") {
8885
private val locale by di<Locale>()
8986
private val iconManifest by di<IconManifest>()
90-
private val _config by di<Config>()
87+
private val _config by di<ConfigModel>()
9188

92-
private val work = Work().apply {
89+
private val worker = Worker().apply {
9390
val version = System.getProperty("os.version").toFloatOrNull()
9491
if (System.getProperty("os.name").startsWith("Windows") && version != null && version >= 6.1f) {
9592
val iTaskbarList3 = CompletableFuture.supplyAsync({ COMRuntime.newInstance(ITaskbarList3::class.java) }, comExecutor).join()
@@ -125,8 +122,8 @@ class MainView : View("Blit") {
125122
@Suppress("UPPER_BOUND_VIOLATED_WARNING") add(Pane<Entry<*>>())
126123
@Suppress("UPPER_BOUND_VIOLATED_WARNING") add(Pane<Entry<*>>())
127124
}
128-
label(work.name)
129-
progressbar(work.progress)
125+
label(worker.name)
126+
progressbar(worker.progress)
130127
}
131128

132129
inner class Pane<T : Entry<T>> : VBox() {
@@ -152,7 +149,7 @@ class MainView : View("Blit") {
152149
left = BreadCrumbBar<String>().apply {
153150
style(true) { paddingTop = 2.0 }
154151

155-
tree.rootProperty().onChange { it?.let { selectedCrumb = BreadCrumbBar.buildTreeModel(*normalizePath(it.value.toString()).split('/').toTypedArray()) } }
152+
tree.rootProperty().onChange { it?.let { selectedCrumb = if (it.value.toString() == "/") BreadCrumbBar.buildTreeModel("/") else BreadCrumbBar.buildTreeModel(*normalizePath(it.value.toString()).split('/').toTypedArray().apply { if (this[0].isEmpty()) this[0] = "/" }) } }
156153
selectedCrumbProperty().onChange {
157154
val path = StringBuilder()
158155
var item = it
@@ -171,14 +168,14 @@ class MainView : View("Blit") {
171168
add(tree)
172169
}
173170

174-
fun navigate(path: String) {
171+
private fun navigate(path: String) {
175172
val normalizedPath = normalizePath(path)
176173

177174
if (::_path.isInitialized && normalizedPath == _path) return
178175
_path = normalizedPath
179176

180177
source.value?.let { source ->
181-
work.launch(locale["main.navigator.task.navigate.name", path]) {
178+
worker.launch(locale["main.navigator.task.navigate.name", normalizedPath]) {
182179
if (source.isValid(normalizedPath)) {
183180
val item = source.get(normalizedPath).item
184181
runLater {
@@ -193,7 +190,7 @@ class MainView : View("Blit") {
193190

194191
fun navigateRelative(path: String) = navigate(if (path.startsWith('/')) path else tree.root.value.toString() + "/$path")
195192

196-
inner class Tree<T : Entry<T>>() : TreeTableView<Entry<T>>() {
193+
inner class Tree<T : Entry<T>> : TreeTableView<Entry<T>>() {
197194
init {
198195
vgrow = Priority.ALWAYS
199196
isShowRoot = false
@@ -213,7 +210,7 @@ class MainView : View("Blit") {
213210
}
214211
column(locale["main.tree.column.size.title"], Entry<T>::self) {
215212
tableColumnBaseSetWidth(this, 75.0)
216-
cellFormat { text = if (it.directory) "" else _config.dataSizeUnit.format(it.size) }
213+
cellFormat { text = if (it.directory) "" else _config.dataSizeUnit.value.format(it.size) }
217214
setComparator { a, b -> a.size.compareTo(b.size) }
218215
}
219216
column(locale["main.tree.column.modified.title"], Entry<T>::modifyTime) {
@@ -227,7 +224,7 @@ class MainView : View("Blit") {
227224
setOnDragDetected {
228225
startDragAndDrop(TransferMode.MOVE).apply {
229226
setContent {
230-
work.runBlocking(locale["main.tree.task.download.name"]) {
227+
worker.runBlocking(locale["main.tree.task.download.name"]) {
231228
suspend fun flatten(entry: Entry<T>, path: String? = null): List<File> = if (entry.directory) {
232229
File(tmpdir, entry.name).mkdir()
233230
entry.list().flatMap { flatten(it, "${path?.let { "$path/" } ?: ""}${entry.name}") }
@@ -256,7 +253,7 @@ class MainView : View("Blit") {
256253
setOnKeyPressed {
257254
when (it.code) {
258255
KeyCode.C -> if (it.isControlDown) Clipboard.getSystemClipboard().setContent {
259-
work.runBlocking(locale["main.tree.task.download.name"]) {
256+
worker.runBlocking(locale["main.tree.task.download.name"]) {
260257
suspend fun flatten(entry: Entry<T>, path: String? = null): List<File> = if (entry.directory) {
261258
File(tmpdir, entry.name).mkdir()
262259
entry.list().flatMap { flatten(it, "${path?.let { "$path/" } ?: ""}${entry.name}") }
@@ -271,12 +268,12 @@ class MainView : View("Blit") {
271268
if (hasFiles()) {
272269
val entry = selectionModel.selectedItem.value
273270
if (!entry.directory) TODO()
274-
files.forEach { file -> work.launch(locale["main.tree.task.upload.name", file.name]) { FileInputStream(file).use { entry.transferFrom(file.name, it, file.length()) } } }
271+
files.forEach { file -> worker.launch(locale["main.tree.task.upload.name", file.name]) { FileInputStream(file).use { entry.transferFrom(file.name, it, file.length()) } } }
275272
}
276273
}
277274
KeyCode.DELETE -> selectionModel.selectedItems.forEach {
278275
val entry = it.value
279-
work.launch(locale["main.tree.task.delete.name", entry]) {
276+
worker.launch(locale["main.tree.task.delete.name", entry]) {
280277
entry.delete()
281278
runLater { populate(it.parent) }
282279
}
@@ -291,7 +288,7 @@ class MainView : View("Blit") {
291288
it.list.firstOrNull { it.value.directory }?.value?.let { navigateRelative(it.name) } ?: if (Desktop.isDesktopSupported()) {
292289
it.list.forEach {
293290
val entry = it.value
294-
work.launch(locale["main.tree.task.download.name", entry]) { Desktop.getDesktop().open(File(tmpdir, entry.name).apply { FileOutputStream(this).use { entry.transferTo(it) } }) } // TODO: Desktop.open throws IOException (No application is associated with the specific file for this operation.)
291+
worker.launch(locale["main.tree.task.download.name", entry]) { Desktop.getDesktop().open(File(tmpdir, entry.name).apply { FileOutputStream(this).use { entry.transferTo(it) } }) } // TODO: Desktop.open throws IOException (No application is associated with the specific file for this operation.)
295292
}
296293
}
297294
}
@@ -301,7 +298,7 @@ class MainView : View("Blit") {
301298
action {
302299
it.list.forEach {
303300
val entry = it.value
304-
work.launch(locale["main.tree.task.delete.name", entry]) {
301+
worker.launch(locale["main.tree.task.delete.name", entry]) {
305302
entry.delete()
306303
runLater { populate(it.parent) }
307304
}
@@ -313,7 +310,7 @@ class MainView : View("Blit") {
313310
}
314311

315312
fun populate(item: TreeItem<Entry<T>>) {
316-
work.launch(locale["main.tree.task.populate.name", item.value]) {
313+
worker.launch(locale["main.tree.task.populate.name", item.value]) {
317314
val children = item.value!!.list()
318315
runLater {
319316
populateTree(item, { entry ->

src/main/kotlin/com/valaphee/blit/util/Work.kt renamed to src/main/kotlin/com/valaphee/blit/Worker.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.valaphee.blit.util
17+
package com.valaphee.blit
1818

1919
import com.google.common.util.concurrent.ThreadFactoryBuilder
2020
import javafx.beans.property.DoubleProperty
@@ -37,8 +37,8 @@ import kotlin.coroutines.CoroutineContext
3737
/**
3838
* @author Kevin Ludwig
3939
*/
40-
class Work {
41-
private val coroutineScope = CoroutineScope(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), ThreadFactoryBuilder().setNameFormat("blit-%d").build()).asCoroutineDispatcher() + SupervisorJob())
40+
class Worker {
41+
private val coroutineScope = CoroutineScope(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), ThreadFactoryBuilder().setNameFormat("blit-%d").setDaemon(true).build()).asCoroutineDispatcher() + SupervisorJob())
4242
private val tasks = ConcurrentLinkedDeque<Task>()
4343

4444
val name: StringProperty = SimpleStringProperty("")
@@ -71,5 +71,5 @@ class Work {
7171
}
7272

7373
var CoroutineContext.progress: Double
74-
get() = get(Work.Task)!!.progress.value
75-
set(value) { get(Work.Task)!!.progress.value = value }
74+
get() = get(Worker.Task)!!.progress.value
75+
set(value) { get(Worker.Task)!!.progress.value = value }

src/main/kotlin/com/valaphee/blit/data/DataModule.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,15 @@ class DataModule(
5050
ClassGraph().acceptPaths("data").scan().use {
5151
val (keyed, other) = (it.allResources.map { it.url } + path.walk().filter { it.isFile }.map { it.toURI().toURL() })
5252
.mapNotNull {
53-
when (it.file.substring(it.file.lastIndexOf('.') + 1)) {
54-
"json" -> {
55-
@Suppress("UNCHECKED_CAST")
56-
objectMapper.readValue<Data>(it)
53+
try {
54+
when (it.file.substring(it.file.lastIndexOf('.') + 1)) {
55+
"json" -> {
56+
@Suppress("UNCHECKED_CAST")
57+
objectMapper.readValue<Data>(it)
58+
}
59+
else -> null
5760
}
58-
else -> null
59-
}
61+
} catch (_: Throwable) {}
6062
}
6163
.partition { it is KeyedData }
6264
keyed.filterIsInstance<KeyedData>()

src/main/kotlin/com/valaphee/blit/data/config/Config.kt

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,11 @@
1717
package com.valaphee.blit.data.config
1818

1919
import com.fasterxml.jackson.annotation.JsonProperty
20-
import com.fasterxml.jackson.core.JsonParser
21-
import com.fasterxml.jackson.core.type.TypeReference
22-
import com.fasterxml.jackson.databind.DeserializationContext
23-
import com.fasterxml.jackson.databind.JsonDeserializer
24-
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
2520
import com.google.inject.Singleton
2621
import com.valaphee.blit.data.Data
2722
import com.valaphee.blit.data.DataType
2823
import com.valaphee.blit.source.Source
2924
import com.valaphee.blit.source.local.LocalSource
30-
import javafx.collections.FXCollections
31-
import javafx.collections.ObservableList
3225
import java.lang.Long.signum
3326
import java.text.StringCharacterIterator
3427
import kotlin.math.abs
@@ -39,8 +32,8 @@ import kotlin.math.abs
3932
@Singleton
4033
@DataType("config")
4134
class Config(
42-
@get:JsonProperty("data_size_unit") var dataSizeUnit: DataSizeUnit = DataSizeUnit.IEC,
43-
@get:JsonProperty("sources") @get:JsonDeserialize(using = SourceObservableListDeserializer::class) val sources: ObservableList<Source<*>> = FXCollections.observableArrayList(LocalSource("local"))
35+
@get:JsonProperty("data_size_unit") val dataSizeUnit: DataSizeUnit = DataSizeUnit.IEC,
36+
@get:JsonProperty("sources") val sources: List<Source<*>> = listOf(LocalSource("local"))
4437
) : Data {
4538
enum class DataSizeUnit(
4639
val format: (Long) -> String
@@ -72,10 +65,4 @@ class Config(
7265
}
7366
})
7467
}
75-
76-
companion object {
77-
class SourceObservableListDeserializer : JsonDeserializer<ObservableList<Source<*>>>() {
78-
override fun deserialize(parser: JsonParser, context: DeserializationContext): ObservableList<Source<*>> = FXCollections.observableList(parser.readValueAs<List<Source<*>>>(object : TypeReference<List<Source<*>>>() {}))
79-
}
80-
}
8168
}

0 commit comments

Comments
 (0)