MVC:观察目录变化的最佳方式

MVC: Best way of watching a directory for changes

上下文:
我是 JavaFX 的新手,但我正在尝试构建一个应用程序,它的基本功能之一是向用户显示特定目录中的所有文件夹,并在该目录中有新文件夹或文件夹被删除时自动更新视图。这些文件夹可以被视为对象(例如在 ListView 中)并且用户应该能够与它们进行交互。 我想使用 MVC 架构构建该应用程序。

到目前为止我做了什么:
所以我有一个视图 (fxml)、一个控制器 class 和处理我的应用程序逻辑的模型。我在我的模型中使用 WatchDir example from Oracle 作为助手 class 并像这样在控制器中启动 WatchService:

@Override
public void initialize(URL location, ResourceBundle resources) {
    this.config = loadConfig(configFile);
    this.facade = new facade(config);
    Path rootPath = Paths.get(config.getDlRootPath());
    try {
        // register WatchService
        new WatchDir(rootPath, false).processEvents();
        statusText(rootPath + "is now being watched for changes");
    } catch (IOException e) {
        statusError("Directory " + e.getLocalizedMessage() + " does not exist.");
    }
}

在 WatchDir 方法 processEvents() 中,我可以做类似的事情:

if (!recursive && (kind == ENTRY_CREATE)) {
  // new folder was created
}

我的问题:
best/most 告诉我的控制器某些内容已更改并更新 ListView 的优雅方式是什么?我想保持它的 MVC 风格。

也欢迎不同的方法:)

我将使用的方法是在 WatchDir 中提供用于注册回调的方法。最简单的方法就是为回调使用 Consumer 属性:

public class WatchDir {

    private Consumer<Path> onCreate ;

    public void setOnCreate(Consumer<Path> onCreate) {
        this.onCreate = onCreate ;
    }

    // other callbacks as needed...

    // ...

    void processEvents() {

        // ...

        Path child = ... ; // file/folder that was created

        if (! recursive && kind == ENTRY_CREATE) {
            if (onCreate != null) {
                onCreate.accept(child);
            }
        }

        // ...
    }

    // ...
}

请注意 WatchDir.processEvents() 是一个(非终止)阻塞调用,因此您需要在后台线程中 运行 它。所以从你的控制器你做:

WatchDir watchDir = new WatchDir(rootPath, false) ;
watchDir.setOnCreate(path -> 
    Platform.runLater(() -> listView.getItems().add(path)));
Thread watchThread = new Thread(watchDir::processEvents);
watchThread.setDaemon(true);
watchThread.start();

请注意,由于回调将在后台线程上调用,因此 UI 的更新应包含在 Platform.runLater(...) 中。如果你愿意,你可以为 WatchDir 配备一个 Executor 来执行回调,这将允许你告诉它一次总是通过 Platform.runLater(...):

执行它们
public class WatchDir {

    // Executor for notifications: by default just run on the current thread
    private Executor notificationExecutor = Runnable::run ;
    public void setNotificationExecutor(Executor notificationExecutor) {
        this.notificationExecutor = notificationExecutor ;
    }

    private Consumer<Path> onCreate ;
    // etc...

    void processEvents() {

        // ...

        if (! recursive && kind == ENTRY_CREATE) {
            if (onCreate != null) {
                notificationExecutor.execute(() -> onCreate.accept(child));
            }
        }

        // ...
    }

}

然后是

WatchDir watchDir = new WatchDir(rootPath, false) ;
watchDir.setNotificationExecutor(Platform::runLater);
watchDir.setOnCreate(listView.getItems()::add); /* or path -> listView.getItems().add(path) */
Thread watchThread = new Thread(watchDir::processEvents);
watchThread.setDaemon(true);
watchThread.start();