import os import requests from packaging.version import Version from artemis.utils.constants import Constants, Messages from artemis.utils.sql_utils import ArtemisDatabase from artemis.utils.sys_utils import is_windows, is_linux, is_macos, delete_file, delete_dir, match_hash, unpack_tar, open_file from artemis.utils.path_utils import DATA_DIR, APP_DIR class UpdateManager: """ Class used to manage DB and software updates """ def __init__(self, parent): self._parent = parent self.sigid_db_path = DATA_DIR / 'SigID' / Constants.SQL_NAME self.db_update = None self.art_update = None self.remote_db_url = None self.remote_db_hash = None self.remote_db_version = None self.remote_db_size = None self.remote_db_file_name = None self.remote_artemis_version = None self.remote_artemis_url = None self.remote_artemis_file_name = None self.check_updates() def check_updates(self, show_popup=False): """ Checks if a software or DB update is available. Prioritize Artemis updates over the DB one. Args: show_popup (bool, optional): If False, suppress the "already up-to-date" message on startup. Defaults to False. True is usefull when the user manual check for updates. """ latest_json = self.fetch_remote_json(Constants.LATEST_VERSION_URL, show_popup) if latest_json: local_db = self._load_local_db() remote_db = latest_json['sigID_DB'] self.remote_db_version = remote_db['version'] self.remote_db_url = remote_db['url'] self.remote_db_hash = remote_db['sha256_hash'] self.remote_db_size = remote_db['total_bytes'] self.remote_db_file_name = self.remote_db_url.split('/')[-1] if is_windows(): self.remote_artemis_version = latest_json['windows']['version'] self.remote_artemis_url = latest_json['windows']['url'] elif is_linux(): self.remote_artemis_version = latest_json['linux']['version'] self.remote_artemis_url = latest_json['linux']['url'] elif is_macos(): self.remote_artemis_version = latest_json['mac']['version'] self.remote_artemis_url = latest_json['mac']['url'] self.remote_artemis_file_name = self.remote_artemis_url.split('/')[-1] if Version(self.remote_artemis_version) > Version(Constants.APPLICATION_VERSION): self.art_update = True else: self.art_update = False if self.art_update: self._show_popup_art_update() else: if local_db: if self.remote_db_version > local_db.version: self._show_popup_db_update() elif show_popup: self._show_popup_up_to_date() else: self._show_popup_initial_db_download() def fetch_remote_json(self, url, show_popup=False): """ Fetches the remote json from a url Args: show_popup (bool, optional): If false, suppress any error message Defaults to False (to avoid error if the program is used offline) """ try: response = requests.get(url) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: if show_popup: self._parent.dialog_popup( Messages.DIALOG_TYPE_ERROR, Messages.NO_CONNECTION, Messages.NO_CONNECTION_MSG.format(e) ) return None def _load_local_db(self): """ Loads the local database if exists """ if os.path.exists(self.sigid_db_path): local_db = ArtemisDatabase('SigID') local_db.load() return local_db return None def download_db(self): """ Open the downloader and download the sigID database in the DATA_DIR folder. After a succesfull download the callback function from the downloader is post_download_db """ self._parent.downloader.finished.connect(self.post_download_db) self._parent.downloader.on_start( self.remote_db_url, DATA_DIR ) def post_download_db(self): """ After a succesfull DB download, this function check the hash for possible corrupted data, delete old sigID DB and extract the new one """ latest_db_tar_path = DATA_DIR / self.remote_db_file_name if match_hash(latest_db_tar_path, self.remote_db_hash): delete_dir(DATA_DIR / 'SigID') unpack_tar(latest_db_tar_path, DATA_DIR / 'SigID') self._parent.load_db('SigID') self._show_popup_db_download_complete() else: self._show_popup_db_hash_failed() delete_file(latest_db_tar_path) def download_artemis(self): """ Open the downloader and download Artemis in the APP_DIR folder. After a succesfull download the callback function from the downloader is post_download_artemis """ self._parent.downloader.finished.connect(self.post_download_artemis) self._parent.downloader.on_start( self.remote_artemis_url, APP_DIR ) def post_download_artemis(self): """ After a succesfull Artemis download, this open the installer and close the application """ if is_windows(): open_file(APP_DIR / self.remote_artemis_file_name) self._parent.close_ui.emit() def _show_popup_db_update(self): """ Prompts the user to download the updated version of the database """ self._parent.dialog_download_db( Messages.DIALOG_TYPE_WARN, Messages.DB_NEW_VER, Messages.DB_NEW_VER_MSG.format(self.remote_db_version) ) def _show_popup_art_update(self): """ Alerts the user of a new version of Artemis. Windows - asks to download with automatic update Linux, macOS - redirects to GitHub page """ if is_windows(): self._parent.dialog_update_artemis( Messages.DIALOG_TYPE_QUEST, Messages.ART_NEW_VER, Messages.ART_NEW_VER_AUTO_MSG.format(self.remote_artemis_version), True ) else: self._parent.dialog_update_artemis( Messages.DIALOG_TYPE_QUEST, Messages.ART_NEW_VER, Messages.ART_NEW_VER_MANUAL_MSG.format(self.remote_artemis_version), False ) def _show_popup_up_to_date(self): """ Notifies the user that the database is up to date """ self._parent.dialog_popup( Messages.DIALOG_TYPE_INFO, Messages.UP_TO_DATE, Messages.UP_TO_DATE_MSG ) def _show_popup_initial_db_download(self): """ Prompts the user to download the database for the first time """ self._parent.dialog_download_db( Messages.DIALOG_TYPE_QUEST, Messages.NO_DB_DETECTED, Messages.NO_DB_DETECTED_MSG ) def _show_popup_db_download_complete(self): """ DB has been succesfully downloaded """ self._parent.dialog_popup( Messages.DIALOG_TYPE_INFO, Messages.GENERIC_SUCCESS, Messages.DB_DOWNLOAD_SUCCESS_MSG ) def _show_popup_db_hash_failed(self): """ Notify the user after detection of a corrupted database """ self._parent.dialog_popup( Messages.DIALOG_TYPE_ERROR, Messages.DB_CORRUPTED, Messages.DB_CORRUPTED_MSG )