diff --git a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/CheckStatusOfHaaS.java b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/CheckStatusOfHaaS.java index b4c6a2367e5c428b26b6e237dbc0097a8c14b70e..c448de1cdea98a0f9c38e806009cc03f34ace84a 100644 --- a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/CheckStatusOfHaaS.java +++ b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/CheckStatusOfHaaS.java @@ -1,16 +1,24 @@ package cz.it4i.fiji.haas; +import java.awt.Frame; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import org.scijava.Context; import org.scijava.command.Command; import org.scijava.log.LogService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; +import org.scijava.ui.ApplicationFrame; +import org.scijava.ui.UIService; +import org.scijava.widget.UIComponent; +import cz.it4i.fiji.haas.JobManager.JobInfo; +import javafx.application.Platform; import net.imagej.ImageJ; + /** * * @author koz01 @@ -21,29 +29,55 @@ public class CheckStatusOfHaaS extends CommandBase implements Command { @Parameter private LogService log; - - @Parameter(label="Work directory",persist=true) + @Parameter(label = "Work directory", persist = true, style = "directory") private File workDirectory; - - @Parameter(label="Work directory",persist=true,required = false) - private File workDirectory2; - - @SuppressWarnings("unused") + + @Parameter + private UIService uiService; + + @Parameter + private Context context; + private JobManager jobManager; - - - + @Override public void run() { try { - jobManager = new JobManager(getWorkingDirectoryPath(), getGate()); + jobManager = new JobManager(getWorkingDirectoryPath(), context); + if (uiService.isHeadless()) { + downloadAll(); + } else { + CheckStatusOfHaaSWindow window; + (window = new CheckStatusOfHaaSWindow(getFrame(),context)).setVisible(true); + Platform.runLater(() -> jobManager.getJobs().forEach(job -> window.addJob(job))); + } } catch (IOException e) { log.error(e); } + + } + + private Frame getFrame() { + ApplicationFrame af = uiService.getDefaultUI().getApplicationFrame(); + if (af instanceof Frame) { + return (Frame) af; + } else if (af instanceof UIComponent) { + Object component = ((UIComponent<?>) af).getComponent(); + if (component instanceof Frame) { + return (Frame) component; + } + } + return null; + } + + private void downloadAll() { + for (JobInfo id : jobManager.getJobsNeedingDownload()) { + System.out.println("Job " + id.getId() + " needs download"); + jobManager.downloadJob(id.getId()); + } } - private Path getWorkingDirectoryPath() { return Paths.get(workDirectory.toString()); } @@ -52,9 +86,8 @@ public class CheckStatusOfHaaS extends CommandBase implements Command { // Launch ImageJ as usual. final ImageJ ij = new ImageJ(); ij.launch(args); - - //ij.command().run(CheckStatusOfHaaS.class, true); + + // ij.command().run(CheckStatusOfHaaS.class, true); } - } diff --git a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/CheckStatusOfHaaSWindow.java b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/CheckStatusOfHaaSWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..00928d7f7cb42838d1e63fbd443078421436d4b4 --- /dev/null +++ b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/CheckStatusOfHaaSWindow.java @@ -0,0 +1,37 @@ +package cz.it4i.fiji.haas; + +import java.awt.Frame; + +import org.scijava.Context; +import org.scijava.plugin.Parameter; + +import cz.it4i.fiji.haas.JobManager.JobInfo; +import cz.it4i.fiji.haas.ui.CheckStatusOfHaaSController; + +public class CheckStatusOfHaaSWindow extends FXFrame<CheckStatusOfHaaSController> { + + private static final long serialVersionUID = 1L; + + @Parameter + private Context context; + + private CheckStatusOfHaaSController controller; + public CheckStatusOfHaaSWindow(Frame applicationFrame, Context context) { + super(applicationFrame,"/cz/it4i/fiji/haas/ui/CheckStatusOfHaaS.fxml"); + this.context = context; + init(this::initController); + this.setResizable(false); + this.setTitle("Manage status of HaaS jobs"); + } + + public void addJob(JobInfo job) { + controller.addJob(job); + } + + private void initController(CheckStatusOfHaaSController controller) { + this.controller = controller; + context.inject(controller); + controller.init(); + } + +} diff --git a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/FXFrame.java b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/FXFrame.java new file mode 100644 index 0000000000000000000000000000000000000000..a1bac967f0c0ce3239701daa95de82cf77c71979 --- /dev/null +++ b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/FXFrame.java @@ -0,0 +1,84 @@ +package cz.it4i.fiji.haas; + +import java.awt.Dimension; +import java.awt.Frame; +import java.io.IOException; +import java.net.URL; +import java.util.function.Consumer; + +import javax.swing.JDialog; + +import javafx.application.Platform; +import javafx.embed.swing.JFXPanel; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; + +public class FXFrame<C> extends JDialog { + + private static final long serialVersionUID = 1L; + private JFXPanel fxPanel; + private String fxmlFile; + private Consumer<C> controlerInit; + + public FXFrame(String fxmlFile) { + this(null, fxmlFile); + } + + public FXFrame(Frame applicationFrame, String string) { + super(applicationFrame); + fxmlFile = string; + } + + /** + * Create the JFXPanel that make the link between Swing (IJ) and JavaFX plugin. + */ + protected void init( Consumer<C> controlerInit) { + this.controlerInit = controlerInit; + this.fxPanel = new JFXPanel(); + this.add(this.fxPanel); + this.setVisible(true); + + // The call to runLater() avoid a mix between JavaFX thread and Swing thread. + Platform.runLater(new Runnable() { + @Override + public void run() { + initFX(fxPanel); + } + + }); + + } + + private void initFX(JFXPanel fxPanel) { + // Init the root layout + try { + FXMLLoader loader = new FXMLLoader(); + URL res = FXFrame.class.getResource(fxmlFile); + loader.setLocation(res); + Parent rootLayout = (Parent) loader.load(); + + // Get the controller and add an ImageJ context to it. + C controller = loader.<C>getController(); + controlerInit.accept(controller); + + // Show the scene containing the root layout. + Scene scene = new Scene(rootLayout); + this.fxPanel.setScene(scene); + this.fxPanel.setVisible(true); + + // Resize the JFrame to the JavaFX scene + Dimension dim = new Dimension((int) scene.getWidth(), (int) scene.getHeight()); + this.fxPanel.setMinimumSize(dim); + this.fxPanel.setMaximumSize(dim); + this.fxPanel.setPreferredSize(dim); + //this.setSize((int) scene.getWidth(), (int) scene.getHeight()); + this.pack(); + + } catch (IOException e) { + e.printStackTrace(); +} + + } + +} diff --git a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/Job.java b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/Job.java index b85264eef33c79d6d2acf96aecd9019cb9e8a683..f5eff4f225c757875d8e87f0b9346b8963d28c9a 100644 --- a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/Job.java +++ b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/Job.java @@ -17,89 +17,109 @@ import cz.it4i.fiji.haas_java_client.JobState; public class Job { - - private static final String JOB_ID_PROPERTY = "job.id"; private static final String JOB_STATE_PROPERTY = "job.state"; + private static final String JOB_HAS_DATA_TO_DOWNLOAD_PROPERTY = "job.needDownload"; + public static boolean isJobPath(Path p) { return isValidPath(p); } - + private static String JOB_INFO_FILE = ".jobinfo"; - + private Path jobDir; private Supplier<HaaSClient> haasClientSupplier; - + private JobState state; - private ImageJGate gate; - public Job(Path path, Collection<Path> files, Supplier<HaaSClient> haasClientSupplier, ImageJGate gate) throws IOException { - this(haasClientSupplier,gate); + + private Boolean needsDownload; + + private Long jobId; + + public Job(Path path, Collection<Path> files, Supplier<HaaSClient> haasClientSupplier) + throws IOException { + this(haasClientSupplier); HaaSClient client = this.haasClientSupplier.get(); - long id = client.start(files, "TestOutRedirect", - Collections.emptyList()); + long id = client.start(files, "TestOutRedirect", Collections.emptyList()); jobDir = path.resolve("" + id); Files.createDirectory(jobDir); state = client.obtainJobInfo(id).getState(); saveJobinfo(); } - - - public Job(Path p, Supplier<HaaSClient> haasClientSupplier, ImageJGate gate) throws IOException { - this(haasClientSupplier,gate); + public Job(Path p, Supplier<HaaSClient> haasClientSupplier) throws IOException { + this(haasClientSupplier); jobDir = p; loadJobInfo(); - checkStateForDownload(); + updateState(); + } + + private Job(Supplier<HaaSClient> haasClientSupplier) { + this.haasClientSupplier = haasClientSupplier; + } + + public boolean needsDownload() { + return needsDownload != null && needsDownload; } - private synchronized void checkStateForDownload() throws IOException { + synchronized public long getJobId() { + if (jobId == null) { + jobId = getJobId(jobDir); + } + return jobId; + } + + synchronized public void updateState() throws IOException { long jobId = getJobId(); JobState actualState = haasClientSupplier.get().obtainJobInfo(jobId).getState(); - gate.getLog().info("Job: " + jobId + " is " + actualState); - if(EnumSet.of(JobState.Failed, JobState.Finished, JobState.Canceled).contains(actualState) && state != actualState) { - gate.getLog().info("Downloading data."); - haasClientSupplier.get().download(jobId, jobDir); + if (EnumSet.of(JobState.Failed, JobState.Finished, JobState.Canceled).contains(actualState) + && state != actualState) { + needsDownload = true; state = actualState; saveJobinfo(); } } - - - - private Job(Supplier<HaaSClient> haasClientSupplier, ImageJGate gate) { - this.haasClientSupplier = haasClientSupplier; - this.gate = gate; + synchronized public void download() { + if(!needsDownload()) { + throw new IllegalStateException("Job: " + getJobId() + " dosn't need download"); + } + haasClientSupplier.get().download(getJobId(), jobDir); + needsDownload = false; } + public JobState getState() { + return state; + } + private synchronized void saveJobinfo() throws IOException { - try(OutputStream ow= Files.newOutputStream(jobDir.resolve(JOB_INFO_FILE), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { + try (OutputStream ow = Files.newOutputStream(jobDir.resolve(JOB_INFO_FILE), + StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { Properties prop = new Properties(); prop.setProperty(JOB_ID_PROPERTY, "" + getJobId()); prop.setProperty(JOB_STATE_PROPERTY, "" + state); + if (needsDownload != null) { + prop.setProperty(JOB_HAS_DATA_TO_DOWNLOAD_PROPERTY, needsDownload.toString()); + } prop.store(ow, null); } } - - + private synchronized void loadJobInfo() throws IOException { - try(InputStream is= Files.newInputStream(jobDir.resolve(JOB_INFO_FILE))) { + try (InputStream is = Files.newInputStream(jobDir.resolve(JOB_INFO_FILE))) { Properties prop = new Properties(); prop.load(is); state = JobState.valueOf(prop.getProperty(JOB_STATE_PROPERTY)); assert getJobId() == Long.parseLong(prop.getProperty(JOB_ID_PROPERTY)); + if (prop.containsKey(JOB_HAS_DATA_TO_DOWNLOAD_PROPERTY)) { + needsDownload = Boolean.parseBoolean(prop.getProperty(JOB_HAS_DATA_TO_DOWNLOAD_PROPERTY)); + } } } - - private long getJobId() { - return getJobId(jobDir); - } - - private static boolean isValidPath(Path path) { diff --git a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/JobManager.java b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/JobManager.java index d3da7e99a10d812fe843ec8adc5cb989f945be05..278df42d228fafd410875624cfbee0c52efb7ffb 100644 --- a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/JobManager.java +++ b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/JobManager.java @@ -4,28 +4,37 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedList; +import org.scijava.Context; + import cz.it4i.fiji.haas_java_client.HaaSClient; +import cz.it4i.fiji.haas_java_client.JobState; public class JobManager { + + private Path workDirectory; private Collection<Job> jobs = new LinkedList<>(); private HaaSClient haasClient; - private ImageJGate gate; + private Context context; - public JobManager(Path workDirectory, ImageJGate gate) throws IOException { + + + public JobManager(Path workDirectory, Context ctx) throws IOException { super(); - this.gate = gate; + this.context = ctx; this.workDirectory = workDirectory; + context.inject(this); Files.list(this.workDirectory).filter(p -> Files.isDirectory(p) && Job.isJobPath(p)) .forEach(p -> { try { - jobs.add(new Job(p,this::getHaasClient, gate)); + jobs.add(inject(new Job(p,this::getHaasClient))); } catch (IOException e) { e.printStackTrace(); } @@ -33,8 +42,28 @@ public class JobManager { } + private Job inject(Job job) { + context.inject(job); + return job; + } + public void startJob(Path path, Collection<Path> files) throws IOException { - jobs.add(new Job(path, files,this::getHaasClient,gate)); + jobs.add(new Job(path, files,this::getHaasClient)); + } + + public Iterable<JobInfo> getJobsNeedingDownload() { + return ()->jobs.stream().filter(j->j.needsDownload()).map(j->new JobInfo(j)).iterator(); + } + + public Iterable<JobInfo> getJobs() { + return ()->jobs.stream().map(j->new JobInfo(j)).iterator(); + } + + public void downloadJob(Long id) { + Iterator<Job> job =jobs.stream().filter(j->j.getJobId() == id).iterator(); + assert job.hasNext(); + job.next().download(); + } private HaaSClient getHaasClient() { @@ -44,4 +73,25 @@ public class JobManager { return haasClient; } + public static class JobInfo { + + private Job job; + + + public JobInfo(Job job) { + this.job = job; + } + + public Long getId() { + return job.getJobId(); + } + + public JobState getState() { + return job.getState(); + } + + public boolean needsDownload() { + return job.needsDownload(); + } + } } diff --git a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/RunWithHaaS.java b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/RunWithHaaS.java index b120487c72a1c9256b70f250df37f5cd87b515f1..2728790bda49dfbe12d05ca9e5aa8c55dcaddcd1 100644 --- a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/RunWithHaaS.java +++ b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/RunWithHaaS.java @@ -8,6 +8,7 @@ import java.nio.file.Paths; import java.util.Collection; import java.util.stream.Collectors; +import org.scijava.Context; import org.scijava.command.Command; import org.scijava.log.LogService; import org.scijava.plugin.Parameter; @@ -31,12 +32,15 @@ public class RunWithHaaS extends CommandBase implements Command { @Parameter(label="Data directory") private File dataDirectory; + @Parameter + private Context context; + private JobManager jobManager; @Override public void run() { try { - jobManager = new JobManager(getWorkingDirectoryPath(), getGate()); + jobManager = new JobManager(getWorkingDirectoryPath(), context); jobManager.startJob(getWorkingDirectoryPath(),getContent(dataDirectory)); } catch (IOException e) { log.error(e); diff --git a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/ui/CheckStatusOfHaaS.fxml b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/ui/CheckStatusOfHaaS.fxml new file mode 100644 index 0000000000000000000000000000000000000000..7596f26bd38bb4e1ca8e9e763168b24b80fb1828 --- /dev/null +++ b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/ui/CheckStatusOfHaaS.fxml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.TableColumn?> +<?import javafx.scene.control.TableView?> +<?import javafx.scene.layout.BorderPane?> + + +<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="cz.it4i.fiji.haas.ui.CheckStatusOfHaaSController"> + <center> + <TableView fx:id="jobs" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER"> + <columns> + <TableColumn prefWidth="75.0" text="Job Id" /> + <TableColumn prefWidth="145.0" text="Status" /> + </columns> + </TableView> + </center> +</BorderPane> diff --git a/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/ui/CheckStatusOfHaaSController.java b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/ui/CheckStatusOfHaaSController.java new file mode 100644 index 0000000000000000000000000000000000000000..73428a1f1eab7b1d2f2d1e573552534a38c71c15 --- /dev/null +++ b/haas-imagej-client/src/main/java/cz/it4i/fiji/haas/ui/CheckStatusOfHaaSController.java @@ -0,0 +1,110 @@ +package cz.it4i.fiji.haas.ui; + + + +import org.scijava.log.LogService; +import org.scijava.plugin.Parameter; + +import cz.it4i.fiji.haas.JobManager.JobInfo; +import javafx.beans.InvalidationListener; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumn.CellDataFeatures; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.ContextMenuEvent; +import javafx.util.Callback; + +public class CheckStatusOfHaaSController { + + + @Parameter + private LogService logService; + + @FXML + private TableView<JobInfo> jobs; + + public CheckStatusOfHaaSController() { + + } + + public void addJob(JobInfo job) { + jobs.getItems().add(job); + } + + @SuppressWarnings("unchecked") + public void init() { + ContextMenu cm = new ContextMenu(); + MenuItem download = new MenuItem("Download"); + cm.getItems().add(download); + ((TableColumn<JobInfo, String>)jobs.getColumns().get(0)).setCellValueFactory(new PropertyValueFactory<JobInfo,String>("id")); + ((TableColumn<JobInfo, String>)jobs.getColumns().get(1)).setCellValueFactory(new P_Factory()); + jobs.setContextMenu(cm); + jobs.setOnContextMenuRequested(new EventHandler<ContextMenuEvent>() { + + @Override + public void handle(ContextMenuEvent event) { + if(jobs.getSelectionModel().getSelectedCells().size() < 1) { + return; + } + int row = jobs.getSelectionModel().getSelectedCells().get(0).getRow(); + + if(0 >= row && row < jobs.getItems().size() && jobs.getItems().get(row).needsDownload()) { + download.setDisable(false); + } else { + download.setDisable(true); + } + + + } + }); + logService.info("init"); + } + + + + private class P_Factory implements Callback<CellDataFeatures<JobInfo, String>, ObservableValue<String>> { + + @Override + public ObservableValue<String> call(final CellDataFeatures<JobInfo, String> param) { + return new ObservableValue<String>() { + + @Override + public void addListener(InvalidationListener listener) { + + + } + + @Override + public void removeListener(InvalidationListener listener) { + + + } + + @Override + public void addListener(ChangeListener<? super String> listener) { + + + } + + @Override + public void removeListener(ChangeListener<? super String> listener) { + + + } + + @Override + public String getValue() { + JobInfo ji = param.getValue(); + return ji.getState().toString() + (ji.needsDownload()?" - needs download":""); + } + }; + } + + } +}