Skip to content

Commit

Permalink
[6.2.0] 更新 FileWatcher
Browse files Browse the repository at this point in the history
  • Loading branch information
Bkm016 committed Oct 27, 2024
1 parent c988a68 commit 0a72d90
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 71 deletions.
2 changes: 2 additions & 0 deletions common-legacy-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ dependencies {
compileOnly(project(":common-env"))
compileOnly(project(":common-platform-api"))
compileOnly(project(":common-util"))
testImplementation(project(":common-platform-api"))
testImplementation(project(":common-util"))
}
198 changes: 129 additions & 69 deletions common-legacy-api/src/main/java/taboolib/common5/FileWatcher.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package taboolib.common5;

import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.commons.lang3.tuple.Triple;
import taboolib.common.Inject;
import taboolib.common.LifeCycle;
import taboolib.common.platform.Awake;
Expand All @@ -11,9 +10,8 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
Expand All @@ -29,98 +27,160 @@
@SkipTo(LifeCycle.ENABLE)
public class FileWatcher implements Releasable {

public final static FileWatcher INSTANCE = new FileWatcher();

private final ScheduledExecutorService service = Executors.newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("TConfigWatcherService-%d").build());
private final Map<WatchService, Triple<File, Object, Consumer<Object>>> map = new HashMap<>();

public FileWatcher() {
service.scheduleAtFixedRate(() -> {
synchronized (map) {
map.forEach((service, triple) -> {
WatchKey key;
while ((key = service.poll()) != null) {
for (WatchEvent<?> watchEvent : key.pollEvents()) {
if (triple.getLeft().getName().equals(Objects.toString(watchEvent.context()))) {
triple.getRight().accept(triple.getMiddle());
}
}
key.reset();
}
});
}
}, 1000, 100, TimeUnit.MILLISECONDS);
/**
* 文件监听器单例
*/
public final static FileWatcher INSTANCE = new FileWatcher(500);

/**
* 定时执行服务,用于定期检查文件变动
*/
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(
1,
new BasicThreadFactory.Builder()
.namingPattern("TConfigWatcherService-%d")
.uncaughtExceptionHandler((t, e) -> e.printStackTrace())
.build()
);

/**
* 当前已注册的文件监听器列表
*/
private final Map<File, FileListener> fileListenerMap = new ConcurrentHashMap<>();

/**
* 共享的 WatchService 实例
*/
private final WatchService watchService;

public FileWatcher(int interval) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
this.executorService.scheduleAtFixedRate(() -> fileListenerMap.forEach((file, listener) -> {
try {
listener.poll();
} catch (Throwable ex) {
ex.printStackTrace();
}
}), 1000, interval, TimeUnit.MILLISECONDS);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void addSimpleListener(File file, Runnable runnable) {
/**
* 添加简单的文件监听器
*
* @param file 要监听的文件
* @param runnable 文件变动时执行的操作
*/
public void addSimpleListener(File file, Consumer<File> runnable) {
addSimpleListener(file, runnable, false);
}

public void addSimpleListener(File file, Runnable runnable, boolean runFirst) {
if (runFirst) {
runnable.run();
/**
* 添加简单的文件监听器
*
* @param file 要监听的文件
* @param runnable 文件变动时执行的操作
* @param runImmediately 是否在添加监听器时立即执行一次
*/
public void addSimpleListener(File file, Consumer<File> runnable, boolean runImmediately) {
if (runImmediately) {
runnable.accept(file);
}
try {
addListener(file, null, obj -> runnable.run());
} catch (Throwable ignored) {
fileListenerMap.put(file, new FileListener(file, runnable, this));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void addOnListen(File file, Object obj, Consumer<Object> consumer) {
try {
WatchService service = FileSystems.getDefault().newWatchService();
file.getParentFile().toPath().register(service, StandardWatchEventKinds.ENTRY_MODIFY);
map.putIfAbsent(service, Triple.of(file, obj, consumer));
} catch (Throwable ignored) {
/**
* 移除文件的监听器
*
* @param file 要移除监听的文件
*/
public void removeListener(File file) {
FileListener listener = fileListenerMap.remove(file);
if (listener != null) {
listener.cancel();
}
}

@SuppressWarnings("unchecked")
public <T> void addListener(File file, T obj, Consumer<T> consumer) {
addOnListen(file, obj, (Consumer<Object>) consumer);
/**
* 释放资源
*/
@Override
public void release() {
executorService.shutdown();
fileListenerMap.values().forEach(FileListener::cancel);
}

public boolean hasListener(File file) {
synchronized (map) {
return map.values().stream().anyMatch(t -> t.getLeft().getPath().equals(file.getPath()));
}
}
/**
* 监听器对象
*/
static class FileListener {

final File file;
final Consumer<File> callback;
final FileWatcher fileWatcher;
final WatchKey watchKey;

public void runListener(File file) {
synchronized (map) {
map.values().stream().filter(t -> t.getLeft().getPath().equals(file.getPath())).forEach(f -> f.getRight().accept(null));
FileListener(File file, Consumer<File> callback, FileWatcher fileWatcher) throws IOException {
this.file = file;
this.callback = callback;
this.fileWatcher = fileWatcher;
Path path;
if (file.isDirectory()) {
path = file.toPath();
} else {
path = file.getParentFile().toPath();
}
watchKey = path.register(
fileWatcher.watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY
);
}
}

public void removeListener(File file) {
synchronized (map) {
map.entrySet().removeIf(entry -> {
if (entry.getValue().getLeft().getPath().equals(file.getPath())) {
try {
entry.getKey().close();
} catch (IOException ignored) {
public void poll() {
watchKey.pollEvents().forEach(event -> {
if (event.context() instanceof Path) {
Path path = (Path) event.context();
Path fullPath = file.getParentFile().toPath().resolve(path);
// 监听目录
if (file.isDirectory()) {
try {
// 使用 relativize 检查路径关系,更加准确
file.toPath().relativize(fullPath);
callback.accept(fullPath.toFile());
} catch (IllegalArgumentException ignored) {
// 如果不是子路径,会抛出异常,直接忽略
}
}
// 监听文件
else if (isSameFile(fullPath, file.toPath())) {
callback.accept(fullPath.toFile()); // 使用完整路径
}
return true;
}
return false;
});
}
}

@SuppressWarnings("CallToPrintStackTrace")
public void unregisterAll() {
service.shutdown();
map.forEach((service, pair) -> {
public boolean isSameFile(Path path1, Path path2) {
try {
service.close();
// 使用 Files.isSameFile() 判断两个路径是否指向同一个文件
// 该方法会考虑符号链接等情况
return Files.isSameFile(path1, path2);
} catch (IOException e) {
e.printStackTrace();
// 如果出现 IO 异常则返回 false
return false;
}
});
}
}

@Override
public void release() {
unregisterAll();
public void cancel() {
watchKey.cancel();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,19 @@ inline val Any?.cbool: Boolean
get() = Coerce.toBoolean(this)

fun Double.format(digits: Int = 2, roundingMode: RoundingMode = RoundingMode.HALF_UP): Double {
return Coerce.format(this, digits, roundingMode)
return try {
Coerce.format(this, digits, roundingMode)
} catch (_: Throwable) {
0.0
}
}

fun Float.format(digits: Int = 2, roundingMode: RoundingMode = RoundingMode.HALF_UP): Float {
return Coerce.format(this, digits, roundingMode)
return try {
Coerce.format(this, digits, roundingMode)
} catch (_: Throwable) {
0f
}
}

infix fun String.eqic(other: String): Boolean {
Expand Down

0 comments on commit 0a72d90

Please sign in to comment.