Skip to content

Commit

Permalink
More workflow refactoring (#111)
Browse files Browse the repository at this point in the history
* Extract subprocess running logic into Subprocess
* TempDir is now a separate class
* Extract Spinner into more general StatusBar
* Merge WorkflowCommand core logic into a single method
  • Loading branch information
emilmelnikov committed Aug 14, 2023
1 parent 4aa8e3d commit 7b1e602
Show file tree
Hide file tree
Showing 4 changed files with 274 additions and 192 deletions.
74 changes: 74 additions & 0 deletions src/main/java/org/ilastik/ilastik4ij/util/StatusBar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.ilastik.ilastik4ij.util;

import org.scijava.app.StatusService;

import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
* Update status bar while functions are running or collections are iterated.
*/
public final class StatusBar implements AutoCloseable {
private static final String SPINNER_CHARS = "|/-\\";

private final ScheduledExecutorService pool;
private final StatusService status;
private final int period;

public StatusBar(StatusService statusService, int updatePeriodMillis) {
pool = Executors.newScheduledThreadPool(1);
status = Objects.requireNonNull(statusService);
period = updatePeriodMillis;
if (updatePeriodMillis <= 0) {
throw new IllegalArgumentException("update period should be positive");
}
}

@Override
public void close() {
pool.shutdown();
status.clearStatus();
}

/**
* Periodically update status bar from another thread by showing message with textual spinner.
*/
public void withSpinner(String message, Runnable func) {
Objects.requireNonNull(message);
Objects.requireNonNull(func);

final int[] index = {0};
Runnable update = () -> {
status.showStatus(message + " " + SPINNER_CHARS.charAt(index[0]));
index[0] = (index[0] + 1) % SPINNER_CHARS.length();
};

ScheduledFuture<?> sf = pool.scheduleAtFixedRate(update, 0, period, TimeUnit.MILLISECONDS);
try {
func.run();
} finally {
sf.cancel(true);
}
}

/**
* Update status bar each time a new collection item is processed.
*/
public <E> void withProgress(String message, Collection<E> items, Consumer<E> func) {
Objects.requireNonNull(message);
Objects.requireNonNull(items);
Objects.requireNonNull(func);

int progress = 0;
status.showStatus(progress++, items.size(), message);
for (E item : items) {
func.accept(item);
status.showStatus(progress++, items.size(), message);
}
}
}
82 changes: 82 additions & 0 deletions src/main/java/org/ilastik/ilastik4ij/util/Subprocess.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.ilastik.ilastik4ij.util;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.IntSupplier;

/**
* Wrapper around {@link ProcessBuilder}.
* Run subprocess with the specified arguments and environment,
* optionally capturing output and error streams.
*/
public final class Subprocess implements IntSupplier {
private final List<String> args;
private final Map<String, String> env;
private final Consumer<String> stdout;
private final Consumer<String> stderr;

public Subprocess(String arg) {
this(Collections.singletonList(arg));
}

public Subprocess(List<String> args) {
this(args, Collections.emptyMap());
}

public Subprocess(List<String> args, Map<String, String> env) {
this(args, env, null);
}

public Subprocess(List<String> args, Map<String, String> env, Consumer<String> sink) {
this(args, env, sink, sink);
}

public Subprocess(
List<String> args,
Map<String, String> env,
Consumer<String> stdout,
Consumer<String> stderr) {
this.args = Objects.requireNonNull(args);
this.env = Objects.requireNonNull(env);
this.stdout = stdout;
this.stderr = stderr;
}

@Override
public int getAsInt() {
ExecutorService pool = Executors.newFixedThreadPool(2);
ProcessBuilder pb = new ProcessBuilder(args);
pb.environment().putAll(env);
try {
Process process = pb.start();
redirect(pool, process.getInputStream(), stdout);
redirect(pool, process.getErrorStream(), stderr);
return process.waitFor();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
} finally {
pool.shutdown();
}
}

private static void redirect(ExecutorService pool, InputStream src, Consumer<String> dst) {
if (dst == null) {
return;
}
pool.submit(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(src, StandardCharsets.UTF_8))) {
reader.lines().forEachOrdered(dst);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
}
36 changes: 36 additions & 0 deletions src/main/java/org/ilastik/ilastik4ij/util/TempDir.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.ilastik.ilastik4ij.util;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;

/**
* Create a temporary directory {@link #path}, and recursively delete it on {@link #close()}.
*/
public final class TempDir implements AutoCloseable {
private static final FileVisitor<Path> RECURSIVE_DELETE = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
};

public final Path path;

public TempDir(String prefix) throws IOException {
path = Files.createTempDirectory(Objects.requireNonNull(prefix));
}

@Override
public void close() throws IOException {
Files.walkFileTree(path, RECURSIVE_DELETE);
}
}
Loading

0 comments on commit 7b1e602

Please sign in to comment.