-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
24 changed files
with
297 additions
and
152 deletions.
There are no files selected for viewing
106 changes: 0 additions & 106 deletions
106
scx-common/src/main/java/cool/scx/common/util/FileWatcher.java
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
scx-io/src/main/java/cool/scx/io/file/FileAttributesReader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package cool.scx.io.file; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.NoSuchFileException; | ||
import java.nio.file.Path; | ||
import java.nio.file.attribute.BasicFileAttributes; | ||
import java.nio.file.attribute.FileTime; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
/** | ||
* 通过 FileWatcher 主动更新 代替高频的 Files.readAttributes 查询 | ||
*/ | ||
public class FileAttributesReader { | ||
|
||
//为了解决 ConcurrentHashMap 无法存储空值的问题 | ||
private static final NotExist NOT_EXIST = new NotExist(); | ||
|
||
private final Path target; | ||
private final FileWatcher fileWatcher; | ||
private final Map<Path, BasicFileAttributes> cache; | ||
|
||
public FileAttributesReader(Path target) throws IOException { | ||
this.target = target; | ||
this.fileWatcher = new FileWatcher(target).listener(this::onChange).start(); | ||
this.cache = new ConcurrentHashMap<>(); | ||
} | ||
|
||
private void onChange(FileWatcher.ChangeEvent event) { | ||
try { | ||
if (event.type() == FileWatcher.ChangeEventType.DELETED) { | ||
cache.put(event.target(), NOT_EXIST); | ||
} else { | ||
cache.put(event.target(), Files.readAttributes(event.target(), BasicFileAttributes.class)); | ||
} | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
/** | ||
* 这里为了性能考虑并没有在 方法内部判断 | ||
* 但是请保证 参数 path 一定是 target 的子目录或者子文件 否则将无法正确判断文件变化 | ||
* | ||
* @param path path | ||
* @return 文件内容 (可能为 null ) | ||
* @throws IOException a | ||
*/ | ||
public BasicFileAttributes get(Path path) throws IOException { | ||
var f = cache.computeIfAbsent(path, c -> { | ||
try { | ||
return Files.readAttributes(c, BasicFileAttributes.class); | ||
} catch (NoSuchFileException e) { | ||
return NOT_EXIST; | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
}); | ||
if (f == NOT_EXIST) { | ||
return null; | ||
} | ||
return f; | ||
} | ||
|
||
private static class NotExist implements BasicFileAttributes { | ||
|
||
@Override | ||
public FileTime lastModifiedTime() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public FileTime lastAccessTime() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public FileTime creationTime() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public boolean isRegularFile() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean isDirectory() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean isSymbolicLink() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean isOther() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public long size() { | ||
return 0; | ||
} | ||
|
||
@Override | ||
public Object fileKey() { | ||
return null; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package cool.scx.io.file; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.*; | ||
import java.util.function.Consumer; | ||
|
||
import static java.nio.file.StandardWatchEventKinds.*; | ||
|
||
/** | ||
* 文件监听器 | ||
*/ | ||
public final class FileWatcher { | ||
|
||
private final WatchService watchService; | ||
private final Path target; | ||
private final Path watchTarget; | ||
private final boolean isFile; | ||
private Thread watchThread; | ||
private Consumer<ChangeEvent> listener; | ||
|
||
public FileWatcher(Path target) throws IOException { | ||
this.watchService = FileSystems.getDefault().newWatchService(); | ||
this.target = target; | ||
this.isFile = !Files.isDirectory(target); | ||
this.watchTarget = isFile ? target.getParent() : target; | ||
watchTarget.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); | ||
} | ||
|
||
public void _do() { | ||
while (true) { | ||
WatchKey watchKey; | ||
try { | ||
watchKey = watchService.take(); | ||
} catch (InterruptedException e) { | ||
//中断则退出 | ||
break; | ||
} | ||
var watchEvents = watchKey.pollEvents(); | ||
for (var event : watchEvents) { | ||
var eventPath = (Path) event.context(); | ||
var eventKind = event.kind(); | ||
|
||
//使用全路径方便处理 | ||
eventPath = watchTarget.resolve(eventPath); | ||
|
||
//如果监听的是单个文件 但是发生变化的并不是这个文件 我们跳过 | ||
if (isFile && !target.equals(eventPath)) { | ||
continue; | ||
} | ||
//调用监听 | ||
_callListener(eventPath, eventKind); | ||
} | ||
// reset the key | ||
var valid = watchKey.reset(); | ||
if (!valid) { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
public FileWatcher listener(Consumer<ChangeEvent> listener) { | ||
this.listener = listener; | ||
return this; | ||
} | ||
|
||
private void _callListener(Path target, WatchEvent.Kind<?> kind) { | ||
if (listener != null) { | ||
this.listener.accept(new ChangeEvent(target, ChangeEventType.of(kind))); | ||
} | ||
} | ||
|
||
public FileWatcher start() { | ||
this.watchThread = Thread.ofVirtual().name("FileWatcher-WatchThread").start(this::_do); | ||
return this; | ||
} | ||
|
||
public void stop() { | ||
if (this.watchThread != null) { | ||
this.watchThread.interrupt(); | ||
this.watchThread = null; | ||
} | ||
try { | ||
watchService.close(); | ||
} catch (IOException _) { | ||
|
||
} | ||
} | ||
|
||
public enum ChangeEventType { | ||
|
||
MODIFY, | ||
|
||
DELETED, | ||
|
||
CREATED; | ||
|
||
public static ChangeEventType of(WatchEvent.Kind<?> t) { | ||
if (t == ENTRY_CREATE) { | ||
return CREATED; | ||
} | ||
if (t == ENTRY_MODIFY) { | ||
return MODIFY; | ||
} | ||
if (t == ENTRY_DELETE) { | ||
return DELETED; | ||
} | ||
throw new IllegalArgumentException("未知类型 : " + t.toString()); | ||
} | ||
|
||
} | ||
|
||
public record ChangeEvent(Path target, ChangeEventType type) { | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.