borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
@@ -35,7 +35,7 @@ .setParams("--version") .setDescription("Getting borg version."); JobResult<String> jobResult = execute(command).getResult(); if (jobResult.getStatus() != JobResult.Status.OK) { if (jobResult == null || jobResult.getStatus() != JobResult.Status.OK) { return null; } String version = jobResult.getResultObject(); @@ -56,7 +56,7 @@ .setParams("--json") // --progress has no effect. .setDescription("Loading info of repo '" + repoConfig.getDisplayName() + "'."); JobResult<String> jobResult = execute(command).getResult(); if (jobResult.getStatus() != JobResult.Status.OK) { if (jobResult == null || jobResult.getStatus() != JobResult.Status.OK) { return null; } String result = jobResult.getResultObject(); @@ -89,7 +89,7 @@ .setParams("--json") // --progress has no effect. .setDescription("Loading list of archives of repo '" + repoConfig.getDisplayName() + "'."); JobResult<String> jobResult = execute(command).getResult(); if (jobResult.getStatus() != JobResult.Status.OK) { if (jobResult == null || jobResult.getStatus() != JobResult.Status.OK) { log.error("Can't load archives from repo '" + repository.getName() + "'."); return; } @@ -131,7 +131,7 @@ .setParams("--json", "--log-json", "--progress") .setDescription("Loading info of archive '" + archive.getName() + "' of repo '" + repoConfig.getDisplayName() + "'."); JobResult<String> jobResult = execute(command).getResult(); if (jobResult.getStatus() != JobResult.Status.OK) { if (jobResult == null || jobResult.getStatus() != JobResult.Status.OK) { return; } String result = jobResult.getResultObject(); borgbutler-core/src/main/java/de/micromata/borgbutler/BorgJob.java
@@ -108,6 +108,7 @@ @Override public BorgJob<?> clone() { BorgJob<?> clone = new BorgJob<>(); clone.setUniqueJobNumber(getUniqueJobNumber()); clone.setTitle(getTitle()); clone.setExecuteStarted(isExecuteStarted()); clone.setCommandLineAsString(getCommandLineAsString()); borgbutler-core/src/main/java/de/micromata/borgbutler/BorgQueueExecutor.java
@@ -28,11 +28,33 @@ */ public List<String> getRepos() { List<String> list = new ArrayList<>(); list.addAll(queueMap.keySet()); synchronized (queueMap) { list.addAll(queueMap.keySet()); } Collections.sort(list); return list; } public void cancelJob(long uniqueJobNumber) { AbstractJob<?> job = getQueuedJobByUniqueJobNumber(uniqueJobNumber); if (job == null) { log.info("Can't cancel job #" + uniqueJobNumber + ". Not found as queued job (may-be already cancelled or finished). Nothing to do."); return; } job.cancel(); } private AbstractJob<?> getQueuedJobByUniqueJobNumber(long uniqueJobNumber) { Iterator<JobQueue<String>> it = queueMap.values().iterator(); while (it.hasNext()) { AbstractJob<?> job = it.next().getQueuedJobByUniqueJobNumber(uniqueJobNumber); if (job != null) { return job; } } return null; } /** * For displaying purposes. * @@ -56,7 +78,9 @@ } private JobQueue<String> getQueue(String repo) { return queueMap.get(getQueueName(repo)); synchronized (queueMap) { return queueMap.get(getQueueName(repo)); } } private JobQueue<String> ensureAndGetQueue(String repo) { borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractCommandLineJob.java
@@ -124,7 +124,7 @@ @Override protected void cancelRunningProcess() { if (watchdog != null) { log.info("Cancelling job: " + getId()); log.info("Cancelling job #" + getUniqueJobNumber() + ": " + getId()); watchdog.destroyProcess(); watchdog = null; setCancelled(); borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractJob.java
@@ -27,8 +27,8 @@ @Setter(AccessLevel.PACKAGE) private Future<JobResult<T>> future; @Getter @Setter(AccessLevel.PACKAGE) private long uniqueJobNumber; @Setter(AccessLevel.PROTECTED) private long uniqueJobNumber = -1; public void cancel() { if (this.getStatus() == Status.QUEUED) { borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/JobQueue.java
@@ -29,6 +29,22 @@ } /** * Searches only for queued jobs (not done jobs). * @param uniqueJobNumber * @return The job if any job with the given unique job number is queued, otherwise null. */ public AbstractJob<T> getQueuedJobByUniqueJobNumber(long uniqueJobNumber) { Iterator<AbstractJob<T>> it = queue.iterator(); while (it.hasNext()) { AbstractJob<T> job = it.next(); if (job.getUniqueJobNumber() == uniqueJobNumber) { return job; } } return null; } /** * Appends the job if not alread in the queue. Starts the execution if no execution thread is already running. * * @param job borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java
@@ -59,34 +59,43 @@ } @Path("/cancel") @PUT @Produces(MediaType.APPLICATION_JSON) @GET /** * @param testMode If true, then a test job list is created. * @param prettyPrinter If true then the json output will be in pretty format. * @return Job queues as json string. * @see JsonUtils#toJson(Object, boolean) * @param uniqueJobNumberString The id of the job to cancel. */ public void cancelJob(@QueryParam("job") String job, @QueryParam("prettyPrinter") boolean prettyPrinter) { log.info("Cancelling job"); public void cancelJob(@QueryParam("uniqueJobNumber") String uniqueJobNumberString) { Long uniqueJobNumber = null; try { uniqueJobNumber = Long.parseLong(uniqueJobNumberString); } catch (NumberFormatException ex) { log.error("Can't cancel job, because unique job number couln't be parsed (long value expected): " + uniqueJobNumberString); return; } BorgQueueExecutor.getInstance().cancelJob(uniqueJobNumber); } /** * Only for test purposes and development. * * @param prettyPrinter * @return */ private String returnTestList(boolean prettyPrinter) { if (testList == null) { testList = new ArrayList<>(); long uniqueJobNumber = 100000; JsonJobQueue queue = new JsonJobQueue().setRepo("My Computer"); addTestJob(queue, "info", "my-macbook", 0, 2342); addTestJob(queue, "list", "my-macbook", -1, -1); addTestJob(queue, "info", "my-macbook", 0, 2342) .setUniqueJobNumber(uniqueJobNumber++); addTestJob(queue, "list", "my-macbook", -1, -1) .setUniqueJobNumber(uniqueJobNumber++); testList.add(queue); queue = new JsonJobQueue().setRepo("My Server"); addTestJob(queue, "list", "my-server", 0, 1135821); addTestJob(queue, "info", "my-server", -1, -1); addTestJob(queue, "list", "my-server", 0, 1135821) .setUniqueJobNumber(uniqueJobNumber++); addTestJob(queue, "info", "my-server", -1, -1) .setUniqueJobNumber(uniqueJobNumber++); testList.add(queue); } else { for (JsonJobQueue jobQueue : testList) { @@ -117,6 +126,7 @@ /** * Only for test purposes and development. * * @param queue * @param operation * @param host borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/queue/JsonJob.java
@@ -31,11 +31,15 @@ @Getter @Setter private String commandLineAsString; @Getter @Setter private long uniqueJobNumber; public JsonJob() { } public JsonJob(BorgJob<?> borgJob) { this.uniqueJobNumber = borgJob.getUniqueJobNumber(); this.cancellationRequested = borgJob.isCancellationRequested(); this.status = borgJob.getStatus(); this.title = borgJob.getTitle(); borgbutler-webapp/src/components/views/jobs/Job.jsx
@@ -1,14 +1,22 @@ import React from 'react'; import {Button, Card, CardBody, Collapse, Progress} from 'reactstrap'; import {IconCancelJob} from '../../general/IconComponents' import {IconCancel} from '../../general/IconComponents' import {getRestServiceUrl} from "../../../utilities/global"; class Job extends React.Component { constructor(props) { super(props); this.toggle = this.toggle.bind(this); this.cacnelJob = this.cancelJob.bind(this); this.state = {collapse: false}; } cancelJob = (jobId) => { fetch(getRestServiceUrl('jobs/cancel', { uniqueJobNumber: jobId })); }; toggle() { this.setState({collapse: !this.state.collapse}); } @@ -25,15 +33,31 @@ } else { content = <Progress color={'info'} value={100}>{job.status}</Progress> } let cancelDisabled = undefined; if (job.status !== 'RUNNING' && job.status !== 'QUEUED') { cancelDisabled = true; } return ( <div> <Button color="link" onClick={this.toggle}>{job.description}</Button> <div>{content}<IconCancelJob/></div> <div>{content} <Button color={'danger'} onClick={() => this.cancelJob(job.uniqueJobNumber)} disabled={cancelDisabled}><IconCancel/></Button> </div> <Collapse isOpen={this.state.collapse}> <Card> <CardBody> <table><tbody><tr><th>Status</th><td>{job.status}</td></tr> <tr><th>Command line</th><td>{job.commandLineAsString}</td></tr></tbody></table> <table> <tbody> <tr> <th>Status</th> <td>{job.status}</td> </tr> <tr> <th>Command line</th> <td>{job.commandLineAsString}</td> </tr> </tbody> </table> </CardBody> </Card> </Collapse>