From f42d412b86e2aa77acc29ed759c2528bdee002da Mon Sep 17 00:00:00 2001 From: death-claw <53543762+death-claw@users.noreply.github.com> Date: Sun, 21 May 2023 15:52:15 +0100 Subject: [PATCH] fixes #89 (#95) --- .../kotlin/me/mnlr/vripper/model/Settings.kt | 12 +-- .../mnlr/vripper/services/SettingsService.kt | 64 +++++++-------- .../vripper/clipboard/ClipboardService.kt | 58 +++++++++++--- .../vripper/controller/SettingsController.kt | 74 +++++++++--------- .../model/settings/ClipboardSettingsModel.kt | 13 +++ .../view/settings/ClipboardSettingsView.kt | 39 +++++++++ .../view/settings/ConnectionSettingsView.kt | 2 +- .../vripper/view/settings/SettingsView.kt | 16 +++- vripper-gui/src/main/resources/clipboard.png | Bin 0 -> 511 bytes 9 files changed, 190 insertions(+), 88 deletions(-) create mode 100644 vripper-gui/src/main/kotlin/me/mnlr/vripper/model/settings/ClipboardSettingsModel.kt create mode 100644 vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/ClipboardSettingsView.kt create mode 100644 vripper-gui/src/main/resources/clipboard.png diff --git a/vripper-core/src/main/kotlin/me/mnlr/vripper/model/Settings.kt b/vripper-core/src/main/kotlin/me/mnlr/vripper/model/Settings.kt index ee307c6..ae80e62 100644 --- a/vripper-core/src/main/kotlin/me/mnlr/vripper/model/Settings.kt +++ b/vripper-core/src/main/kotlin/me/mnlr/vripper/model/Settings.kt @@ -1,11 +1,11 @@ package me.mnlr.vripper.model class Settings { - var desktopClipboard: Boolean = false var maxEventLog: Int = 1_000 - var connectionSettings: ConnectionSettings = ConnectionSettings() - var downloadSettings: DownloadSettings = DownloadSettings() - var viperSettings: ViperSettings = ViperSettings() + var connectionSettings = ConnectionSettings() + var downloadSettings = DownloadSettings() + var viperSettings = ViperSettings() + var clipboardSettings = ClipboardSettings() } data class ViperSettings( @@ -32,4 +32,6 @@ data class ConnectionSettings( var maxTotalThreads: Int = 0, var timeout: Int = 30, var maxAttempts: Int = 3, -) \ No newline at end of file +) + +data class ClipboardSettings(var enable: Boolean = false, var pollingRate: Int = 500) \ No newline at end of file diff --git a/vripper-core/src/main/kotlin/me/mnlr/vripper/services/SettingsService.kt b/vripper-core/src/main/kotlin/me/mnlr/vripper/services/SettingsService.kt index 3eda73d..d377353 100644 --- a/vripper-core/src/main/kotlin/me/mnlr/vripper/services/SettingsService.kt +++ b/vripper-core/src/main/kotlin/me/mnlr/vripper/services/SettingsService.kt @@ -7,16 +7,16 @@ import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.registerKotlinModule import jakarta.annotation.PostConstruct import jakarta.annotation.PreDestroy -import org.apache.commons.codec.digest.DigestUtils -import org.springframework.beans.factory.annotation.Value -import org.springframework.core.io.Resource -import org.springframework.stereotype.Service import me.mnlr.vripper.SpringContext import me.mnlr.vripper.delegate.LoggerDelegate import me.mnlr.vripper.event.Event import me.mnlr.vripper.event.EventBus import me.mnlr.vripper.exception.ValidationException import me.mnlr.vripper.model.Settings +import org.apache.commons.codec.digest.DigestUtils +import org.springframework.beans.factory.annotation.Value +import org.springframework.core.io.Resource +import org.springframework.stereotype.Service import java.io.FileWriter import java.io.IOException import java.nio.file.* @@ -92,7 +92,8 @@ class SettingsService( fun newSettings(settings: Settings) { if (settings.viperSettings.login) { if (this.settings.viperSettings.password != settings.viperSettings.password) { - settings.viperSettings.password = DigestUtils.md5Hex(settings.viperSettings.password) + settings.viperSettings.password = + DigestUtils.md5Hex(settings.viperSettings.password) } } else { settings.viperSettings.username = "" @@ -156,55 +157,54 @@ class SettingsService( val path: Path = try { Paths.get(settings.downloadSettings.downloadPath) } catch (e: InvalidPathException) { - throw ValidationException(String.format("%s is invalid", settings.downloadSettings.downloadPath)) + throw ValidationException( + String.format( + "%s is invalid", settings.downloadSettings.downloadPath + ) + ) } if (!Files.exists(path)) { - throw ValidationException(String.format("%s does not exist", settings.downloadSettings.downloadPath)) + throw ValidationException( + String.format( + "%s does not exist", settings.downloadSettings.downloadPath + ) + ) } else if (!Files.isDirectory(path)) { - throw ValidationException(String.format("%s is not a directory", settings.downloadSettings.downloadPath)) + throw ValidationException( + String.format( + "%s is not a directory", settings.downloadSettings.downloadPath + ) + ) } - if(settings.downloadSettings.autoQueueThreshold < 0) { + if (settings.downloadSettings.autoQueueThreshold < 0) { throw ValidationException("Invalid auto queue settings, value must be a positive integer") } if (settings.connectionSettings.maxTotalThreads < 0 || settings.connectionSettings.maxTotalThreads > 12) { throw ValidationException( - String.format( - "Invalid max global concurrent download settings, values must be in [%d,%d]", - 0, - 12 - ) + "Invalid max global concurrent download settings, values must be in [0,12]" ) } if (settings.connectionSettings.maxThreads < 1 || settings.connectionSettings.maxThreads > 4) { - throw ValidationException( - String.format( - "Invalid max concurrent download settings, values must be in [%d,%d]", 1, 4 - ) - ) + throw ValidationException("Invalid max concurrent download settings, values must be in [1,4]") } if (settings.connectionSettings.timeout < 1 || settings.connectionSettings.timeout > 300) { throw ValidationException( - String.format( - "Invalid connection timeout settings, values must be in [%d,%d]", 1, 300 - ) + "Invalid connection timeout settings, values must be in [1,300]" ) } if (settings.connectionSettings.maxAttempts < 1 || settings.connectionSettings.maxAttempts > 10) { - throw ValidationException( - String.format( - "Invalid maximum attempts settings, values must be in [%d,%d]", 1, 10 - ) - ) + throw ValidationException("Invalid maximum attempts settings, values must be in [1,10]") } if (settings.maxEventLog < 100 || settings.maxEventLog > 10000) { throw ValidationException( - String.format( - "Invalid maximum event log record settings, values must be in [%d,%d]", - 100, - 10000 - ) + "Invalid maximum event log record settings, values must be in [100,10000]" + ) + } + if (settings.clipboardSettings.pollingRate < 500) { + throw ValidationException( + "Invalid clipboard monitoring polling rate settings, values must be >= 500" ) } } diff --git a/vripper-gui/src/main/kotlin/me/mnlr/vripper/clipboard/ClipboardService.kt b/vripper-gui/src/main/kotlin/me/mnlr/vripper/clipboard/ClipboardService.kt index 250816b..d793a32 100644 --- a/vripper-gui/src/main/kotlin/me/mnlr/vripper/clipboard/ClipboardService.kt +++ b/vripper-gui/src/main/kotlin/me/mnlr/vripper/clipboard/ClipboardService.kt @@ -1,42 +1,78 @@ package me.mnlr.vripper.clipboard import jakarta.annotation.PostConstruct +import jakarta.annotation.PreDestroy import javafx.scene.input.Clipboard import me.mnlr.vripper.AppEndpointService -import org.springframework.scheduling.annotation.Scheduled +import me.mnlr.vripper.event.Event +import me.mnlr.vripper.event.EventBus +import me.mnlr.vripper.services.SettingsService import org.springframework.stereotype.Service +import reactor.core.Disposable import reactor.core.publisher.Sinks import reactor.core.scheduler.Schedulers import tornadofx.* +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit @Service -class ClipboardService(private val appEndpointService: AppEndpointService) { - +class ClipboardService( + private val appEndpointService: AppEndpointService, + private val settingsService: SettingsService, + eventBus: EventBus +) { + private val eventBusDisposable: Disposable private val sink = Sinks.many().unicast().onBackpressureBuffer() + private val scheduler = Executors.newSingleThreadScheduledExecutor() private var current: String? = null + private var scheduledFuture: ScheduledFuture<*>? = null - @PostConstruct - fun init() { + init { + runLater { + this.current = if (Clipboard.getSystemClipboard() + .hasString() + ) Clipboard.getSystemClipboard().string else null + } + eventBusDisposable = eventBus + .flux() + .filter { it.kind == Event.Kind.SETTINGS_UPDATE } + .subscribe { init() } sink.asFlux().subscribeOn(Schedulers.single()).subscribe { this.appEndpointService.scanLinks(it) } - runLater { - this.current = if(Clipboard.getSystemClipboard().hasString()) Clipboard.getSystemClipboard().string else null + } + + @PostConstruct + fun init() { + scheduledFuture?.cancel(true) + if(settingsService.settings.clipboardSettings.enable) { + scheduledFuture = scheduler.scheduleWithFixedDelay({ + poll() + }, 500, settingsService.settings.clipboardSettings.pollingRate.toLong(), TimeUnit.MILLISECONDS) + } else { + current = null } } - @Scheduled(fixedDelay = 500) + fun poll() { runLater { val clipboard = Clipboard.getSystemClipboard() - if(clipboard.hasString()) { + if (clipboard.hasString()) { val value: String? = clipboard.string - if(!value.isNullOrBlank() && value != this.current) { + if (!value.isNullOrBlank() && value != this.current) { this.current = value - sink.emitNext(value) { _,_ -> + sink.emitNext(value) { _, _ -> true } } } } } + + @PreDestroy + fun destroy() { + scheduledFuture?.cancel(true) + scheduler.shutdown() + } } \ No newline at end of file diff --git a/vripper-gui/src/main/kotlin/me/mnlr/vripper/controller/SettingsController.kt b/vripper-gui/src/main/kotlin/me/mnlr/vripper/controller/SettingsController.kt index c3fb929..e8646f9 100644 --- a/vripper-gui/src/main/kotlin/me/mnlr/vripper/controller/SettingsController.kt +++ b/vripper-gui/src/main/kotlin/me/mnlr/vripper/controller/SettingsController.kt @@ -1,18 +1,12 @@ package me.mnlr.vripper.controller -import javafx.scene.control.Alert -import me.mnlr.vripper.exception.ValidationException -import me.mnlr.vripper.model.ConnectionSettings -import me.mnlr.vripper.model.DownloadSettings -import me.mnlr.vripper.model.settings.DownloadSettingsModel -import me.mnlr.vripper.model.Settings -import me.mnlr.vripper.model.ViperSettings +import me.mnlr.vripper.model.* +import me.mnlr.vripper.model.settings.ClipboardSettingsModel import me.mnlr.vripper.model.settings.ConnectionSettingsModel +import me.mnlr.vripper.model.settings.DownloadSettingsModel import me.mnlr.vripper.model.settings.ViperSettingsModel import me.mnlr.vripper.services.SettingsService -import tornadofx.Controller -import tornadofx.alert -import tornadofx.warning +import tornadofx.* class SettingsController : Controller() { @@ -30,36 +24,46 @@ class SettingsController : Controller() { return settingsService.settings.viperSettings } + fun findClipboardSettings(): ClipboardSettings { + return settingsService.settings.clipboardSettings + } + fun saveNewSettings( downloadSettingsModel: DownloadSettingsModel, connectionSettingsModel: ConnectionSettingsModel, - viperSettingsModel: ViperSettingsModel + viperSettingsModel: ViperSettingsModel, + clipboardSettingsModel: ClipboardSettingsModel ) { - settingsService.newSettings(Settings().apply { - downloadSettings = DownloadSettings( - downloadSettingsModel.downloadPath, - downloadSettingsModel.autoStart, - downloadSettingsModel.autoQueueThreshold, - downloadSettingsModel.forceOrder, - downloadSettingsModel.forumSubfolder, - downloadSettingsModel.threadSubLocation, - downloadSettingsModel.clearCompleted, - downloadSettingsModel.appendPostId + settingsService.newSettings(Settings().apply { + downloadSettings = DownloadSettings( + downloadSettingsModel.downloadPath, + downloadSettingsModel.autoStart, + downloadSettingsModel.autoQueueThreshold, + downloadSettingsModel.forceOrder, + downloadSettingsModel.forumSubfolder, + downloadSettingsModel.threadSubLocation, + downloadSettingsModel.clearCompleted, + downloadSettingsModel.appendPostId + ) + connectionSettings = ConnectionSettings( + connectionSettingsModel.maxThreads, + connectionSettingsModel.maxTotalThreads, + connectionSettingsModel.timeout, + connectionSettingsModel.maxAttempts, + ) + viperSettings = ViperSettings( + viperSettingsModel.login, + viperSettingsModel.username, + viperSettingsModel.password, + viperSettingsModel.thanks, + viperSettingsModel.host, + ) + clipboardSettings = + ClipboardSettings( + clipboardSettingsModel.enable, + if (clipboardSettingsModel.pollingRate.isBlank()) 500 else clipboardSettingsModel.pollingRate.toInt() ) - connectionSettings = ConnectionSettings( - connectionSettingsModel.maxThreads, - connectionSettingsModel.maxTotalThreads, - connectionSettingsModel.timeout, - connectionSettingsModel.maxAttempts, - ) - viperSettings = ViperSettings( - viperSettingsModel.login, - viperSettingsModel.username, - viperSettingsModel.password, - viperSettingsModel.thanks, - viperSettingsModel.host, - ) - }) + }) } diff --git a/vripper-gui/src/main/kotlin/me/mnlr/vripper/model/settings/ClipboardSettingsModel.kt b/vripper-gui/src/main/kotlin/me/mnlr/vripper/model/settings/ClipboardSettingsModel.kt new file mode 100644 index 0000000..5bc1b85 --- /dev/null +++ b/vripper-gui/src/main/kotlin/me/mnlr/vripper/model/settings/ClipboardSettingsModel.kt @@ -0,0 +1,13 @@ +package me.mnlr.vripper.model.settings + +import javafx.beans.property.SimpleBooleanProperty +import javafx.beans.property.SimpleStringProperty +import tornadofx.* + +class ClipboardSettingsModel { + val enableProperty = SimpleBooleanProperty() + var enable: Boolean by enableProperty + + val pollingRateProperty = SimpleStringProperty() + var pollingRate: String by pollingRateProperty +} diff --git a/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/ClipboardSettingsView.kt b/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/ClipboardSettingsView.kt new file mode 100644 index 0000000..e75e0ca --- /dev/null +++ b/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/ClipboardSettingsView.kt @@ -0,0 +1,39 @@ +package me.mnlr.vripper.view.settings + +import me.mnlr.vripper.controller.SettingsController +import me.mnlr.vripper.model.settings.ClipboardSettingsModel +import tornadofx.* + +class ClipboardSettingsView : View("Clipboard Settings") { + private val settingsController: SettingsController by inject() + val clipboardSettingsModel = ClipboardSettingsModel() + + override fun onDock() { + val clipboardSettings = settingsController.findClipboardSettings() + clipboardSettingsModel.enable = clipboardSettings.enable + clipboardSettingsModel.pollingRate = clipboardSettings.pollingRate.toString() + } + + override val root = vbox { + form { + fieldset { + field("Enable") { + checkbox { + bind(clipboardSettingsModel.enableProperty) + } + } + fieldset { + visibleWhen(clipboardSettingsModel.enableProperty) + field("Polling rate (ms)") { + textfield(clipboardSettingsModel.pollingRateProperty) { + filterInput { + it.controlNewText.isInt() + } + } + } + } + + } + } + } +} \ No newline at end of file diff --git a/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/ConnectionSettingsView.kt b/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/ConnectionSettingsView.kt index 4107f47..db9f37e 100644 --- a/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/ConnectionSettingsView.kt +++ b/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/ConnectionSettingsView.kt @@ -29,7 +29,7 @@ class ConnectionSettingsView : View("Connection settings") { filterInput { it.controlNewText.isInt() } } } - field("Connection timeout") { + field("Connection timeout (s)") { textfield(connectionSettingsModel.timeoutProperty) { filterInput { it.controlNewText.isInt() } } diff --git a/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/SettingsView.kt b/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/SettingsView.kt index c9a3e79..f0cb973 100644 --- a/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/SettingsView.kt +++ b/vripper-gui/src/main/kotlin/me/mnlr/vripper/view/settings/SettingsView.kt @@ -18,25 +18,32 @@ class SettingsView : Fragment("Settings") { private val downloadSettingsView: DownloadSettingsView by inject() private val connectionSettingsView: ConnectionSettingsView by inject() private val viperSettingsView: ViperSettingsView by inject() + private val clipboardSettingsView: ClipboardSettingsView by inject() override val root = vbox(alignment = Pos.CENTER_RIGHT) { spacing = 5.0 tabpane { tabClosingPolicy = TabPane.TabClosingPolicy.UNAVAILABLE VBox.setVgrow(this, Priority.ALWAYS) - tab() { + tab { val imageView = ImageView("downloads-folder.png") imageView.fitWidth = 18.0 imageView.fitHeight = 18.0 graphic = imageView } - tab() { + tab { val imageView = ImageView("data-transfer.png") imageView.fitWidth = 18.0 imageView.fitHeight = 18.0 graphic = imageView } - tab() { + tab { + val imageView = ImageView("clipboard.png") + imageView.fitWidth = 18.0 + imageView.fitHeight = 18.0 + graphic = imageView + } + tab { val imageView = ImageView("icons/32x32.png") imageView.fitWidth = 18.0 imageView.fitHeight = 18.0 @@ -56,7 +63,8 @@ class SettingsView : Fragment("Settings") { settingsController.saveNewSettings( downloadSettingsView.downloadSettingsModel, connectionSettingsView.connectionSettingsModel, - viperSettingsView.viperSettingsModel + viperSettingsView.viperSettingsModel, + clipboardSettingsView.clipboardSettingsModel ) close() } catch (e: ValidationException) { diff --git a/vripper-gui/src/main/resources/clipboard.png b/vripper-gui/src/main/resources/clipboard.png new file mode 100644 index 0000000000000000000000000000000000000000..36e2b7e461a0a2c2e65f769c638f2b259debc7de GIT binary patch literal 511 zcmV0;w!Y8cq~{d zBv2EYjR&z0QiS>gdMWf^MT?i7A{z{h*4En1nxxDJb8C0z+nwDUM$;6~=!9j<4Z5}) zOhi~N=azF>(TYH$6P9g<5O{YWVyjxCPi)T?UxBPyGE5w2xU*m6C;(p}FUQ{jRS*v002ovPDHLkV1lI& B+B5(F literal 0 HcmV?d00001