All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.neko233.config233.reader.fileWatcher.FileWatcherForConfig233.kt Maven / Gradle / Ivy

There is a newer version: 0.1.8
Show newest version
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)
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy