From f7d9aff83829a459abcaf59a8e66539f1f12b862 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 17 Nov 2021 18:25:09 +0300 Subject: [PATCH] [WIP] directory watcher --- numass-viewer/build.gradle.kts | 23 ++- .../kotlin/inr/numass/viewer/AmplitudeView.kt | 7 +- .../inr/numass/viewer/DataController.kt | 2 + .../inr/numass/viewer/DirectoryWatchView.kt | 107 +++++++++++ .../main/kotlin/inr/numass/viewer/MainView.kt | 170 ++++++++++-------- .../kotlin/inr/numass/viewer/StorageView.kt | 52 +----- 6 files changed, 236 insertions(+), 125 deletions(-) create mode 100644 numass-viewer/src/main/kotlin/inr/numass/viewer/DirectoryWatchView.kt diff --git a/numass-viewer/build.gradle.kts b/numass-viewer/build.gradle.kts index 4e4fd3ab..59283a22 100644 --- a/numass-viewer/build.gradle.kts +++ b/numass-viewer/build.gradle.kts @@ -59,7 +59,28 @@ runtime { ) jpackage { jvmArgs = addJvmArgs - //imageOptions = listOf("--linux-deb-maintainer", "nozik.aa@mipt.ru", "--linux-menu-group", "Science") + val currentOs = org.gradle.internal.os.OperatingSystem.current() + installerOptions = installerOptions + listOf("--vendor", "MIPT-NPM lab") + + if (currentOs.isWindows) { + installerOptions = installerOptions + listOf( + "--win-menu", + "--win-menu-group", "Numass", + "--win-dir-chooser", + "--win-shortcut" + ) + } else if (currentOs.isLinux) { + installerType = "deb" + installerOptions = installerOptions + listOf( + "--linux-package-name", "numass-viewer", + "--linux-shortcut" + ) + imageOptions = listOf( + "--linux-deb-maintainer", "nozik.aa@mipt.ru", + "--linux-menu-group", "Science", + "--linux-shortcut" + ) + } } launcher { jvmArgs = addJvmArgs diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/AmplitudeView.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/AmplitudeView.kt index d0f1e3fe..5dff93da 100644 --- a/numass-viewer/src/main/kotlin/inr/numass/viewer/AmplitudeView.kt +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/AmplitudeView.kt @@ -56,7 +56,7 @@ class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = Imag var normalize by normalizeProperty - private val container = PlotContainer(frame).apply { + private val plotContainer = PlotContainer(frame).apply { val binningSelector: ChoiceBox = ChoiceBox(FXCollections.observableArrayList(1, 2, 8, 16, 32, 50)).apply { minWidth = 0.0 selectionModel.selectLast() @@ -71,7 +71,6 @@ class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = Imag private val plotJobs: ObservableMap = FXCollections.observableHashMap() - val isEmpty = booleanBinding(data) { isEmpty() } private val progress = object : DoubleBinding() { init { @@ -102,7 +101,7 @@ class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = Imag replot() } - container.progressProperty.bind(progress) + plotContainer.progressProperty.bind(progress) } private fun replotOne(key: String, point: DataController.CachedPoint) { @@ -159,6 +158,6 @@ class AmplitudeView : View(title = "Numass amplitude spectrum plot", icon = Imag } override val root = borderpane { - center = container.root + center = plotContainer.root } } diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/DataController.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/DataController.kt index dbed138d..4825e8e0 100644 --- a/numass-viewer/src/main/kotlin/inr/numass/viewer/DataController.kt +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/DataController.kt @@ -30,6 +30,8 @@ class DataController : Controller() { val voltage = point.voltage + val index = point.index + val meta = point.meta val channelSpectra: Deferred> = context.async(Dispatchers.IO) { diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/DirectoryWatchView.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/DirectoryWatchView.kt new file mode 100644 index 00000000..d88d4937 --- /dev/null +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/DirectoryWatchView.kt @@ -0,0 +1,107 @@ +package inr.numass.viewer + +import hep.dataforge.fx.dfIconView +import hep.dataforge.io.envelopes.Envelope +import inr.numass.data.NumassDataUtils +import inr.numass.data.NumassEnvelopeType +import inr.numass.data.storage.NumassDataLoader +import javafx.beans.property.SimpleObjectProperty +import javafx.collections.FXCollections +import javafx.collections.MapChangeListener +import javafx.scene.control.ContextMenu +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import tornadofx.* +import java.nio.file.Path +import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE +import java.nio.file.WatchKey + +class DirectoryWatchView : View(title = "Numass storage", icon = dfIconView) { + + val pathProperty = SimpleObjectProperty() + private val dataController by inject() + + private val ampView: AmplitudeView by inject() + private val timeView: TimeView by inject() + + private var watcherProperty = pathProperty.objectBinding { + it?.fileSystem?.newWatchService() + } + + private val files = FXCollections.observableArrayList() + + private var watchJob: Job? = null + + init { + dataController.points.addListener(MapChangeListener { change -> + if (change.wasAdded()) { + files.add(change.valueAdded) + } else if (change.wasRemoved()) { + files.remove(change.valueRemoved) + } + }) + + watcherProperty.onChange { watchService -> + watchJob?.cancel() + if (watchService != null) { + watchJob = app.context.launch(Dispatchers.IO) { + val key: WatchKey = pathProperty.get().register(watchService, ENTRY_CREATE) + coroutineContext[Job]?.invokeOnCompletion { + key.cancel() + } + while (isActive) { + try { + key.pollEvents().forEach { event -> + if (event.kind() == ENTRY_CREATE) { + val path: Path = event.context() as Path + if (path.fileName.toString().startsWith(NumassDataLoader.POINT_FRAGMENT_NAME)) { + val envelope: Envelope = NumassEnvelopeType.infer(path)?.reader?.read(path) + ?: kotlin.error("Can't read point file") + val point = NumassDataUtils.read(envelope) + files.add(dataController.getCachedPoint(path.toString(), point)) + } + } + } + } catch (x: Throwable) { + app.context.logger.error("Error during dynamic point read", x) + } + } + } + } + } + } + + + override val root = splitpane { + listview(files) { + multiSelect(true) + cellFormat { value: DataController.CachedPoint -> + text = "${value.voltage}[${value.index}]" + graphic = null + contextMenu = ContextMenu().apply { + item("Info") { + action { + PointInfoView(value).openModal(escapeClosesWindow = true) + } + } + } + } + } + + tabpane { + tab("Amplitude spectra") { + content = ampView.root + isClosable = false + //visibleWhen(ampView.isEmpty.not()) + } + tab("Time spectra") { + content = timeView.root + isClosable = false + //visibleWhen(ampView.isEmpty.not()) + } + } + setDividerPosition(0, 0.3); + } +} diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/MainView.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/MainView.kt index dd62425c..64877eca 100644 --- a/numass-viewer/src/main/kotlin/inr/numass/viewer/MainView.kt +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/MainView.kt @@ -43,6 +43,17 @@ class MainView : View(title = "Numass viewer", icon = dfIconView) { private val contentViewProperty = SimpleObjectProperty() private var contentView: UIComponent? by contentViewProperty + private val spectrumView by inject() + private val amplitudeView by inject() + private val directoryWatchView by inject() + + init { + contentViewProperty.onChange { + root.center = it?.root + } + } + + override val root = borderpane { prefHeight = 600.0 @@ -72,10 +83,38 @@ class MainView : View(title = "Numass viewer", icon = dfIconView) { if (rootDir != null) { NumassProperties.setNumassProperty("numass.viewer.lastPath", rootDir.absolutePath) app.context.launch { + dataController.clear() runLater { path = rootDir.toPath() + contentView = null + } + if (Files.exists(path.resolve(NumassDataLoader.META_FRAGMENT_NAME))) { + //build set view + runGoal(app.context, "viewer.load.set[$path]", Dispatchers.IO) { + title = "Load set ($path)" + message = "Building numass set..." + NumassDataLoader(app.context, null, path.fileName.toString(), path) + } ui { loader: NumassDataLoader -> + contentView = spectrumView + dataController.addSet(loader.name, loader) + + } except { + alert( + type = Alert.AlertType.ERROR, + header = "Error during set loading", + content = it.toString() + ).show() + } + } else { + //build storage + app.context.launch { + val storageElement = NumassDirectory.INSTANCE.read(app.context, path) as Storage + withContext(Dispatchers.JavaFx) { + contentView = storageView + storageView.storageProperty.set(storageElement) + } + } } - load(rootDir.toPath()) } } } catch (ex: Exception) { @@ -102,10 +141,69 @@ class MainView : View(title = "Numass viewer", icon = dfIconView) { NumassProperties.setNumassProperty("numass.viewer.lastPath", file.parentFile.absolutePath) app.context.launch { + dataController.clear() runLater { path = file.toPath() + contentView = null + } + //Reading individual file + val envelope = try { + NumassFileEnvelope(path) + } catch (ex: Exception) { + runLater { + alert( + type = Alert.AlertType.ERROR, + header = "Can't load DF envelope from file $path", + content = ex.toString() + ).show() + } + null + } + + envelope?.let { + //try to read as point + val point = NumassDataUtils.read(it) + runLater { + contentView = amplitudeView + dataController.addPoint(path.fileName.toString(), point) + } + } + } + } + } catch (ex: Exception) { + NumassProperties.setNumassProperty("numass.viewer.lastPath", null) + error("Error", content = "Failed to laod file with message: ${ex.message}") + } + } + } + + button("Watch directory") { + action { + val chooser = DirectoryChooser() + chooser.title = "Select directory to view" + val homeDir = NumassProperties.getNumassProperty("numass.viewer.lastPath") + try { + if (homeDir == null) { + chooser.initialDirectory = File(".").absoluteFile + } else { + val file = File(homeDir) + if (file.isDirectory) { + chooser.initialDirectory = file + } else { + chooser.initialDirectory = file.parentFile + } + } + + val dir = chooser.showDialog(primaryStage.scene.window) + + if (dir != null) { + NumassProperties.setNumassProperty("numass.viewer.lastPath", dir.absolutePath) + app.context.launch { + dataController.clear() + runLater { + path = dir.toPath() + contentView = null } - load(file.toPath()) } } } catch (ex: Exception) { @@ -131,72 +229,4 @@ class MainView : View(title = "Numass viewer", icon = dfIconView) { bottom = statusBar } - init { - contentViewProperty.onChange { - root.center = it?.root - } - } - - private val spectrumView by inject() - - private suspend fun load(path: Path) { - runLater { - contentView = null - } - dataController.clear() - if (Files.isDirectory(path)) { - if (Files.exists(path.resolve(NumassDataLoader.META_FRAGMENT_NAME))) { - //build set view - runGoal(app.context, "viewer.load.set[$path]", Dispatchers.IO) { - title = "Load set ($path)" - message = "Building numass set..." - NumassDataLoader(app.context, null, path.fileName.toString(), path) - } ui { loader: NumassDataLoader -> - contentView = spectrumView - dataController.addSet(loader.name, loader) - - } except { - alert( - type = Alert.AlertType.ERROR, - header = "Error during set loading", - content = it.toString() - ).show() - } - } else { - //build storage - app.context.launch { - val storageElement = NumassDirectory.INSTANCE.read(app.context, path) as Storage - withContext(Dispatchers.JavaFx) { - contentView = storageView - storageView.storageProperty.set(storageElement) - } - } - } - } else { - //Reading individual file - val envelope = try { - NumassFileEnvelope(path) - } catch (ex: Exception) { - runLater { - alert( - type = Alert.AlertType.ERROR, - header = "Can't load DF envelope from file $path", - content = ex.toString() - ).show() - } - null - } - - envelope?.let { - //try to read as point - val point = NumassDataUtils.read(it) - runLater { - contentView = AmplitudeView().apply { - dataController.addPoint(path.fileName.toString(), point) - } - } - } - } - } - } diff --git a/numass-viewer/src/main/kotlin/inr/numass/viewer/StorageView.kt b/numass-viewer/src/main/kotlin/inr/numass/viewer/StorageView.kt index 24062e29..15334251 100644 --- a/numass-viewer/src/main/kotlin/inr/numass/viewer/StorageView.kt +++ b/numass-viewer/src/main/kotlin/inr/numass/viewer/StorageView.kt @@ -33,12 +33,6 @@ class StorageView : View(title = "Numass storage", icon = dfIconView) { private val hvView: HVView by inject() private val scView: SlowControlView by inject() -// private var watcher: WatchService? = null - - fun clear() { - dataController.clear() - } - private inner class Container(val id: String, val content: Any) { val checkedProperty = SimpleBooleanProperty(false) var checked by checkedProperty @@ -102,38 +96,6 @@ class StorageView : View(title = "Numass storage", icon = dfIconView) { private var watchJob: Job? = null -// private fun toggleWatch(watch: Boolean) { -// if (watch) { -// if (watchJob != null && content is NumassDataLoader) { -// watchJob = app.context.launch(Dispatchers.IO) { -// val key: WatchKey = content.path.register(watcher!!, ENTRY_CREATE) -// coroutineContext[Job]?.invokeOnCompletion { -// key.cancel() -// } -// while (watcher != null && isActive) { -// try { -// key.pollEvents().forEach { event -> -// if (event.kind() == ENTRY_CREATE) { -// val path: Path = event.context() as Path -// if (path.fileName.toString().startsWith(NumassDataLoader.POINT_FRAGMENT_NAME)) { -// val envelope: Envelope = NumassEnvelopeType.infer(path)?.reader?.read(path) -// ?: kotlin.error("Can't read point file") -// val point = NumassDataUtils.read(envelope) -// children!!.add(buildContainer(point, this@Container)) -// } -// } -// } -// } catch (x: Throwable) { -// app.context.logger.error("Error during dynamic point read", x) -// } -// } -// } -// } -// } else { -// watchJob?.cancel() -// watchJob = null -// } -// } } @@ -141,7 +103,7 @@ class StorageView : View(title = "Numass storage", icon = dfIconView) { treeview { //isShowRoot = false storageProperty.onChange { storage -> - clear() + dataController.clear() if (storage == null) return@onChange root = TreeItem(Container(storage.name, storage)) root.isExpanded = true @@ -150,12 +112,7 @@ class StorageView : View(title = "Numass storage", icon = dfIconView) { }) { it.value.children } -// watcher?.close() -// watcher = if (storage is FileStorage) { -// storage.path.fileSystem.newWatchService() -// } else { -// null -// } + } cellFormat { value: Container -> @@ -198,11 +155,6 @@ class StorageView : View(title = "Numass storage", icon = dfIconView) { value.infoView.openModal(escapeClosesWindow = true) } } -// if(value.content is NumassDataLoader) { -// checkmenuitem("Watch") { -// selectedProperty().bindBidirectional(value.watchedProperty) -// } -// } } } }