diff --git a/app/Global.java b/app/Global.java index a2aa5df..37c9967 100644 --- a/app/Global.java +++ b/app/Global.java @@ -138,7 +138,7 @@ public void run() { for (Job job : jobs) { if (job.getFinished() != null && job.getFinished().before(timeoutDate)) { Logger.info("Deleting old job: "+job.getId()+" ("+job.getNicename()+")"); - job.delete(); + job.deleteFromEngineAndWebUi(); } } } catch (javax.persistence.PersistenceException e) { @@ -175,7 +175,7 @@ public void run() { Date lastAccessed = Job.lastAccessed.get(webUiJob.getId()); final int deleteNewJobsAfterSeconds = 600; if (lastAccessed == null || (new Date().getTime() - lastAccessed.getTime())/1000 > deleteNewJobsAfterSeconds) { - webUiJob.delete(); + webUiJob.deleteFromEngineAndWebUi(); } continue; @@ -197,7 +197,7 @@ public void run() { */ //if ( should delete job ) { //Logger.info("Deleting job that no longer exists in the Pipeline engine: "+webUiJob.getId()+" ("+webUiJob.getEngineId()+" - "+webUiJob.getNicename()+")"); - //webUiJob.delete(); + //webUiJob.deleteFromEngineAndWebUi(); //} } } diff --git a/app/assets/stylesheets/main.less b/app/assets/stylesheets/main.less index f8c5a16..6f54195 100644 --- a/app/assets/stylesheets/main.less +++ b/app/assets/stylesheets/main.less @@ -388,4 +388,25 @@ body { font-size: 62.5%; font-family:Arial, Helvetica, sans-serif } .control-group .help-block.no-validation-color, .control-group .help-inline.no-validation-color { color: #595959; +} + +/* #### enum datatype ####*/ + +dl.enum-terms dt em { + display: none; +} + +dl.enum-terms dt.enum-term-selected em { + display: inline; +} + +dl.enum-terms dt.enum-term-selected { + font-size: 120%; +} + +/* #### highlight inputs with errors #### */ + +input:invalid { + border-color: #b94a48; + color: #b94a48; } \ No newline at end of file diff --git a/app/controllers/Administrator.java b/app/controllers/Administrator.java index 4972873..140b679 100644 --- a/app/controllers/Administrator.java +++ b/app/controllers/Administrator.java @@ -220,8 +220,8 @@ public static void save(Form filledForm) { templatesPath += System.getProperty("file.separator"); Setting.set("uploads", uploadPath); - Setting.set("jobs", uploadPath); - Setting.set("templates", uploadPath); + Setting.set("jobs", jobsPath); + Setting.set("templates", templatesPath); } public String getUploaddir() { diff --git a/app/controllers/Jobs.java b/app/controllers/Jobs.java index 2f79ded..a0ed52a 100644 --- a/app/controllers/Jobs.java +++ b/app/controllers/Jobs.java @@ -5,6 +5,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystemException; +import java.nio.file.StandardCopyOption; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -219,21 +221,23 @@ public static Result restart(Long jobId) { webuiJob.cancelPushNotifications(); if (webuiJob.getEngineId() != null) { - Logger.info("deleting old job: "+webuiJob.getEngineId()); - Application.ws.deleteJob(webuiJob.getEngineId()); + Logger.debug("Deleting old job before restarting job: "+webuiJob.getEngineId()); + org.daisy.pipeline.client.models.Job engineJob = Application.ws.getJob(webuiJob.getEngineId(), 0); + if (engineJob != null) { + boolean deleted = Application.ws.deleteJob(webuiJob.getEngineId()); + if (!deleted) { + Logger.info("unable to delete old job: "+webuiJob.getEngineId()); + flash("error", "An error occured while trying to delete the previous job. Please try creating a new job instead."); + return redirect(routes.Jobs.getJob(jobId)); + } + } webuiJob.setEngineId(null); } - webuiJob.setStatus("NEW"); - webuiJob.setNotifiedComplete(false); - org.daisy.pipeline.client.models.Job clientlibJob = webuiJob.asJob(); - clientlibJob.setStatus(org.daisy.pipeline.client.models.Job.Status.IDLE); - webuiJob.setStatus("IDLE"); - webuiJob.setStarted(null); - webuiJob.setFinished(null); - webuiJob.save(); + webuiJob.reset(); - Logger.info("------------------------------ Posting job... ------------------------------"); + Logger.debug("------------------------------ Posting job... ------------------------------"); + org.daisy.pipeline.client.models.Job clientlibJob = webuiJob.asJob(); Logger.debug(XML.toString(clientlibJob.toJobRequestXml(true))); clientlibJob = Application.ws.postJob(clientlibJob); if (clientlibJob == null) { @@ -444,13 +448,16 @@ public static Result getJobJson(Long id) { Map output = new HashMap(); output.put("webuiJob", webuiJob); - - org.daisy.pipeline.client.models.Job clientlibJob; + org.daisy.pipeline.client.models.Job clientlibJob = null; + boolean jobAvailableInEngine = false; if (webuiJob.getEngineId() != null) { clientlibJob = webuiJob.getJobFromEngine(0); - } else { + jobAvailableInEngine = clientlibJob != null; + } + if (clientlibJob == null) { clientlibJob = webuiJob.asJob(); } + output.put("jobAvailableInEngine", jobAvailableInEngine); if (clientlibJob == null) { Logger.error("An error occured while retrieving the job"); @@ -518,7 +525,7 @@ public static Result getResult(Long id, String href) { filename = id+""; } } - response().setHeader("Content-Disposition", "attachment; filename=\""+filename+"\""); + response().setHeader("Content-Disposition", "inline; filename=\""+filename+"\""); File resultFile = result.asFile(); if (resultFile == null || !resultFile.exists()) { @@ -637,7 +644,7 @@ public static Result postJob(Long jobId) { return internalServerError("Could not read form data"); } - String scriptId = params.get("id")[0]; + String scriptId = params.get("_id")[0]; if ("false".equals(UserSetting.get(user.id, "scriptEnabled-"+scriptId))) { return forbidden(); } @@ -657,35 +664,15 @@ public static Result postJob(Long jobId) { org.daisy.pipeline.client.models.Job clientlibJob = job.asJob(); clientlibJob.setScript(script); - - for (String paramName : params.keySet()) { - String[] values = params.get(paramName); - Argument arg = script.getArgument(paramName); - if (arg != null) { - arg.clear(); - if (values.length == 1 && "".equals(values[0])) { - // don't add value; treat empty strings as unset values - } else { - for (String value : values) { - arg.add(value); - } - } - } else { - Logger.warn(paramName+" is not a valid argument for the script "+script.getNicename()); - } - } - // Parse and validate the submitted form (also create any necessary output directories in case of local mode) - // TODO: see if clientlib can be used for validation instead + // Parse the submitted form Scripts.ScriptForm scriptForm = new Scripts.ScriptForm(user.id, script, params); scriptForm.validate(); // If we're posting a template; delegate further processing to Templates.postTemplate - for (String paramName : params.keySet()) { - if (paramName.startsWith("submit_template")) { - Logger.debug("posted job is a template"); - return Templates.postTemplate(user, job, clientlibJob); - } + if (params.containsKey("_submit_template")) { + Logger.debug("posted job is a template"); + return Templates.postTemplate(user, job, clientlibJob); } Logger.debug("posted job is not a template"); @@ -744,8 +731,13 @@ public static Result delete(Long jobId) { } Logger.debug("deleting "+jobId); - webuiJob.delete(); - return ok(); + boolean deletedSuccessfully = webuiJob.deleteFromEngineAndWebUi(); + if (deletedSuccessfully) { + return ok(); + } else { + flash("error", "An error occured while trying to delete the job. Please try creating a new job instead."); + return internalServerError(); + } } public static Result postUpload(Long jobId) { @@ -771,7 +763,19 @@ public static Result postUpload(Long jobId) { Logger.info("uploaded file: "+file.getFile()); // rename the uploaded file so that it is not automatically deleted by Play! File renamedFile = new File(file.getFile().getParentFile(), file.getFile().getName()+"_"); - file.getFile().renameTo(renamedFile); + try { + java.nio.file.Files.move(file.getFile().toPath(), renamedFile.toPath()); + + } catch (IOException e) { + Logger.error("Could not rename uploaded file. Might be a problem with permissions. Trying copying instead...", e); + try { + Files.copy(file.getFile(), renamedFile); + + } catch (IOException ex) { + Logger.error("Could not copy uploaded file.", ex); + return internalServerError("Could not rename or make a copy of uploaded file."); + } + } Logger.debug(request().method()+" | "+file.getContentType()+" | "+file.getFilename()+" | "+renamedFile.getAbsolutePath()); diff --git a/app/controllers/Log.java b/app/controllers/Log.java index 3ba3487..fd74f90 100644 --- a/app/controllers/Log.java +++ b/app/controllers/Log.java @@ -105,6 +105,10 @@ public static String logText(String title, List>> additi { List daisyPipelineLog = new ArrayList(); File daisyPipelineLogFile = new File(new File(new File(controllers.Application.DP2DATA).getParentFile(), "daisy-pipeline2"), "daisy-pipeline.log"); + if (!daisyPipelineLogFile.exists()) { + // try alternative location + daisyPipelineLogFile = new File("/var/log/daisy-pipeline2/daisy-pipeline.log"); + } try { FileInputStream stream = new FileInputStream(daisyPipelineLogFile); try { @@ -131,6 +135,10 @@ public static String logText(String title, List>> additi { List derbyLog = new ArrayList(); File derbyLogFile = new File(new File(new File(controllers.Application.DP2DATA).getParentFile(), "daisy-pipeline2"), "derby.log"); + if (!derbyLogFile.exists()) { + // try alternative location + derbyLogFile = new File("/var/log/daisy-pipeline2/derby.log"); + } if (derbyLogFile.exists()) { try { FileInputStream stream = new FileInputStream(derbyLogFile); @@ -152,7 +160,6 @@ public static String logText(String title, List>> additi } } else { derbyLog.add("There is no Derby log file at: "+derbyLogFile.getAbsolutePath()); - derbyLog.add("This probably means that you use a MySQL database instead."); } Map> log = new HashMap>(); log.put("Pipeline 2 Engine - derby.log", derbyLog); diff --git a/app/controllers/Scripts.java b/app/controllers/Scripts.java index 61c297f..a337fc3 100644 --- a/app/controllers/Scripts.java +++ b/app/controllers/Scripts.java @@ -83,51 +83,43 @@ public static String chooseWidget(Argument arg) { public static class ScriptForm { - //public Script script; public Map> errors; public String guestEmail; - // kind position part name - private static final Pattern PARAM_NAME = Pattern.compile("^([A-Za-z]+)(\\d*)([A-Za-z]*?)-(.*)$"); - public ScriptForm(Long userId, Script script, Map params) { - //this.script = script; // Parse all arguments for (String param : params.keySet()) { - Matcher matcher = PARAM_NAME.matcher(param); - if (!matcher.find()) { - Logger.debug("Unable to parse argument parameter: "+param); - } else { - String kind = matcher.group(1); - String name = matcher.group(4); - Logger.debug("script form: "+kind+": "+name); - - Argument argument = script.getArgument(name); - if (argument == null) { - Logger.debug("'"+name+"' is not an argument for the script '"+script.getId()+"'; ignoring it"); + if (param == null || param.startsWith("_")) continue; // skip arguments starting with an underscore + Argument argument = script.getArgument(param); + if (argument == null) { + Logger.debug("'"+param+"' is not an argument for the script '"+script.getId()+"'; ignoring it"); + continue; + } + + argument.clear(); + for (String value : params.get(param)) { + String type = argument.getType(); + if ("".equals(value) && ("anyDirURI".equals(type) || "anyFileURI".equals(type) || "anyURI".equals(type))) { continue; } - - for (int i = 0; i < params.get(param).length; i++) { - argument.add(params.get(param)[i]); - } + argument.add(value); } } - if (userId < 0 && params.containsKey("guest-email")) - this.guestEmail = params.get("guest-email")[0]; + if (userId < 0 && params.containsKey("_guest-email")) + this.guestEmail = params.get("_guest-email")[0]; this.errors = new HashMap>(); } public void validate() { if (guestEmail != null && !"".equals(guestEmail) && !guestEmail.matches("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$")) { - addError("guest-email", "Please enter a valid e-mail address."); + addError("_guest-email", "Please enter a valid e-mail address."); } - // TODO: validate arguments + // TODO: validate arguments (consider implementing validation in pipeline-clientlib-java) } public boolean hasErrors() { diff --git a/app/controllers/Templates.java b/app/controllers/Templates.java index b807547..b270606 100644 --- a/app/controllers/Templates.java +++ b/app/controllers/Templates.java @@ -90,7 +90,7 @@ public static Result postTemplate(User user, Job job, org.daisy.pipeline.client. Template template = Template.create(clientlibJob, user); if ("NEW".equals(job.getStatus())) { - job.delete(); + job.deleteFromEngineAndWebUi(); } String highlightTemplateName = template.name == null ? "" : ""+template.name; @@ -110,6 +110,7 @@ public static Result postTemplate(User user, Job job, org.daisy.pipeline.client. Logger.debug("return redirect(controllers.routes.Templates.getTemplates());"); Logger.debug("highlightTemplate:"+template.name); Logger.debug("highlightTemplateOwner:"+template.ownerId); + flash("highlightNewTemplate", "true"); flash("highlightTemplateName", highlightTemplateName); flash("highlightTemplateOwner", ""+highlightTemplateOwner); return redirect(controllers.routes.Templates.getTemplates()); diff --git a/app/models/Job.java b/app/models/Job.java index 4bd9902..e5aca23 100644 --- a/app/models/Job.java +++ b/app/models/Job.java @@ -16,6 +16,7 @@ import javax.persistence.Id; import javax.persistence.Transient; +import org.daisy.pipeline.client.Pipeline2Exception; import org.daisy.pipeline.client.Pipeline2Logger; import org.daisy.pipeline.client.filestorage.JobStorage; import org.daisy.pipeline.client.models.Job.Status; @@ -29,6 +30,7 @@ import com.avaje.ebean.Model; import controllers.Application; +import controllers.routes; @Entity public class Job extends Model implements Comparable { @@ -202,6 +204,7 @@ public void run() { } Logger.debug(" saving"); + webUiJob.setJob(job); webUiJob.save(); } @@ -277,14 +280,15 @@ public static Object jsonifiableResults(org.daisy.pipeline.client.models.Job job return jsonResults; } - @Override - public void delete() { + /** Same as `delete` but with boolean return value. Returns false if unable to delete the job in the engine. */ + public boolean deleteFromEngineAndWebUi() { try { - if (Status.valueOf(status) != null) { + if (Status.valueOf(status) != null && Application.ws.getJob(this.engineId, 0) != null) { Logger.debug("deleting "+this.id+" (sending DELETE request)"); boolean success = Application.ws.deleteJob(this.engineId); if (!success) { Pipeline2Logger.logger().error("An error occured when trying to delete job "+this.id+" ("+this.engineId+") from the Pipeline 2 Engine"); + return false; // don't delete Web UI job when an error occured attempting to delete the engine job } } @@ -294,6 +298,12 @@ public void delete() { asJob().getJobStorage().delete(); lastAccessed.remove(id); super.delete(); + return true; + } + + @Override + public void delete() { + deleteFromEngineAndWebUi(); } /* @@ -316,6 +326,7 @@ public void save() { // save to job storage as well if (asJob() != null) { + Logger.debug("save to job storage"); asJob().getJobStorage().save(); jobUpdateHelper(); } @@ -435,7 +446,7 @@ public void setJob(org.daisy.pipeline.client.models.Job job) { /** Use this method to get the job from the engine to ensure that the XML in the webuis job storage is always up to date */ public org.daisy.pipeline.client.models.Job getJobFromEngine(int fromSequence) { - Logger.debug("Getting job from engine: "+engineId); // TODO: change to debug + Logger.debug("Getting job from engine: "+engineId); if (engineId == null) { return null; } @@ -551,5 +562,25 @@ public boolean isNotifiedComplete() { public void setNotifiedComplete(boolean notifiedComplete) { this.notifiedComplete = notifiedComplete; } + + public void reset() { + setStatus("NEW"); + setNotifiedComplete(false); + asJob(); + clientlibJob.setStatus(org.daisy.pipeline.client.models.Job.Status.IDLE); + clientlibJob.setMessages(null); + clientlibJob.setResults(null, null); + JobStorage jobStorage = clientlibJob.getJobStorage(); + try { + clientlibJob = new org.daisy.pipeline.client.models.Job(clientlibJob.toXml()); + } catch (Pipeline2Exception e) { + Logger.error("An error occured when trying to reset the job", e); + } + clientlibJob.setJobStorage(jobStorage); + setStatus("IDLE"); + setStarted(null); + setFinished(null); + save(); + } } diff --git a/app/models/User.java b/app/models/User.java index 9bfdb11..7c0024c 100644 --- a/app/models/User.java +++ b/app/models/User.java @@ -348,7 +348,7 @@ public void delete() { try { List jobs = getJobs(); for (Job job : jobs) - job.delete(); + job.deleteFromEngineAndWebUi(); super.delete(); } catch (javax.persistence.OptimisticLockException e) { Logger.warn("Could not delete user "+this.id+" ("+this.name+" / "+this.email+")", e); diff --git a/app/utils/ContentType.java b/app/utils/ContentType.java index 7175359..1669208 100644 --- a/app/utils/ContentType.java +++ b/app/utils/ContentType.java @@ -1119,6 +1119,7 @@ private static String[] parseAttribute(String element) { extMap.put("ics", "text/calendar"); extMap.put("ifb", "text/calendar"); extMap.put("css", "text/css"); + extMap.put("scss", "text/scss"); extMap.put("csv", "text/csv"); extMap.put("html", "text/html"); extMap.put("htm", "text/html"); diff --git a/app/views/Administrator/Forms/setMaintenance.scala.html b/app/views/Administrator/Forms/setMaintenance.scala.html index 48c886b..8a587b9 100644 --- a/app/views/Administrator/Forms/setMaintenance.scala.html +++ b/app/views/Administrator/Forms/setMaintenance.scala.html @@ -9,8 +9,16 @@
+
+ + +
+
+
diff --git a/app/views/Jobs/getJob.scala.html b/app/views/Jobs/getJob.scala.html index d6267c3..36482f6 100644 --- a/app/views/Jobs/getJob.scala.html +++ b/app/views/Jobs/getJob.scala.html @@ -164,9 +164,11 @@

Results

Execution Log

+

@@ -367,7 +369,7 @@

Input files and parameters

$("#started").html(typeof job.webuiJob.started === "number" ? new Date(job.webuiJob.started)+"" : " "); $("#finished").html(typeof job.webuiJob.finished === "number" ? new Date(job.webuiJob.finished)+"" : " "); - $("#engine-id").html(job.engineJob != null && typeof job.engineJob.id === "string" ? job.engineJob.id : job.webuiJob.engineId); + $("#engine-id").html(job.webuiJob.engineId); $("#header-nicename, #nicename").html(job.webuiJob.nicename); $("#scriptid").html(job.webuiJob.scriptId); $("#scriptname").html(job.webuiJob.scriptName); @@ -378,17 +380,23 @@

Input files and parameters

$("#status-"+job.webuiJob.status.toLowerCase()).show(); $("#message-list li").remove(); - if (job.engineJob !== undefined) { - if (job.engineJob.messages instanceof Array) { - for (var m = 0; m < job.engineJob.messages.length; m++) { - addMessage(job.engineJob.messages[m]); - } + if (job.engineJob.messages instanceof Array) { + for (var m = 0; m < job.engineJob.messages.length; m++) { + addMessage(job.engineJob.messages[m]); } } $("#arguments").html(argumentsToHtml(job.engineJob.arguments)); - updateResults(job.results); + if (job.jobAvailableInEngine) { + $("#engine-id").html(job.engineJob.id); + updateResults(job.results); + + } else { + $("#results").hide(); + $("#detailed-log").hide(); + $(".container:has(h1)").prepend("
Result files are not available. Please restart the job.
"); + } } }); }); diff --git a/app/views/Jobs/getJobs.scala.html b/app/views/Jobs/getJobs.scala.html index 78b0e60..160e09f 100644 --- a/app/views/Jobs/getJobs.scala.html +++ b/app/views/Jobs/getJobs.scala.html @@ -150,6 +150,7 @@

Jobs

}, error: function() { $("#job-delete-"+jobId).show(); + // TODO: show alert somehow about the failed job deletion? }, context: this }); diff --git a/app/views/Jobs/getScript.scala.html b/app/views/Jobs/getScript.scala.html index 8f8f673..9735d14 100644 --- a/app/views/Jobs/getScript.scala.html +++ b/app/views/Jobs/getScript.scala.html @@ -11,8 +11,8 @@

@script.getNicename()


- - + + diff --git a/app/views/Templates/getTemplates.scala.html b/app/views/Templates/getTemplates.scala.html index 6eba331..0d57929 100644 --- a/app/views/Templates/getTemplates.scala.html +++ b/app/views/Templates/getTemplates.scala.html @@ -2,6 +2,13 @@ @main("Templates", "templates") { +@if(Controller.flash("highlightNewTemplate")){ + +} +

Templates

Your templates @@ -124,7 +131,6 @@

Templates

html += '
'+"\n"; html += '
'+"\n"; - console.log(template.inputs); html += argumentsToHtml(template.inputs); html += '
'+"\n"; diff --git a/build.sbt b/build.sbt index cab88f3..07c1767 100644 --- a/build.sbt +++ b/build.sbt @@ -97,7 +97,7 @@ libraryDependencies ++= Seq( "org.hibernate" % "hibernate-entitymanager" % "4.3.10.Final", "org.avaje.ebeanorm" % "avaje-ebeanorm-api" % "3.1.1", "org.apache.derby" % "derby" % "10.11.1.1", - "org.daisy.pipeline" % "clientlib-java" % "4.3.0", + "org.daisy.pipeline" % "clientlib-java" % "4.4.1", "org.daisy.pipeline" % "clientlib-java-httpclient" % "1.1.0", "org.apache.commons" % "commons-compress" % "1.9", "org.apache.commons" % "commons-email" % "1.4",