Skip to content

Commit

Permalink
重构 io 模块 (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
scx567888 authored Nov 4, 2024
1 parent da96680 commit 13e0b6c
Show file tree
Hide file tree
Showing 24 changed files with 297 additions and 152 deletions.
106 changes: 0 additions & 106 deletions scx-common/src/main/java/cool/scx/common/util/FileWatcher.java

This file was deleted.

6 changes: 3 additions & 3 deletions scx-core/src/test/java/cool/scx/core/test/TestModule.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package cool.scx.core.test;

import cool.scx.common.util.*;
import cool.scx.common.zip.UnZipBuilder;
import cool.scx.common.zip.ZipBuilder;
import cool.scx.common.zip.ZipOptions;
import cool.scx.core.Scx;
import cool.scx.core.ScxContext;
import cool.scx.core.ScxModule;
Expand All @@ -20,6 +17,9 @@
import cool.scx.http.media.multi_part.MultiPart;
import cool.scx.http.routing.handler.StaticHandler;
import cool.scx.http.uri.ScxURI;
import cool.scx.io.zip.UnZipBuilder;
import cool.scx.io.zip.ZipBuilder;
import cool.scx.io.zip.ZipOptions;
import cool.scx.jdbc.sql.SQL;
import cool.scx.scheduling.ScxScheduling;
import org.testng.annotations.BeforeTest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import cool.scx.common.util.HashUtils;
import cool.scx.common.util.RandomUtils;
import cool.scx.common.util.ScxExceptionHelper;
import cool.scx.common.zip.ZipBuilder;
import cool.scx.core.ScxContext;
import cool.scx.core.test.car.CarService;
import cool.scx.core.test.person.Person;
Expand All @@ -14,6 +13,7 @@
import cool.scx.http.helidon.ScxHttpClientHelper;
import cool.scx.http.media.multi_part.MultiPartPart;
import cool.scx.http.routing.RoutingContext;
import cool.scx.io.zip.ZipBuilder;
import cool.scx.web.ScxWeb;
import cool.scx.web.annotation.FromQuery;
import cool.scx.web.annotation.FromUpload;
Expand Down
114 changes: 114 additions & 0 deletions scx-io/src/main/java/cool/scx/io/file/FileAttributesReader.java
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;
}
}

}
116 changes: 116 additions & 0 deletions scx-io/src/main/java/cool/scx/io/file/FileWatcher.java
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) {

}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cool.scx.common.util.image;
package cool.scx.io.image;

import cool.scx.common.io_stream_source.InputStreamSource;
import cool.scx.common.io_stream_source.OutputStreamSource;
import cool.scx.io.io_stream_source.InputStreamSource;
import cool.scx.io.io_stream_source.OutputStreamSource;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
Expand All @@ -15,7 +15,7 @@
import java.nio.file.Path;
import java.util.function.Supplier;

import static cool.scx.common.io_stream_source.InputStreamSource.of;
import static cool.scx.io.io_stream_source.InputStreamSource.of;

/**
* 将图片转换为 渐进式 JPEG
Expand Down
Loading

0 comments on commit 13e0b6c

Please sign in to comment.