Utilities for File Handling

Morimekta Docs Pipeline Coverage
Java module with utilities for handling files, paths and directories. See morimekta.net/utils for procedures on releases.

File Watching

Watch updates on files and directories. This is updated for handling file structures like that of modifiable kubernetes configmap, meaning it will detect updates on intermediary symlinks.

Consists of two inilities and a listener interface.

  • DirWatcher is a simple wrapper around the native java watcher creating a simple watcher class using listeners. It listens to direcories and gives events of file updates there. Each listener is registered per directory, and will onlt get events for directories where it was registered. It can also add weak listeners, which will be garbage collected when no other pointer to the listener exists.
  • FileWatcher is a wrapper around a DirWatcher, and listens to files instead of directories. If the file listens to is a symbolic link, it will generate a full chain of symlink intermediaries, and listen to all of those, and if any of them changes, normalize that to a single file event on the origin symlink file.
import net.morimekta.file.DirWatcher;
import net.morimekta.file.FileWatcher;

import java.nio.file.Files;
import java.nio.file.Paths;

class MyWatcher implements FileEventListener {
  void onFileEvent(Path file, FileEvent event) {
    System.out.println(file.toString() + " " + event);
  }

  void main(String... args) {
    var dirWatcher = new DirWatcher();
    var fileWatcher = new FileWatcher(dirWatcher);
    var path = Paths.get(args[0]);
    if (Files.isDirecory(path)) {
      dirWatcher.addWatcher(path, this);
    } else {
      fileWatcher.addWatcher(path, this);
    }
    Thread.sleep(Long.parseLong(args[1]));
    System.exit(0);
  }
}

Temporary Asset Folder

Wrapper around a temporary folder that will be deleted immediately when the util is closed. Handy when needing a set of temporary files for some operation that will be generated outside the programmers control.

Since normal temporary files will live until the app exits, especially in limited contexts it may have unintended side effects, e.g. on kubernetes temporary files are stored in on-memory-disk, so accumulating temporary files will eat memory.

import net.morimekta.file.TemporaryAssetFolder;

import java.io.IOException;

class FileHandler {
  public Image reEncode(Image image, Format format) throws IOException {
    try (var tmp = new TemporaryAssetFolder()) {
      // add files to tmp, manipulate etc.
      var tmpSource = tmp.resolveFile("source." + image.format.suffix);
      image.writeTo(tmpSource);
      var tmpTarget = tmp.resolveFile("target." + format.suffix);
      var result = new SubProcessRunner().exec(
              "ffmpeg", tmpSource.toString(), tmpTarget.toString());
      if (result != 0) {
        throw new IOException("failed to write " + tmpTarget);
      }
      return Image.readFrom(tmpTarget);
    }
    // all content of 'tmp' is deleted.
  }
}

Utilities

Utility classes used both internally and can be used elsewhere.

FileUtil

readCanonicalPath(path): Read a canonical path directly from file system. This essentially does the same as File.toCanonicalPath() or Path.toRealPath(), but will never cache the resolved path and reuse that for the next response. This is a problem when using an ongoing process to monitor symbolic links and wanting the resolved content from underneath the hood. Java will pretty aggresively cache resolved paths under the hood effectively making symbolic links static. By using paths and this method you can enforce a new read of the symbolic link to get the new target content instead of the old.

replaceSymbolicLink(link, newTarget): Handy when testing file events and watching of symbolic links. This will replace a symbolic link with another, but enforce that it only generates 1 event on the link itself. Files.createSymbolicLink() can only create where none exist, and a 'delete + create' chain will make 2 file events.

list(path, recursive): List all files in a directory, including recursive listing if specified.

deleteRecursively(path): Delete files and directories recursively.

PathUtil:

getFileBaseName(path): Get the file base name, which is the name of the file without the file suffix. This will include leading '.' chars, but stop before the last '.' with letters or numbers after itself.

getFileSuffix(path): Get the suffix of a file name. The suffix is the content after the last '.' that has name content before itself. See getFileBaseName().

getFileName(path): Get the file name from a path, which is the content after the last '/' in the path. If the path ends with '/', it is empty.