50 Commits

Author SHA1 Message Date
Marco Dalla Tiezza
6c0b1b7e04 Merge branch 'master' into revised-networking 2024-06-13 23:07:30 +00:00
Marco Dalla Tiezza
c17c1fdbea Updated resources 2024-06-14 01:01:53 +02:00
Marco Dalla Tiezza
8138cf8e38 Docs update 2 2024-06-14 00:14:04 +02:00
Marco Dalla Tiezza
0b416f0a2e Docs update 2024-06-13 23:55:41 +02:00
Marco Dalla Tiezza
1d0c459402 Fixed a potential issue where the downloader window closes but the downloader instance keeps running 2024-06-13 19:19:21 +02:00
Marco Dalla Tiezza
0f898ff6f5 Introduced temporary folder 2024-06-13 16:16:36 +02:00
Marco Dalla Tiezza
c58d85c6a2 Fixed bug of space_weather introduced with the new update_manager 2024-06-13 15:52:47 +02:00
Marco Dalla Tiezza
f73034c35c Preparatory update for the new update manager 2024-06-13 15:46:56 +02:00
Marco Dalla Tiezza
77c43813a0 Final ieration of the new downloader and update_manager, implemented automatic updates only for windows 2024-06-13 15:46:25 +02:00
Marco Dalla Tiezza
b01bd99ecf Preparatory update for the new update manager 2024-06-13 14:57:36 +02:00
Marco Dalla Tiezza
146a5d1605 First implementation of the auto update for windows 2024-06-13 14:40:07 +02:00
Marco Dalla Tiezza
ea245c853b Updated changelog and documentation 2024-06-13 12:20:12 +02:00
Marco Dalla Tiezza
e57e9bf8e4 Artemis can be fully navigated (and so the signal list) just with the keyboard, fixed #50 2024-06-13 11:42:24 +02:00
Marco Dalla Tiezza
70ed158b02 Updated resources, fixed typo 2024-06-13 02:06:45 +02:00
Marco Dalla Tiezza
1d795b688e File size request from HTTP header, handled the indeterminate size case 2024-06-13 01:54:59 +02:00
Marco Dalla Tiezza
52c4fbcce9 Downloader has been generalized, now network_utils is update_utils 2024-06-13 01:05:53 +02:00
Marco Dalla Tiezza
79891899ce Improved label readability, handled non critical exception about filtering without a loaded DB 2024-06-12 23:16:48 +02:00
Marco Dalla Tiezza
dde223d2ac Updated documentation 2024-06-12 21:20:18 +02:00
Marco Dalla Tiezza
43ddee410a Removed unnecessay arguments (now default) for nuitka 2024-06-11 18:37:50 +02:00
Marco Dalla Tiezza
ecbf10ebb5 Rolling out v4.0.3 2024-06-10 22:20:37 +02:00
Marco Dalla Tiezza
5d3cdb7abb Updated to v4.0.3 (hotfix) 2024-06-10 21:57:47 +02:00
Marco Dalla Tiezza
5b8670814b Removed setProperty and findChild in dowloader.py for code uniformity 2024-06-10 21:51:53 +02:00
Marco Dalla Tiezza
19acf11b1a Fixed a potential bug affecting ARM architecture 2024-06-10 21:30:28 +02:00
Marco Dalla Tiezza
39056d1d91 Updated name of excluded DLLs (only for linux since it is using a lib prefix) 2024-06-10 21:22:32 +02:00
Marco Dalla Tiezza
436c54b733 Fixed #48 an error occurig on Linux where configuration file path are not properly resolved during startup with the binary version of the program 2024-06-10 20:40:46 +02:00
Marco Dalla Tiezza
b48a42dcc8 Explicit DLLs inclusion/exclusion in Nuitka, close #47 2024-06-10 16:55:04 +02:00
Marco Dalla Tiezza
e00e21c46a Fixed #46 2024-06-10 11:56:27 +02:00
Marco Dalla Tiezza
2aa821ee65 Minor docs fix 2024-06-09 16:42:19 +02:00
Marco Dalla Tiezza
5ab0c39aa9 Rolling out v4.0.1 2024-06-09 16:31:21 +02:00
Marco Dalla Tiezza
f09bbd3311 Revert "Bump Nuitka 2.3.1"
This reverts commit 91589018a3.
2024-06-09 16:07:24 +02:00
Marco Dalla Tiezza
91589018a3 Bump Nuitka 2.3.1 2024-06-09 15:42:40 +02:00
Marco Dalla Tiezza
d41c9e1ed6 Added installer icon, bump version 4.0.1 2024-06-09 15:36:54 +02:00
Marco Dalla Tiezza
0cbd9a7a0b Updated documentation 2024-06-09 15:17:13 +02:00
Marco Dalla Tiezza
7db68ab2ad Integrity check for the configuration parser 2024-06-06 22:34:51 +02:00
Marco Dalla Tiezza
c7c53b5a68 Solved clipping of the scrollbar on different textareas 2024-06-06 20:12:29 +02:00
Marco Dalla Tiezza
9d2443b0f0 Updated docs 2024-06-06 19:20:03 +02:00
Marco Dalla Tiezza
2c3ffd5e66 Updated readme image 2024-06-06 19:15:46 +02:00
Marco Dalla Tiezza
485eccb373 Added DB autoload on startup option 2024-06-06 19:06:45 +02:00
Marco Dalla Tiezza
faf9a5293a Normalized K and A index lights to NOAA standard nomnclature 2024-06-06 18:11:15 +02:00
Marco Dalla Tiezza
e58cf4d206 Updated docs 2024-06-06 15:51:20 +02:00
Marco Dalla Tiezza
1b48405c14 Rolling out DB v61 2024-06-05 22:20:43 +02:00
Marco Dalla Tiezza
c8fcb2c3e0 Added project webpage in help section 2024-06-05 21:03:06 +02:00
Marco Dalla Tiezza
ab8403c16e Corrected few docs typo 2024-06-05 00:05:00 +02:00
Marco Dalla Tiezza
7929c23ac5 Fixed a bug introduced in 10607c88ea 2024-06-04 22:15:44 +02:00
Marco Dalla Tiezza
707cc001cf Updated docs about new text search behaviour 2024-06-04 21:57:56 +02:00
Marco Dalla Tiezza
4594237c09 Added volume slider and mute bbutton to audio player, closed #45 2024-06-04 21:43:57 +02:00
Marco Dalla Tiezza
10607c88ea Searches in the name field has been extended to the description as well (close #44) 2024-06-04 19:51:21 +02:00
Marco Dalla Tiezza
f61527ed70 Locked audio player upon signal deletion 2024-06-04 19:30:15 +02:00
Marco Dalla Tiezza
4e7ebcc2f5 Read-only ops are separated in different standard and OS dependent folders from read-write ones (fixed #43), bump Nuitka 2.3 2024-06-04 19:25:12 +02:00
Marco Dalla Tiezza
16e2668fe9 Forced std error and std output in building script for Windows for logging purposes 2024-06-02 00:57:32 +02:00
57 changed files with 933 additions and 7777 deletions

3
.gitignore vendored
View File

@@ -6,3 +6,6 @@ data/
*.qtds *.qtds
artemis_rc.py artemis_rc.py
site site
Generated
app.build
app.dist

View File

@@ -3,7 +3,27 @@
> [!NOTE] > [!NOTE]
> This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). > This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] - 2024-05-28 ## [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
### Changed
- Optimized final package size (reduced by 30% to 50%) by explicitly including necessary plugins/DLLs and excluding unnecessary ones with Nuitka [#47](https://github.com/AresValley/Artemis/issues/47)
### Fixed
- When the links/urls in the description field of a signal are clicked, they open the default browser [#46](https://github.com/AresValley/Artemis/issues/46)
- Fixed an error occurig on Linux where configuration file path are not properly resolved during startup with the binary version of the program (they are if running from source) [#48](https://github.com/AresValley/Artemis/issues/48)
## [4.0.1] - 2024-06-9
### Added ### Added
- Database format has been changed from .csv to a proper relational DB (sqlite) which is much easier handled thanks to the native library shipped with python - Database format has been changed from .csv to a proper relational DB (sqlite) which is much easier handled thanks to the native library shipped with python
- Possibility to create an arbitrary number of new databases for storing new custom signals - Possibility to create an arbitrary number of new databases for storing new custom signals
@@ -11,14 +31,19 @@
- All signal parameters (such as frequency, modulation, location, etc.) are now followed by a description - All signal parameters (such as frequency, modulation, location, etc.) are now followed by a description
- Databases can be exported/imported for easy sharing - Databases can be exported/imported for easy sharing
- Possibility to store and view all type of documents related to a signal entry - Possibility to store and view all type of documents related to a signal entry
- Filtration process is now much more efficient due to usage of SQL queries - Filtration process is now much more efficient due to usage of SQL queries
- D-Region Absorption Predictions (DRAP) and Aurora OVATION model are now present in the Space Weather window
### Changed ### Changed
- Updated GUI libray from PyQt5 to PySide6. Artemis 4 now relies on the QtQuick framework. - Updated GUI libray from PyQt5 to PySide6. Artemis 4 now relies on the QtQuick framework.
- Undefined value for frequency and bandwidth is now deprecated. - SigID standard database is now hosted on GitHub (the server is much faster) along with the website parser
- Undefined value for frequency and bandwidth is now deprecated
- Drastically reduced the number of third party libraries - Drastically reduced the number of third party libraries
- The signals filtering page has been simplified to be more immediate and user friendly - The signals filtering page has been simplified to be more immediate and user friendly
- Space weather page now relies on Poseidon daemon (hosted on aresvalley.com) - Space weather page has been greatly improved and now relies on Poseidon daemon (hosted on aresvalley.com)
### Fixed
- Artemis can be execunted inside standard pretected folder (such as Program Files) without using elevated privileges
## [3.2.4] - 2022-09-30 ## [3.2.4] - 2022-09-30
### Fixed ### Fixed
@@ -84,7 +109,9 @@ First release.
<!-- Links definitions --> <!-- Links definitions -->
[Unreleased]: https://github.com/AresValley/Artemis/compare/v3.2.4...HEAD [Unreleased]: https://github.com/AresValley/Artemis/compare/v4.0.3...HEAD
[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
[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
[3.2.2]: https://github.com/AresValley/Artemis/compare/v3.2.1...v3.2.2 [3.2.2]: https://github.com/AresValley/Artemis/compare/v3.2.1...v3.2.2

View File

@@ -15,7 +15,7 @@
**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. **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.
<div align="center"> <div align="center">
<img src="docs/assets/artemis_preview.png"> <img src="docs/assets/artemis_preview.webp">
</div> </div>
## Documentation ## Documentation

View File

@@ -12,6 +12,7 @@
<file>images/icons/player_play.svg</file> <file>images/icons/player_play.svg</file>
<file>images/icons/player_stop.svg</file> <file>images/icons/player_stop.svg</file>
<file>images/icons/player_loop.svg</file> <file>images/icons/player_loop.svg</file>
<file>images/icons/player_mute.svg</file>
<file>images/icons/save.svg</file> <file>images/icons/save.svg</file>
<file>images/icons/delete.svg</file> <file>images/icons/delete.svg</file>
<file>images/icons/add.svg</file> <file>images/icons/add.svg</file>

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,13 @@ from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QObject, Slot, Signal 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, pack_db, unpack_db 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 check_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.ui.preferences import UIPreferences from artemis.ui.preferences import UIPreferences
from artemis.ui.dbmanager import UIdbmanager from artemis.ui.dbmanager import UIdbmanager
@@ -24,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)
@@ -36,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)
@@ -61,15 +63,15 @@ 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)
check_data_dir() self.autoload_db()
def _connect(self): def _connect(self):
@@ -78,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)
@@ -97,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)
@@ -167,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):
@@ -212,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):
@@ -232,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):
@@ -277,7 +288,7 @@ class UIArtemis(QObject):
""" """
try: try:
dest_path = normalize_dialog_path(save_path) dest_path = normalize_dialog_path(save_path)
pack_db(dest_path, self.loaded_db.db_dir) make_tar(dest_path, self.loaded_db.db_dir)
self.dialog_popup( self.dialog_popup(
Messages.DIALOG_TYPE_INFO, Messages.DIALOG_TYPE_INFO,
Messages.GENERIC_SUCCESS, Messages.GENERIC_SUCCESS,
@@ -300,7 +311,8 @@ class UIArtemis(QObject):
""" """
try: try:
origin_path = normalize_dialog_path(tar_path) origin_path = normalize_dialog_path(tar_path)
unpack_db(origin_path, str(uuid.uuid4())) save_path = DATA_DIR / str(uuid.uuid4())
unpack_tar(origin_path, save_path)
self.dialog_popup( self.dialog_popup(
Messages.DIALOG_TYPE_INFO, Messages.DIALOG_TYPE_INFO,
Messages.GENERIC_SUCCESS, Messages.GENERIC_SUCCESS,
@@ -332,6 +344,13 @@ class UIArtemis(QObject):
self.cateditor.load_cateditor_ui() self.cateditor.load_cateditor_ui()
def autoload_db(self):
sig_id_path = DATA_DIR / 'SigID' / Constants.SQL_NAME
autoload = CONFIGURE_QT.value("Database", "autoload", 0)
if sig_id_path.exists() and int(autoload):
self.load_db('SigID')
def dialog_popup(self, message_type, title, message): def dialog_popup(self, message_type, title, message):
""" Opens a general dialog popup """ Opens a general dialog popup

View File

@@ -1,10 +1,13 @@
import os
from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QObject, Signal, Slot from PySide6.QtCore import QObject, Signal, Slot
from artemis.utils.path_utils import * from artemis.utils.path_utils import DATA_DIR
from artemis.utils.generic_utils import * from artemis.utils.generic_utils import *
from artemis.utils.sql_utils import ArtemisDatabase from artemis.utils.sql_utils import ArtemisDatabase
from artemis.utils.constants import Constants from artemis.utils.constants import Constants
from artemis.utils.sys_utils import delete_dir
class UIdbmanager(QObject): class UIdbmanager(QObject):
@@ -67,7 +70,7 @@ class UIdbmanager(QObject):
self._parent.lock_menu.emit(True) self._parent.lock_menu.emit(True)
self._parent.clear_list.emit() self._parent.clear_list.emit()
self._parent.clear_signal_page.emit() self._parent.clear_signal_page.emit()
delete_db_dir(db_dir_name) delete_dir(DATA_DIR / db_dir_name)
self.load_local_db_list() self.load_local_db_list()
@@ -85,10 +88,10 @@ class UIdbmanager(QObject):
return a dictionary containing only the valid ones with a summary return a dictionary containing only the valid ones with a summary
""" """
valid_db_list = [] valid_db_list = []
db_dirs = next(os.walk(Constants.DB_DIR))[1] db_dirs = next(os.walk(DATA_DIR))[1]
for db_dir_name in db_dirs: for db_dir_name in db_dirs:
if valid_db(db_dir_name): if self._valid_db(db_dir_name):
database = ArtemisDatabase(db_dir_name) database = ArtemisDatabase(db_dir_name)
database.load() database.load()
valid_db_list.append( valid_db_list.append(
@@ -103,3 +106,22 @@ class UIdbmanager(QObject):
) )
return valid_db_list return valid_db_list
def _valid_db(self, db_dir_name):
""" Checks if db_dir_name is a valid db dir containing a `data.sqlite` file.
Db must be valid as well and should be properly initialized and loaded with
no errors.
Args:
db_dir_name (str): name of the db folder
"""
if os.path.exists(DATA_DIR / db_dir_name / Constants.SQL_NAME):
try:
database = ArtemisDatabase(db_dir_name)
database.load()
return True
except:
return False # Invalid or corrupted DB
else:
return False # The dir is not containing a data.sqlite file

View File

@@ -1,17 +1,21 @@
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, match_hash, unpack_db
from artemis.utils.constants import Messages from artemis.utils.constants import Messages
from artemis.utils.sys_utils import delete_db_dir
class UIDownloader(QObject): class UIDownloader(QObject):
# Python > QML Signals # Python > QML Signals
show_ui = Signal() show_ui = Signal()
close_ui = Signal() close_ui = Signal()
update_progress_bar = Signal(int, int)
set_indeterminate_bar = Signal()
update_status = Signal(str)
finished = Signal()
def __init__(self, parent): def __init__(self, parent):
super().__init__() super().__init__()
@@ -21,8 +25,13 @@ class UIDownloader(QObject):
self._engine = QQmlApplicationEngine() self._engine = QQmlApplicationEngine()
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._progress_bar = self._window.findChild(QObject, "progressBar")
self._label_progress = self._window.findChild(QObject, "labelProgress") 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()
@@ -34,49 +43,55 @@ class UIDownloader(QObject):
# Python > QML connections # Python > QML connections
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.set_indeterminate_bar.connect(self._window.setIndeterminateBar)
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(Constants.DB_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._progress_bar.setProperty("value", 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())
@@ -84,34 +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._label_progress.setProperty("text", "Checking DB integrity (SHA-256)") self.close_ui.emit()
if match_hash(self.dest_file, self._parent.network_manager.remote_db_hash):
self._label_progress.setProperty("text", "Unpacking archive...")
delete_db_dir('SigID')
unpack_db(self.dest_file, '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._label_progress.setProperty("text", "{:.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._progress_bar.setProperty("to", total_bytes) self.update_progress_bar.emit(bytesReceived, self.file_size)
self._progress_bar.setProperty("value", bytesReceived) else:
self.update_status.emit("{:.1f} Mb".format(bytesReceived/10**6))
@Slot(QNetworkReply.NetworkError) @Slot(QNetworkReply.NetworkError)
@@ -125,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,

View File

@@ -9,6 +9,7 @@ class UIPreferences(QObject):
show_ui = Signal() show_ui = Signal()
load_material_accent = Signal(str) load_material_accent = Signal(str)
load_material_theme = Signal(str) load_material_theme = Signal(str)
load_autoload = Signal(int)
def __init__(self, parent): def __init__(self, parent):
@@ -27,18 +28,21 @@ class UIPreferences(QObject):
# QML > Python connections # QML > Python connections
self._window.saveMaterialAccent.connect(self.save_material_accent) self._window.saveMaterialAccent.connect(self.save_material_accent)
self._window.saveMaterialTheme.connect(self.save_material_theme) self._window.saveMaterialTheme.connect(self.save_material_theme)
self._window.saveAutoload.connect(self.save_autoload)
# Python > QML connections # Python > QML connections
self.show_ui.connect(self._window.show) self.show_ui.connect(self._window.show)
self.load_material_accent.connect(self._window.loadMaterialAccent) self.load_material_accent.connect(self._window.loadMaterialAccent)
self.load_material_theme.connect(self._window.loadMaterialTheme) self.load_material_theme.connect(self._window.loadMaterialTheme)
self.load_autoload.connect(self._window.loadAutoload)
def load_preferences_ui(self): def load_preferences_ui(self):
""" Loading all the initial preferences from the conf file to the UI """ Loading all the initial preferences from the conf file to the UI
""" """
self.load_material_accent.emit(CONFIGURE_QT.get_or_default("Material", "Accent", "Green")) self.load_material_accent.emit(CONFIGURE_QT.value("Material", "Accent", "Green"))
self.load_material_theme.emit(CONFIGURE_QT.get_or_default("Material", "Theme", "System")) self.load_material_theme.emit(CONFIGURE_QT.value("Material", "Theme", "System"))
self.load_autoload.emit(int(CONFIGURE_QT.value("Database", "autoload", 0)))
self.show_ui.emit() self.show_ui.emit()
@@ -54,3 +58,10 @@ class UIPreferences(QObject):
""" Saving material theme setting """ Saving material theme setting
""" """
CONFIGURE_QT.set("Material", "Theme", material_theme) CONFIGURE_QT.set("Material", "Theme", material_theme)
@Slot(int)
def save_autoload(self, autoload):
""" Saving autoload setting
"""
CONFIGURE_QT.set("Database", "autoload", str(autoload))

View File

@@ -4,6 +4,7 @@ from PySide6.QtCore import QObject, Signal, Slot
from artemis.utils.path_utils import * from artemis.utils.path_utils import *
from artemis.utils.generic_utils import * from artemis.utils.generic_utils import *
from artemis.utils.sql_utils import ArtemisSignal from artemis.utils.sql_utils import ArtemisSignal
from artemis.utils.sys_utils import delete_file
class UIsignaleditor(QObject): class UIsignaleditor(QObject):
@@ -104,6 +105,7 @@ class UIsignaleditor(QObject):
""" """
if param_type == 'Signal': if param_type == 'Signal':
self._parent.loaded_sig.delete_signal() self._parent.loaded_sig.delete_signal()
self._parent.lock_audio_player.emit()
for doc in self._parent.loaded_sig.documents: for doc in self._parent.loaded_sig.documents:
doc_file_name = '{}.{}'.format(str(doc[0]), doc[1]) doc_file_name = '{}.{}'.format(str(doc[0]), doc[1])
doc_file_path = self._parent.loaded_db.media_dir / doc_file_name doc_file_path = self._parent.loaded_db.media_dir / doc_file_name

View File

@@ -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)

View File

@@ -1,10 +1,12 @@
from configparser import ConfigParser from configparser import ConfigParser
from artemis.utils.constants import Constants
from artemis.utils.path_utils import PREFERENCES_DIR, BASE_DIR
from artemis.utils.sys_utils import copy_file
class Config(ConfigParser): class Config(ConfigParser):
""" Custom configuration class derived from ConfigParser. """ Custom configuration class derived from ConfigParser.
Used to get, set, save and remove any configuration from the conf file Used to get value, set, save and remove any configuration from the conf file
""" """
def __init__(self, config_file_path, space_around_delimiters=False): def __init__(self, config_file_path, space_around_delimiters=False):
@@ -13,11 +15,13 @@ class Config(ConfigParser):
self.read(self._config_file_path) self.read(self._config_file_path)
self._space_around_delimiters = space_around_delimiters self._space_around_delimiters = space_around_delimiters
def get_or_default(self, section, option, default_value): def value(self, section, option, default_value):
value = super().get(section, option) value = super().get(section, option, fallback=default_value)
return value if value else default_value return value
def set(self, section, option, value=None): def set(self, section, option, value=None):
if not self.has_section(section):
self.add_section(section)
super().set(section, option, value) super().set(section, option, value)
self.save() self.save()
@@ -29,4 +33,43 @@ class Config(ConfigParser):
with open(self._config_file_path, 'w') as f: with open(self._config_file_path, 'w') as f:
self.write(f, space_around_delimiters=self._space_around_delimiters) self.write(f, space_around_delimiters=self._space_around_delimiters)
CONFIGURE_QT = Config((Constants.PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve().as_posix())
def merge_config_files(old_config_path, template_config_path):
""" Merge two configuration files: if the old one lacks some
sections or options from a comparison with a template,
this function will add what is missing to the old conf file
"""
old_config = ConfigParser()
old_config.read(old_config_path)
new_config = ConfigParser()
new_config.read(template_config_path)
for section in new_config.sections():
if not old_config.has_section(section):
old_config.add_section(section)
for option in new_config.options(section):
if not old_config.has_option(section, option):
old_config.set(section, option, new_config.get(section, option))
with open(old_config_path, 'w') as f:
old_config.write(f)
def check_conf_file():
""" Check the integrity of the used conf file.
If it is not present it will add a copy to the PREF_DIR
and if it is different in structure (different section/options)
it will merge the conf file with the new template one
"""
active_conf = (PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve()
template_conf = (BASE_DIR / 'config' / 'qtquickcontrols2.conf').resolve()
if not active_conf.exists():
copy_file(template_conf, active_conf)
else:
merge_config_files(active_conf, template_conf)
check_conf_file()
CONFIGURE_QT = Config((PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve().as_posix())

View File

@@ -1,9 +1,7 @@
import os
import locale import locale
import sys import sys
from PySide6.QtCore import qVersion from PySide6.QtCore import qVersion
from pathlib import Path
class Constants(): class Constants():
@@ -12,14 +10,7 @@ class Constants():
APPLICATION_NAME = 'Artemis' APPLICATION_NAME = 'Artemis'
ORGANIZATION_NAME = 'AresValley' ORGANIZATION_NAME = 'AresValley'
ORGANIZATION_DOMAIN = 'aresvalley.com' ORGANIZATION_DOMAIN = 'aresvalley.com'
APPLICATION_VERSION = '4.0.0' APPLICATION_VERSION = '4.0.3'
BASE_DIR = Path(os.path.dirname(__file__)) / '../..'
PREFERENCES_DIR = BASE_DIR / 'config'
DB_DIR = BASE_DIR / 'data'
UI_DIR = BASE_DIR / 'ui'
IMAGES_DIR = BASE_DIR / 'images'
LOGS_DIR = BASE_DIR / 'logs'
SQL_NAME = 'data.sqlite' SQL_NAME = 'data.sqlite'
@@ -48,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."
@@ -59,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():
@@ -68,13 +62,13 @@ class Query():
############################## SELECT ############################## SELECT
SELECT_ALL_SIGNALS = "SELECT SIG_ID, NAME FROM signals ORDER BY NAME ASC" SELECT_ALL_SIGNALS = "SELECT SIG_ID, NAME, DESCRIPTION FROM signals ORDER BY NAME ASC"
SELECT_ALL_MODULATION = "SELECT DISTINCT VALUE FROM modulation ORDER BY VALUE ASC" SELECT_ALL_MODULATION = "SELECT DISTINCT VALUE FROM modulation ORDER BY VALUE ASC"
SELECT_ALL_LOCATION = "SELECT DISTINCT VALUE FROM location ORDER BY VALUE ASC" SELECT_ALL_LOCATION = "SELECT DISTINCT VALUE FROM location ORDER BY VALUE ASC"
SELECT_SIG_ID = "SELECT SIG_ID, NAME FROM signals WHERE SIG_ID IN ({}) ORDER BY NAME ASC" SELECT_SIG_ID = "SELECT SIG_ID, NAME, DESCRIPTION FROM signals WHERE SIG_ID IN ({}) ORDER BY NAME ASC"
SELECT_ALL_CAT_LABELS = "SELECT CLB_ID, VALUE FROM category_label ORDER BY VALUE ASC" SELECT_ALL_CAT_LABELS = "SELECT CLB_ID, VALUE FROM category_label ORDER BY VALUE ASC"

View File

@@ -1,134 +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
class NetworkManager:
""" Class that checks for DB or software updates
"""
def __init__(self, parent):
self._parent = parent
self.sigid_db_path = Constants.DB_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
)

View File

@@ -1,14 +1,8 @@
import os import os
from pathlib import Path from pathlib import Path
from artemis.utils.sql_utils import ArtemisDatabase
from artemis.utils.constants import Constants from artemis.utils.constants import Constants
from artemis.utils.sys_utils import * from artemis.utils.sys_utils import is_windows, is_linux, is_macos
def check_data_dir():
if not os.path.exists(Constants.DB_DIR):
os.makedirs(Constants.DB_DIR)
def normalize_dialog_path(path): def normalize_dialog_path(path):
@@ -19,38 +13,47 @@ def normalize_dialog_path(path):
return norm_path return norm_path
def logs_dir(): def _app_dir():
if is_macos(): if is_macos():
logs_dir_path = Path.home() / 'Library/Logs/' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME app_dir_path = Path.home() / 'Library' / 'Application Support' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME
elif is_windows(): elif is_windows():
logs_dir_path = Path.home() / 'AppData/Local/' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME / 'logs' app_dir_path = Path.home() / 'AppData' / 'Local' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME
elif is_linux(): elif is_linux():
logs_dir_path = Path.home() / '/var/log/' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME app_dir_path = Path.home() / '.local' / 'share' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME
else: else:
logs_dir_path = Constants.LOGS_DIR app_dir_path = BASE_DIR.resolve()
if not logs_dir_path.exists(): if not app_dir_path.exists():
logs_dir_path.mkdir(parents=True) app_dir_path.mkdir(parents=True)
return logs_dir_path return app_dir_path
def valid_db(db_dir_name): def _data_dir():
""" Checks if db_dir_name is a valid db dir containing a `data.sqlite` file. data_dir_path = APP_DIR / 'data'
Db must be valid as well and should be properly initialized and loaded with if not data_dir_path.exists():
no errors. data_dir_path.mkdir(parents=True)
return data_dir_path
Args:
db_dir_name (str): name of the db folder def _tmp_dir():
""" if is_windows():
if os.path.exists(Constants.DB_DIR / db_dir_name / Constants.SQL_NAME): tmp_dir_path = Path.home() / 'AppData' / 'Local' / 'Temp'
try:
database = ArtemisDatabase(db_dir_name)
database.load()
return True
except Exception as e:
# Invalid or corrupted DB
return False
else: else:
# The dir is not containing a data.sqlite file tmp_dir_path = Path('/tmp')
return False
return tmp_dir_path
def _preference_dir():
preference_dir_path = APP_DIR / 'config'
if not preference_dir_path.exists():
preference_dir_path.mkdir(parents=True)
return preference_dir_path
BASE_DIR = Path(os.path.dirname(__file__)) / '../..'
APP_DIR = _app_dir()
DATA_DIR = _data_dir()
TMP_DIR = _tmp_dir()
PREFERENCES_DIR = _preference_dir()

View File

@@ -1,13 +1,14 @@
import sqlite3
import os import os
import sqlite3
from PySide6.QtCore import QUrl from PySide6.QtCore import QUrl
from operator import itemgetter from operator import itemgetter
from datetime import datetime from datetime import datetime
from contextlib import closing
from artemis.utils.constants import Query, Constants from artemis.utils.constants import Query, Constants
from artemis.utils.generic_utils import * from artemis.utils.path_utils import DATA_DIR
from contextlib import closing from artemis.utils.generic_utils import format_frequency
class Database(): class Database():
@@ -51,7 +52,7 @@ class ArtemisDatabase(Database):
def __init__(self, db_dir_name): def __init__(self, db_dir_name):
self.db_dir_name = db_dir_name self.db_dir_name = db_dir_name
self.db_dir = Constants.DB_DIR / db_dir_name self.db_dir = DATA_DIR / db_dir_name
self.sql_path = self.db_dir / Constants.SQL_NAME self.sql_path = self.db_dir / Constants.SQL_NAME
self.media_dir = self.db_dir / 'media' self.media_dir = self.db_dir / 'media'
super().__init__(self.sql_path) super().__init__(self.sql_path)
@@ -95,7 +96,7 @@ class ArtemisDatabase(Database):
contains the SIG_ID and the NAME of the signal contains the SIG_ID and the NAME of the signal
""" """
self.all_signals = self.execute(Query.SELECT_ALL_SIGNALS) self.all_signals = self.execute(Query.SELECT_ALL_SIGNALS)
keys = ('SIG_ID', 'name') keys = ('SIG_ID', 'name', 'description')
result = [dict(zip(keys, values)) for values in self.all_signals] result = [dict(zip(keys, values)) for values in self.all_signals]
self.all_signals = result self.all_signals = result
@@ -131,7 +132,7 @@ class ArtemisDatabase(Database):
sig_ids = ",".join(str(num[0]) for num in matching_sig_ids) sig_ids = ",".join(str(num[0]) for num in matching_sig_ids)
self.all_signals = self.execute(Query.SELECT_SIG_ID.format(sig_ids)) self.all_signals = self.execute(Query.SELECT_SIG_ID.format(sig_ids))
keys = ('SIG_ID', 'name') keys = ('SIG_ID', 'name', 'description')
result = [dict(zip(keys, values)) for values in self.all_signals] result = [dict(zip(keys, values)) for values in self.all_signals]
self.all_signals = result self.all_signals = result

View File

@@ -6,7 +6,7 @@ import hashlib
from shutil import rmtree, copyfile, make_archive, unpack_archive from shutil import rmtree, copyfile, make_archive, unpack_archive
from pathlib import Path from pathlib import Path
from artemis.utils.constants import Constants, Messages from artemis.utils.constants import Messages
def is_windows(): def is_windows():
@@ -46,29 +46,38 @@ def open_directory(directory, timeout=3):
return return
def delete_db_dir(db_dir_name):
"""Delete the db folder"""
db_dir = Constants.DB_DIR / db_dir_name
if os.path.exists(db_dir):
rmtree(db_dir)
def copy_file(src_file_path, dst_file_path): def copy_file(src_file_path, dst_file_path):
copyfile(src_file_path, dst_file_path) copyfile(src_file_path, dst_file_path)
def delete_dir(dir_path):
if os.path.exists(dir_path):
rmtree(dir_path)
def delete_file(file_path): def delete_file(file_path):
if os.path.exists(file_path): if os.path.exists(file_path):
os.remove(file_path) os.remove(file_path)
def pack_db(save_path, db_dir): def make_tar(save_path, origin_path):
make_archive(save_path, 'tar', db_dir.resolve().as_posix()) """ Create a tar archive from a folder
Args:
save_path: destination path where new tar is saved
origin_path: directory path of the folder to be archived
"""
make_archive(save_path, 'tar', origin_path.resolve().as_posix())
def unpack_db(tar_path, db_dir_name): def unpack_tar(tar_path, destination_path):
db_dir = Constants.DB_DIR / db_dir_name """ Unpack a tar archive in a folder
unpack_archive(tar_path, db_dir, 'tar')
Args:
tar_path: path of the tar to be unpacked
destination_path: path where the tar is extracted
"""
unpack_archive(tar_path, destination_path, 'tar')
def match_hash(data, reference_hash): def match_hash(data, reference_hash):

View File

@@ -5,10 +5,10 @@ from artemis.utils.config_utils import CONFIGURE_QT
def set_ui(): def set_ui():
os.environ['QT_QUICK_CONTROLS_STYLE'] = CONFIGURE_QT.get_or_default('Controls', 'style', 'Material') os.environ['QT_QUICK_CONTROLS_STYLE'] = CONFIGURE_QT.value('Controls', 'style', 'Material')
os.environ['QT_QUICK_CONTROLS_MATERIAL_VARIANT'] = CONFIGURE_QT.get_or_default('Material', 'variant', 'Dense') os.environ['QT_QUICK_CONTROLS_MATERIAL_VARIANT'] = CONFIGURE_QT.value('Material', 'variant', 'Dense')
os.environ['QT_QUICK_CONTROLS_MATERIAL_THEME'] = CONFIGURE_QT.get_or_default('Material', 'theme', 'System') os.environ['QT_QUICK_CONTROLS_MATERIAL_THEME'] = CONFIGURE_QT.value('Material', 'theme', 'System')
os.environ['QT_QUICK_CONTROLS_MATERIAL_ACCENT'] = CONFIGURE_QT.get_or_default('Material', 'accent', 'Green') os.environ['QT_QUICK_CONTROLS_MATERIAL_ACCENT'] = CONFIGURE_QT.value('Material', 'accent', 'Green')
if is_windows(): if is_windows():
os.environ['QSG_RHI_BACKEND'] = 'opengl' os.environ['QSG_RHI_BACKEND'] = 'opengl'

View 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
)

View File

@@ -4,22 +4,27 @@ echo "Building Linux target ..."
echo "Installing requirements ..." echo "Installing requirements ..."
pip install -r requirements.txt pip install -r requirements.txt
pip install nuitka 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 \
--disable-console \
--enable-plugin=pyside6 \ --enable-plugin=pyside6 \
--include-qt-plugins=sensible,styles,qml,multimedia \ --noinclude-dlls=libQt6Charts* \
--noinclude-dlls=libQt6Quick3D* \
--noinclude-dlls=libQt6Sensors* \
--noinclude-dlls=libQt6Test* \
--noinclude-dlls=libQt6WebEngine* \
--include-qt-plugins=styles \
--include-qt-plugins=qml \
--include-qt-plugins=multimedia \
--include-data-files=./artemis/resources.py=./artemis/resources.py \ --include-data-files=./artemis/resources.py=./artemis/resources.py \
--include-data-files=./config/qtquickcontrols2.conf=./config/qtquickcontrols2.conf \ --include-data-files=./config/qtquickcontrols2.conf=./config/qtquickcontrols2.conf \
--include-data-files=./building/Linux/create_shortcut.sh=./create_shortcut.sh \ --include-data-files=./building/Linux/create_shortcut.sh=./create_shortcut.sh \
--include-data-files=./images/artemis_icon.svg=./images/artemis_icon.svg --include-data-files=./images/artemis_icon.svg=./images/artemis_icon.svg \
--force-stderr-spec="{TEMP}/artemis.err.log" \
chmod 755 ./app.dist/app.bin --force-stdout-spec="{TEMP}/artemis.out.log"
echo "Building Linux target finished." echo "Building Linux target finished."

View File

@@ -2,23 +2,31 @@ Write-Output "Building Windows target"
Write-Output "Installing requirements ..." Write-Output "Installing requirements ..."
pip install -r requirements.txt pip install -r requirements.txt
pip install nuitka 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 `
--disable-console ` --windows-console-mode=disable `
--enable-plugin=pyside6 ` --enable-plugin=pyside6 `
--include-qt-plugins=sensible,styles,qml,multimedia ` --noinclude-dlls="Qt6Charts*" `
--noinclude-dlls="Qt6Quick3D*" `
--noinclude-dlls="Qt6Sensors*" `
--noinclude-dlls="Qt6Test*" `
--noinclude-dlls="Qt6WebEngine*" `
--include-qt-plugins=styles `
--include-qt-plugins=qml `
--include-qt-plugins=multimedia `
--include-data-files=.\artemis\resources.py=.\artemis\resources.py ` --include-data-files=.\artemis\resources.py=.\artemis\resources.py `
--include-data-files=.\config\qtquickcontrols2.conf=.\config\qtquickcontrols2.conf ` --include-data-files=.\config\qtquickcontrols2.conf=.\config\qtquickcontrols2.conf `
--force-stderr-spec="{TEMP}\artemis.err.log" `
--force-stdout-spec="{TEMP}\artemis.out.log" `
--windows-company-name=Aresvalley.com ` --windows-company-name=Aresvalley.com `
--windows-product-name=Artemis ` --windows-product-name=Artemis `
--windows-file-version=4.0.0 ` --windows-file-version=4.0.3 `
--windows-product-version=4.0.0 ` --windows-product-version=4.0.3 `
--windows-file-description=Artemis ` --windows-file-description=Artemis `
--windows-icon-from-ico=images\artemis_icon.ico --windows-icon-from-ico=images\artemis_icon.ico

View File

@@ -1,5 +1,5 @@
#define MyAppName "Artemis" #define MyAppName "Artemis"
#define MyAppVersion "4.0.0" #define MyAppVersion "4.0.3"
#define MyAppPublisher "AresValley" #define MyAppPublisher "AresValley"
#define MyAppURL "https://www.aresvalley.com/" #define MyAppURL "https://www.aresvalley.com/"
#define MyAppExeName "artemis.exe" #define MyAppExeName "artemis.exe"
@@ -18,7 +18,7 @@ LicenseFile=..\..\LICENSE
PrivilegesRequiredOverridesAllowed=dialog PrivilegesRequiredOverridesAllowed=dialog
OutputDir=..\ OutputDir=..\
OutputBaseFilename=Artemis OutputBaseFilename=Artemis
SetupIconFile=..\..\images\artemis_icon.ico SetupIconFile=..\..\images\installer_icon.ico
Compression=lzma2/ultra64 Compression=lzma2/ultra64
SolidCompression=yes SolidCompression=yes
VersionInfoVersion={#MyAppVersion} VersionInfoVersion={#MyAppVersion}
@@ -42,3 +42,7 @@ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de
[Run] [Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[UninstallDelete]
Type: filesandordirs; Name: "{localappdata}\{#MyAppPublisher}\{#MyAppName}\cache"
Type: filesandordirs; Name: "{localappdata}\{#MyAppPublisher}\{#MyAppName}\config"

View File

@@ -4,7 +4,7 @@ echo "Building maacOS target ..."
echo "Installing requirements ..." echo "Installing requirements ..."
pip install -r requirements.txt pip install -r requirements.txt
pip install nuitka imageio pip install nuitka==2.3 imageio
echo "Building with Nuitka ..." echo "Building with Nuitka ..."
python -m nuitka app.py \ python -m nuitka app.py \
@@ -12,7 +12,6 @@ python -m nuitka app.py \
--follow-imports \ --follow-imports \
--show-modules \ --show-modules \
--assume-yes-for-downloads \ --assume-yes-for-downloads \
--disable-console \
--enable-plugin=pyside6 \ --enable-plugin=pyside6 \
--include-qt-plugins=sensible,styles,qml,multimedia \ --include-qt-plugins=sensible,styles,qml,multimedia \
--include-data-files=./artemis/resources.py=./artemis/resources.py \ --include-data-files=./artemis/resources.py=./artemis/resources.py \
@@ -24,6 +23,6 @@ python -m nuitka app.py \
--macos-app-name=Artemis \ --macos-app-name=Artemis \
--macos-app-mode=gui \ --macos-app-mode=gui \
--macos-sign-identity=ad-hoc \ --macos-sign-identity=ad-hoc \
--macos-app-version=4.0.0 --macos-app-version=4.0.3
echo "Building Linux target finished." echo "Building Linux target finished."

View File

@@ -5,3 +5,6 @@ style=Material
variant=Dense variant=Dense
theme=System theme=System
accent=Green accent=Green
[Database]
autoload=0

View File

@@ -1,17 +1,20 @@
{ {
"sigID_DB": { "sigID_DB": {
"version": 60, "version": 61,
"url": "https://github.com/AresValley/Artemis-DB/releases/download/v60/v60.tar", "url": "https://github.com/AresValley/Artemis-DB/releases/download/v61/v61.tar",
"sha256_hash": "78a2c2e5fc00ef4e6c3c975436177eb726fe38ad05463e5cc84b16797225b803", "sha256_hash": "c65d2ab65e9420cd7789190c100abef2f1575ea15489776c2d97b5b09cdc8410",
"total_bytes": 244070400 "total_bytes": 244449280
}, },
"windows": { "windows": {
"version": "4.0.0" "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.0" "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": ""
} }
} }

View File

@@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

BIN
docs/assets/stanag_4285.wav Normal file

Binary file not shown.

View File

@@ -12,7 +12,7 @@
* **Edit Tags:** Open the tags editor window. From here, you can add, rename, or delete tags. The tags can be added to a signal from the [tags menu](#4-tags) * **Edit Tags:** Open the tags editor window. From here, you can add, rename, or delete tags. The tags can be added to a signal from the [tags menu](#4-tags)
* **Open Database Folder:** Shows the folder of the currently loaded database in the explorer. * **Open Database Folder:** Shows the folder of the currently loaded database in the explorer.
* **Preferences:** Open the program settings window. * **Preferences:** Open the program settings window.
* **Exit:** This will simply close the application. * **Exit:** This will close the application.
### Signal ### Signal
* **New:** Add a new signal to the database. * **New:** Add a new signal to the database.
@@ -22,7 +22,10 @@
* **Check Report:** Open the main [Space Weather window](space_weather/current.md) and retrieve all the live data from Poseidon Crawler. * **Check Report:** Open the main [Space Weather window](space_weather/current.md) and retrieve all the live data from Poseidon Crawler.
## 2. Signal List ## 2. Signal List
This is the signal list where all the database entries are shown. When a signal is selected, it will load on the right panel. On top of the list, there is a field for filtering signals by name: this filter has the highest priority among all the filters. This is the signal list where all the database entries are shown. When a signal is selected, it will load on the right panel.
### Filter by Name/Description
On top of the list, there is a field for filtering signals by name or any keyword inside the description of the signal: this filter has the highest priority among all the filters.
## 3. Signal Menu ## 3. Signal Menu
Here you can swithc between the main **signal** window and the **filter** page. Here you can swithc between the main **signal** window and the **filter** page.

View File

@@ -1,5 +1,5 @@
# Build Package # Build Package
Building a distributable package with an executable for Artemis creates a practical solution for end-users, as they can run the application without needing to interact with the terminal and they can easily share the application as a stand-alone package. Building a distributable package with an executable for Artemis creates a practical solution for end-users, as they can run the application without needing to interact with the terminal and they can easily share the application as a standalone package.
## Requirements ## Requirements
* Python (3.11 or higher) * Python (3.11 or higher)
@@ -8,7 +8,7 @@ Building a distributable package with an executable for Artemis creates a practi
We assume that Python is already installed on the system and the Artemis source code has been downloaded and extracted. If these prerequisites are not met, please follow steps 1 to 3 in the [run from source section](run_from_source.md). We assume that Python is already installed on the system and the Artemis source code has been downloaded and extracted. If these prerequisites are not met, please follow steps 1 to 3 in the [run from source section](run_from_source.md).
!!! warning "Cross-Compilation" !!! warning "Cross-Compilation"
To generate standalone packages, an operating system that matches the target OS must be used, as Nuitka does not support cross-compilation. For example: you cannot build binaries on Windows that work on Linux or macOS. An operating system that matches the target OS must be used to generate standalone packages, as Nuitka does not support cross-compilation. For example, you cannot build binaries on Windows that work on Linux or macOS.
## :simple-windows: Windows ## :simple-windows: Windows
@@ -20,7 +20,7 @@ Building a distributable package with an executable for Artemis creates a practi
.\building\Windows\build_windows.ps1 .\building\Windows\build_windows.ps1
``` ```
2. Wait for the build process to complete. This may take a few minutes depending on your system's performance. Once the process finishes, check the `artemis.dist/` directory: it will contain the stand-alone software with the `artemis.exe` executable. 2. Wait for the build process to complete. This may take a few minutes depending on your system's performance. Once the process finishes, check the `artemis.dist/` directory: it will contain the standalone software with the `artemis.exe` executable.
--- ---
@@ -33,7 +33,7 @@ Building a distributable package with an executable for Artemis creates a practi
. ./building/Linux/build_linux.sh . ./building/Linux/build_linux.sh
``` ```
2. Wait for the build process to complete. This may take a few minutes depending on your system's performance. Once the process finishes, check the `artemis.dist/` directory: it will contain the stand-alone software with the `app.bin` executable. 2. Wait for the build process to complete. This may take a few minutes depending on your system's performance. Once the process finishes, check the `artemis.dist/` directory: it will contain the standalone software with the `app.bin` executable.
3. If you wish to create a shortcut, follows the procedure in the [installation section](installation.md/#create-a-shortcut) 3. If you wish to create a shortcut, follows the procedure in the [installation section](installation.md/#create-a-shortcut)
--- ---

View File

@@ -1,37 +1,37 @@
# Contribute # Contribute
Artemis is an open source project an every contribution, no matter how small, is valuable and greatly appreciated. Don't worry about getting everything perfect, we are happy to work with you on your contribution and help you along the way. This guide will help you get started by outlining various ways you can contribute. Artemis is an open-source project, and every contribution, no matter how small, is valuable and greatly appreciated. Don't worry about getting everything perfect; we are happy to work with you on your contribution and help you along the way. This guide will help you get started by outlining various ways you can contribute.
<div class="grid cards" markdown> <div class="grid cards" markdown>
- :material-bug: __Spot a bug?__ - :material-bug: __Spot a bug?__
--- ---
Open an issue (or pull request) and let us know the problem you faced (or you're working on) Please open an issue (or pull request) and let us know the problem you faced (or you're working on)
[:octicons-arrow-right-24: Open an Issue](https://github.com/AresValley/Artemis/issues) [:octicons-arrow-right-24: Open an Issue](https://github.com/AresValley/Artemis/issues)
- :material-source-fork: __Fork the repository__ - :material-source-fork: __Fork the repository__
--- ---
Create your own copy of the codebase that you can modify and submit pull requests from. Create a copy of the codebase from which you can modify and submit pull requests.
[:octicons-arrow-right-24: Fork the repo](https://github.com/AresValley/Artemis) [:octicons-arrow-right-24: Fork the repo](https://github.com/AresValley/Artemis)
- :material-lightbulb-on: __Ideas?__ - :material-lightbulb-on: __Ideas?__
--- ---
Idea for a new feature? Open an issue on the project's GitHub repository to describe your proposal in detail. Idea for a new feature? Open an issue on the project's GitHub repository to describe your proposal.
[:octicons-arrow-right-24: Open an Issue](https://github.com/AresValley/Artemis/issues) [:octicons-arrow-right-24: Open an Issue](https://github.com/AresValley/Artemis/issues)
- :material-heart: __Spreading the word!__ - :material-heart: __Spreading the word!__
--- ---
Do you like Artemis? Don't forgeto to share it with your network and your friends! Do you like Artemis? Remember to share it with your network and your friends!
</div> </div>

View File

@@ -12,65 +12,3 @@ Artemis is maintained by Marco Dalla Tiezza and released under the [GPLv3](https
* [**Eric Wiessner (KI7POL)**](https://github.com/WheezyE "GitHub profile") - *ARM port (Raspberry Pi3B+ and Pi4B)* * [**Eric Wiessner (KI7POL)**](https://github.com/WheezyE "GitHub profile") - *ARM port (Raspberry Pi3B+ and Pi4B)*
* [**Pierpaolo Pravatto**](https://github.com/ppravatto "GitHub profile") - *Wiki page, β Tester* * [**Pierpaolo Pravatto**](https://github.com/ppravatto "GitHub profile") - *Wiki page, β Tester*
* [**Francesco Capostagno**](https://github.com/fcapostagno "GitHub profile"), **Luca**, **Pietro** - *β Tester* * [**Francesco Capostagno**](https://github.com/fcapostagno "GitHub profile"), **Luca**, **Pietro** - *β Tester*
## Donators
* Eric Hahn
* Alan Lawrence
* Diego Gil Fernandez
* Torsten Teichert
* Charles Preston
* Brad Hein
* Paolo Romani
* Michelle Corbani
* Martin van Duinen
* Valentino Zardi
* Emmanuel Fabre
* Oscar Nilsson
* Pierre Declercq
* Detlef Jahn
* Oliver Schellenberg
* Stephane Imbertone
* Roel Ketelaars
* Timothy Ehrhart
* George Mager
* Gerhard Amon
* Gerald Schmidt
* Carlos Rocha
* Joshua Frohberg
* Bill Riches
* Jeffrey Krehbiel
* Володимир Багмет
* Philip Hamlin
* David Davies
* Nigel P. Lawrence
* Marco Rissi (PP5ZX)
* Martin van Duinen
* Alex Diamantopulo
* Joseph Winter
* Mark Bender
* Rolf Gerhardt
* Denese Harris
* Benjamin Steele
* Alexander Irmscher
* Jonathan Chang
* Torsten Lipke
* Massimo Petrantoni
* William Arcand
* Jon Carp
* Robert Crone
* William Houston
* Richard Quasne
* Tom Krugliakov
* Francisco Neira Basso
* Alistair Macrae
* Kevin Arburn
* Marek Barłóg
* Gabriel Glösmann
* Corbin Williams
* Ton Machielsen
* Ivan Rancic
* Alipio Fernandez
* Matt Eisele
* Martin Dudel
* Harald Geier

18
docs/faq.md Normal file
View 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:
- [OShea, 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.

View File

@@ -1,11 +1,14 @@
--- ---
title: Documentation title: Documentation
--- ---
# #
<div align="center">
<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>
</div>
<p align="center" markdown> <p align="center" markdown>
![Logo](assets/logo_large_black.svg#only-light){width="400"} [Artemis Homepage](https://www.aresvalley.com){ .md-button }
![Logo](assets/logo_large_white.svg#only-dark){width="400"}
</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. **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.

View File

@@ -12,7 +12,18 @@
--- ---
## :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 and run the executable `app.bin`. 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:
``` 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
```
### Create a Shortcut ### Create a Shortcut
@@ -38,5 +49,5 @@ The support for the macOS compiled version of the program is temporarily limited
* **Run the program directly from the source:** Follow the instructions provided in [this chapter](run_from_source.md) to launch the program from the source code. * **Run the program directly from the source:** Follow the instructions provided in [this chapter](run_from_source.md) to launch the program from the source code.
* **Compile the Artemis 4 binaries on your machine:** In this case, you can contribute by reporting any issues you encounter by [opening an Issue](https://github.com/AresValley/Artemis/issues). * **Compile the Artemis 4 binaries on your machine:** In this case, you can contribute by reporting any issues you encounter by [opening an Issue](https://github.com/AresValley/Artemis/issues).
* **Use the last available compiled version (3.2.1):** Although this version is no longer officially supported, it remains available for use: [:material-download: Artemis-3.2.1.dmg](https://aresvalley.com/download/11/). * **Use the last available compiled version (3.2.1):** Although this version is no longer officially supported, it remains available for use: [:material-download: Artemis-3.2.1.dmg](https://www.aresvalley.com/?sdm_process_download=1&download_id=377).

View File

@@ -27,3 +27,18 @@ Running Artemis directly from the source code using the Python interpreter is co
``` ```
pyside6-rcc ./artemis.qrc -o artemis/resources.py pyside6-rcc ./artemis.qrc -o artemis/resources.py
``` ```
## Folders Structure
Artemis can be safely executed and/or installed in any folder (even protected ones, such as `Program Files (x86)` in Windows) because Artemis performs read-only operations in the `BASE_DIR` folder from where it runs. All the reading-writing operations (such as database ops, logging, etc.) are performed in standard folders as follow:
### :simple-windows: Windows
* Data, Cache, Configurations: `$USER\AppData\Local\AresValley\Artemis`
* Logs: `$USER\AppData\Local\Temp`
### :simple-linux: Linux
* Data, Cache, Configurations: `~/.local/share/AresValley/Artemis`
* Logs: `/tmp`
### :simple-apple: Mac OS
* Data, Cache, Configurations: `~/Library/Application Support/AresValley/Artemis`
* Logs: `/tmp`

View File

@@ -5,8 +5,21 @@
## 1. Kp Index ## 1. Kp Index
The **K index** is a number (from 0 to 9) that shows how much Earth's magnetic field is disturbed. A K index of 1 means things are calm, while a K index of 5 or higher indicates a geomagnetic storm. These disturbances are measured with magnetometers that track changes in Earth's magnetic field every three hours. The K itself comes from a German word "Kennziffer" meaning "characteristic digit". To get a big picture of what's happening around the world, an official planetary **Kp index** is calculated. This is done by averaging the K indices from a special network of 13 geomagnetic observatories located around the globe at mid-latitudes. The **K index** is a number (from 0 to 9) that shows how much Earth's magnetic field is disturbed. A K index of 1 means things are calm, while a K index of 5 or higher indicates a geomagnetic storm. These disturbances are measured with magnetometers that track changes in Earth's magnetic field every three hours. The K itself comes from a German word "Kennziffer" meaning "characteristic digit". To get a big picture of what's happening around the world, an official planetary **Kp index** is calculated. This is done by averaging the K indices from a special network of 13 geomagnetic observatories located around the globe at mid-latitudes.
## 2. A Index |Index|Activity Level|High Latitudes|Low Latitudes|Possible Source|
The **A index** represents the three-hourly equivalent amplitude of geomagnetic activity at a specific magnetometer station, derived from the station-specific K index. Due to the quasi-logarithmic nature of the K-scale in relation to magnetometer fluctuations, directly averaging a set of K indices is not really meaningful. Instead each K is converted back into a linear scale. The **Ap index** is determined by averaging the eight daily A values, providing a measure of geomagnetic activity for a specific day. Days with higher levels of geomagnetic activity correspond to higher daily Ap values. |-|-|-|-|-|
|**Kp 0**|Inactive|Weak & slow aurora possible|Aurora extremely unlikely|Small influx of particles due to some reconnections mostly at the magnetotail|
|**Kp 1**|Very Quiet|Weak & slow aurora likely|Aurora very unlikely|Vide supra|
|**Kp 2**|Quiet|Moderate auroral display|Aurora unlikely|Vide supra|
|**Kp 3**|Unsettled|Active auroral display, sporadic substorm possible|Weak aurora display possible|Coronal hole sending fast winds or remains after days of storming, enhanced solar wind|
|**Kp 4**|Active|Active auroral display, multiple sporadic substorms possible|Weak Aurora Display Possible|Vide supra|
|**Kp 5**|Minor Storm (G1)|Very active auroral display, multiple substorms likely|Aurora display likely|Coronal hole sending fast winds or coronal mass ejection (CME), enhanced solar wind|
|**Kp 6**|Moderate Storm (G2)|Strong auroral display, longer substorms|Active auroral display very likely|Vide supra|
|**Kp 7**|Strong Storm (G3)|Very strong auroral display|Strong auroral display extremely likely|Large CMEs caused by solar storms or flares, very enhanced solar wind with strong shock wave|
|**Kp 8**|Severe Storm (G4)|Extremely strong aurora, long periods of substorming|Strong auroral display extremely likely|Vide supra|
|**Kp 9**|Extreme Storm (G5)|Extremely strong aurora, long periods of substorming|Very strong auroral display, overhead aurora possible|Super CMEs, Carrington-class events, devastating solar wind with extreme shock waves|
## 2. Ap Index
The **A index** represents the three-hourly equivalent amplitude of geomagnetic activity at a specific magnetometer station, derived from the station-specific K index. Due to the quasi-logarithmic nature of the K-scale in relation to magnetometer fluctuations, directly averaging a set of K indices is not really meaningful. Instead each K is converted back into a linear scale. The **Ap index** is determined by averaging the eight daily Ap values (3-hour) and using the same stations grid explained for the Kp index in the previous section. This provides a measure of geomagnetic activity for a specific day. Days with higher levels of geomagnetic activity correspond to higher daily Ap values.
## 3. NOAA Space Weather Scale ## 3. NOAA Space Weather Scale

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M671-177q-11 7-22 13t-23 11q-15 7-30.5 0T574-176q-6-15 1.5-29.5T598-227q7-3 13-6.5t12-7.5L480-368v111q0 27-24.5 37.5T412-228L280-360H160q-17 0-28.5-11.5T120-400v-160q0-17 11.5-28.5T160-600h88L84-764q-11-11-11-28t11-28q11-11 28-11t28 11l680 680q11 11 11 28t-11 28q-11 11-28 11t-28-11l-93-93Zm89-304q0-83-44-151.5T598-735q-15-7-22-21.5t-2-29.5q6-16 21.5-23t31.5 0q97 43 155 131t58 197q0 33-6 65.5T817-353q-8 22-24.5 27.5t-30.5.5q-14-5-22.5-18t-.5-30q11-26 16-52.5t5-55.5ZM591-623q33 21 51 63t18 80v10q0 5-1 10-2 13-14 17t-22-6l-51-51q-6-6-9-13.5t-3-15.5v-77q0-12 10.5-17.5t20.5.5Zm-201-59q-6-6-6-14t6-14l22-22q19-19 43.5-8.5T480-703v63q0 14-12 19t-22-5l-56-56Z"/></svg>

After

Width:  |  Height:  |  Size: 783 B

BIN
images/installer_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

View File

@@ -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'

View File

@@ -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)
@@ -55,8 +56,9 @@ Window {
listModel.clear() listModel.clear()
for (var i = 0; i < loadedList.length; i++) { for (var i = 0; i < loadedList.length; i++) {
var name = loadedList[i].name.toLowerCase() var name = loadedList[i].name.toLowerCase()
var description = loadedList[i].description.toLowerCase()
var search = textFieldSearch.text.toLowerCase() var search = textFieldSearch.text.toLowerCase()
if (name.includes(search)) { if (name.includes(search) || description.includes(search)) {
listModel.append(loadedList[i]) listModel.append(loadedList[i])
} }
} }
@@ -120,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 {
@@ -134,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");
}
} }
} }
@@ -214,9 +223,13 @@ Window {
Page { Page {
anchors.fill: parent anchors.fill: parent
leftPadding: 5 leftPadding: 10
rightPadding: 5 bottomPadding: 10
bottomPadding: 5
focus: true
Keys.onDownPressed: listView.incrementCurrentIndex()
Keys.onUpPressed: listView.decrementCurrentIndex()
header: MenuBar { header: MenuBar {
id: topBar id: topBar
@@ -315,11 +328,16 @@ Window {
MenuItem { MenuItem {
text: "Check for Updates" text: "Check for Updates"
onClicked: {checkDbUpdates()} onClicked: {checkForUpdate()}
} }
MenuSeparator {} MenuSeparator {}
MenuItem {
text: "Project Homepage"
onClicked: {Qt.openUrlExternally('https://aresvalley.com/')}
}
MenuItem { MenuItem {
text: "Documentation" text: "Documentation"
onClicked: {Qt.openUrlExternally('https://AresValley.github.io/Artemis')} onClicked: {Qt.openUrlExternally('https://AresValley.github.io/Artemis')}
@@ -358,16 +376,18 @@ Window {
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
spacing: 20 spacing: 10
ColumnLayout { ColumnLayout {
Layout.maximumWidth: 250 Layout.maximumWidth: 250
TextField { TextField {
id: textFieldSearch id: textFieldSearch
Layout.preferredHeight: 39
Layout.topMargin: 5
enabled: false enabled: false
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 10
placeholderText: qsTr("Search") placeholderText: qsTr("Search")
onTextChanged: { onTextChanged: {
refreshList() refreshList()
@@ -382,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() }
@@ -416,16 +435,14 @@ Window {
TabBar { TabBar {
id: tabBar id: tabBar
width: parent.width
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
TabButton { TabButton {
text: qsTr("Signal") text: qsTr("SIGNAL")
} }
TabButton { TabButton {
text: qsTr("Filter") text: qsTr("FILTERS")
} }
} }

View File

@@ -82,18 +82,6 @@ Window {
} }
} }
function contentChanged() {
if (listView.currentIndex !== -1) {
myModel.set(
listView.currentIndex,
{
'name': nameField.text,
'description': descriptionField.text,
}
)
}
}
function lockMenu(toggle) { function lockMenu(toggle) {
if (toggle) { if (toggle) {
openButton.enabled = false openButton.enabled = false
@@ -236,16 +224,18 @@ Window {
} }
} }
ScrollView { Flickable {
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
ScrollBar.vertical.interactive: true Layout.fillHeight: true
TextArea.flickable: TextArea {
TextArea {
id: newDescriptionField id: newDescriptionField
placeholderText: qsTr("Description") placeholderText: qsTr("Description")
font.pointSize: 10
wrapMode: TextEdit.WordWrap wrapMode: TextEdit.WordWrap
} }
ScrollBar.vertical: ScrollBar {
width: 10
}
} }
} }
@@ -293,16 +283,18 @@ Window {
} }
} }
ScrollView { Flickable {
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
ScrollBar.vertical.interactive: true Layout.fillHeight: true
TextArea.flickable: TextArea {
TextArea {
id: editDescriptionField id: editDescriptionField
placeholderText: qsTr("Description") placeholderText: qsTr("Description")
font.pointSize: 10
wrapMode: TextEdit.WordWrap wrapMode: TextEdit.WordWrap
} }
ScrollBar.vertical: ScrollBar {
width: 10
}
} }
} }
@@ -380,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() }
@@ -446,9 +437,7 @@ Window {
id: nameField id: nameField
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: qsTr("Name") placeholderText: qsTr("Name")
onTextChanged: { readOnly: true
contentChanged()
}
} }
TextField { TextField {
@@ -458,18 +447,18 @@ Window {
readOnly: true readOnly: true
} }
ScrollView { Flickable {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
ScrollBar.vertical.interactive: true TextArea.flickable: TextArea {
TextArea {
id: descriptionField id: descriptionField
wrapMode: TextEdit.WordWrap placeholderText: qsTr("Description")
readOnly: true
font.pointSize: 10 font.pointSize: 10
onTextChanged: { wrapMode: TextEdit.WordWrap
contentChanged() }
} ScrollBar.vertical: ScrollBar {
width: 10
} }
} }

View File

@@ -4,6 +4,7 @@ import QtQuick.Controls
import QtQuick.Controls.Material import QtQuick.Controls.Material
import QtQuick.Layouts import QtQuick.Layouts
Window { Window {
id: windowDownloader id: windowDownloader
@@ -23,6 +24,23 @@ Window {
signal onAbort() signal onAbort()
onClosing: {
onAbort()
}
function updateProgressBar(bytesReceived, bytesTotal) {
progressBar.value = bytesReceived
progressBar.to = bytesTotal
}
function setIndeterminateBar() {
progressBar.indeterminate = true
}
function updateStatus(arg) {
progressLabel.text = arg
}
Page { Page {
id: page id: page
anchors.fill: parent anchors.fill: parent
@@ -37,22 +55,26 @@ Window {
} }
ProgressBar { ProgressBar {
objectName: "progressBar" id: progressBar
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
} }
Label { Label {
objectName: "labelProgress" id: progressLabel
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
} }
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() }
} }
} }

View File

@@ -213,6 +213,8 @@ Page {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 10
anchors.topMargin: 10
GridLayout { GridLayout {
rows: 2 rows: 2
@@ -299,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
} }
} }
} }
@@ -388,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
} }
} }
} }
@@ -463,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
} }
} }
} }
@@ -501,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
} }
@@ -553,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
} }
@@ -605,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
} }

View File

@@ -23,10 +23,12 @@ Window {
signal saveMaterialAccent(string arg) signal saveMaterialAccent(string arg)
signal saveMaterialTheme(string arg) signal saveMaterialTheme(string arg)
signal saveAutoload(int arg)
function saveAll() { function saveAll() {
saveMaterialAccent(comboBoxAccent.currentText) saveMaterialAccent(comboBoxAccent.currentText)
saveMaterialTheme(comboBoxTheme.currentText) saveMaterialTheme(comboBoxTheme.currentText)
saveAutoload(checkBoxAutoload.checked)
} }
function loadMaterialAccent(accent) { function loadMaterialAccent(accent) {
@@ -47,6 +49,14 @@ Window {
} }
} }
function loadAutoload(toggle) {
if (toggle) {
checkBoxAutoload.checked = true
} else {
checkBoxAutoload.checked = false
}
}
DialogMessage { DialogMessage {
id: dialogPreferencesSaved id: dialogPreferencesSaved
modal: true modal: true
@@ -67,10 +77,10 @@ Window {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 15 anchors.rightMargin: 10
anchors.leftMargin: 15 anchors.leftMargin: 10
anchors.bottomMargin: 15 anchors.bottomMargin: 10
anchors.topMargin: 15 anchors.topMargin: 10
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
@@ -132,6 +142,21 @@ Window {
} }
} }
RowLayout {
Layout.fillWidth: true
Label {
text: "Auto-load SigID Database on Startup"
font.pixelSize: 12
clip: true
Layout.fillWidth: true
}
CheckBox {
id: checkBoxAutoload
}
}
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }

View File

@@ -154,17 +154,18 @@ Window {
} }
} }
ScrollView { Flickable {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 5
Layout.fillHeight: true Layout.fillHeight: true
ScrollBar.vertical.interactive: true Layout.topMargin: 5
TextArea.flickable: TextArea {
TextArea {
id: paramDescription id: paramDescription
placeholderText: qsTr("Description") placeholderText: qsTr("Description")
wrapMode: TextEdit.WordWrap
font.pointSize: 10 font.pointSize: 10
wrapMode: TextEdit.WordWrap
}
ScrollBar.vertical: ScrollBar {
width: 10
} }
} }

View File

@@ -168,6 +168,8 @@ Page {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 10
anchors.topMargin: 10
Label { Label {
id: signalName id: signalName
@@ -272,7 +274,6 @@ Page {
Layout.fillWidth: true Layout.fillWidth: true
} }
RowLayout { RowLayout {
width: 100 width: 100
height: 100 height: 100
@@ -571,18 +572,23 @@ Page {
} }
} }
ScrollView { Flickable {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 5
Layout.fillHeight: true Layout.fillHeight: true
ScrollBar.vertical.interactive: true Layout.topMargin: 5
TextArea.flickable: TextArea {
TextArea {
id: descriptionTextArea id: descriptionTextArea
placeholderText: qsTr("Description")
font.pointSize: 10
wrapMode: TextEdit.WordWrap wrapMode: TextEdit.WordWrap
textFormat: Text.MarkdownText textFormat: Text.MarkdownText
font.pointSize: 10
readOnly: true readOnly: true
onLinkActivated: (link) => {
Qt.openUrlExternally(link)
}
}
ScrollBar.vertical: ScrollBar {
width: 10
} }
} }
} }

View File

@@ -34,10 +34,10 @@ Page {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 20 anchors.rightMargin: 10
anchors.leftMargin: 20 anchors.leftMargin: 10
anchors.bottomMargin: 20 anchors.bottomMargin: 10
anchors.topMargin: 20 anchors.topMargin: 10
Image { Image {
id: imageBox id: imageBox

View File

@@ -79,10 +79,10 @@ Page {
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 20 anchors.rightMargin: 10
anchors.leftMargin: 20 anchors.leftMargin: 10
anchors.bottomMargin: 20 anchors.bottomMargin: 10
anchors.topMargin: 20 anchors.topMargin: 10
ColumnLayout { ColumnLayout {
Layout.fillHeight: true Layout.fillHeight: true

View File

@@ -38,10 +38,10 @@ Page {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 20 anchors.rightMargin: 10
anchors.leftMargin: 20 anchors.leftMargin: 10
anchors.bottomMargin: 20 anchors.bottomMargin: 10
anchors.topMargin: 20 anchors.topMargin: 10
Image { Image {
id: imageBox id: imageBox

View File

@@ -120,10 +120,10 @@ Page {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 20 anchors.rightMargin: 10
anchors.leftMargin: 20 anchors.leftMargin: 10
anchors.bottomMargin: 20 anchors.bottomMargin: 10
anchors.topMargin: 20 anchors.topMargin: 10
ColumnLayout { ColumnLayout {
Layout.fillHeight: true Layout.fillHeight: true

View File

@@ -62,7 +62,7 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
clip: true clip: true
Label { Label {
text: qsTr("MAJOR STORM") text: qsTr("STRONG STORM")
anchors.fill: parent anchors.fill: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -78,7 +78,7 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
clip: true clip: true
Label { Label {
text: qsTr("MINOR STORM") text: qsTr("MODERATE STORM")
anchors.fill: parent anchors.fill: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -94,7 +94,7 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
clip: true clip: true
Label { Label {
text: qsTr("ACTIVE") text: qsTr("ACTIVE-STORM")
anchors.fill: parent anchors.fill: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter

View File

@@ -7,8 +7,8 @@ import QtMultimedia
Item { Item {
width: 200 width: 180
height: 80 height: 132
property bool loop: false property bool loop: false
@@ -28,7 +28,7 @@ Item {
buttonPause.enabled = true buttonPause.enabled = true
buttonStop.enabled = true buttonStop.enabled = true
buttonLoop.enabled = true buttonLoop.enabled = true
playerPosition.enabled = player.seekable positionSlider.enabled = player.seekable
player.play() player.play()
} }
@@ -65,10 +65,12 @@ Item {
buttonPause.enabled = false buttonPause.enabled = false
buttonStop.enabled = false buttonStop.enabled = false
buttonLoop.enabled = false buttonLoop.enabled = false
playerPosition.enabled = false positionSlider.enabled = false
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent
spacing: 0
RowLayout { RowLayout {
spacing: 0 spacing: 0
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
@@ -83,6 +85,10 @@ Item {
onClicked: playSound() onClicked: playSound()
} }
Item {
Layout.fillWidth: true
}
RoundButton { RoundButton {
id: buttonPause id: buttonPause
icon.color: Material.foreground icon.color: Material.foreground
@@ -93,6 +99,10 @@ Item {
onClicked: pauseSound() onClicked: pauseSound()
} }
Item {
Layout.fillWidth: true
}
RoundButton { RoundButton {
id: buttonStop id: buttonStop
icon.color: Material.foreground icon.color: Material.foreground
@@ -103,6 +113,10 @@ Item {
onClicked: stopSound() onClicked: stopSound()
} }
Item {
Layout.fillWidth: true
}
RoundButton { RoundButton {
id: buttonLoop id: buttonLoop
icon.color: Material.foreground icon.color: Material.foreground
@@ -122,21 +136,43 @@ Item {
} }
} }
Slider { RowLayout {
id: playerPosition Slider {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter id: positionSlider
Layout.preferredHeight: 20 Layout.preferredHeight: 20
enabled: player.seekable enabled: player.seekable
value: player.duration > 0 ? player.position / player.duration : 0 value: player.duration > 0 ? player.position / player.duration : 0
onMoved: { Layout.fillWidth: true
player.position = player.duration * playerPosition.position onMoved: {
player.position = player.duration * positionSlider.position
}
}
}
RowLayout {
Slider {
id: volumeSlider
Layout.preferredHeight: 20
value: 0.5
Layout.fillWidth: true
}
RoundButton {
id: buttonMute
icon.color: Material.foreground
icon.source: "qrc:/images/icons/player_mute.svg"
display: AbstractButton.IconOnly
enabled: true
flat: true
onClicked: {
volumeSlider.value = 0
}
} }
} }
MediaPlayer { MediaPlayer {
id: player id: player
audioOutput: audioOutput audioOutput: audioOutput
onPlaybackStateChanged: { onPlaybackStateChanged: {
if (player.playbackState === MediaPlayer.StoppedState) { if (player.playbackState === MediaPlayer.StoppedState) {
if (loop) { if (loop) {
@@ -150,7 +186,7 @@ Item {
AudioOutput { AudioOutput {
id: audioOutput id: audioOutput
//volume: volumeSlider.value volume: volumeSlider.value
} }
} }
} }

View File

@@ -58,7 +58,7 @@ Item {
topLeftRadius: 10 topLeftRadius: 10
topRightRadius: 10 topRightRadius: 10
Label { Label {
text: qsTr("SUPER STORM") text: qsTr("EXTREME STORM")
anchors.fill: parent anchors.fill: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -74,7 +74,7 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
clip: true clip: true
Label { Label {
text: qsTr("EXTREME STORM") text: qsTr("SEVERE STORM")
anchors.fill: parent anchors.fill: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -90,7 +90,7 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
clip: true clip: true
Label { Label {
text: qsTr("SEVERE STORM") text: qsTr("STRONG STORM")
anchors.fill: parent anchors.fill: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -106,7 +106,7 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
clip: true clip: true
Label { Label {
text: qsTr("MAJOR STORM") text: qsTr("MODERATE STORM")
anchors.fill: parent anchors.fill: parent
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter