diff --git a/src/mapclient/core/managers/workflowmanager.py b/src/mapclient/core/managers/workflowmanager.py index 58f655a..e6eb0b5 100644 --- a/src/mapclient/core/managers/workflowmanager.py +++ b/src/mapclient/core/managers/workflowmanager.py @@ -39,7 +39,7 @@ def _get_workflow_configuration(location): - return QtCore.QSettings(_get_workflow_configuration_absolute_filename(location), QtCore.QSettings.IniFormat) + return QtCore.QSettings(_get_workflow_configuration_absolute_filename(location), QtCore.QSettings.Format.IniFormat) def _get_workflow_requirements(location): @@ -115,7 +115,7 @@ def getFilteredStepModel(self): def updateAvailableSteps(self): self._steps.reload() - self._filtered_steps.sort(1, QtCore.Qt.AscendingOrder) + self._filtered_steps.sort(1, QtCore.Qt.SortOrder.AscendingOrder) def undoStackIndexChanged(self, index): self._currentStateIndex = index @@ -156,21 +156,47 @@ def changeIdentifier(self, old_identifier, new_identifier): def _checkRequirements(self): requirements_file = _get_workflow_requirements_absolute_filename(self._location) - def new(self, location): - """ - Create a new workflow at the given location. The location is a directory, it must exist - it will not be created. A file is created in the directory at 'location' which holds - information describing the workflow. - """ + @staticmethod + def _check_workflow_location(location): if location is None: raise WorkflowError('No location given to create new Workflow.') if not os.path.exists(location): - raise WorkflowError('Location %s does not exist.' % location) + raise WorkflowError(f'Location {location} does not exist.') - self.set_location(location) + if not os.path.isdir(location): + raise WorkflowError(f'Location {location} is not a directory.') + + wf = _get_workflow_configuration(location) + if wf.contains('version'): + workflow_version = wf.value('version') + if version.parse(workflow_version) > version.parse('0.20.0'): + if wf.value('id') != info.DEFAULT_WORKFLOW_PROJECT_IDENTIFIER: + raise WorkflowError(f'Location {location} does not have a valid workflow configuration file.') + else: + raise WorkflowError(f'Location {location} does not have a valid workflow configuration file.') + + def load_workflow_virtually(self, location): + self._check_workflow_location(location) + + return _get_workflow_configuration(location) + + @staticmethod + def create_empty_workflow(location): wf = _get_workflow_configuration(location) wf.setValue('version', info.VERSION_STRING) + wf.setValue('id', info.DEFAULT_WORKFLOW_PROJECT_IDENTIFIER) + return wf + + def new(self, location): + """ + Create a new workflow at the given location. The location is a directory, it must exist + it will not be created. A file is created in the directory at 'location' which holds + information describing the workflow. + """ + self._check_workflow_location(location) + self.set_location(location) + self.create_empty_workflow(location) self._scene.clear() def exists(self, location): @@ -178,25 +204,18 @@ def exists(self, location): Determines whether a workflow exists in the given location. Returns True if a valid workflow exists, False otherwise. """ - if location is None: - return False - - if not os.path.exists(location): + try: + self._check_workflow_location(location) + except WorkflowError: return False - wf = _get_workflow_configuration(location) - if wf.contains('version'): - return True - - return False + return True @staticmethod def is_restricted(location): - if location is None or not os.path.exists(location) or not os.path.isdir(location): - return False - - wf = _get_workflow_configuration(location) - if not wf.contains('version'): + try: + WorkflowManager._check_workflow_location(location) + except WorkflowError: return False return is_workflow_in_use(location) @@ -207,18 +226,12 @@ def load(self, location, scene_rect=QtCore.QPointF(0, 0)): :param location: :param scene_rect: Rectangle of the scene rect to load the workflow into. """ - if location is None: - raise WorkflowError('No location given to open Workflow.') - - if not os.path.exists(location): - raise WorkflowError('Given location %s does not exist' % location) - if os.path.isfile(location): location = os.path.dirname(location) + self._check_workflow_location(location) + wf = _get_workflow_configuration(location) - if not wf.contains('version'): - raise WorkflowError('The given Workflow configuration file is not valid.') workflow_version = version.parse(wf.value('version')) application_version = version.parse(info.VERSION_STRING) diff --git a/src/mapclient/core/utils.py b/src/mapclient/core/utils.py index 06167af..3cff5b9 100644 --- a/src/mapclient/core/utils.py +++ b/src/mapclient/core/utils.py @@ -19,6 +19,7 @@ """ import os import re +import shutil import sys from pathlib import Path @@ -150,6 +151,27 @@ def load_configuration(location, identifier): return configuration +def copy_step_additional_config_files(step, source_configuration_dir, target_configuration_dir): + for additional_cfg_file in step.getAdditionalConfigFiles(): + source_cfg_dir = os.path.dirname(additional_cfg_file) + if os.path.isabs(additional_cfg_file): + relative_dir = os.path.relpath(source_configuration_dir, source_cfg_dir) + source_cfg_file = additional_cfg_file + else: + relative_dir = source_cfg_dir + source_cfg_file = os.path.join(source_configuration_dir, additional_cfg_file) + + source_basename = os.path.basename(additional_cfg_file) + source_workflow_relative_cfg = os.path.join(relative_dir, source_basename) + + target_cfg_file = os.path.realpath(os.path.join(target_configuration_dir, source_workflow_relative_cfg)) + if os.path.isfile(source_cfg_file): + required_path = os.path.join(target_configuration_dir, relative_dir) + if not os.path.exists(required_path): + os.makedirs(required_path) + shutil.copyfile(source_cfg_file, target_cfg_file) + + class FileTypeObject(object): def __init__(self): self.messages = list() diff --git a/src/mapclient/core/workflow/workflowitems.py b/src/mapclient/core/workflow/workflowitems.py index 5248765..c842e10 100644 --- a/src/mapclient/core/workflow/workflowitems.py +++ b/src/mapclient/core/workflow/workflowitems.py @@ -40,7 +40,7 @@ class MetaStep(Item): def __init__(self, step): Item.__init__(self) self._step = step - self._pos = QtCore.QPointF(0, 0) + self._pos = QtCore.QPointF(10, 10) self._uid = str(uuid.uuid1()) self._id = step.getIdentifier() diff --git a/src/mapclient/core/workflow/workflowscene.py b/src/mapclient/core/workflow/workflowscene.py index 95e7fec..927efd7 100644 --- a/src/mapclient/core/workflow/workflowscene.py +++ b/src/mapclient/core/workflow/workflowscene.py @@ -22,6 +22,7 @@ from PySide6 import QtCore from mapclient.core.workflow.workflowdependencygraph import WorkflowDependencyGraph +from mapclient.core.workflow.workflowerror import WorkflowError from mapclient.core.workflow.workflowitems import MetaStep, Connection from mapclient.mountpoints.workflowstep import workflowStepFactory from mapclient.core.utils import load_configuration @@ -154,6 +155,61 @@ def _read_step_names(ws): return step_names + def create_from(self, ws, name_identifiers, location): + """ + Create a workflow from the given names at the given location. + Returns a list of + + :param ws: Workflow settings object. + :param name_identifiers: List of tuples consisting of step names and associated identifiers. + :param location: Location of the workflow on the local disk. + :return: List of steps. + """ + steps = [] + try: + ws.beginGroup('nodes') + ws.beginWriteArray('nodelist') + for i, name_identifier in enumerate(name_identifiers): + ws.setArrayIndex(i) + step = workflowStepFactory(name_identifier[0], location) + step.setIdentifier(name_identifier[1]) + meta_step = MetaStep(step) + ws.setValue('name', step.getName()) + ws.setValue('position', meta_step.getPos()) + ws.setValue('selected', meta_step.getSelected()) + ws.setValue('identifier', meta_step.getIdentifier()) + ws.setValue('unique_identifier', meta_step.getUniqueIdentifier()) + steps.append(step) + + ws.endArray() + ws.endGroup() + + except ValueError: + names = [name_identifier[0] for name_identifier in name_identifiers] + raise WorkflowError(f'Could not create workflow from names: {names}') + + return steps + + @staticmethod + def get_step_name_from_identifier(ws, target_identifier): + ws.beginGroup('nodes') + step_name = '' + node_count = ws.beginReadArray('nodelist') + i = 0 + while i < node_count and not step_name: + ws.setArrayIndex(i) + name = ws.value('name') + identifier = ws.value('identifier') + if identifier == target_identifier: + step_name = name + + i += 1 + + ws.endArray() + ws.endGroup() + + return step_name + def doStepReport(self, ws): report = {} ws.beginGroup('nodes') diff --git a/src/mapclient/settings/info.py b/src/mapclient/settings/info.py index db3378c..bf551c2 100644 --- a/src/mapclient/settings/info.py +++ b/src/mapclient/settings/info.py @@ -54,6 +54,7 @@ DEFAULT_WORKFLOW_PROJECT_FILENAME = f'{_BASE_WORKFLOW_FILENAME}.proj' DEFAULT_WORKFLOW_ANNOTATION_FILENAME = f'.{_BASE_WORKFLOW_FILENAME}.rdf' DEFAULT_WORKFLOW_REQUIREMENTS_FILENAME = f'.{_BASE_WORKFLOW_FILENAME}.req' +DEFAULT_WORKFLOW_PROJECT_IDENTIFIER = _BASE_WORKFLOW_FILENAME def set_applications_settings(app): diff --git a/src/mapclient/view/workflow/importconfigdialog.py b/src/mapclient/view/workflow/importconfigdialog.py index a172eb8..4b16d52 100644 --- a/src/mapclient/view/workflow/importconfigdialog.py +++ b/src/mapclient/view/workflow/importconfigdialog.py @@ -9,7 +9,7 @@ from packaging import version from mapclient.settings.info import DEFAULT_WORKFLOW_PROJECT_FILENAME, VERSION_STRING -from mapclient.core.utils import load_configuration +from mapclient.core.utils import load_configuration, copy_step_additional_config_files from mapclient.core.workflow.workflowitems import MetaStep from mapclient.view.workflow.workflowgraphicsitems import Node from mapclient.view.workflow.ui.ui_importconfigdialog import Ui_ImportConfigDialog @@ -153,33 +153,7 @@ def _do_import(self, configuration_dir, node_dict): current_step_location = step.getLocation() step.setLocation(configuration_dir) step.deserialize(configuration) - - # print(f'{current_text} ==> {identifier}') - # print('current location:', current_step_location) - # print('source step location:', step.getLocation()) - for additional_cfg_file in step.getAdditionalConfigFiles(): - target_cfg_dir = os.path.dirname(additional_cfg_file) - if os.path.isabs(additional_cfg_file): - relative_dir = os.path.relpath(configuration_dir, target_cfg_dir) - source_cfg_file = additional_cfg_file - else: - relative_dir = target_cfg_dir - source_cfg_file = os.path.join(configuration_dir, additional_cfg_file) - - source_basename = os.path.basename(additional_cfg_file) - source_workflow_relative_cfg = os.path.join(relative_dir, source_basename) - - target_cfg_file = os.path.realpath(os.path.join(current_step_location, source_workflow_relative_cfg)) - # print('reported cfg file.', additional_cfg_file) - # print('source cfg file.', source_cfg_file) - # print('target cfg file.', target_cfg_file) - # print('is file:', os.path.isfile(source_cfg_file)) - if os.path.isfile(source_cfg_file): - required_path = os.path.join(current_step_location, relative_dir) - if not os.path.exists(required_path): - os.makedirs(required_path) - shutil.copyfile(source_cfg_file, target_cfg_file) - + copy_step_additional_config_files(step, configuration_dir, current_step_location) step.setLocation(current_step_location) self._workflow_scene.changeIdentifier(meta_step)