MVC: Best Way to Browse a Directory for Changes

Context:
I am new to JavaFX but I am trying to create an application, one of which is the basic function is to show the user all folders in a specific directory and automatically update the view when new folders or folders are deleted in that directory. These folders can be thought of as objects (for example, in a ListView) and the user should be able to interact with them. I want to create this application using MVC architecture.

What I have done so far:
So I have a view (fxml), a controller class, and my model that handles my application logic. I am using the WatchDir example from Oracle as a helper class in my model and starts the WatchService in the controller like this:

@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.");
    }
}

      

In WatchDir's processEvents () method, I can do something like:

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

      

My question is:
What's the best / most elegant way to tell my controller that something has changed and update the ListView? I want to keep it MVC style.

Another approach is also welcome :)

+3


source to share


1 answer


The approach I would like to use would be to provide methods in WatchDir

for registering callbacks. The easiest way to do this is to use Consumer

properties for callbacks:

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);
            }
        }

        // ...
    }

    // ...
}

      

Note that this WatchDir.processEvents()

is a (non-terminating) blocking call, so you need to run it on a background thread. So, from your controller, you do:

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();

      



Please note, since the callback will be called on a background thread, updates to the UI must be wrapped in Platform.runLater(...)

. If you like, you can equip WatchDir

Executor

to do callbacks, which allows you to tell about it only once, to always execute them via 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));
            }
        }

        // ...
    }

}

      

and then

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();

      

+4


source







All Articles