Merge pull request #51 from AresValley/revised-networking
Revised network manager, update manager and downloader
This commit is contained in:
7336
artemis/resources.py
7336
artemis/resources.py
File diff suppressed because it is too large
Load Diff
@@ -6,10 +6,10 @@ from PySide6.QtCore import QObject, Slot, Signal
|
|||||||
from artemis.utils.constants import Constants, Messages
|
from artemis.utils.constants import Constants, Messages
|
||||||
from artemis.utils.sys_utils import open_directory, make_tar, unpack_tar
|
from artemis.utils.sys_utils import open_directory, make_tar, unpack_tar
|
||||||
from artemis.utils.sql_utils import ArtemisDatabase, ArtemisSignal
|
from artemis.utils.sql_utils import ArtemisDatabase, ArtemisSignal
|
||||||
from artemis.utils.path_utils import DATA_DIR
|
from artemis.utils.update_utils import UpdateManager
|
||||||
from artemis.utils.network_utils import NetworkManager
|
|
||||||
from artemis.utils.generic_utils import generate_filter_query
|
from artemis.utils.generic_utils import generate_filter_query
|
||||||
from artemis.utils.path_utils import normalize_dialog_path
|
from artemis.utils.path_utils import normalize_dialog_path
|
||||||
|
from artemis.utils.path_utils import DATA_DIR
|
||||||
from artemis.utils.config_utils import CONFIGURE_QT
|
from artemis.utils.config_utils import CONFIGURE_QT
|
||||||
|
|
||||||
from artemis.ui.preferences import UIPreferences
|
from artemis.ui.preferences import UIPreferences
|
||||||
@@ -25,6 +25,7 @@ import artemis.resources
|
|||||||
|
|
||||||
class UIArtemis(QObject):
|
class UIArtemis(QObject):
|
||||||
# Python > QML Signals
|
# Python > QML Signals
|
||||||
|
close_ui = Signal()
|
||||||
populate_sig_list = Signal(list)
|
populate_sig_list = Signal(list)
|
||||||
populate_sig_details = Signal(list)
|
populate_sig_details = Signal(list)
|
||||||
populate_filter_modulation = Signal(list)
|
populate_filter_modulation = Signal(list)
|
||||||
@@ -37,7 +38,7 @@ class UIArtemis(QObject):
|
|||||||
|
|
||||||
show_dialog_popup = Signal(str, str, str)
|
show_dialog_popup = Signal(str, str, str)
|
||||||
show_dialog_download_db = Signal(str, str, str)
|
show_dialog_download_db = Signal(str, str, str)
|
||||||
show_dialog_download_art = Signal(str, str, str)
|
show_dialog_update_artemis = Signal(str, str, str, bool)
|
||||||
update_info_bar = Signal(str, str)
|
update_info_bar = Signal(str, str)
|
||||||
|
|
||||||
|
|
||||||
@@ -62,13 +63,13 @@ class UIArtemis(QObject):
|
|||||||
# Creating istances for other windows
|
# Creating istances for other windows
|
||||||
self.preferences = UIPreferences(self)
|
self.preferences = UIPreferences(self)
|
||||||
self.dbmanager = UIdbmanager(self)
|
self.dbmanager = UIdbmanager(self)
|
||||||
self.downloader = UIDownloader(self)
|
|
||||||
self.spaceweather = UIspaceweather(self)
|
self.spaceweather = UIspaceweather(self)
|
||||||
self.docmanager = UIdocumentsmanager(self)
|
self.docmanager = UIdocumentsmanager(self)
|
||||||
self.sigeditor = UIsignaleditor(self)
|
self.sigeditor = UIsignaleditor(self)
|
||||||
self.cateditor = UIcategoryeditor(self)
|
self.cateditor = UIcategoryeditor(self)
|
||||||
|
self.downloader = UIDownloader(self)
|
||||||
|
|
||||||
self.network_manager = NetworkManager(self)
|
self.update_manager = UpdateManager(self)
|
||||||
|
|
||||||
self.autoload_db()
|
self.autoload_db()
|
||||||
|
|
||||||
@@ -79,8 +80,9 @@ class UIArtemis(QObject):
|
|||||||
self._window.loadSignal.connect(self.load_sig)
|
self._window.loadSignal.connect(self.load_sig)
|
||||||
self._window.showPref.connect(self.show_pref_ui)
|
self._window.showPref.connect(self.show_pref_ui)
|
||||||
self._window.openSigEditor.connect(self.open_sig_editor)
|
self._window.openSigEditor.connect(self.open_sig_editor)
|
||||||
self._window.startDownloader.connect(self.start_download_db)
|
self._window.checkForUpdate.connect(self.check_for_update)
|
||||||
self._window.checkDbUpdates.connect(self.check_update_db)
|
self._window.updateDb.connect(self.update_db)
|
||||||
|
self._window.updateArtemis.connect(self.update_artemis)
|
||||||
self._window.showSpaceWeather.connect(self.show_space_weather_ui)
|
self._window.showSpaceWeather.connect(self.show_space_weather_ui)
|
||||||
self._window.openDbDirectory.connect(self.open_db_directory)
|
self._window.openDbDirectory.connect(self.open_db_directory)
|
||||||
self._window.showCatManager.connect(self.open_cat_manager)
|
self._window.showCatManager.connect(self.open_cat_manager)
|
||||||
@@ -98,12 +100,13 @@ class UIArtemis(QObject):
|
|||||||
self._window_signal.addCatTag.connect(self.add_cat_tag)
|
self._window_signal.addCatTag.connect(self.add_cat_tag)
|
||||||
|
|
||||||
# Python > QML connections
|
# Python > QML connections
|
||||||
|
self.close_ui.connect(self._window.close)
|
||||||
self.populate_sig_list.connect(self._window.populateList)
|
self.populate_sig_list.connect(self._window.populateList)
|
||||||
self.clear_list.connect(self._window.clearList)
|
self.clear_list.connect(self._window.clearList)
|
||||||
self.update_info_bar.connect(self._window.bottomInfoBar)
|
self.update_info_bar.connect(self._window.bottomInfoBar)
|
||||||
self.show_dialog_popup.connect(self._window.openGeneralDialog)
|
self.show_dialog_popup.connect(self._window.openGeneralDialog)
|
||||||
self.show_dialog_download_db.connect(self._window.openDialogDownloadDb)
|
self.show_dialog_download_db.connect(self._window.openDialogDownloadDb)
|
||||||
self.show_dialog_download_art.connect(self._window.openDialogDownloadArtemis)
|
self.show_dialog_update_artemis.connect(self._window.openDialogUpdateArtemis)
|
||||||
self.lock_menu.connect(self._window.lockMenu)
|
self.lock_menu.connect(self._window.lockMenu)
|
||||||
|
|
||||||
self.populate_sig_details.connect(self._window_signal.populateSignalParam)
|
self.populate_sig_details.connect(self._window_signal.populateSignalParam)
|
||||||
@@ -214,18 +217,10 @@ class UIArtemis(QObject):
|
|||||||
self.docmanager.load_documentsmanager_ui()
|
self.docmanager.load_documentsmanager_ui()
|
||||||
|
|
||||||
|
|
||||||
def check_update_db(self):
|
def check_for_update(self):
|
||||||
""" User manual check for updates db updates
|
""" User manual check for updates updates
|
||||||
"""
|
"""
|
||||||
self.network_manager.show_popup = True
|
self.update_manager.check_updates(True)
|
||||||
self.network_manager.check_updates()
|
|
||||||
|
|
||||||
|
|
||||||
def start_download_db(self):
|
|
||||||
""" Show the downloader and start the download of the sigid db
|
|
||||||
"""
|
|
||||||
self.downloader.show_ui.emit()
|
|
||||||
self.downloader.on_start()
|
|
||||||
|
|
||||||
|
|
||||||
def dialog_download_db(self, message_type, title, message):
|
def dialog_download_db(self, message_type, title, message):
|
||||||
@@ -234,10 +229,24 @@ class UIArtemis(QObject):
|
|||||||
self.show_dialog_download_db.emit(message_type, title, message)
|
self.show_dialog_download_db.emit(message_type, title, message)
|
||||||
|
|
||||||
|
|
||||||
def dialog_download_artemis(self, message_type, title, message):
|
def dialog_update_artemis(self, message_type, title, message, auto=False):
|
||||||
""" Dialog popup for artemis download confirmation
|
""" Dialog popup for Artemis download confirmation
|
||||||
"""
|
"""
|
||||||
self.show_dialog_download_art.emit(message_type, title, message)
|
self.show_dialog_update_artemis.emit(message_type, title, message, auto)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def update_db(self):
|
||||||
|
""" Start the download of the sigID DB
|
||||||
|
"""
|
||||||
|
self.update_manager.download_db()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def update_artemis(self):
|
||||||
|
""" Start the download of Artemis
|
||||||
|
"""
|
||||||
|
self.update_manager.download_artemis()
|
||||||
|
|
||||||
|
|
||||||
def open_db_directory(self):
|
def open_db_directory(self):
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
from PySide6.QtQml import QQmlApplicationEngine
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
from PySide6.QtCore import QObject, Slot, Signal, QUrl, QSaveFile, QDir, QIODevice
|
from PySide6.QtCore import QObject, Slot, Signal, QUrl, QSaveFile, QDir, QIODevice
|
||||||
from PySide6.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager
|
from PySide6.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager
|
||||||
|
|
||||||
from artemis.utils.config_utils import *
|
|
||||||
from artemis.utils.sys_utils import delete_file, delete_dir, match_hash, unpack_tar
|
|
||||||
from artemis.utils.constants import Messages
|
from artemis.utils.constants import Messages
|
||||||
from artemis.utils.path_utils import DATA_DIR
|
|
||||||
|
|
||||||
|
|
||||||
class UIDownloader(QObject):
|
class UIDownloader(QObject):
|
||||||
@@ -13,7 +12,9 @@ class UIDownloader(QObject):
|
|||||||
show_ui = Signal()
|
show_ui = Signal()
|
||||||
close_ui = Signal()
|
close_ui = Signal()
|
||||||
update_progress_bar = Signal(int, int)
|
update_progress_bar = Signal(int, int)
|
||||||
|
set_indeterminate_bar = Signal()
|
||||||
update_status = Signal(str)
|
update_status = Signal(str)
|
||||||
|
finished = Signal()
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
@@ -25,6 +26,13 @@ class UIDownloader(QObject):
|
|||||||
self._engine.load('qrc:/ui/Downloader.qml')
|
self._engine.load('qrc:/ui/Downloader.qml')
|
||||||
self._window = self._engine.rootObjects()[0]
|
self._window = self._engine.rootObjects()[0]
|
||||||
|
|
||||||
|
self.file_url = None
|
||||||
|
self.file_size = None
|
||||||
|
self.dest_file = None
|
||||||
|
self.file = None
|
||||||
|
self.manager = None
|
||||||
|
self.reply = None
|
||||||
|
|
||||||
self._connect()
|
self._connect()
|
||||||
|
|
||||||
|
|
||||||
@@ -36,50 +44,54 @@ class UIDownloader(QObject):
|
|||||||
self.show_ui.connect(self._window.show)
|
self.show_ui.connect(self._window.show)
|
||||||
self.close_ui.connect(self._window.close)
|
self.close_ui.connect(self._window.close)
|
||||||
self.update_progress_bar.connect(self._window.updateProgressBar)
|
self.update_progress_bar.connect(self._window.updateProgressBar)
|
||||||
|
self.set_indeterminate_bar.connect(self._window.setIndeterminateBar)
|
||||||
self.update_status.connect(self._window.updateStatus)
|
self.update_status.connect(self._window.updateStatus)
|
||||||
|
|
||||||
|
|
||||||
@Slot()
|
def on_start(self, url, save_path):
|
||||||
def on_start(self):
|
""" Start the download process using the specified URL
|
||||||
""" Start the download of the DB taking the needed url and size from
|
|
||||||
the attributes of the UpdatesController class
|
Args:
|
||||||
|
url (str): url from where download the file
|
||||||
|
save_path (str): path where to save the downloaded file
|
||||||
"""
|
"""
|
||||||
url_file = QUrl(self._parent.network_manager.remote_db_url)
|
self._clear_ui()
|
||||||
dest_path = QDir(DATA_DIR)
|
self.show_ui.emit()
|
||||||
self.dest_file = dest_path.filePath(url_file.fileName())
|
|
||||||
|
self.file_url = QUrl(url)
|
||||||
|
self.file_size = self._get_filesize(url)
|
||||||
|
dest_path = QDir(save_path)
|
||||||
|
self.dest_file = dest_path.filePath(self.file_url.fileName())
|
||||||
self.file = QSaveFile(self.dest_file)
|
self.file = QSaveFile(self.dest_file)
|
||||||
|
|
||||||
if self.file.open(QIODevice.WriteOnly):
|
if self.file.open(QIODevice.WriteOnly):
|
||||||
# Start a GET HTTP request
|
# Start a GET HTTP request
|
||||||
self.manager = QNetworkAccessManager(self)
|
self.manager = QNetworkAccessManager(self)
|
||||||
self.reply = self.manager.get(QNetworkRequest(url_file))
|
self.reply = self.manager.get(QNetworkRequest(self.file_url))
|
||||||
self.reply.downloadProgress.connect(self.on_progress)
|
self.reply.downloadProgress.connect(self.on_progress)
|
||||||
self.reply.finished.connect(self.on_finished)
|
self.reply.finished.connect(self.on_finished)
|
||||||
self.reply.readyRead.connect(self.on_ready_read)
|
self.reply.readyRead.connect(self.on_ready_read)
|
||||||
self.reply.errorOccurred.connect(self.on_error)
|
self.reply.errorOccurred.connect(self.on_error)
|
||||||
else:
|
else:
|
||||||
self.close_ui.emit()
|
self.close_ui.emit()
|
||||||
self.show_popup_error(
|
self.show_popup_error(self.file.errorString())
|
||||||
self.file.errorString()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def on_abort(self):
|
def on_abort(self):
|
||||||
""" Stop the download when user press abort button """
|
""" Stop the download when user presses the abort button
|
||||||
|
"""
|
||||||
if self.reply:
|
if self.reply:
|
||||||
self.reply.abort()
|
self.reply.abort()
|
||||||
self.update_progress_bar.emit(0, 0)
|
|
||||||
|
|
||||||
if self.file:
|
if self.file:
|
||||||
self.file.cancelWriting()
|
self.file.cancelWriting()
|
||||||
|
|
||||||
self.close_ui.emit()
|
|
||||||
|
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def on_ready_read(self):
|
def on_ready_read(self):
|
||||||
""" Get available bytes and store them into the file """
|
""" Write available bytes to the file
|
||||||
|
"""
|
||||||
if self.reply:
|
if self.reply:
|
||||||
if self.reply.error() == QNetworkReply.NoError:
|
if self.reply.error() == QNetworkReply.NoError:
|
||||||
self.file.write(self.reply.readAll())
|
self.file.write(self.reply.readAll())
|
||||||
@@ -87,8 +99,9 @@ class UIDownloader(QObject):
|
|||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def on_finished(self):
|
def on_finished(self):
|
||||||
""" Delete reply, close the file, check the hash for integrity,
|
""" Finalize the download process and, if no errors
|
||||||
extract the database and delete the downloaded zip
|
occurs, emits the finished signal usefull for
|
||||||
|
a callback
|
||||||
"""
|
"""
|
||||||
if self.reply:
|
if self.reply:
|
||||||
self.reply.deleteLater()
|
self.reply.deleteLater()
|
||||||
@@ -96,24 +109,21 @@ class UIDownloader(QObject):
|
|||||||
if self.file:
|
if self.file:
|
||||||
self.file.commit()
|
self.file.commit()
|
||||||
|
|
||||||
self.update_status.emit("Checking DB integrity (SHA-256)")
|
if self.reply.error() == QNetworkReply.NoError:
|
||||||
|
self.finished.emit()
|
||||||
|
|
||||||
if match_hash(self.dest_file, self._parent.network_manager.remote_db_hash):
|
self.close_ui.emit()
|
||||||
self.update_status.emit("Unpacking archive...")
|
|
||||||
delete_dir(DATA_DIR / 'SigID')
|
|
||||||
unpack_tar(self.dest_file, DATA_DIR / 'SigID')
|
|
||||||
delete_file(self.dest_file)
|
|
||||||
self._parent.load_db('SigID')
|
|
||||||
self.close_ui.emit()
|
|
||||||
|
|
||||||
|
|
||||||
@Slot(int, int)
|
@Slot(int, int)
|
||||||
def on_progress(self, bytesReceived: int):
|
def on_progress(self, bytesReceived: int):
|
||||||
""" Update progress bar and label
|
""" Update progress bar and status label
|
||||||
"""
|
"""
|
||||||
total_bytes = self._parent.network_manager.remote_db_size
|
if self.file_size is not None:
|
||||||
self.update_status.emit("{:.1f} Mb / {:.1f} Mb".format(bytesReceived/10**6, total_bytes/10**6))
|
self.update_status.emit("{:.1f} Mb / {:.1f} Mb".format(bytesReceived/10**6, self.file_size/10**6))
|
||||||
self.update_progress_bar.emit(bytesReceived, total_bytes)
|
self.update_progress_bar.emit(bytesReceived, self.file_size)
|
||||||
|
else:
|
||||||
|
self.update_status.emit("{:.1f} Mb".format(bytesReceived/10**6))
|
||||||
|
|
||||||
|
|
||||||
@Slot(QNetworkReply.NetworkError)
|
@Slot(QNetworkReply.NetworkError)
|
||||||
@@ -127,6 +137,28 @@ class UIDownloader(QObject):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_filesize(self, url):
|
||||||
|
""" Get the file size by sending a HEAD request to the URL.
|
||||||
|
If the Content-Length in HTTP headers is missing, returns None
|
||||||
|
and set the progress_bar as 'indeterminate' like a 'busy indicator'
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): URL to check the file size
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = requests.get(url, stream=True)
|
||||||
|
size = int(response.headers.get('content-length'))
|
||||||
|
return size
|
||||||
|
except:
|
||||||
|
self.set_indeterminate_bar.emit()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_ui(self):
|
||||||
|
self.update_progress_bar.emit(0, 0)
|
||||||
|
self.update_status.emit('')
|
||||||
|
|
||||||
|
|
||||||
def show_popup_error(self, error_msg):
|
def show_popup_error(self, error_msg):
|
||||||
self._parent.dialog_popup(
|
self._parent.dialog_popup(
|
||||||
Messages.DIALOG_TYPE_ERROR,
|
Messages.DIALOG_TYPE_ERROR,
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ class UIspaceweather(QObject):
|
|||||||
|
|
||||||
|
|
||||||
def download_poseidon_report(self):
|
def download_poseidon_report(self):
|
||||||
network_manager = self._parent.network_manager
|
update_manager = self._parent.update_manager
|
||||||
network_manager.show_popup = True
|
poseidon_data = update_manager.fetch_remote_json(
|
||||||
poseidon_data = network_manager.fetch_remote_json(
|
Constants.POSEIDON_REPORT_URL,
|
||||||
Constants.POSEIDON_REPORT_URL
|
True
|
||||||
)
|
)
|
||||||
if poseidon_data:
|
if poseidon_data:
|
||||||
self.load_poseidon_report.emit(poseidon_data)
|
self.load_poseidon_report.emit(poseidon_data)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class Messages:
|
|||||||
UP_TO_DATE = "You're up to date!"
|
UP_TO_DATE = "You're up to date!"
|
||||||
DB_NEW_VER = "New SigID DB version available!"
|
DB_NEW_VER = "New SigID DB version available!"
|
||||||
ART_NEW_VER = "New Artemis version available!"
|
ART_NEW_VER = "New Artemis version available!"
|
||||||
|
DB_CORRUPTED = "Database Corruption Detected"
|
||||||
|
|
||||||
# Messages
|
# Messages
|
||||||
DB_CREATION_SUCCESS_MSG = "The new database has been created succesfully."
|
DB_CREATION_SUCCESS_MSG = "The new database has been created succesfully."
|
||||||
@@ -50,8 +51,10 @@ class Messages:
|
|||||||
NO_CONNECTION_MSG = "Unable to check for updates. It appears that there is a problem with your internet connection. Please check your network settings and try again later. {}"
|
NO_CONNECTION_MSG = "Unable to check for updates. It appears that there is a problem with your internet connection. Please check your network settings and try again later. {}"
|
||||||
UP_TO_DATE_MSG = "The latest version of Artemis and SigID wiki is installed on your computer."
|
UP_TO_DATE_MSG = "The latest version of Artemis and SigID wiki is installed on your computer."
|
||||||
DB_NEW_VER_MSG = "A new version of the database ({}) is available for download. Download now?"
|
DB_NEW_VER_MSG = "A new version of the database ({}) is available for download. Download now?"
|
||||||
ART_NEW_VER_MSG = "A new version of Artemis ({}) is available for download. Check GitHub page now?"
|
ART_NEW_VER_MANUAL_MSG = "A new version of Artemis ({}) is available for download. Check GitHub page now?"
|
||||||
DOWNLOAD_CORRUPTED_MSG = "Downloaded data corrupted or invalid. Please retry."
|
ART_NEW_VER_AUTO_MSG = "A new version of Artemis ({}) is available for download. Update Artemis now?"
|
||||||
|
DB_CORRUPTED_MSG = "Downloaded data corrupted or invalid. Please retry."
|
||||||
|
DB_DOWNLOAD_SUCCESS_MSG = "The database has been successfully downloaded and is now being loaded."
|
||||||
|
|
||||||
|
|
||||||
class Query():
|
class Query():
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
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
|
|
||||||
from artemis.utils.path_utils import DATA_DIR
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkManager:
|
|
||||||
""" Class that checks for DB or software updates
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
|
||||||
self._parent = parent
|
|
||||||
self.sigid_db_path = DATA_DIR / 'SigID' / Constants.SQL_NAME
|
|
||||||
|
|
||||||
self.show_popup = False
|
|
||||||
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_art_version = None
|
|
||||||
|
|
||||||
self.check_updates()
|
|
||||||
|
|
||||||
|
|
||||||
def check_updates(self):
|
|
||||||
""" Checks if a new DB update is available.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
popup (bool, optional): Suppress the "already up-to-date" message on startup.
|
|
||||||
Defaults to False.
|
|
||||||
"""
|
|
||||||
latest_json = self.fetch_remote_json(Constants.LATEST_VERSION_URL)
|
|
||||||
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']
|
|
||||||
|
|
||||||
if is_windows():
|
|
||||||
self.remote_art_version = latest_json['windows']['version']
|
|
||||||
elif is_linux():
|
|
||||||
self.remote_art_version = latest_json['linux']['version']
|
|
||||||
elif is_macos():
|
|
||||||
self.remote_art_version = latest_json['mac']['version']
|
|
||||||
|
|
||||||
if Version(self.remote_art_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 self.show_popup:
|
|
||||||
self.show_popup_up_to_date()
|
|
||||||
else:
|
|
||||||
self.show_popup_initial_db_download()
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_remote_json(self, url):
|
|
||||||
""" Fetches the remote json from a url
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
response = requests.get(url)
|
|
||||||
response.raise_for_status()
|
|
||||||
return response.json()
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
if self.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 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):
|
|
||||||
"""Prompts the user to download the updated version of the database."""
|
|
||||||
self._parent.dialog_download_artemis(
|
|
||||||
Messages.DIALOG_TYPE_WARN,
|
|
||||||
Messages.ART_NEW_VER,
|
|
||||||
Messages.ART_NEW_VER_MSG.format(self.remote_art_version)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
@@ -36,6 +36,15 @@ def _data_dir():
|
|||||||
return data_dir_path
|
return data_dir_path
|
||||||
|
|
||||||
|
|
||||||
|
def _tmp_dir():
|
||||||
|
if is_windows():
|
||||||
|
tmp_dir_path = Path.home() / 'AppData' / 'Local' / 'Temp'
|
||||||
|
else:
|
||||||
|
tmp_dir_path = Path('/tmp')
|
||||||
|
|
||||||
|
return tmp_dir_path
|
||||||
|
|
||||||
|
|
||||||
def _preference_dir():
|
def _preference_dir():
|
||||||
preference_dir_path = APP_DIR / 'config'
|
preference_dir_path = APP_DIR / 'config'
|
||||||
if not preference_dir_path.exists():
|
if not preference_dir_path.exists():
|
||||||
@@ -46,4 +55,5 @@ def _preference_dir():
|
|||||||
BASE_DIR = Path(os.path.dirname(__file__)) / '../..'
|
BASE_DIR = Path(os.path.dirname(__file__)) / '../..'
|
||||||
APP_DIR = _app_dir()
|
APP_DIR = _app_dir()
|
||||||
DATA_DIR = _data_dir()
|
DATA_DIR = _data_dir()
|
||||||
|
TMP_DIR = _tmp_dir()
|
||||||
PREFERENCES_DIR = _preference_dir()
|
PREFERENCES_DIR = _preference_dir()
|
||||||
|
|||||||
234
artemis/utils/update_utils.py
Normal file
234
artemis/utils/update_utils.py
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
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, TMP_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
|
||||||
|
TMP_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,
|
||||||
|
TMP_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 = TMP_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
|
||||||
|
TMP_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,
|
||||||
|
TMP_DIR
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def post_download_artemis(self):
|
||||||
|
""" After a succesfull Artemis download, this open the installer
|
||||||
|
and close the application
|
||||||
|
"""
|
||||||
|
if is_windows():
|
||||||
|
open_file(TMP_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
|
||||||
|
)
|
||||||
@@ -31,8 +31,9 @@ Window {
|
|||||||
signal showCatManager()
|
signal showCatManager()
|
||||||
signal openSigEditor(string type, var sig_param, bool is_new)
|
signal openSigEditor(string type, var sig_param, bool is_new)
|
||||||
signal showSpaceWeather()
|
signal showSpaceWeather()
|
||||||
signal checkDbUpdates()
|
signal checkForUpdate()
|
||||||
signal startDownloader()
|
signal updateDb()
|
||||||
|
signal updateArtemis()
|
||||||
signal openDbDirectory()
|
signal openDbDirectory()
|
||||||
signal newDb(string name)
|
signal newDb(string name)
|
||||||
signal exportDb(string path)
|
signal exportDb(string path)
|
||||||
@@ -121,11 +122,12 @@ Window {
|
|||||||
dialogDownloadDb.open()
|
dialogDownloadDb.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDialogDownloadArtemis(messageType, title, message) {
|
function openDialogUpdateArtemis(messageType, title, message, auto) {
|
||||||
dialogDownloadArtemis.messageType = messageType
|
dialogUpdateArtemis.messageType = messageType
|
||||||
dialogDownloadArtemis.title = title
|
dialogUpdateArtemis.title = title
|
||||||
dialogDownloadArtemis.message = message
|
dialogUpdateArtemis.message = message
|
||||||
dialogDownloadArtemis.open()
|
dialogUpdateArtemis.autoUpdate = auto
|
||||||
|
dialogUpdateArtemis.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogMessage {
|
DialogMessage {
|
||||||
@@ -135,18 +137,24 @@ Window {
|
|||||||
standardButtons: Dialog.Cancel | Dialog.Yes
|
standardButtons: Dialog.Cancel | Dialog.Yes
|
||||||
|
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
startDownloader()
|
updateDb()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogMessage {
|
DialogMessage {
|
||||||
id: dialogDownloadArtemis
|
id: dialogUpdateArtemis
|
||||||
modal: true
|
modal: true
|
||||||
|
|
||||||
|
property bool autoUpdate
|
||||||
|
|
||||||
standardButtons: Dialog.Cancel | Dialog.Yes
|
standardButtons: Dialog.Cancel | Dialog.Yes
|
||||||
|
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
Qt.openUrlExternally("https://github.com/AresValley/Artemis")
|
if (autoUpdate) {
|
||||||
|
updateArtemis();
|
||||||
|
} else {
|
||||||
|
Qt.openUrlExternally("https://github.com/AresValley/Artemis");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +328,7 @@ Window {
|
|||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Check for Updates"
|
text: "Check for Updates"
|
||||||
onClicked: {checkDbUpdates()}
|
onClicked: {checkForUpdate()}
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuSeparator {}
|
MenuSeparator {}
|
||||||
|
|||||||
@@ -24,11 +24,19 @@ Window {
|
|||||||
|
|
||||||
signal onAbort()
|
signal onAbort()
|
||||||
|
|
||||||
|
onClosing: {
|
||||||
|
onAbort()
|
||||||
|
}
|
||||||
|
|
||||||
function updateProgressBar(bytesReceived, bytesTotal) {
|
function updateProgressBar(bytesReceived, bytesTotal) {
|
||||||
progressBar.value = bytesReceived
|
progressBar.value = bytesReceived
|
||||||
progressBar.to = bytesTotal
|
progressBar.to = bytesTotal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setIndeterminateBar() {
|
||||||
|
progressBar.indeterminate = true
|
||||||
|
}
|
||||||
|
|
||||||
function updateStatus(arg) {
|
function updateStatus(arg) {
|
||||||
progressLabel.text = arg
|
progressLabel.text = arg
|
||||||
}
|
}
|
||||||
@@ -51,6 +59,7 @@ Window {
|
|||||||
Layout.rightMargin: 20
|
Layout.rightMargin: 20
|
||||||
Layout.leftMargin: 20
|
Layout.leftMargin: 20
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
indeterminate: false
|
||||||
value: 0
|
value: 0
|
||||||
to: 0
|
to: 0
|
||||||
}
|
}
|
||||||
@@ -61,10 +70,11 @@ Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||||
text: qsTr("Abort")
|
text: qsTr("Abort")
|
||||||
icon.source: "qrc:/images/icons/abort.svg"
|
icon.source: "qrc:/images/icons/abort.svg"
|
||||||
display: AbstractButton.TextBesideIcon
|
display: AbstractButton.TextBesideIcon
|
||||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
flat: true
|
||||||
onClicked: { onAbort() }
|
onClicked: { onAbort() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user