private void addRecursively(String prefix, Path root, long timestampMs) throws IOException {
    boolean traceEnabled = LOG.isTraceEnabled();
    if (traceEnabled) LOG.trace("dir=" + root + " prefix=" + prefix);

    Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
      @Override
      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        String name = dir == root ? prefix : entryName(dir);
        if (name.isEmpty()) {
          return FileVisitResult.CONTINUE;
        }
        else if (accept(name, dir)) {
          if (traceEnabled) LOG.trace("  " + dir + " -> " + name + '/');
          writeDirectoryEntry(name, timestampMs == -1 ? attrs.lastModifiedTime().toMillis() : timestampMs);
          return FileVisitResult.CONTINUE;
        }
        else {
          return FileVisitResult.SKIP_SUBTREE;
        }
      }

      @Override
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        String name = entryName(file);
        if (accept(name, file)) {
          if (traceEnabled) LOG.trace("  " + file + " -> " + name + (attrs.isSymbolicLink() ? " symlink" : " size=" + attrs.size()));
          addFile(file, attrs, name, timestampMs);
        }
        return FileVisitResult.CONTINUE;
      }

      private String entryName(Path fileOrDir) {
        String relativeName = Compressor.entryName(root.relativize(fileOrDir).toString());
        return prefix.isEmpty() ? relativeName : prefix + '/' + relativeName;
      }
    });

    LOG.trace(".");
  }
private void addRecursively(String prefix, Path root, long timestampMs)