1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
|
# 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 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
# Settings that were merged into one. Each one is a pair of settings. If both
# are overwritten, the key wins. If only the key or the value is overwritten,
# that value is used in the key.
_merged_settings = {
"machine_head_with_fans_polygon": "machine_head_polygon",
"support_wall_count": "support_tree_wall_count"
}
_removed_settings = {
"support_tree_wall_thickness"
}
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.
"""
# 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 = 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:
latest_version = max(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.
"""
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?
exclude_directories = {"plugins"}
# First find all of the hidden container stacks.
data_storage = Resources.getDataStoragePath()
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)
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)
# 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 second time to find all extruder stacks referring to these hidden container stacks.
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)
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)
# 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.
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:
try:
os.remove(os.path.join(root, filename))
except OSError: # Is a directory, file not found, or insufficient rights.
continue
def upgradePreferences(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
"""Upgrades Preferences to have the new version number.
This renames the renamed settings in the list of visible settings.
"""
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
# Update version number.
parser["metadata"]["setting_version"] = "11"
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]
def upgradeInstanceContainer(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
"""Upgrades instance containers to have the new version number.
This renames the renamed settings in the containers.
"""
parser = configparser.ConfigParser(interpolation = None, comment_prefixes = ())
parser.read_string(serialized)
# Update version number.
parser["metadata"]["setting_version"] = "11"
if "values" in parser:
# Merged settings: When two settings are merged, one is preferred.
# If the preferred one is available, that value is taken regardless
# of the other one. If only the non-preferred one is available, that
# value is moved to the preferred setting value.
for preferred, removed in _merged_settings.items():
if removed in parser["values"]:
if preferred not in parser["values"]:
parser["values"][preferred] = parser["values"][removed]
del parser["values"][removed]
for removed in _removed_settings:
if removed in parser["values"]:
del parser["values"][removed]
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]
def upgradeStack(self, serialized: str, filename: str) -> Tuple[List[str], List[str]]:
"""Upgrades stacks to have the new version number."""
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
# Update version number.
if "metadata" not in parser:
parser["metadata"] = {}
parser["metadata"]["setting_version"] = "11"
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]
|