From ba9a586f9b2ac1dec8b8a9cb3128abb718c49c1c Mon Sep 17 00:00:00 2001
From: Jan Kozusznik <jan@kozusznik.cz>
Date: Thu, 16 Nov 2017 13:51:05 +0100
Subject: [PATCH] list of performed jobs

---
 .../cz/it4i/fiji/haas/CheckStatusOfHaaS.java  |  63 +++++++---
 .../fiji/haas/CheckStatusOfHaaSWindow.java    |  37 ++++++
 .../main/java/cz/it4i/fiji/haas/FXFrame.java  |  84 +++++++++++++
 .../src/main/java/cz/it4i/fiji/haas/Job.java  |  92 +++++++++------
 .../java/cz/it4i/fiji/haas/JobManager.java    |  60 +++++++++-
 .../java/cz/it4i/fiji/haas/RunWithHaaS.java   |   6 +-
 .../it4i/fiji/haas/ui/CheckStatusOfHaaS.fxml  |  17 +++
 .../haas/ui/CheckStatusOfHaaSController.java  | 110 ++++++++++++++++++
 8 files changed, 412 insertions(+), 57 deletions(-)
 create mode 100644 haas-imagej-client/src/main/java/cz/it4i/fiji/haas/CheckStatusOfHaaSWindow.java
 create mode 100644 haas-imagej-client/src/main/java/cz/it4i/fiji/haas/FXFrame.java
 create mode 100644 haas-imagej-client/src/main/java/cz/it4i/fiji/haas/ui/CheckStatusOfHaaS.fxml
 create mode 100644 haas-imagej-client/src/main/java/cz/it4i/fiji/haas/ui/CheckStatusOfHaaSController.java

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 b4c6a236..c448de1c 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 00000000..00928d7f
--- /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 00000000..a1bac967
--- /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 b85264ee..f5eff4f2 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 d3da7e99..278df42d 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 b120487c..2728790b 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 00000000..7596f26b
--- /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 00000000..73428a1f
--- /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":"");
+				}
+			};
+		}
+		
+	}
+}
-- 
GitLab