Ghostkeeper 8ec7d6dba3
Fix type issues in old version upgrade plug-ins
The one actual change was this: To give a KeyError when stuff can't be found in a dictionary, rather than returning None there and then getting a TypeError later.

Contributes to issue CURA-5936.
2018-11-14 13:56:46 +01:00

145 lines
7.8 KiB
Python

# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import configparser #To read config files.
import io #To write config files to strings as if they were files.
import os.path #To get the path to write new user profiles to.
from typing import Dict, List, Optional, Set, Tuple
import urllib #To serialise the user container file name properly.
import UM.VersionUpgrade #To indicate that a file is of incorrect format.
import UM.VersionUpgradeManager #To schedule more files to be upgraded.
from UM.Resources import Resources #To get the config storage path.
## Creates a new machine instance instance by parsing a serialised machine
# instance in version 1 of the file format.
#
# \param serialised The serialised form of a machine instance in version 1.
# \param filename The supposed file name of this machine instance, without
# extension.
# \return A machine instance instance, or None if the file format is
# incorrect.
def importFrom(serialised: str, filename: str) -> Optional["MachineInstance"]:
try:
return MachineInstance(serialised, filename)
except (configparser.Error, UM.VersionUpgrade.FormatException, UM.VersionUpgrade.InvalidVersionException):
return None
## A representation of a machine instance used as intermediary form for
# conversion from one format to the other.
class MachineInstance:
## Reads version 1 of the file format, storing it in memory.
#
# \param serialised A string with the contents of a machine instance file,
# without extension.
# \param filename The supposed file name of this machine instance.
def __init__(self, serialised: str, filename: str) -> None:
self._filename = filename
config = configparser.ConfigParser(interpolation = None)
config.read_string(serialised) # Read the input string as config file.
# Checking file correctness.
if not config.has_section("general"):
raise UM.VersionUpgrade.FormatException("No \"general\" section.")
if not config.has_option("general", "version"):
raise UM.VersionUpgrade.FormatException("No \"version\" in \"general\" section.")
if not config.has_option("general", "name"):
raise UM.VersionUpgrade.FormatException("No \"name\" in \"general\" section.")
if not config.has_option("general", "type"):
raise UM.VersionUpgrade.FormatException("No \"type\" in \"general\" section.")
if int(config.get("general", "version")) != 1: # Explicitly hard-code version 1, since if this number changes the programmer MUST change this entire function.
raise UM.VersionUpgrade.InvalidVersionException("The version of this machine instance is wrong. It must be 1.")
self._type_name = config.get("general", "type")
self._variant_name = config.get("general", "variant", fallback = "empty_variant")
self._name = config.get("general", "name", fallback = "")
self._key = config.get("general", "key", fallback = "")
self._active_profile_name = config.get("general", "active_profile", fallback = "empty_quality")
self._active_material_name = config.get("general", "material", fallback = "empty_material")
self._machine_setting_overrides = {} # type: Dict[str, str]
for key, value in config["machine_settings"].items():
self._machine_setting_overrides[key] = value
## Serialises this machine instance as file format version 2.
#
# This is where the actual translation happens in this case.
#
# \return A tuple containing the new filename and a serialised form of
# this machine instance, serialised in version 2 of the file format.
def export(self) -> Tuple[List[str], List[str]]:
config = configparser.ConfigParser(interpolation = None) # Build a config file in the form of version 2.
config.add_section("general")
config.set("general", "name", self._name)
config.set("general", "id", self._name)
config.set("general", "version", "2") # Hard-code version 2, since if this number changes the programmer MUST change this entire function.
import VersionUpgrade21to22 # Import here to prevent circular dependencies.
has_machine_qualities = self._type_name in VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.machinesWithMachineQuality()
type_name = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translatePrinter(self._type_name)
active_material = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateMaterial(self._active_material_name)
variant = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateVariant(self._variant_name, type_name)
variant_materials = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateVariantForMaterials(self._variant_name, type_name)
#Convert to quality profile if we have one of the built-in profiles, otherwise convert to a quality-changes profile.
if self._active_profile_name in VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.builtInProfiles():
active_quality = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateProfile(self._active_profile_name)
active_quality_changes = "empty_quality_changes"
else:
active_quality = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.getQualityFallback(type_name, variant, active_material)
active_quality_changes = self._active_profile_name
if has_machine_qualities: #This machine now has machine-quality profiles.
active_material += "_" + variant_materials
#Create a new user profile and schedule it to be upgraded.
user_profile = configparser.ConfigParser(interpolation = None)
user_profile["general"] = {
"version": "2",
"name": "Current settings",
"definition": type_name
}
user_profile["metadata"] = {
"type": "user",
"machine": self._name
}
user_profile["values"] = {}
version_upgrade_manager = UM.VersionUpgradeManager.VersionUpgradeManager.getInstance()
user_version_to_paths_dict = version_upgrade_manager.getStoragePaths("user")
paths_set = set() # type: Set[str]
for paths in user_version_to_paths_dict.values():
paths_set |= paths
user_storage = os.path.join(Resources.getDataStoragePath(), next(iter(paths_set)))
user_profile_file = os.path.join(user_storage, urllib.parse.quote_plus(self._name) + "_current_settings.inst.cfg")
if not os.path.exists(user_storage):
os.makedirs(user_storage)
with open(user_profile_file, "w", encoding = "utf-8") as file_handle:
user_profile.write(file_handle)
version_upgrade_manager.upgradeExtraFile(user_storage, urllib.parse.quote_plus(self._name), "user")
containers = [
self._name + "_current_settings", #The current profile doesn't know the definition ID when it was upgraded, only the instance ID, so it will be invalid. Sorry, your current settings are lost now.
active_quality_changes,
active_quality,
active_material,
variant,
type_name
]
config.set("general", "containers", ",".join(containers))
config.add_section("metadata")
config.set("metadata", "type", "machine")
VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateSettings(self._machine_setting_overrides)
config.add_section("values")
for key, value in self._machine_setting_overrides.items():
config.set("values", key, str(value))
output = io.StringIO()
config.write(output)
return [self._filename], [output.getvalue()]