diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 5113cfb0..05b0dfe2 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -29,7 +29,7 @@ except ImportError: from importlib import resources as importlib_resources from ..config import Config -from ..utils import parse_version +from ..utils import parse_version, md5sum from ..utils.images import default_images_directory from .project import Project @@ -289,12 +289,21 @@ class Controller: except OSError as e: log.error(str(e)) + @staticmethod - def install_resource_files(dst_path, resource_name): + def install_resource_files(dst_path, resource_name, upgrade_resources=True): """ Install files from resources to user's file system """ + def should_copy(src, dst, upgrade_resources): + if not os.path.exists(dst): + return True + if upgrade_resources is False: + return False + # copy the resource if it is different + return md5sum(src) != md5sum(dst) + if hasattr(sys, "frozen") and sys.platform.startswith("win"): resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name)) for filename in os.listdir(resource_path): @@ -303,7 +312,7 @@ class Controller: else: for entry in importlib_resources.files('gns3server').joinpath(resource_name).iterdir(): full_path = os.path.join(dst_path, entry.name) - if entry.is_file() and not os.path.exists(full_path): + if entry.is_file() and should_copy(str(entry), full_path, upgrade_resources): log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"') shutil.copy(str(entry), os.path.join(dst_path, entry.name)) elif entry.is_dir(): @@ -319,7 +328,7 @@ class Controller: dst_path = self.configs_path() log.info(f"Installing base configs in '{dst_path}'") try: - Controller.install_resource_files(dst_path, "configs") + Controller.install_resource_files(dst_path, "configs", upgrade_resources=False) except OSError as e: log.error(f"Could not install base config files to {dst_path}: {e}") @@ -332,7 +341,7 @@ class Controller: dst_path = self.disks_path() log.info(f"Installing built-in disks in '{dst_path}'") try: - Controller.install_resource_files(dst_path, "disks") + Controller.install_resource_files(dst_path, "disks", upgrade_resources=False) except OSError as e: log.error(f"Could not install disk files to {dst_path}: {e}") diff --git a/gns3server/controller/appliance_manager.py b/gns3server/controller/appliance_manager.py index 39012129..9607f629 100644 --- a/gns3server/controller/appliance_manager.py +++ b/gns3server/controller/appliance_manager.py @@ -82,7 +82,7 @@ class ApplianceManager: os.makedirs(appliances_path, exist_ok=True) return appliances_path - def builtin_appliances_path(self, delete_first=False): + def builtin_appliances_path(self): """ Get the built-in appliance storage directory """ @@ -91,8 +91,6 @@ class ApplianceManager: appname = vendor = "GNS3" resources_path = os.path.expanduser(server_config.get("resources_path", platformdirs.user_data_dir(appname, vendor, roaming=True))) appliances_dir = os.path.join(resources_path, "appliances") - if delete_first: - shutil.rmtree(appliances_dir, ignore_errors=True) os.makedirs(appliances_dir, exist_ok=True) return appliances_dir @@ -101,7 +99,7 @@ class ApplianceManager: At startup we copy the built-in appliances files. """ - dst_path = self.builtin_appliances_path(delete_first=True) + dst_path = self.builtin_appliances_path() log.info(f"Installing built-in appliances in '{dst_path}'") from . import Controller try: diff --git a/gns3server/utils/__init__.py b/gns3server/utils/__init__.py index 3740066b..0ee4377a 100644 --- a/gns3server/utils/__init__.py +++ b/gns3server/utils/__init__.py @@ -23,6 +23,7 @@ import textwrap import posixpath import socket import errno +import hashlib def force_unix_path(path): @@ -120,3 +121,14 @@ def is_ipv6_enabled() -> bool: if e.errno == errno.EADDRINUSE: return True raise + +def md5sum(filename): + """ + Calculate the MD5 checksum of a file. + """ + + hash_md5 = hashlib.md5() + with open(filename, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() diff --git a/requirements.txt b/requirements.txt index 9a94f5ce..398250ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,6 @@ psutil>=6.1.0 async-timeout>=4.0.3,<4.1 distro>=1.9.0 py-cpuinfo>=9.0.0,<10.0 -platformdirs>=2.4.0 +platformdirs>=2.4.0,<3 # platformdirs >=3 conflicts when building Debian packages importlib-resources>=1.3; python_version < '3.9' truststore>=0.10.0; python_version >= '3.10'