Skip to content
Snippets Groups Projects
Commit d5a6e295 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Added input field for changing node name during copy of single nodes and added...

Added input field for changing node name during copy of single nodes and added comments on moveOrCopyNodes method
parent 5b9e1610
No related branches found
No related tags found
No related merge requests found
Pipeline #2301 passed
...@@ -210,6 +210,7 @@ public class NodesController extends BaseController { ...@@ -210,6 +210,7 @@ public class NodesController extends BaseController {
@PostMapping(value = "/moveOrCopy") @PostMapping(value = "/moveOrCopy")
public ResponseEntity<List<Job>> moveOrCopyNodes(@RequestBody MoveOrCopyRequest request) throws Exception { public ResponseEntity<List<Job>> moveOrCopyNodes(@RequestBody MoveOrCopyRequest request) throws Exception {
// Creates a transfer request for each copy or move operation
CompletableFuture<JobSummary>[] futureJobs = request.getTargets().stream().map(t -> { CompletableFuture<JobSummary>[] futureJobs = request.getTargets().stream().map(t -> {
String target = urlEncodePath(t); String target = urlEncodePath(t);
String direction = urlEncodePath(request.getDirection()); String direction = urlEncodePath(request.getDirection());
...@@ -220,12 +221,21 @@ public class NodesController extends BaseController { ...@@ -220,12 +221,21 @@ public class NodesController extends BaseController {
return CompletableFuture.supplyAsync(() -> client.startTransferJob(transfer), Runnable::run); return CompletableFuture.supplyAsync(() -> client.startTransferJob(transfer), Runnable::run);
}).collect(Collectors.toList()).toArray(CompletableFuture[]::new); }).collect(Collectors.toList()).toArray(CompletableFuture[]::new);
// starts all HTTP requests in parallel
CompletableFuture.allOf(futureJobs).join(); CompletableFuture.allOf(futureJobs).join();
// parses all the responses and populates a list of jobs to check the
// completion status using polling mechanism
List<JobSummary> jobs = Stream.of(futureJobs).map(j -> j.join()).collect(Collectors.toList()); List<JobSummary> jobs = Stream.of(futureJobs).map(j -> j.join()).collect(Collectors.toList());
// Job statuses are checked for some time and if completion takes too much
// time sends the execution phase to the UI and let it handles the polling.
// Since CompletableFutures can't really be aborted (see cancel method
// documentation) an AtomicReference containing a boolean flag it is
// passed to handle polling abortion.
AtomicReference<Boolean> cancelled = new AtomicReference<>(false); AtomicReference<Boolean> cancelled = new AtomicReference<>(false);
// CompletableFuture that triggers the timeout
CompletableFuture timeout = CompletableFuture.runAsync(() -> { CompletableFuture timeout = CompletableFuture.runAsync(() -> {
try { try {
Thread.sleep(pollingTimeout * 1000); Thread.sleep(pollingTimeout * 1000);
...@@ -235,6 +245,7 @@ public class NodesController extends BaseController { ...@@ -235,6 +245,7 @@ public class NodesController extends BaseController {
}); });
try { try {
// performs polling of job statuses or timeout
CompletableFuture.anyOf(jobsPolling(jobs, cancelled), timeout).join(); CompletableFuture.anyOf(jobsPolling(jobs, cancelled), timeout).join();
} catch (CompletionException ex) { } catch (CompletionException ex) {
if (ex.getCause() != null && ex.getCause() instanceof VOSpaceException) { if (ex.getCause() != null && ex.getCause() instanceof VOSpaceException) {
...@@ -243,8 +254,6 @@ public class NodesController extends BaseController { ...@@ -243,8 +254,6 @@ public class NodesController extends BaseController {
throw ex; throw ex;
} }
// Try to perform polling until completion. If it takes too much time
// sends the execution phase to the UI and let it handles the polling.
Job.JobType type = request.isKeepBytes() ? Job.JobType.COPY : Job.JobType.MOVE; Job.JobType type = request.isKeepBytes() ? Job.JobType.COPY : Job.JobType.MOVE;
return ResponseEntity.ok(jobs.stream().map(j -> new Job(j, type)) return ResponseEntity.ok(jobs.stream().map(j -> new Job(j, type))
......
...@@ -5,6 +5,12 @@ ...@@ -5,6 +5,12 @@
--> -->
<template> <template>
<b-modal id="move-or-copy-modal" :title="title" :okTitle="okTitle" @show="afterShow" @ok.prevent="moveOrCopyNodes" :ok-disabled="!writable" size="lg"> <b-modal id="move-or-copy-modal" :title="title" :okTitle="okTitle" @show="afterShow" @ok.prevent="moveOrCopyNodes" :ok-disabled="!writable" size="lg">
<b-form inline v-if="this.nodesToMoveOrCopy.length === 1 && this.moveOrCopy === 'copy'" class="mb-3">
<label class="w-25" for="new-name-input">New name</label>
<b-form-input v-model.trim="newName" id="new-name-input" class="w-75" aria-describedby="new-name-input-feedback" :state="newNameState" v-on:input="resetNewNameError" @keydown.native.enter="moveOrCopyNodes">
</b-form-input>
<b-form-invalid-feedback id="new-folder-name-input-feedback" class="text-right">{{newNameError}}</b-form-invalid-feedback>
</b-form>
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item" v-for="(item, i) in breadcrumbs" :key="i" :class="{ 'active' : item.active }"> <li class="breadcrumb-item" v-for="(item, i) in breadcrumbs" :key="i" :class="{ 'active' : item.active }">
<a href="#" @click.stop.prevent="breadcrumbClick(i)" v-if="!item.active">{{item.text}}</a> <a href="#" @click.stop.prevent="breadcrumbClick(i)" v-if="!item.active">{{item.text}}</a>
...@@ -66,13 +72,31 @@ export default { ...@@ -66,13 +72,31 @@ export default {
} }
} }
return items; return items;
},
newNameState() {
if (this.newNameError) {
return false;
}
return null;
}
},
data() {
return {
newName: null,
newNameError: null
} }
}, },
methods: { methods: {
afterShow() { afterShow() {
// starts from parent path // starts from parent path
let firstSelectedNode = this.nodesToMoveOrCopy[0]; let firstSelectedNode = this.nodesToMoveOrCopy[0];
this.$store.dispatch('openNodeInMoveOrCopyModal', firstSelectedNode.substring(0, firstSelectedNode.lastIndexOf('/'))); let lastSlashPos = firstSelectedNode.lastIndexOf('/');
this.$store.dispatch('openNodeInMoveOrCopyModal', firstSelectedNode.substring(0, lastSlashPos));
if (this.nodesToMoveOrCopy.length === 1 && this.moveOrCopy === 'copy') {
this.newName = firstSelectedNode.substring(lastSlashPos + 1);
} else {
this.newName = null;
}
}, },
breadcrumbClick(i) { breadcrumbClick(i) {
let pathSplit = this.destinationPath.split('/'); let pathSplit = this.destinationPath.split('/');
...@@ -84,14 +108,31 @@ export default { ...@@ -84,14 +108,31 @@ export default {
window.openNodeInMoveOrCopyModal(event, path); window.openNodeInMoveOrCopyModal(event, path);
}, },
moveOrCopyNodes() { moveOrCopyNodes() {
let direction = this.destinationPath;
if (this.nodesToMoveOrCopy.length === 1 && this.moveOrCopy === 'copy') {
if (!this.newName) {
this.newNameError = "Name is required";
return;
} else if (/[<>?":\\/`|'*]/.test(this.newName)) {
this.newNameError = "Name contains an illegal character. Following characters are not allowed: < > ? \" : \\ / | ' * `";
return;
}
direction += '/' + this.newName;
}
this.$store.dispatch('moveOrCopyNodes', { this.$store.dispatch('moveOrCopyNodes', {
targets: this.nodesToMoveOrCopy, targets: this.nodesToMoveOrCopy,
direction: this.destinationPath, direction,
keepBytes: this.moveOrCopy === 'copy' keepBytes: this.moveOrCopy === 'copy'
}) })
.then(() => { .then(() => {
this.$bvModal.hide('move-or-copy-modal'); this.$bvModal.hide('move-or-copy-modal');
}) })
},
resetNewNameError() {
this.newNameError = null;
} }
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment