
com.github.weisj.swingdsl.components.file.WatchFileTreeModel.kt Maven / Gradle / Ivy
/*
* MIT License
*
* Copyright (c) 2021 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.github.weisj.swingdsl.components.file
import java.io.IOException
import java.nio.file.*
import java.util.*
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.ScheduledThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.logging.Logger
import javax.swing.filechooser.FileSystemView
class WatchFileTreeModel(
fsv: FileSystemView,
isShowHiddenFiles: Boolean,
roots: List?
) : FileTreeModel(fsv, isShowHiddenFiles, roots) {
// Gets initialized in beforeInit, which amounts to an early initialization.
private lateinit var nodeMap: MutableMap
private var watchService: WatchService? = null
private val isScheduled = AtomicBoolean(false)
private var watchTask: ScheduledFuture<*>? = null
override fun beforeInit() {
watchService = createWatchService()
nodeMap = Collections.synchronizedMap(HashMap())
}
fun startWatching() {
synchronized(this) {
if (watchTask != null) return
isScheduled.set(true)
watchTask = SCHEDULER.schedule(this::watch, 0, TimeUnit.SECONDS)
}
}
fun stopWatching() {
synchronized(this) {
if (watchTask != null) {
isScheduled.set(false)
watchTask!!.cancel(true)
watchTask = null
}
}
}
private fun watch() {
while (isScheduled.get()) {
val key: WatchKey = try {
watchService!!.take()
} catch (x: InterruptedException) {
x.printStackTrace()
return
}
val parent = nodeMap[key.watchable()]
if (parent != null) {
LOGGER.fine { "Event for \"$parent\"" }
parent.parent?.reload(1)
parent.reload(0)
}
val watchEventList = key.pollEvents()
for (event in watchEventList) {
val kind = event.kind()
val path = event.context() as Path
if (kind === StandardWatchEventKinds.OVERFLOW) {
continue
}
LOGGER.finer("Event Type " + kind.name())
val node = nodeMap[(key.watchable() as Path).resolve(path)]
LOGGER.finer { "Affected node \"$node\"" }
node?.reload(0)
}
key.reset()
}
}
override fun register(node: FileTreeNode) {
synchronized(this) {
val ws = watchService
if (ws == null || !node.fileNode.isDirectory) return
val path = node.fileNode.path ?: return
if (nodeMap.containsKey(path)) return
try {
LOGGER.finer { "Register watch service for \"$node\"" }
node.watchKey = path.register(
ws, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY
)
nodeMap.put(path, node)
} catch (ignored: IOException) {
}
}
}
override fun unregister(node: FileTreeNode) {
synchronized(this) {
val key = node.watchKey ?: return
LOGGER.finer { "Unregister watch service for \"$node\"" }
node.fileNode.path?.let {
nodeMap.remove(it)
}
key.cancel()
}
}
companion object {
private val LOGGER = Logger.getLogger(WatchFileTreeModel::class.java.name)
private val SCHEDULER = createScheduler()
private fun createWatchService(): WatchService? = kotlin.runCatching {
FileSystems.getDefault().newWatchService()
}.getOrNull()
private fun createScheduler(): ScheduledExecutorService {
val executor = ScheduledThreadPoolExecutor(1) { r: Runnable? ->
val thread = Thread(r, "File Tree Watch Thread")
thread.isDaemon = true
thread.priority = Thread.MIN_PRIORITY
thread
}
executor.continueExistingPeriodicTasksAfterShutdownPolicy = false
executor.executeExistingDelayedTasksAfterShutdownPolicy = false
return executor
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy