diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index aa07b5b1..202beef3 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -30,7 +30,7 @@ except ImportError: 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 @@ -308,12 +308,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): @@ -322,7 +331,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(): @@ -338,7 +347,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}") @@ -351,7 +360,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 d132fe19..9bf1eb80 100644 --- a/gns3server/controller/appliance_manager.py +++ b/gns3server/controller/appliance_manager.py @@ -95,7 +95,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 """ @@ -107,8 +107,6 @@ class ApplianceManager: else: resources_path = os.path.expanduser(resources_path) 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 @@ -117,7 +115,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 faae1210..e00d24b7 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): @@ -109,3 +110,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 79b65556..75541539 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ Jinja2>=3.1.4,<3.2 sentry-sdk>=2.17,<2.18 # optional dependency psutil>=6.1.0 distro>=1.9.0 -py-cpuinfo==9.0.0 +py-cpuinfo>=9.0.0,<10.0 sqlalchemy==2.0.36 aiosqlite==0.20.0 alembic==1.13.3 @@ -19,6 +19,6 @@ python-jose[cryptography]==3.3.0 email-validator==2.2.0 watchfiles==0.24.0 zstandard==0.23.0 -platformdirs==4.3.6 +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'