diff --git a/app/api/tasks.py b/app/api/tasks.py index a04e0389..10930569 100644 --- a/app/api/tasks.py +++ b/app/api/tasks.py @@ -153,6 +153,10 @@ class TaskViewSet(viewsets.ViewSet): def remove(self, *args, **kwargs): return self.set_pending_action(pending_actions.REMOVE, *args, perms=('delete_project', ), **kwargs) + @action(detail=True, methods=['post']) + def compact(self, *args, **kwargs): + return self.set_pending_action(pending_actions.COMPACT, *args, perms=('delete_project', ), **kwargs) + @action(detail=True, methods=['get']) def output(self, request, pk=None, project_pk=None): """ diff --git a/app/models/task.py b/app/models/task.py index 72529f65..c84f1b1b 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -235,6 +235,7 @@ class Task(models.Model): (pending_actions.RESTART, 'RESTART'), (pending_actions.RESIZE, 'RESIZE'), (pending_actions.IMPORT, 'IMPORT'), + (pending_actions.COMPACT, 'COMPACT'), ) TASK_PROGRESS_LAST_VALUE = 0.85 @@ -807,6 +808,14 @@ class Task(models.Model): # Stop right here! return + + elif self.pending_action == pending_actions.COMPACT: + logger.info("Compacting {}".format(self)) + time.sleep(2) # Purely to make sure the user sees the "compacting..." message in the UI since this is so fast + self.compact() + self.pending_action = None + self.save() + return if self.processing_node: # Need to update status (first time, queued or running?) @@ -1142,6 +1151,18 @@ class Task(models.Model): plugin_signals.task_removed.send_robust(sender=self.__class__, task_id=task_id) + def compact(self): + # Remove all images + images_path = self.task_path() + images = [os.path.join(images_path, i) for i in self.scan_images()] + for im in images: + try: + os.unlink(im) + except Exception as e: + logger.warning(e) + + self.update_size(commit=True) + def set_failure(self, error_message): logger.error("FAILURE FOR {}: {}".format(self, error_message)) self.last_error = error_message @@ -1157,7 +1178,8 @@ class Task(models.Model): def check_if_canceled(self): # Check if task has been canceled/removed if Task.objects.only("pending_action").get(pk=self.id).pending_action in [pending_actions.CANCEL, - pending_actions.REMOVE]: + pending_actions.REMOVE, + pending_actions.COMPACT]: raise TaskInterruptedException() def resize_images(self): diff --git a/app/pending_actions.py b/app/pending_actions.py index c7f02532..df21044c 100644 --- a/app/pending_actions.py +++ b/app/pending_actions.py @@ -3,3 +3,4 @@ REMOVE = 2 RESTART = 3 RESIZE = 4 IMPORT = 5 +COMPACT = 6 diff --git a/app/static/app/js/classes/PendingActions.js b/app/static/app/js/classes/PendingActions.js index 2b89fb04..a059151f 100644 --- a/app/static/app/js/classes/PendingActions.js +++ b/app/static/app/js/classes/PendingActions.js @@ -4,7 +4,8 @@ const CANCEL = 1, REMOVE = 2, RESTART = 3, RESIZE = 4, - IMPORT = 5; + IMPORT = 5, + COMPACT = 6; let pendingActions = { [CANCEL]: { @@ -21,6 +22,9 @@ let pendingActions = { }, [IMPORT]: { descr: _("Importing...") + }, + [COMPACT]: { + descr: _("Compacting...") } }; @@ -30,6 +34,7 @@ export default { RESTART: RESTART, RESIZE: RESIZE, IMPORT: IMPORT, + COMPACT: COMPACT, description: function(pendingAction) { if (pendingActions[pendingAction]) return pendingActions[pendingAction].descr; diff --git a/app/static/app/js/components/NewTaskPanel.jsx b/app/static/app/js/components/NewTaskPanel.jsx index 0c9189fc..a8900cfc 100644 --- a/app/static/app/js/components/NewTaskPanel.jsx +++ b/app/static/app/js/components/NewTaskPanel.jsx @@ -239,7 +239,7 @@ class NewTaskPanel extends React.Component { ref={(domNode) => { if (domNode) this.taskForm = domNode; }} /> - {this.state.editTaskFormLoaded && this.props.showAlign && this.state.showMapPreview ? + {this.state.editTaskFormLoaded && this.props.showAlign && this.state.showMapPreview && this.state.alignTasks.length > 0 ?
diff --git a/app/static/app/js/components/TaskListItem.jsx b/app/static/app/js/components/TaskListItem.jsx index 8740228c..664852ad 100644 --- a/app/static/app/js/components/TaskListItem.jsx +++ b/app/static/app/js/components/TaskListItem.jsx @@ -461,11 +461,13 @@ class TaskListItem extends React.Component { const disabled = this.state.actionButtonsDisabled || ([pendingActions.CANCEL, - pendingActions.REMOVE, + pendingActions.REMOVE, + pendingActions.COMPACT, pendingActions.RESTART].indexOf(task.pending_action) !== -1); const editable = this.props.hasPermission("change") && [statusCodes.FAILED, statusCodes.COMPLETED, statusCodes.CANCELED].indexOf(task.status) !== -1; const actionLoading = this.state.actionLoading; - + const showAssetButtons = task.status === statusCodes.COMPLETED; + let expanded = ""; if (this.state.expanded){ let showOrthophotoMissingWarning = false, @@ -484,7 +486,7 @@ class TaskListItem extends React.Component { }); }; - if (task.status === statusCodes.COMPLETED){ + if (showAssetButtons){ if (task.available_assets.indexOf("orthophoto.tif") !== -1 || task.available_assets.indexOf("dsm.tif") !== -1){ addActionButton(" " + _("View Map"), "btn-primary", "fa fa-globe", () => { location.href = `/map/project/${task.project}/task/${task.id}/`; @@ -534,7 +536,7 @@ class TaskListItem extends React.Component { } actionButtons = (
- {task.status === statusCodes.COMPLETED ? + {showAssetButtons ? : ""} {actionButtons.map(button => { @@ -672,7 +674,6 @@ class TaskListItem extends React.Component {
- {actionButtons}
@@ -734,6 +735,10 @@ class TaskListItem extends React.Component { type = 'neutral'; } + if (task.pending_action === pendingActions.COMPACT){ + statusIcon = 'fa fa-cog fa-spin fa-fw'; + } + statusLabel = getStatusLabel(status, type, progress); } @@ -764,8 +769,15 @@ class TaskListItem extends React.Component { if (this.props.hasPermission("delete")){ taskActions.push( -
  • , +
  • ); + + if (task.status === statusCodes.COMPLETED){ + addTaskAction(_("Compact"), "fa fa-database", this.genActionApiCall("compact", { + confirm: _("Compacting will free disk space by permanently deleting the original images used for processing. It will no longer be possible to restart the task. Maps and models will remain in place. Continue?"), + defaultError: _("Cannot compact task.") + })); + } addTaskAction(_("Delete"), "fa fa-trash", this.genActionApiCall("remove", { confirm: _("All information related to this task, including images, maps and models will be deleted. Continue?"), @@ -816,6 +828,7 @@ class TaskListItem extends React.Component { : ""}
    + {expanded} ); diff --git a/nodeodm/models.py b/nodeodm/models.py index 270abf03..2a0a1f32 100644 --- a/nodeodm/models.py +++ b/nodeodm/models.py @@ -71,7 +71,11 @@ class ProcessingNode(models.Model): self.api_version = info.version self.queue_count = info.task_queue_count - self.max_images = info.max_images + + if isinstance(info.max_images, (int, float)): + self.max_images = max(0, info.max_images) + else: + self.max_images = None self.engine_version = info.engine_version self.engine = info.engine