diff --git a/tryton/CHANGELOG b/tryton/CHANGELOG index 7eb3c8bef2c..f45940a9a91 100644 --- a/tryton/CHANGELOG +++ b/tryton/CHANGELOG @@ -1,3 +1,5 @@ +* Transfer configuration, profiles and plugins from previous version + Version 6.4.6 - 2022-11-17 -------------------------- * Bug fixes (see mercurial logs for details) diff --git a/tryton/tryton/client.py b/tryton/tryton/client.py index 0791bf8afa6..5ff0c4501f8 100644 --- a/tryton/tryton/client.py +++ b/tryton/tryton/client.py @@ -9,7 +9,7 @@ from gi.repository import Gdk, Gio, Gtk from tryton import common, gui, translate -from tryton.config import CONFIG, get_config_dir +from tryton.config import CONFIG, copy_previous_configuration, get_config_dir from tryton.gui.window.dblogin import DBLogin @@ -59,6 +59,9 @@ def excepthook(type_, value, traceback_): common.error(value, ''.join(traceback.format_tb(traceback_))) sys.excepthook = excepthook + copy_previous_configuration('tryton.cfg') + copy_previous_configuration('profiles.cfg') + copy_previous_configuration('plugins') CONFIG.parse() if CONFIG.arguments: url = CONFIG.arguments[0] diff --git a/tryton/tryton/config.py b/tryton/tryton/config.py index 8333119f2dc..21ecf590334 100644 --- a/tryton/tryton/config.py +++ b/tryton/tryton/config.py @@ -6,6 +6,7 @@ import logging import optparse import os +import shutil import sys from gi.repository import GdkPixbuf @@ -15,16 +16,48 @@ _ = gettext.gettext -def get_config_dir(): +def _reverse_series_iterator(starting_version): + major, minor = map(int, starting_version.split('.')) + while major >= 0 and minor >= 0: + yield f"{major}.{minor}" + if minor == 0: + major -= 1 + minor = 8 + else: + minor -= 2 if not minor % 2 else 1 + + +def copy_previous_configuration(config_element): + current_version = __version__.rsplit('.', 1)[0] + config_dir = get_config_root() + for version in _reverse_series_iterator(current_version): + config_path = os.path.join(config_dir, version, config_element) + if version == current_version and os.path.exists(config_path): + break + elif os.path.exists(config_path): + if os.path.isfile(config_path): + shutil.copy(config_path, get_config_dir()) + elif os.path.isdir(config_path): + shutil.copytree( + config_path, + os.path.join(get_config_dir(), config_element)) + break + + +def get_config_root(): if os.name == 'nt': appdata = os.environ['APPDATA'] if not isinstance(appdata, str): appdata = str(appdata, sys.getfilesystemencoding()) - return os.path.join(appdata, '.config', 'tryton', - __version__.rsplit('.', 1)[0]) - config_path = os.getenv('XDG_CONFIG_HOME', os.path.join('~', '.config')) - return os.path.expanduser( - os.path.join(config_path, 'tryton', __version__.rsplit('.', 1)[0])) + config_path = os.path.join(appdata, '.config') + else: + config_path = os.path.expanduser(os.getenv( + 'XDG_CONFIG_HOME', os.path.join('~', '.config'))) + return os.path.join(config_path, 'tryton') + + +def get_config_dir(): + return os.path.join(get_config_root(), __version__.rsplit('.', 1)[0]) if not os.path.isdir(get_config_dir()): diff --git a/tryton/tryton/gui/window/dblogin.py b/tryton/tryton/gui/window/dblogin.py index b6f598dba46..14e1a0629e5 100644 --- a/tryton/tryton/gui/window/dblogin.py +++ b/tryton/tryton/gui/window/dblogin.py @@ -6,6 +6,9 @@ import gettext import logging import os +import re +import shutil +import tempfile import threading from gi.repository import GLib, GObject, Gtk @@ -19,6 +22,7 @@ _ = gettext.gettext logger = logging.getLogger(__name__) +DEMO_HOSTNAME = re.compile(r"demo\d+\.\d+\.tryton\.org") class DBListEditor(object): @@ -495,20 +499,31 @@ def __init__(self): grid.attach(self.entry_date, 1, 6, 2, 1) # Profile informations - self.profile_cfg = os.path.join(get_config_dir(), 'profiles.cfg') + config_dir = get_config_dir() + self.profile_cfg = os.path.join(config_dir, 'profiles.cfg') self.profiles = configparser.ConfigParser() - if not os.path.exists(self.profile_cfg): + try: + self.profiles.read(self.profile_cfg) + except configparser.Error: + # reset self.profiles as parsing errors may leave wrong data in + # the parser + self.profiles = configparser.ConfigParser() + with tempfile.NamedTemporaryFile( + delete=False, prefix='profiles_', suffix='.cfg', + dir=config_dir) as temp_file: + temp_name = temp_file.name + shutil.copy(self.profile_cfg, temp_name) + logger.error( + f"Failed to parse {self.profiles_cfg}. " + f"A backup can be found at {temp_name}", + exc_info=True) + if not self.profiles.sections(): short_version = '.'.join(__version__.split('.', 2)[:2]) name = 'demo%s.tryton.org' % short_version self.profiles.add_section(name) self.profiles.set(name, 'host', name) self.profiles.set(name, 'database', 'demo%s' % short_version) self.profiles.set(name, 'username', 'demo') - else: - try: - self.profiles.read(self.profile_cfg) - except configparser.ParsingError: - logger.error("Fail to parse profiles.cfg", exc_info=True) for section in self.profiles.sections(): active = all(self.profiles.has_option(section, option) for option in ('host', 'database'))