Compare commits
20 Commits
v4.0.3
...
revised-ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c0b1b7e04 | ||
|
|
c17c1fdbea | ||
|
|
8138cf8e38 | ||
|
|
0b416f0a2e | ||
|
|
1d0c459402 | ||
|
|
0f898ff6f5 | ||
|
|
c58d85c6a2 | ||
|
|
f73034c35c | ||
|
|
77c43813a0 | ||
|
|
b01bd99ecf | ||
|
|
146a5d1605 | ||
|
|
ea245c853b | ||
|
|
e57e9bf8e4 | ||
|
|
70ed158b02 | ||
|
|
1d795b688e | ||
|
|
52c4fbcce9 | ||
|
|
79891899ce | ||
|
|
dde223d2ac | ||
|
|
43ddee410a | ||
|
|
ecbf10ebb5 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -5,6 +5,14 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Possibility to navigate Artemis just with the keyboard [#50](https://github.com/AresValley/Artemis/issues/50)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved readability of labels for filter ranges for frequency, bandwidth, and ACF
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Added a database load check to avoid (non critical) exceptions when applying filters without having loaded a database.
|
||||||
|
|
||||||
## [4.0.3] - 2024-06-10
|
## [4.0.3] - 2024-06-10
|
||||||
|
|
||||||
@@ -102,7 +110,7 @@ First release.
|
|||||||
|
|
||||||
<!-- Links definitions -->
|
<!-- Links definitions -->
|
||||||
[Unreleased]: https://github.com/AresValley/Artemis/compare/v4.0.3...HEAD
|
[Unreleased]: https://github.com/AresValley/Artemis/compare/v4.0.3...HEAD
|
||||||
[4.0.1]: https://github.com/AresValley/Artemis/compare/v4.0.1...v4.0.3
|
[4.0.3]: https://github.com/AresValley/Artemis/compare/v4.0.1...v4.0.3
|
||||||
[4.0.1]: https://github.com/AresValley/Artemis/compare/v3.2.4...v4.0.1
|
[4.0.1]: https://github.com/AresValley/Artemis/compare/v3.2.4...v4.0.1
|
||||||
[3.2.4]: https://github.com/AresValley/Artemis/compare/v3.2.1...v3.2.4
|
[3.2.4]: https://github.com/AresValley/Artemis/compare/v3.2.1...v3.2.4
|
||||||
[3.2.3]: https://github.com/AresValley/Artemis/compare/v3.2.2...v3.2.3
|
[3.2.3]: https://github.com/AresValley/Artemis/compare/v3.2.2...v3.2.3
|
||||||
|
|||||||
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)
|
||||||
@@ -168,17 +171,18 @@ class UIArtemis(QObject):
|
|||||||
the details to generate a search query
|
the details to generate a search query
|
||||||
"""
|
"""
|
||||||
filter_status = filter_status.toVariant()
|
filter_status = filter_status.toVariant()
|
||||||
if filter_status != {}:
|
if self.loaded_db is not None:
|
||||||
filter_query = generate_filter_query(filter_status)
|
if filter_status != {}:
|
||||||
self.loaded_db.select_by_filter(filter_query)
|
filter_query = generate_filter_query(filter_status)
|
||||||
|
self.loaded_db.select_by_filter(filter_query)
|
||||||
self.clear_signal_page.emit()
|
|
||||||
self.populate_sig_list.emit(self.loaded_db.all_signals)
|
self.clear_signal_page.emit()
|
||||||
|
self.populate_sig_list.emit(self.loaded_db.all_signals)
|
||||||
|
|
||||||
total_signals = len(self.loaded_db.all_signals)
|
total_signals = len(self.loaded_db.all_signals)
|
||||||
self.bottom_info_bar("FILTERS ACTIVE: {} signals found".format(total_signals), "warning")
|
self.bottom_info_bar("FILTERS ACTIVE: {} signals found".format(total_signals), "warning")
|
||||||
else:
|
else:
|
||||||
self.load_db(self.loaded_db.db_dir_name)
|
self.load_db(self.loaded_db.db_dir_name)
|
||||||
|
|
||||||
|
|
||||||
def show_pref_ui(self):
|
def show_pref_ui(self):
|
||||||
@@ -213,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):
|
||||||
@@ -233,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,33 +99,31 @@ 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()
|
||||||
|
|
||||||
if self.file:
|
if self.file:
|
||||||
self.file.commit()
|
self.file.commit()
|
||||||
|
|
||||||
|
if self.reply.error() == QNetworkReply.NoError:
|
||||||
|
self.finished.emit()
|
||||||
|
|
||||||
self.update_status.emit("Checking DB integrity (SHA-256)")
|
self.close_ui.emit()
|
||||||
|
|
||||||
if match_hash(self.dest_file, self._parent.network_manager.remote_db_hash):
|
|
||||||
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
|
||||||
|
)
|
||||||
@@ -9,7 +9,6 @@ pip install nuitka==2.3
|
|||||||
echo "Building with Nuitka ..."
|
echo "Building with Nuitka ..."
|
||||||
python -m nuitka app.py \
|
python -m nuitka app.py \
|
||||||
--standalone \
|
--standalone \
|
||||||
--follow-imports \
|
|
||||||
--show-modules \
|
--show-modules \
|
||||||
--assume-yes-for-downloads \
|
--assume-yes-for-downloads \
|
||||||
--enable-plugin=pyside6 \
|
--enable-plugin=pyside6 \
|
||||||
@@ -18,7 +17,6 @@ python -m nuitka app.py \
|
|||||||
--noinclude-dlls=libQt6Sensors* \
|
--noinclude-dlls=libQt6Sensors* \
|
||||||
--noinclude-dlls=libQt6Test* \
|
--noinclude-dlls=libQt6Test* \
|
||||||
--noinclude-dlls=libQt6WebEngine* \
|
--noinclude-dlls=libQt6WebEngine* \
|
||||||
--include-qt-plugins=sensible \
|
|
||||||
--include-qt-plugins=styles \
|
--include-qt-plugins=styles \
|
||||||
--include-qt-plugins=qml \
|
--include-qt-plugins=qml \
|
||||||
--include-qt-plugins=multimedia \
|
--include-qt-plugins=multimedia \
|
||||||
@@ -29,6 +27,4 @@ python -m nuitka app.py \
|
|||||||
--force-stderr-spec="{TEMP}/artemis.err.log" \
|
--force-stderr-spec="{TEMP}/artemis.err.log" \
|
||||||
--force-stdout-spec="{TEMP}/artemis.out.log"
|
--force-stdout-spec="{TEMP}/artemis.out.log"
|
||||||
|
|
||||||
chmod 755 ./app.dist/app.bin
|
|
||||||
|
|
||||||
echo "Building Linux target finished."
|
echo "Building Linux target finished."
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ pip install nuitka==2.3
|
|||||||
Write-Output "Building with Nuitka ..."
|
Write-Output "Building with Nuitka ..."
|
||||||
python -m nuitka app.py `
|
python -m nuitka app.py `
|
||||||
--standalone `
|
--standalone `
|
||||||
--follow-imports `
|
|
||||||
--show-modules `
|
--show-modules `
|
||||||
--assume-yes-for-downloads `
|
--assume-yes-for-downloads `
|
||||||
--windows-console-mode=disable `
|
--windows-console-mode=disable `
|
||||||
@@ -17,7 +16,6 @@ python -m nuitka app.py `
|
|||||||
--noinclude-dlls="Qt6Sensors*" `
|
--noinclude-dlls="Qt6Sensors*" `
|
||||||
--noinclude-dlls="Qt6Test*" `
|
--noinclude-dlls="Qt6Test*" `
|
||||||
--noinclude-dlls="Qt6WebEngine*" `
|
--noinclude-dlls="Qt6WebEngine*" `
|
||||||
--include-qt-plugins=sensible `
|
|
||||||
--include-qt-plugins=styles `
|
--include-qt-plugins=styles `
|
||||||
--include-qt-plugins=qml `
|
--include-qt-plugins=qml `
|
||||||
--include-qt-plugins=multimedia `
|
--include-qt-plugins=multimedia `
|
||||||
|
|||||||
@@ -6,12 +6,15 @@
|
|||||||
"total_bytes": 244449280
|
"total_bytes": 244449280
|
||||||
},
|
},
|
||||||
"windows": {
|
"windows": {
|
||||||
"version": "4.0.1"
|
"version": "4.0.3",
|
||||||
|
"url": "https://github.com/AresValley/Artemis/releases/download/v4.0.3/Artemis-Windows-x86_64-4.0.3.exe"
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"version": "4.0.1"
|
"version": "4.0.3",
|
||||||
|
"url": "https://github.com/AresValley/Artemis/releases/download/v4.0.3/Artemis-Linux-x86_64-4.0.3.zip"
|
||||||
},
|
},
|
||||||
"mac": {
|
"mac": {
|
||||||
"version": "4.0.0"
|
"version": "4.0.0",
|
||||||
|
"url": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,16 @@ where $Y_{i+τ}$ is a lagged data by $τ$ of $Y_i$ and $\bar{Y}$ is the average
|
|||||||
## Example: ACF Analysis
|
## Example: ACF Analysis
|
||||||
### STANAG 4285
|
### STANAG 4285
|
||||||
|
|
||||||
The first example will be a NATO standard known as STANAG 4285. You can download a .wav sample from HERE. We start to collect some information about the structure of this signal: a good starting point is the official declassified NATO document dated 1989. The main frame structure can be found at **Annex A-3/5** with a graphical view at **Annex A-7**, also reported below:
|
The first example will be a NATO standard known as STANAG 4285. You can download a .wav sample from [:material-download: HERE](assets/stanag_4285.wav).
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<audio controls>
|
||||||
|
<source src="https://raw.githubusercontent.com/AresValley/Artemis/master/docs/assets/stanag_4285.wav" type="audio/wav">
|
||||||
|
Your browser does not support the audio player.
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
We start to collect some information about the structure of this signal: a good starting point is the official declassified NATO document dated 1989. The main frame structure can be found at **Annex A-3/5** with a graphical view at **Annex A-7**, also reported below:
|
||||||
|
|
||||||
* [STANAG 4285 (i)](assets/acf_2.png)
|
* [STANAG 4285 (i)](assets/acf_2.png)
|
||||||
* [STANAG 4285 A-3](assets/acf_3.png)
|
* [STANAG 4285 A-3](assets/acf_3.png)
|
||||||
|
|||||||
BIN
docs/assets/stanag_4285.wav
Normal file
BIN
docs/assets/stanag_4285.wav
Normal file
Binary file not shown.
18
docs/faq.md
Normal file
18
docs/faq.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Frequently Asked Questions
|
||||||
|
|
||||||
|
### What about an automatic signal recognition feature?
|
||||||
|
This question has been asked countless times, and for good reason: in many fields, machine learning (ML) has disrupted the way we solve problems, and when applied to the right field, you can get outstanding results. **So why not on signals?**
|
||||||
|
|
||||||
|
Well, the story dates back to at least 2017 when I started discussing it with several people on the rtl-sdr blog: at first, it seemed promising, but like many things, all that glitters is not gold. Let us proceed in order:
|
||||||
|
|
||||||
|
#### ML / Deep Learning approach
|
||||||
|
A machine learning/neural network approach would not be complex (from a technical standpoint) if there were not the problem of the dataframe completeness. To be effective, neural models need to be trained on a large number of entries. The precise number can vary and strongly depends on the nature of data used for training, but commonly, numbers can be tens of thousands of entries or even more, for example. This would not be a significant concern in the case of RF signals because the various encodings that differentiate can be created artificially using for example [Fldigi](http://www.w1hkj.com/). To make these synthetic signals more like a real one, noise and artificial distortions can be added. However, this approach allows us to have a spectrum that ranges only over civilian, non-proprietary, and non-military signals (a little bit more than 150 out of 500, in the best-case scenario). Many efforts have been made in this field and some excellent results have been reported below:
|
||||||
|
|
||||||
|
- [O’Shea, Corgan, and Clancy](https://arxiv.org/pdf/1602.04105) - Training Size: 900000 signals, 11 modulations
|
||||||
|
- [Stefan Scholl](https://arxiv.org/pdf/1906.04459) - Training Size: 120000 signals, 18 modulations
|
||||||
|
- [Stefan Scholl](https://panoradio-sdr.de/automatic-identification-of-160-shortwave-rf-signals-with-deep-learning/) - Training Size: 1.2 million signals, 143 modulations tested in HF
|
||||||
|
|
||||||
|
Therefore, The main point is to have a good quality data frame to train the model; this is not always an easy task for the above reasons. Subsequently, as Stefan Scholl pointed out on his blog, similar modulations (MFSK-32 vs Olivia 16/500) can significantly decrease the effectiveness of discrimination. The quality of reception can also influence the result as well: high SNR cannot always be an ideal point since the reception can be disrupted by interference or fading.
|
||||||
|
|
||||||
|
#### Classical Audio Analysis
|
||||||
|
More classical methods, such as similarity recognition between audio samples (such as using Mel-frequency cepstral coefficients, for example), could be effective, albeit marginally, if applied to the 500 signals audio sample library from sigID wiki. With the same signal encoding, the content of the signal alone can affect the similarity index and, thus, the method's effectiveness. Listening to a signal with the same modulation but encoding different information can significantly decrease the accuracy of signal recognition.
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: Documentation
|
title: Documentation
|
||||||
---
|
---
|
||||||
#
|
#
|
||||||
|
|
||||||
<p align="center" markdown>
|
<div align="center">
|
||||||
{width="400"}
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/W_8Y_4FvoHI?si=0hBqRnxnzCUWmTxK" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
||||||
{width="400"}
|
</div>
|
||||||
</p>
|
|
||||||
|
|
||||||
**Artemis** is a software designed to assist **radio frequency (RF) signal identification and storage**. It simplifies real-time spectrum analysis by leveraging one of the most extensive and community-driven databases, containing nearly **500 recognized signals**. This comprehensive software solution allows users to collect RF signals with specific parameters such as frequency, bandwidth, modulation, etc. Users can also store spectrum waterfalls, audio samples, and all types of documents for future reference. Artemis provides a robust platform to manage a wide range of RF data with precision and ease.
|
|
||||||
|
|
||||||
<p align="center" markdown>
|
<p align="center" markdown>
|
||||||
[Artemis Homepage](https://www.aresvalley.com){ .md-button }
|
[Artemis Homepage](https://www.aresvalley.com){ .md-button }
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
**Artemis** is a software designed to assist **radio frequency (RF) signal identification and storage**. It simplifies real-time spectrum analysis by leveraging one of the most extensive and community-driven databases, containing nearly **500 recognized signals**. This comprehensive software solution allows users to collect RF signals with specific parameters such as frequency, bandwidth, modulation, etc. Users can also store spectrum waterfalls, audio samples, and all types of documents for future reference. Artemis provides a robust platform to manage a wide range of RF data with precision and ease.
|
||||||
|
|||||||
@@ -12,8 +12,14 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
## :simple-linux: Linux
|
## :simple-linux: Linux
|
||||||
1. Download `Artemis-Linux-x86_64-4.x.x.tar` in the Assets menu from the [:material-download: LATEST RELEASE](https://github.com/AresValley/Artemis/releases) and extract the tarball archive in a folder of your choice.
|
1. On Linux, the xcb plugin is utilized to supply the essential functionality required for Qt GUI and Qt Widgets to operate on [X11](https://doc.qt.io/qt-6/linux-requirements.html). On some Linux distributions the required dependencies are already met, but in many cases, you will need to install them. To install the dependencies use:
|
||||||
2. Before running `app.bin`, be sure to have the executable permissions to the binary file with:
|
|
||||||
|
``` bash title="Debian-based distro (Ubuntu, Mint, Pop! OS, Kali, ...)"
|
||||||
|
sudo apt install libxcb-cursor0
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Download `Artemis-Linux-x86_64-4.x.x.tar` in the Assets menu from the [:material-download: LATEST RELEASE](https://github.com/AresValley/Artemis/releases) and extract the tarball archive in a folder of your choice.
|
||||||
|
3. Before running `app.bin`, be sure to have the executable permissions to the binary file with:
|
||||||
|
|
||||||
```
|
```
|
||||||
chmod 700 app.bin
|
chmod 700 app.bin
|
||||||
|
|||||||
@@ -73,8 +73,9 @@ nav:
|
|||||||
- Forecasts: 'space_weather/forecasts.md'
|
- Forecasts: 'space_weather/forecasts.md'
|
||||||
- DRAP: 'space_weather/drap.md'
|
- DRAP: 'space_weather/drap.md'
|
||||||
- Aurora: 'space_weather/aurora.md'
|
- Aurora: 'space_weather/aurora.md'
|
||||||
- Example:
|
- Signal Analysis:
|
||||||
- ACF Analysis: 'acf_analysis.md'
|
- Autocorrelation (ACF): 'acf_analysis.md'
|
||||||
|
- FAQ: 'faq.md'
|
||||||
- Contribute: 'contribute.md'
|
- Contribute: 'contribute.md'
|
||||||
- Changelog: https://github.com/AresValley/Artemis/blob/master/CHANGELOG.md
|
- Changelog: https://github.com/AresValley/Artemis/blob/master/CHANGELOG.md
|
||||||
- License & Credits: 'credits.md'
|
- License & Credits: 'credits.md'
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +226,11 @@ Window {
|
|||||||
leftPadding: 10
|
leftPadding: 10
|
||||||
bottomPadding: 10
|
bottomPadding: 10
|
||||||
|
|
||||||
|
focus: true
|
||||||
|
|
||||||
|
Keys.onDownPressed: listView.incrementCurrentIndex()
|
||||||
|
Keys.onUpPressed: listView.decrementCurrentIndex()
|
||||||
|
|
||||||
header: MenuBar {
|
header: MenuBar {
|
||||||
id: topBar
|
id: topBar
|
||||||
|
|
||||||
@@ -315,7 +328,7 @@ Window {
|
|||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Check for Updates"
|
text: "Check for Updates"
|
||||||
onClicked: {checkDbUpdates()}
|
onClicked: {checkForUpdate()}
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuSeparator {}
|
MenuSeparator {}
|
||||||
@@ -389,7 +402,6 @@ Window {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
highlightMoveDuration: 0
|
highlightMoveDuration: 0
|
||||||
clip: true
|
clip: true
|
||||||
focus: true
|
|
||||||
ScrollBar.vertical: bar
|
ScrollBar.vertical: bar
|
||||||
highlight: Rectangle { color: Material.accent; radius: 5 }
|
highlight: Rectangle { color: Material.accent; radius: 5 }
|
||||||
onCurrentIndexChanged: { itemChangedList() }
|
onCurrentIndexChanged: { itemChangedList() }
|
||||||
|
|||||||
@@ -372,7 +372,6 @@ Window {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
highlightMoveDuration: 0
|
highlightMoveDuration: 0
|
||||||
clip: true
|
clip: true
|
||||||
focus: true
|
|
||||||
ScrollBar.vertical: bar
|
ScrollBar.vertical: bar
|
||||||
highlight: Rectangle { color: Material.accent; radius: 5 }
|
highlight: Rectangle { color: Material.accent; radius: 5 }
|
||||||
onCurrentIndexChanged: { itemChanged() }
|
onCurrentIndexChanged: { itemChanged() }
|
||||||
|
|||||||
@@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,11 +301,12 @@ Page {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: summaryFreq
|
id: summaryFreq
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
color: Material.color(Material.Green)
|
color: Material.color(Material.Green)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
Layout.fillWidth: true
|
font.pointSize: 16
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,11 +391,12 @@ Page {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
id: summaryBand
|
id: summaryBand
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
color: Material.color(Material.Green)
|
color: Material.color(Material.Green)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
Layout.fillWidth: true
|
font.pointSize: 16
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,11 +467,12 @@ Page {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
id: summaryACF
|
id: summaryACF
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
color: Material.color(Material.Green)
|
color: Material.color(Material.Green)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
Layout.fillWidth: true
|
font.pointSize: 16
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,7 +506,6 @@ Page {
|
|||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
highlightMoveDuration: 0
|
highlightMoveDuration: 0
|
||||||
clip: true
|
clip: true
|
||||||
focus: true
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: ScrollBar {
|
||||||
active: true
|
active: true
|
||||||
}
|
}
|
||||||
@@ -555,7 +557,6 @@ Page {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
highlightMoveDuration: 0
|
highlightMoveDuration: 0
|
||||||
clip: true
|
clip: true
|
||||||
focus: true
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: ScrollBar {
|
||||||
active: true
|
active: true
|
||||||
}
|
}
|
||||||
@@ -607,7 +608,6 @@ Page {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
highlightMoveDuration: 0
|
highlightMoveDuration: 0
|
||||||
clip: true
|
clip: true
|
||||||
focus: true
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
ScrollBar.vertical: ScrollBar {
|
||||||
active: true
|
active: true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user