Loading...

Blog

Latest blog posts

Watch files on folder with Kotlin

Watch files on folder with Kotlin

Recenlty I've been developing a desktop app in Kotlin (Compose) where I need to display the files in a directory. The files need to be live updated (if a file is added to a directory I need to update the view). To do this Java provides the class WatchService, that enables you to watch changes on a directory. Let's make it Kotlin friendly. The idea is at the end be able to achieve the following function that retuns a flow with the files inside a directory:

fun Path.listDirectoryEntriesFlow(glob: String): Flow<List<Path>>

The first step is to make easier the creation of a watch service. Thats easy.

/**
 * Creates a new WatchService for any Event
 */
fun Path.watch() : WatchService {
    return watch(StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY,
        StandardWatchEventKinds.OVERFLOW, StandardWatchEventKinds.ENTRY_DELETE)
}

/**
 * Creates a new watch service
 */
fun Path.watch(vararg events: WatchEvent.Kind<out Any>) = fileSystem.newWatchService()!!.apply { register(this, events)

Now it comes the dificult part. The watch service has a method take() that waits for changes on a directory. The problem is that its in Java, so nothing of nice coroutines. We need to send an interrupt signal to stop it. To do so we have the runInterruptible method. So after some try and test I came out with the following:

/**
 * Creates a flow WatchEvent from a watchService
 */
fun WatchService.eventFlow() : Flow<List<WatchEvent<out Any>>> = flow {
    while (currentCoroutineContext().isActive) {
        coroutineScope {
            var key: WatchKey? = null
            val job = launch {
                runInterruptible(Dispatchers.IO) {
                    key = take()
                }
            }
            job.join()
            val currentKey = key
            if (currentKey != null) {
                emit(currentKey.pollEvents())
                currentKey.reset()
            }
        }
    }
}

And that is most of it. We just need to map each event to a list of files (that can be done with a flow.map). Need to list the files at the begining of the flow, and close the watcher on the end of the flow.

/**
 * Returns a flow with the files inside a folder (with a given glob)
 */
fun Path.listDirectoryEntriesFlow(glob: String): Flow<List<Path>> {
    val watchService = watch()
    return watchService.eventFlow()
        .map { listDirectoryEntries(glob) }
        .onStart { emit(listDirectoryEntries(glob)) }
        .onCompletion { watchService.close() }
        .flowOn(Dispatchers.IO)
}

And that all! we have a flow with the files inside a folder.

You can copy all the code from the following gist: https://gist.github.com/trito-dev/31c45b50b4aa7488962b717252a42cbb