From fa44da31557b0b94de6da0fc6336f1be240e6536 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 11:46:29 +0100 Subject: [PATCH 01/10] Slightly improve wording 'Layer data' is a bit technical. --- plugins/SimulationView/SimulationView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index adbeb7b6db..98eda48477 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -118,7 +118,7 @@ class SimulationView(CuraView): self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled."), title = catalog.i18nc("@info:title", "Simulation View")) - self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."), title = catalog.i18nc("@info:title", "No layer data")) + self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."), title = catalog.i18nc("@info:title", "No layers to show")) QtApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated) From 94e97aff379927b76db268605f9b96ec62b8e63a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 13:02:07 +0100 Subject: [PATCH 02/10] Add a function that only gets called when upgrading from version 4.4 Contributes to issue CURA-7024. --- .../VersionUpgrade44to45.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py index 3ae25e05ae..10c9844dd6 100644 --- a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py +++ b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py @@ -1,6 +1,13 @@ +# Copyright (c) 2020 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + import configparser from typing import Tuple, List import io +import os # To get the path to check for hidden stacks to delete. +import re # To filter directories to search for hidden stacks to delete. +from UM.Resources import Resources # To get the path to check for hidden stacks to delete. +from UM.Version import Version # To sort folders by version number. from UM.VersionUpgrade import VersionUpgrade # Settings that were merged into one. Each one is a pair of settings. If both @@ -16,6 +23,36 @@ _removed_settings = { } class VersionUpgrade44to45(VersionUpgrade): + def __init__(self) -> None: + """ + Creates the version upgrade plug-in from 4.4 to 4.5. + + In this case the plug-in will also check for stacks that need to be + deleted. + """ + data_storage_root = os.path.dirname(Resources.getDataStoragePath()) + folders = os.listdir(data_storage_root) # All version folders. + folders = filter(lambda p: re.fullmatch(r"\d+\.\d+", p), folders) # Only folders with a correct version number as name. + latest_version = max(list(folders), key = Version) # Sort them by semantic version numbering. + if latest_version == "4.4": + self.removeHiddenStacks() + + def removeHiddenStacks(self) -> None: + """ + If starting the upgrade from 4.4, this will remove any hidden printer + stacks from the configuration folder as well as all of the user profiles + and definition changes profiles. + + This will ONLY run when upgrading from 4.4, not when e.g. upgrading from + 4.3 to 4.6 (through 4.4). This is because it's to fix a bug + (https://github.com/Ultimaker/Cura/issues/6731) that occurred in 4.4 + only, so only there will it have hidden stacks that need to be deleted. + If people upgrade from 4.3 they don't need to be deleted. If people + upgrade from 4.5 they have already been deleted previously or never got + the broken hidden stacks. + """ + pass + def getCfgVersion(self, serialised: str) -> int: parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialised) From bb738318891863ecd399f7a41702541a34ee35df Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 14:11:41 +0100 Subject: [PATCH 03/10] Cura directory may be empty Contributes to issue CURA-7024. --- .../VersionUpgrade44to45/VersionUpgrade44to45.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py index 10c9844dd6..917cd7f8a2 100644 --- a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py +++ b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py @@ -30,12 +30,18 @@ class VersionUpgrade44to45(VersionUpgrade): In this case the plug-in will also check for stacks that need to be deleted. """ + + # Only delete hidden stacks when upgrading from version 4.4. Not 4.3 or 4.5, just when you're starting out from 4.4. + # If you're starting from an earlier version, you can't have had the bug that produces too many hidden stacks (https://github.com/Ultimaker/Cura/issues/6731). + # If you're starting from a later version, the bug was already fixed. data_storage_root = os.path.dirname(Resources.getDataStoragePath()) folders = os.listdir(data_storage_root) # All version folders. - folders = filter(lambda p: re.fullmatch(r"\d+\.\d+", p), folders) # Only folders with a correct version number as name. - latest_version = max(list(folders), key = Version) # Sort them by semantic version numbering. - if latest_version == "4.4": - self.removeHiddenStacks() + folders = set(filter(lambda p: re.fullmatch(r"\d+\.\d+", p), folders)) # Only folders with a correct version number as name. + folders.difference_update({os.path.basename(Resources.getDataStoragePath())}) # Remove current version from candidates (since the folder was just copied). + if folders: + latest_version = max(folders, key = Version) # Sort them by semantic version numbering. + if latest_version == "4.4": + self.removeHiddenStacks() def removeHiddenStacks(self) -> None: """ From f38dc82ac8f3986707d83129d6f3656d8d033054 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 14:12:57 +0100 Subject: [PATCH 04/10] Remove hidden stacks and their dependencies This searches for stacks that are hidden and removes them, and also removes any extruder stacks that depended on those hidden global stacks and any user and quality changes profiles referred to by those removed stacks. Contributes to issue CURA-7024. --- .../VersionUpgrade44to45.py | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py index 917cd7f8a2..4daf8cef83 100644 --- a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py +++ b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py @@ -3,9 +3,12 @@ import configparser from typing import Tuple, List +import fnmatch # To filter files that we need to delete. import io import os # To get the path to check for hidden stacks to delete. +import urllib.parse # To get the container IDs from file names. import re # To filter directories to search for hidden stacks to delete. +from UM.Logger import Logger from UM.Resources import Resources # To get the path to check for hidden stacks to delete. from UM.Version import Version # To sort folders by version number. from UM.VersionUpgrade import VersionUpgrade @@ -57,7 +60,44 @@ class VersionUpgrade44to45(VersionUpgrade): upgrade from 4.5 they have already been deleted previously or never got the broken hidden stacks. """ - pass + Logger.log("d", "Removing all hidden container stacks.") + hidden_global_stacks = set() # Which global stacks have been found? We'll delete anything referred to by these. Set of stack IDs. + hidden_extruder_stacks = set() # Which extruder stacks refer to the hidden global profiles? + hidden_instance_containers = set() # Which instance containers are referred to by the hidden stacks? + + # First find all of the hidden container stacks. + data_storage = Resources.getDataStoragePath() + for root, _, files in os.walk(data_storage): + for filename in fnmatch.filter(files, "*.global.cfg"): + parser = configparser.ConfigParser(interpolation = None) + parser.read(os.path.join(root, filename)) + if "metadata" in parser and "hidden" in parser["metadata"] and parser["metadata"]["hidden"] == "True": + stack_id = urllib.parse.unquote_plus(os.path.basename(filename).split(".")[0]) + hidden_global_stacks.add(stack_id) + # The user container and definition changes container are specific to this stack. We need to delete those too. + if "containers" in parser: + if "0" in parser["containers"]: + hidden_instance_containers.add(parser["containers"]["0"]) + if "6" in parser["containers"]: + hidden_instance_containers.add(parser["containers"]["6"]) + os.remove(os.path.join(root, filename)) + + # Walk a second time to find all extruder stacks referring to these hidden container stacks. + for root, _, files in os.walk(data_storage): + for filename in fnmatch.filter(files, "*.extruder.cfg"): + parser = configparser.ConfigParser(interpolation = None) + parser.read(os.path.join(root, filename)) + if "metadata" in parser and "machine" in parser["metadata"] and parser["metadata"]["machine"] in hidden_global_stacks: + stack_id = urllib.parse.unquote_plus(os.path.basename(filename).split(".")[0]) + hidden_extruder_stacks.add(stack_id) + os.remove(os.path.join(root, filename)) + + # Walk a third time to remove all instance containers that are referred to by either of those. + for root, _, files in os.walk(data_storage): + for filename in fnmatch.filter(files, "*.inst.cfg"): + container_id = urllib.parse.unquote_plus(os.path.basename(filename).split(".")[0]) + if container_id in hidden_instance_containers: + os.remove(os.path.join(root, filename)) def getCfgVersion(self, serialised: str) -> int: parser = configparser.ConfigParser(interpolation = None) From 5a7ec98f1e9def6e17a9387c23642bee89e4c35d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 11:46:29 +0100 Subject: [PATCH 05/10] Slightly improve wording 'Layer data' is a bit technical. --- plugins/SimulationView/SimulationView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index adbeb7b6db..98eda48477 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -118,7 +118,7 @@ class SimulationView(CuraView): self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled."), title = catalog.i18nc("@info:title", "Simulation View")) - self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."), title = catalog.i18nc("@info:title", "No layer data")) + self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."), title = catalog.i18nc("@info:title", "No layers to show")) QtApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated) From ccc295e2bccaddab56890c095f60edb295bcf56f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 14:20:08 +0100 Subject: [PATCH 06/10] Use sets for folder names from the start We use a set here because we want to remove the current version from it later on. However if we change the variable to contain a set instead of a list, MyPy will start complaining that we change the type. Contributes to issue CURA-7024. --- .../VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py index 4daf8cef83..09aab9b123 100644 --- a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py +++ b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py @@ -38,7 +38,7 @@ class VersionUpgrade44to45(VersionUpgrade): # If you're starting from an earlier version, you can't have had the bug that produces too many hidden stacks (https://github.com/Ultimaker/Cura/issues/6731). # If you're starting from a later version, the bug was already fixed. data_storage_root = os.path.dirname(Resources.getDataStoragePath()) - folders = os.listdir(data_storage_root) # All version folders. + folders = set(os.listdir(data_storage_root)) # All version folders. folders = set(filter(lambda p: re.fullmatch(r"\d+\.\d+", p), folders)) # Only folders with a correct version number as name. folders.difference_update({os.path.basename(Resources.getDataStoragePath())}) # Remove current version from candidates (since the folder was just copied). if folders: From b549d20ec519e4412a89bfcfc114fa68d35e4186 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 15:17:31 +0100 Subject: [PATCH 07/10] Document which stack layer is which container type Contributes to issue CURA-7024. --- .../VersionUpgrade44to45/VersionUpgrade44to45.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py index 09aab9b123..6fa19c686d 100644 --- a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py +++ b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py @@ -76,9 +76,9 @@ class VersionUpgrade44to45(VersionUpgrade): hidden_global_stacks.add(stack_id) # The user container and definition changes container are specific to this stack. We need to delete those too. if "containers" in parser: - if "0" in parser["containers"]: + if "0" in parser["containers"]: # User container. hidden_instance_containers.add(parser["containers"]["0"]) - if "6" in parser["containers"]: + if "6" in parser["containers"]: # Definition changes container. hidden_instance_containers.add(parser["containers"]["6"]) os.remove(os.path.join(root, filename)) From 4307942544e3aa247783027c7d5bb2e9c1ad9aae Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 15:33:06 +0100 Subject: [PATCH 08/10] Also remove instance containers referred to by extruder stacks Contributes to issue CURA-7024. --- .../VersionUpgrade44to45/VersionUpgrade44to45.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py index 6fa19c686d..e40ed03bbf 100644 --- a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py +++ b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py @@ -90,6 +90,12 @@ class VersionUpgrade44to45(VersionUpgrade): if "metadata" in parser and "machine" in parser["metadata"] and parser["metadata"]["machine"] in hidden_global_stacks: stack_id = urllib.parse.unquote_plus(os.path.basename(filename).split(".")[0]) hidden_extruder_stacks.add(stack_id) + # The user container and definition changes container are specific to this stack. We need to delete those too. + if "containers" in parser: + if "0" in parser["containers"]: # User container. + hidden_instance_containers.add(parser["containers"]["0"]) + if "6" in parser["containers"]: # Definition changes container. + hidden_instance_containers.add(parser["containers"]["6"]) os.remove(os.path.join(root, filename)) # Walk a third time to remove all instance containers that are referred to by either of those. From a1f3444271ff447e0ef194b3f017786861e483cc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 15:46:06 +0100 Subject: [PATCH 09/10] Don't delete containers from plugins directory Some plug-ins provide extra profiles and we don't want to check them or delete them regardless of what their content is. Contributes to issue CURA-7024. --- .../VersionUpgrade44to45/VersionUpgrade44to45.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py index e40ed03bbf..48edd6faf2 100644 --- a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py +++ b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py @@ -64,10 +64,12 @@ class VersionUpgrade44to45(VersionUpgrade): hidden_global_stacks = set() # Which global stacks have been found? We'll delete anything referred to by these. Set of stack IDs. hidden_extruder_stacks = set() # Which extruder stacks refer to the hidden global profiles? hidden_instance_containers = set() # Which instance containers are referred to by the hidden stacks? + exclude_directories = {"plugins"} # First find all of the hidden container stacks. data_storage = Resources.getDataStoragePath() - for root, _, files in os.walk(data_storage): + for root, dirs, files in os.walk(data_storage): + dirs[:] = [dir for dir in dirs if dir not in exclude_directories] for filename in fnmatch.filter(files, "*.global.cfg"): parser = configparser.ConfigParser(interpolation = None) parser.read(os.path.join(root, filename)) @@ -83,7 +85,8 @@ class VersionUpgrade44to45(VersionUpgrade): os.remove(os.path.join(root, filename)) # Walk a second time to find all extruder stacks referring to these hidden container stacks. - for root, _, files in os.walk(data_storage): + for root, dirs, files in os.walk(data_storage): + dirs[:] = [dir for dir in dirs if dir not in exclude_directories] for filename in fnmatch.filter(files, "*.extruder.cfg"): parser = configparser.ConfigParser(interpolation = None) parser.read(os.path.join(root, filename)) @@ -99,7 +102,8 @@ class VersionUpgrade44to45(VersionUpgrade): os.remove(os.path.join(root, filename)) # Walk a third time to remove all instance containers that are referred to by either of those. - for root, _, files in os.walk(data_storage): + for root, dirs, files in os.walk(data_storage): + dirs[:] = [dir for dir in dirs if dir not in exclude_directories] for filename in fnmatch.filter(files, "*.inst.cfg"): container_id = urllib.parse.unquote_plus(os.path.basename(filename).split(".")[0]) if container_id in hidden_instance_containers: From 35132665490aa1e059e51a5dd1ba127dcc642726 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 4 Feb 2020 15:52:12 +0100 Subject: [PATCH 10/10] Guard against unexpected file structures We shouldn't break on that. Contributes to issue CURA-7024. --- .../VersionUpgrade44to45.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py index 48edd6faf2..f300cb1c2d 100644 --- a/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py +++ b/plugins/VersionUpgrade/VersionUpgrade44to45/VersionUpgrade44to45.py @@ -72,7 +72,12 @@ class VersionUpgrade44to45(VersionUpgrade): dirs[:] = [dir for dir in dirs if dir not in exclude_directories] for filename in fnmatch.filter(files, "*.global.cfg"): parser = configparser.ConfigParser(interpolation = None) - parser.read(os.path.join(root, filename)) + try: + parser.read(os.path.join(root, filename)) + except OSError: # File not found or insufficient rights. + continue + except configparser.Error: # Invalid file format. + continue if "metadata" in parser and "hidden" in parser["metadata"] and parser["metadata"]["hidden"] == "True": stack_id = urllib.parse.unquote_plus(os.path.basename(filename).split(".")[0]) hidden_global_stacks.add(stack_id) @@ -89,7 +94,12 @@ class VersionUpgrade44to45(VersionUpgrade): dirs[:] = [dir for dir in dirs if dir not in exclude_directories] for filename in fnmatch.filter(files, "*.extruder.cfg"): parser = configparser.ConfigParser(interpolation = None) - parser.read(os.path.join(root, filename)) + try: + parser.read(os.path.join(root, filename)) + except OSError: # File not found or insufficient rights. + continue + except configparser.Error: # Invalid file format. + continue if "metadata" in parser and "machine" in parser["metadata"] and parser["metadata"]["machine"] in hidden_global_stacks: stack_id = urllib.parse.unquote_plus(os.path.basename(filename).split(".")[0]) hidden_extruder_stacks.add(stack_id) @@ -107,7 +117,10 @@ class VersionUpgrade44to45(VersionUpgrade): for filename in fnmatch.filter(files, "*.inst.cfg"): container_id = urllib.parse.unquote_plus(os.path.basename(filename).split(".")[0]) if container_id in hidden_instance_containers: - os.remove(os.path.join(root, filename)) + try: + os.remove(os.path.join(root, filename)) + except OSError: # Is a directory, file not found, or insufficient rights. + continue def getCfgVersion(self, serialised: str) -> int: parser = configparser.ConfigParser(interpolation = None)