All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.neko233.config233.reader.fileWatcher.FileWatcherForConfig233.kt Maven / Gradle / Ivy
package com.neko233.config233.reader.fileWatcher
import com.neko233.config233.utils.FileUtilsForConfig233
import org.slf4j.LoggerFactory
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardWatchEventKinds.*
import java.nio.file.WatchService
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.io.path.pathString
object FileWatcherForConfig233 {
val LOGGER = LoggerFactory.getLogger(FileWatcherForConfig233::class.java)!!
// 监听文件
private lateinit var watchService: WatchService
// 文件监听器数量
val FILE_WATCHER_COUNTER = AtomicInteger(1)
// 是否初始化
val IS_INIT = AtomicBoolean(false)
// 创建一个缓存Map,用于存储文件路径和最近一次事件的时间戳
private val fileEventCache = ConcurrentHashMap()
// 创建一个定时任务执行器,用于定时清理缓存
private val hotUpdateScheduler = Executors.newSingleThreadScheduledExecutor()
// <监听的文件路径, 最后一次更新时间>
private var filePathToUpdateTimeMsMap: Map = emptyMap()
fun watchDirectory(
filePathString: String,
carefulFilePathSet: Set,
listener: FileWatchListener,
windowSizeTimeMs: Long = 500,
) {
val filePath = Paths.get(filePathString)
val fileWatcherThread = Thread {
this.watchDirFilesByBio(filePath, listener, windowSizeTimeMs, carefulFilePathSet)
}
fileWatcherThread.name = "FileWatcher-${FILE_WATCHER_COUNTER.getAndIncrement()}"
fileWatcherThread.start()
}
private val scheduler = Executors.newSingleThreadScheduledExecutor { r ->
val thread = Thread(r)
thread.name = "config233-hot-update-scheduler"
thread
}
/**
* 轮询 BIO 方案 | 稳定
*/
private fun watchDirFilesByBio(
dirPath: Path,
listener: FileWatchListener,
windowSizeTimeMs: Long = 500,
carefulFilePathSet: Set,
) {
startCleanupTask()
scheduler.scheduleAtFixedRate({
val tempFilePathToUpdateTimeMsMap = mutableMapOf()
// 递归获取目录下的所有文件,排除临时文件
val fileList = Files.walk(dirPath)
fileList
.filter {
// 只处理关心的文件
val pathStr = it.pathString
return@filter carefulFilePathSet.contains(pathStr)
}
.forEach { filePath ->
val attrs = Files.readAttributes(filePath, BasicFileAttributes::class.java)
val lastModifiedTime = attrs.lastModifiedTime().toMillis()
tempFilePathToUpdateTimeMsMap[filePath] = lastModifiedTime
}
// 首次不触发
if (this.filePathToUpdateTimeMsMap.isNotEmpty()) {
// 对比前后两次文件状态,触发 Change 事件
tempFilePathToUpdateTimeMsMap.toList().forEach { (file, currentLastModifiedTime) ->
val previousLastModifiedTime = this.filePathToUpdateTimeMsMap[file]
if (previousLastModifiedTime == null || currentLastModifiedTime != previousLastModifiedTime) {
val fileEvent = FileEvent(ENTRY_MODIFY, file)
handleFileEvent(fileEvent, listener, windowSizeTimeMs)
}
}
}
// 更新上一次文件状态
this.filePathToUpdateTimeMsMap = tempFilePathToUpdateTimeMsMap
}, 0, 5, TimeUnit.SECONDS)
}
//
// /**
// * 递归监听所有文件 NIO 方案 | 有 BUG
// */
// private fun watchDirFilesByNio(
// dirPath: Path,
// listener: FileWatchListener,
// windowSizeTimeMs: Long = 500,
// watchKeys: MutableList = mutableListOf()
// ) {
// startCleanupTask()
//
//
// val watchService = FileSystems.getDefault().newWatchService()
// val key: WatchKey = dirPath.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
//
// // 遍历目录下的所有文件和子目录,并递归注册监听器
// Files.walkFileTree(dirPath, object : SimpleFileVisitor() {
// override fun preVisitDirectory(
// dir: Path,
// attrs: BasicFileAttributes,
// ): FileVisitResult {
// dir.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
// return FileVisitResult.CONTINUE
// }
// })
//
// while (true) {
// val watchKey = watchService.take()
//
// for (event in watchKey.pollEvents()) {
// val kind = event.kind()
//
// if (kind == OVERFLOW) {
// continue
// }
//
// val fileName = event.context() as Path
// val filePath = dirPath.resolve(fileName)
//
// val fileEvent = FileEvent(kind, filePath)
// handleFileEvent(fileEvent, listener, windowSizeTimeMs)
//
// // 如果是一个新创建的子目录,递归注册监听器
// if (kind == ENTRY_CREATE && Files.isDirectory(filePath)) {
// watchDirectoryRecursive0(filePath, listener)
// }
//
// // 如果是删除事件,取消对该文件的监听
// if (kind == ENTRY_DELETE) {
// val watchable = watchKey.watchable() as? Path
// if (watchable != null && watchable == filePath) {
// watchKey.cancel()
// }
// }
// }
//
// watchKey.reset()
// }
// }
/**
* 处理文件监听
*/
private fun handleFileEvent(
event: FileEvent,
listener: FileWatchListener,
windowSizeTimeMs: Long = 500,
) {
val filePath = event.filePath
val currentTime = System.currentTimeMillis()
// 窗口期 500ms 更新一次文件
val isDuplicateEvent = isDuplicateEvent(filePath, currentTime, windowSizeTimeMs)
if (isDuplicateEvent) {
return
}
FileUtilsForConfig233.waitForFileLock(filePath)
when (event.kind) {
ENTRY_CREATE -> {
// 处理文件新增事件
listener.onFileCreate(filePath)
return
}
ENTRY_MODIFY -> {
listener.onFileUpdate(filePath)
return
}
ENTRY_DELETE -> {
// 处理文件删除事件
listener.onFileDelete(filePath)
return
}
}
}
/**
* 检查文件事件是否是重复事件
*
* @param windowSize 时间窗口大小. 单位: ms
*/
private fun isDuplicateEvent(
filePath: Path,
currentTime: Long,
windowSize: Long,
): Boolean {
val previousTimestamp = fileEventCache.getOrDefault(filePath, 0)
// 如果存在之前的时间戳,检查时间间隔是否小于窗口大小
if (currentTime - windowSize < previousTimestamp) {
// 是重复事件,返回true
return true
}
fileEventCache.put(filePath, currentTime)
return false
}
/**
* 启动定时任务,定时清理缓存
*/
private fun startCleanupTask() {
val isGet = IS_INIT.compareAndSet(false, true)
if (!isGet) {
return
}
hotUpdateScheduler.scheduleAtFixedRate({
val currentTime = System.currentTimeMillis()
// 遍历缓存,移除超过窗口大小的事件
fileEventCache.entries.removeIf { (filePath, timestamp) ->
currentTime - timestamp >= 100
}
}, 100, 500, TimeUnit.MILLISECONDS)
}
}