Gtihub
可以传递类中的每个函数。但是整个班级都做不到。
WatchService获取文件更改并按频道发送。
Path.subscribe
都是通过调试成功完成的。所有路径均已添加到地图。
但是watchKey.pollEvents
中没有最后两个文件。而且本地测试失败了
但是测试是通过Github Actions gradle构建通过的。
@Test
fun `file create event should be fired`() {
var receive: FileEvent<String?>?
val path = Paths.get("testresources", "create")
path.file.delete()
val channel = path.subscribe<String?> {
converter { path, kind ->
if (kind != FileEvent.Kind.Delete) {
return@converter path.file.readText()
} else {
return@converter null
}
}
}
path.file.createNewFile()
runBlocking {
withTimeout(5000) {
receive = channel.receive()
}
}
assertNotNull(receive)
assertEquals(path.absolutePath, receive!!.path)
assertEquals(FileEvent.Kind.Create, receive!!.kind)
assertNotNull(receive!!.data)
assert(receive!!.data!!.isBlank())
}
ReactiveFile.kt
@ExperimentalCoroutinesApi
object ReactiveFile {
private val watchService = FileSystems.getDefault().newWatchService()
private val pathToHandlerMap = mutableMapOf<Path, FileEventChannel<*>>()
init {
GlobalScope.launch(Dispatchers.IO) {
while (true) {
val watchKey = watchService.take()
val path = watchKey.watchable()
if (path is Path) {
val pollEvents = watchKey.pollEvents()
val events = pollEvents.reversed().distinctBy { it.context() }
events.forEach {
val currentPath = path.resolve(it.context() as Path).absolutePath
val kind = FileEvent.Kind.getByKind(it.kind())
if (kind != null) {
@Suppress("UNCHECKED_CAST")
val fileEventChannel = pathToHandlerMap[currentPath] as? FileEventChannel<Any?>
fileEventChannel?.apply {
if (isClosedForSend) {
pathToHandlerMap.remove(currentPath)
} else {
send(
FileEvent(
path = currentPath,
kind = kind,
data = converter?.invoke(currentPath, kind)
)
)
}
}
}
}
}
if (!watchKey.reset() || path !is Path) {
watchKey.cancel()
continue
}
}
}
}
fun <T> Path.subscribe(consumer: FileEventChannel<T>.() -> Unit): FileEventChannel<T> {
val path = this.absolutePath
path.directory.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
val channel = FileEventChannel<T>()
consumer(channel)
pathToHandlerMap[path] = channel
return channel
}
}
class FileEventChannel<T>(
private val channel: Channel<FileEvent<T>> = Channel()
) : Channel<FileEvent<T>> by channel {
internal var converter: ((path: Path, kind: FileEvent.Kind) -> T)? = null
fun converter(consumer: (path: Path, kind: FileEvent.Kind) -> T) {
this.converter = consumer
}
}
data class FileEvent<T>(
/**
* Path of file/directory
*/
val path: Path,
/**
* Kind of event
*/
val kind: Kind,
val data: T
) {
enum class Kind(val kind: WatchEvent.Kind<Path>?) {
Create(ENTRY_CREATE), Modify(ENTRY_MODIFY), Delete(ENTRY_DELETE);
companion object {
fun getByKind(kind: WatchEvent.Kind<*>) = values()
.filter { it.kind != null }
.singleOrNull { it.kind!! == kind }
}
}
}
我该怎么办?