Artemis 4 RC1

This commit is contained in:
Marco Dalla Tiezza
2024-05-28 22:40:45 +02:00
parent acc44c93b3
commit 528c816508
254 changed files with 14757 additions and 30137 deletions

View File

@@ -0,0 +1,32 @@
from configparser import ConfigParser
from artemis.utils.constants import Constants
class Config(ConfigParser):
""" Custom configuration class derived from ConfigParser.
Used to get, set, save and remove any configuration from the conf file
"""
def __init__(self, config_file_path, space_around_delimiters=False):
super().__init__()
self._config_file_path = config_file_path
self.read(self._config_file_path)
self._space_around_delimiters = space_around_delimiters
def get_or_default(self, section, option, default_value):
value = super().get(section, option)
return value if value else default_value
def set(self, section, option, value=None):
super().set(section, option, value)
self.save()
def remove(self, section, option):
super().remove_option(section, option)
self.save()
def save(self):
with open(self._config_file_path, 'w') as f:
self.write(f, space_around_delimiters=self._space_around_delimiters)
CONFIGURE_QT = Config((Constants.PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve().as_posix())

499
artemis/utils/constants.py Normal file
View File

@@ -0,0 +1,499 @@
import os
import locale
import sys
from PySide6.QtCore import qVersion
from pathlib import Path
class Constants():
""" Container class for several constants of the software """
APPLICATION_NAME = 'Artemis'
ORGANIZATION_NAME = 'AresValley'
ORGANIZATION_DOMAIN = 'aresvalley.com'
APPLICATION_VERSION = '4.0.0'
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'
DB_LATEST_VERSION = 'https://www.aresvalley.com/artemis/v4/latest.json'
POSEIDON_REPORT = 'https://www.aresvalley.com/poseidon_engine/data.json'
DEFAULT_ENCODING = 'utf-8'
SYSTEM_LANGUAGE = 'en_US' # locale.getdefaultlocale()[0]
PYTHON_VERSION = '.'.join(str(v) for v in sys.version_info[:3])
QT_VERSION = qVersion()
class Messages:
""" Container class for messages to be displayed """
# Type
DIALOG_TYPE_INFO = 'info'
DIALOG_TYPE_QUEST = 'question'
DIALOG_TYPE_WARN = 'warn'
DIALOG_TYPE_ERROR = 'error'
# Titles
GENERIC_SUCCESS = "Success!"
GENERIC_ERROR = "Something went wrong!"
NO_DB_DETECTED = "No SigID database detected..."
NO_CONNECTION = "Connection Error!"
UP_TO_DATE = "You're up to date!"
DB_NEW_VER = "New SigID DB version available!"
ART_NEW_VER = "New Artemis version available!"
# Messages
DB_CREATION_SUCCESS_MSG = "The new database has been created succesfully."
GENERIC_ERROR_MSG = "An error occurred during the process. Details: {}"
IMPORTING_SUCCESS_MSG = "Database importing has been succesfully completed!"
EXPORTING_SUCCESS_MSG = "Database exporting has been succesfully completed!"
FILE_NOT_FOUND_ERR_MSG = "The file you are trying to access cannot be located. This may be because the file has been moved or deleted."
NO_DB_DETECTED_MSG = "Do you want to download it now?"
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."
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?"
DOWNLOAD_CORRUPTED_MSG = "Downloaded data corrupted or invalid. Please retry."
class Query():
""" Container class for all the sqlite queries """
############################## SELECT
SELECT_ALL_SIGNALS = "SELECT SIG_ID, NAME FROM signals ORDER BY NAME 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_SIG_ID = "SELECT SIG_ID, NAME 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_INFO = """
SELECT
NAME,
DATE,
VERSION,
EDITABLE
FROM info
"""
SELECT_SIGNAL = """
SELECT
NAME,
DESCRIPTION,
URL
FROM signals WHERE SIG_ID = ?
"""
SELECT_CATEGORY = """
SELECT
category.CAT_ID,
category_label.VALUE
FROM category
INNER JOIN category_label ON category.CLB_ID = category_label.CLB_ID
WHERE SIG_ID = ?
"""
SELECT_DOCUMENTS = """
SELECT
DOC_ID,
EXTENSION,
NAME,
DESCRIPTION,
TYPE,
PREVIEW
FROM documents WHERE SIG_ID = ?
ORDER BY TYPE ASC
"""
SELECT_FREQUENCY = """
SELECT
FREQ_ID,
VALUE,
DESCRIPTION
FROM frequency WHERE SIG_ID = ?
"""
SELECT_BANDWIDTH = """
SELECT
BAND_ID,
VALUE,
DESCRIPTION
FROM bandwidth WHERE SIG_ID = ?
"""
SELECT_MODULATION = """
SELECT
MDL_ID,
VALUE,
DESCRIPTION
FROM modulation WHERE SIG_ID = ?
"""
SELECT_MODE= """
SELECT
MOD_ID,
VALUE,
DESCRIPTION
FROM mode WHERE SIG_ID = ?
"""
SELECT_LOCATION = """
SELECT
LOC_ID,
VALUE,
DESCRIPTION
FROM location WHERE SIG_ID = ?
"""
SELECT_ACF = """
SELECT
ACF_ID,
VALUE,
DESCRIPTION
FROM acf WHERE SIG_ID = ?
"""
SELECT_STAT_DOCS = """
SELECT COUNT(*)
FROM documents
"""
SELECT_STAT_IMAGES = """
SELECT COUNT(*)
FROM documents
WHERE type IS 'Image'
"""
SELECT_STAT_AUDIO = """
SELECT COUNT(*)
FROM documents
WHERE type IS 'Audio'
"""
############################## CREATE
CREATE_SIGNALS = """
CREATE TABLE signals (
SIG_ID INTEGER PRIMARY KEY AUTOINCREMENT,
NAME TEXT,
DESCRIPTION TEXT,
URL TEXT
)
"""
CREATE_CATEGORY = """
CREATE TABLE category (
CAT_ID INTEGER PRIMARY KEY AUTOINCREMENT,
SIG_ID INTEGER,
CLB_ID INTEGER,
FOREIGN KEY (SIG_ID) REFERENCES signals (SIG_ID) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (CLB_ID) REFERENCES category_label (CLB_ID) ON DELETE CASCADE ON UPDATE CASCADE
)
"""
CREATE_CATEGORY_LABELS = """
CREATE TABLE category_label (
CLB_ID INTEGER PRIMARY KEY AUTOINCREMENT,
VALUE TEXT
)
"""
CREATE_DOCUMENTS = """
CREATE TABLE documents (
DOC_ID INTEGER PRIMARY KEY AUTOINCREMENT,
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
EXTENSION TEXT,
NAME TEXT,
DESCRIPTION TEXT,
TYPE TEXT,
PREVIEW INTEGER
)
"""
CREATE_FREQUENCY = """
CREATE TABLE frequency (
FREQ_ID INTEGER PRIMARY KEY AUTOINCREMENT,
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
VALUE INTEGER,
DESCRIPTION TEXT
)
"""
CREATE_BANDWIDTH = """
CREATE TABLE bandwidth (
BAND_ID INTEGER PRIMARY KEY AUTOINCREMENT,
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
VALUE INTEGER,
DESCRIPTION TEXT
)
"""
CREATE_MODULATION = """
CREATE TABLE modulation (
MDL_ID INTEGER PRIMARY KEY AUTOINCREMENT,
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
VALUE TEXT,
DESCRIPTION TEXT
)
"""
CREATE_MODE = """
CREATE TABLE mode (
MOD_ID INTEGER PRIMARY KEY AUTOINCREMENT,
SIG_ID INTEGER,
VALUE TEXT,
DESCRIPTION TEXT,
FOREIGN KEY (SIG_ID) REFERENCES signals (SIG_ID) ON DELETE CASCADE ON UPDATE CASCADE
)
"""
CREATE_LOCATION = """
CREATE TABLE location (
LOC_ID INTEGER PRIMARY KEY AUTOINCREMENT,
SIG_ID INTEGER,
VALUE TEXT,
DESCRIPTION TEXT,
FOREIGN KEY (SIG_ID) REFERENCES signals (SIG_ID) ON DELETE CASCADE ON UPDATE CASCADE
)
"""
CREATE_ACF = """
CREATE TABLE acf (
ACF_ID INTEGER PRIMARY KEY AUTOINCREMENT,
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
VALUE FLOAT,
DESCRIPTION TEXT
)
"""
CREATE_INFO = """
CREATE TABLE info (
NAME TEXT,
DATE TEXT,
VERSION INTEGER,
EDITABLE INTEGER
)
"""
CREATE_VIEW_FREQ = """
CREATE VIEW FREQ_RANGE AS
SELECT SIG_ID,
MIN(VALUE) AS MIN_VALUE,
MAX(VALUE) AS MAX_VALUE
FROM frequency
GROUP BY SIG_ID
"""
CREATE_VIEW_BAND = """
CREATE VIEW BAND_RANGE AS
SELECT SIG_ID,
MIN(VALUE) AS MIN_VALUE,
MAX(VALUE) AS MAX_VALUE
FROM bandwidth
GROUP BY SIG_ID
"""
############################## INSERT
INSERT_SIGNAL = """
INSERT INTO signals (
NAME,
DESCRIPTION
) VALUES (?,?)
"""
INSERT_CATEGORY = """
INSERT INTO category (
SIG_ID,
CLB_ID
) VALUES (?,?)
"""
INSERT_CATEGORY_LABEL = """
INSERT INTO category_label (
VALUE
) VALUES (?)
"""
INSERT_INFO = """
INSERT INTO info (
NAME,
DATE,
VERSION,
EDITABLE
) VALUES (?,?,?,?)
"""
INSERT_DOCUMENTS = """
INSERT INTO documents (
SIG_ID,
EXTENSION,
NAME,
DESCRIPTION,
TYPE,
PREVIEW
) VALUES (?,?,?,?,?,?)
"""
INSERT_FREQUENCY = """
INSERT INTO frequency (
SIG_ID,
VALUE,
DESCRIPTION
) VALUES (?,?,?)
"""
INSERT_BANDWIDTH = """
INSERT INTO bandwidth (
SIG_ID,
VALUE,
DESCRIPTION
) VALUES (?,?,?)
"""
INSERT_MODULATION = """
INSERT INTO modulation (
SIG_ID,
VALUE,
DESCRIPTION
) VALUES (?,?,?)
"""
INSERT_MODE = """
INSERT INTO mode (
SIG_ID,
VALUE,
DESCRIPTION
) VALUES (?,?,?)
"""
INSERT_LOCATION = """
INSERT INTO location (
SIG_ID,
VALUE,
DESCRIPTION
) VALUES (?,?,?)
"""
INSERT_ACF = """
INSERT INTO acf (
SIG_ID,
VALUE,
DESCRIPTION
) VALUES (?,?,?)
"""
############################## UPDATE
RENAME_DB = "UPDATE info SET NAME = ?"
UPDATE_SIGNAL = """
UPDATE signals SET
NAME = ?,
DESCRIPTION = ?
WHERE SIG_ID = ?
"""
UPDATE_CATEGORY_LABEL = """
UPDATE category_label SET
VALUE = ?
WHERE CLB_ID = ?
"""
UPDATE_FREQUENCY = """
UPDATE frequency SET
VALUE = ?,
DESCRIPTION = ?
WHERE FREQ_ID = ?
"""
UPDATE_BANDWIDTH = """
UPDATE bandwidth SET
VALUE = ?,
DESCRIPTION = ?
WHERE BAND_ID = ?
"""
UPDATE_ACF = """
UPDATE acf SET
VALUE = ?,
DESCRIPTION = ?
WHERE ACF_ID = ?
"""
UPDATE_MODE = """
UPDATE mode SET
VALUE = ?,
DESCRIPTION = ?
WHERE MOD_ID = ?
"""
UPDATE_LOCATION = """
UPDATE location SET
VALUE = ?,
DESCRIPTION = ?
WHERE LOC_ID = ?
"""
UPDATE_MODULATION = """
UPDATE modulation SET
VALUE = ?,
DESCRIPTION = ?
WHERE MDL_ID = ?
"""
UPDATE_DOCUMENTS = """
UPDATE documents SET
NAME = ?,
DESCRIPTION = ?,
TYPE = ?,
PREVIEW = ?
WHERE DOC_ID = ?
"""
############################## DELETE
DELETE_SIGNAL = "DELETE FROM signals WHERE SIG_ID = ?"
DELETE_DOCUMENT = "DELETE FROM documents WHERE DOC_ID = ?"
DELETE_FREQUENCY = "DELETE FROM frequency WHERE FREQ_ID = ?"
DELETE_BANDWIDTH = "DELETE FROM bandwidth WHERE BAND_ID = ?"
DELETE_MODULATION = "DELETE FROM modulation WHERE MDL_ID = ?"
DELETE_MODE = "DELETE FROM mode WHERE MOD_ID = ?"
DELETE_LOCATION = "DELETE FROM location WHERE LOC_ID = ?"
DELETE_ACF = "DELETE FROM acf WHERE ACF_ID = ?"
DELETE_CATEGORY = "DELETE FROM category WHERE CAT_ID = ?"
DELETE_CATEGORY_LABEL = "DELETE FROM category_label WHERE CLB_ID = ?"
############################## FILTER QUERY
FILTER_FREQ = "SELECT SIG_ID FROM FREQ_RANGE WHERE ({} >= MIN_VALUE) AND ({} <= MAX_VALUE)"
FILTER_BAND = "SELECT SIG_ID FROM BAND_RANGE WHERE ({} >= MIN_VALUE) AND ({} <= MAX_VALUE)"
FILTER_ACF = "SELECT SIG_ID FROM acf WHERE ({} >= VALUE) AND ({} <= VALUE)"
FILTER_MODULATION = "SELECT SIG_ID FROM modulation WHERE VALUE IN ({})"
FILTER_LOCATION = "SELECT SIG_ID FROM location WHERE VALUE IN ({})"
FILTER_CATEGORY = "SELECT SIG_ID FROM category WHERE CLB_ID IN ({})"

View File

@@ -0,0 +1,77 @@
from artemis.utils.constants import Query
def format_frequency(freq_hz):
""" Return frequency in a human-readable format
Args:
freq_hz (int): frequency in Hz
"""
scale = _change_unit_freq(freq_hz)
formatted_freq = f'{freq_hz / scale[0]} {scale[1]}'
return formatted_freq
def _change_unit_freq(freq_hz):
""" Return a scale factor and unit based on the number of digits in the frequency
Args:
freq_hz (int): frequency in Hz
"""
digits = len(str(freq_hz))
if digits < 4:
return 1, 'Hz'
elif digits < 7:
return 10**3, 'kHz'
elif digits < 10:
return 10**6, 'MHz'
else:
return 10**9, 'GHz'
def generate_filter_query(filer_status):
""" Returns the sql query according to the selected filter parameters
Args:
filer_status (dic): dictionary containing a summary of the active
filtering options with the related parametes.
"""
query = []
for key, val in filer_status.items():
if key == 'frequency':
query.append(Query.FILTER_FREQ.format(
val['upper_band'],
val['lower_band']
))
elif key == 'bandwidth':
query.append(Query.FILTER_BAND.format(
val['upper_band'],
val['lower_band']
))
elif key == 'acf':
query.append(Query.FILTER_ACF.format(
val['upper_band'],
val['lower_band']
))
elif key == 'modulation':
query.append(Query.FILTER_MODULATION.format(
', '.join(f"'{mod}'" for mod in val)
))
elif key == 'location':
query.append(Query.FILTER_LOCATION.format(
', '.join(f"'{loc}'" for loc in val)
))
elif key == 'category':
query.append(Query.FILTER_CATEGORY.format(
', '.join(f"{cat}" for cat in val)
))
return ' INTERSECT '.join(query)

View File

@@ -0,0 +1,134 @@
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.DB_LATEST_VERSION)
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

@@ -0,0 +1,56 @@
import os
from pathlib import Path
from artemis.utils.sql_utils import ArtemisDatabase
from artemis.utils.constants import Constants
from artemis.utils.sys_utils import *
def check_data_dir():
if not os.path.exists(Constants.DB_DIR):
os.makedirs(Constants.DB_DIR)
def normalize_dialog_path(path):
if is_windows():
norm_path = path.replace('file:///', '')
elif is_linux() or is_macos():
norm_path = path.replace('file:///', '/')
return norm_path
def logs_dir():
if is_macos():
logs_dir_path = Path.home() / 'Library/Logs/' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME
elif is_windows():
logs_dir_path = Path.home() / 'AppData/Local/' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME / 'logs'
elif is_linux():
logs_dir_path = Path.home() / '/var/log/' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME
else:
logs_dir_path = Constants.LOGS_DIR
if not logs_dir_path.exists():
logs_dir_path.mkdir(parents=True)
return logs_dir_path
def valid_db(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(Constants.DB_DIR / db_dir_name / Constants.SQL_NAME):
try:
database = ArtemisDatabase(db_dir_name)
database.load()
return True
except Exception as e:
# Invalid or corrupted DB
return False
else:
# The dir is not containing a data.sqlite file
return False

416
artemis/utils/sql_utils.py Normal file
View File

@@ -0,0 +1,416 @@
import sqlite3
import os
from PySide6.QtCore import QUrl
from operator import itemgetter
from datetime import datetime
from artemis.utils.constants import Query, Constants
from artemis.utils.generic_utils import *
from contextlib import closing
class Database():
""" General superclass for SQLite DB manipulation.
Foreign keys are activated (otherwise disabled by default for compatibility purposes)
"""
def __init__(self, sql_path):
self.sql_path = sql_path
def execute(self, query, parameters=None, last_rowid=False):
""" Open a connection, execute the given query with optional parameters and close the connection.
In the case of a SELECT query, returns the results as a fetchall().
If last_rowid == True, this function returns a tuple with the result of the fetchall() and
the latest modified row id of the current connection.
"""
with closing(sqlite3.connect(self.sql_path, check_same_thread=False)) as conn:
conn.execute('PRAGMA foreign_keys = ON;')
curs = conn.cursor()
if parameters:
curs.execute(query, parameters)
else:
curs.execute(query)
conn.commit()
if last_rowid:
result = (curs.fetchall(), curs.lastrowid)
else:
result = curs.fetchall()
return result
################################## MARK: >>> DATABASE <<<
class ArtemisDatabase(Database):
""" General CRUD class for SQLite DB manipulation.
Foreign keys are activated (otherwise disabled by default for compatibility purposes)
"""
def __init__(self, db_dir_name):
self.db_dir_name = db_dir_name
self.db_dir = Constants.DB_DIR / db_dir_name
self.sql_path = self.db_dir / Constants.SQL_NAME
self.media_dir = self.db_dir / 'media'
super().__init__(self.sql_path)
self.name = None
self.date = None
self.version = None
self.editable = None
self.all_signals = None
self.all_modulation = None
self.all_location = None
self.all_category_labels = None
self.filtered_signals = None
self.stats = {}
def load(self):
self._select_info()
self._select_all()
self._select_all_modulation()
self._select_all_location()
self._select_all_category_labels()
self._select_stats()
def _select_info(self):
""" Load the DB meta INFO from the table 'info'
"""
result = self.execute(Query.SELECT_INFO)[0]
self.name = result[0]
self.date = result[1]
self.version = result[2]
self.editable = result[3]
def _select_all(self):
""" Load a list of tuple for all signals. Each tuple (representing a signal)
contains the SIG_ID and the NAME of the signal
"""
self.all_signals = self.execute(Query.SELECT_ALL_SIGNALS)
keys = ('SIG_ID', 'name')
result = [dict(zip(keys, values)) for values in self.all_signals]
self.all_signals = result
def _select_all_modulation(self):
self.all_modulation = self.execute(Query.SELECT_ALL_MODULATION)
self.all_modulation = [{'value': item[0]} for item in self.all_modulation]
def _select_all_location(self):
self.all_location = self.execute(Query.SELECT_ALL_LOCATION)
self.all_location = [{'value': item[0]} for item in self.all_location]
def _select_all_category_labels(self):
self.all_category_labels = self.execute(Query.SELECT_ALL_CAT_LABELS)
self.all_category_labels = [{'clb_id': item[0], 'value': item[1]} for item in self.all_category_labels]
def _select_stats(self):
tot_docs = self.execute(Query.SELECT_STAT_DOCS)[0][0]
tot_images = self.execute(Query.SELECT_STAT_IMAGES)[0][0]
tot_audio = self.execute(Query.SELECT_STAT_AUDIO)[0][0]
self.stats['documents'] = tot_docs
self.stats['images'] = tot_images
self.stats['audio'] = tot_audio
self.stats['signals'] = len(self.all_signals)
def select_by_filter(self, filter_query):
matching_sig_ids = self.execute(filter_query)
sig_ids = ",".join(str(num[0]) for num in matching_sig_ids)
self.all_signals = self.execute(Query.SELECT_SIG_ID.format(sig_ids))
keys = ('SIG_ID', 'name')
result = [dict(zip(keys, values)) for values in self.all_signals]
self.all_signals = result
def create(self, name):
""" Create new db in the data folder.
The name of folder containing the new db has a unique id as name (db_dir_name).
"""
meta = [name, datetime.now(), 0, 0]
os.makedirs(self.db_dir)
os.makedirs(self.media_dir)
self.execute(Query.CREATE_INFO)
self.execute(Query.INSERT_INFO, meta)
self.execute(Query.CREATE_SIGNALS)
self.execute(Query.CREATE_CATEGORY)
self.execute(Query.CREATE_CATEGORY_LABELS)
self.execute(Query.CREATE_FREQUENCY)
self.execute(Query.CREATE_BANDWIDTH)
self.execute(Query.CREATE_MODULATION)
self.execute(Query.CREATE_MODE)
self.execute(Query.CREATE_LOCATION)
self.execute(Query.CREATE_ACF)
self.execute(Query.CREATE_DOCUMENTS)
self.execute(Query.CREATE_VIEW_FREQ)
self.execute(Query.CREATE_VIEW_BAND)
def rename(self, name):
self.execute(Query.RENAME_DB, [name])
def insert_category_label(self, value):
self.execute(Query.INSERT_CATEGORY_LABEL, [value])
def update_category_label(self, clb_id, value):
self.execute(Query.UPDATE_CATEGORY_LABEL, [value, clb_id])
def delete_category_label(self, clb_id):
self.execute(Query.DELETE_CATEGORY_LABEL, [clb_id])
################################## MARK: >>> SIGNAL <<<
class ArtemisSignal():
""" Main class of the object signal
"""
def __init__(self, loaded_db):
self.db = loaded_db
self.sig_id = None
self.name = None
self.description = None
self.url = None
self.category = None
self.frequency = None
self.bandwidth = None
self.modulation = None
self.mode = None
self.location = None
self.acf = None
self.documents = None
self.spectrum_path = None
self.audio_path = None
def load(self, sig_id):
self.sig_id = sig_id
self._select_signals()
self._select_category()
self._select_frequency()
self._select_bandwidth()
self._select_modulation()
self._select_mode()
self._select_location()
self._select_acf()
self.select_documents()
def generate_dic(self):
dic = {
'name': self.name,
'description': self.description,
'url': self.url,
'category': self.category,
'frequency': self.frequency,
'bandwidth': self.bandwidth,
'modulation': self.modulation,
'mode': self.mode,
'location': self.location,
'acf': self.acf,
'spectrum_path': self.spectrum_path,
'audio_path': self.audio_path,
'all_category': self.db.all_category_labels
}
return dic
################################## MARK: SELECT Methods
def _select_signals(self):
signal = self.db.execute(Query.SELECT_SIGNAL, [self.sig_id])[0]
self.name = signal[0]
self.description = signal[1]
self.url = signal[2]
def _select_category(self):
self.category = self.db.execute(Query.SELECT_CATEGORY, [self.sig_id])
self.category = [list(x) for x in self.category]
def _select_frequency(self):
result = self.db.execute(Query.SELECT_FREQUENCY, [self.sig_id])
sorted_list = sorted(result, key=itemgetter(1))
self.frequency = [list(x) + [format_frequency(x[1])] for x in sorted_list]
def _select_bandwidth(self):
result = self.db.execute(Query.SELECT_BANDWIDTH, [self.sig_id])
sorted_list = sorted(result, key=itemgetter(1))
self.bandwidth = [list(x) + [format_frequency(x[1])] for x in sorted_list]
def _select_acf(self):
self.acf = self.db.execute(Query.SELECT_ACF, [self.sig_id])
self.acf = [list(x) for x in self.acf]
def _select_modulation(self):
self.modulation = self.db.execute(Query.SELECT_MODULATION, [self.sig_id])
self.modulation = [list(x) for x in self.modulation]
def _select_mode(self):
self.mode = self.db.execute(Query.SELECT_MODE, [self.sig_id])
self.mode = [list(x) for x in self.mode]
def _select_location(self):
self.location = self.db.execute(Query.SELECT_LOCATION, [self.sig_id])
self.location = [list(x) for x in self.location]
def select_documents(self):
self.documents = self.db.execute(Query.SELECT_DOCUMENTS, [self.sig_id])
default_spectrum = [doc for doc in self.documents if doc[4] == 'Image' and doc[5] == 1]
default_audio = [doc for doc in self.documents if doc[4] == 'Audio' and doc[5] == 1]
if default_spectrum != []:
default_spectrum_filename = '{}.{}'.format(str(default_spectrum[0][0]), default_spectrum[0][1])
self.spectrum_path = self.db.media_dir / default_spectrum_filename
self.spectrum_path = QUrl.fromLocalFile(self.spectrum_path.resolve())
else:
self.spectrum_path = 'qrc:///images/spectrum_not_available.svg'
if default_audio != []:
default_audio_filename = '{}.{}'.format(str(default_audio[0][0]), default_audio[0][1])
self.audio_path = self.db.media_dir / default_audio_filename
self.audio_path = QUrl.fromLocalFile(self.audio_path.resolve())
else:
self.audio_path = ''
################################## MARK: UPDATE Methods
def update_signal(self, sig_id, value, description):
self.db.execute(Query.UPDATE_SIGNAL, [value, description, sig_id])
def update_frequency(self, freq_id, value, description):
self.db.execute(Query.UPDATE_FREQUENCY, [value, description, freq_id])
def update_bandwidth(self, band_id, value, description):
self.db.execute(Query.UPDATE_BANDWIDTH, [value, description, band_id])
def update_modulation(self, modu_id, value, description):
self.db.execute(Query.UPDATE_MODULATION, [value, description, modu_id])
def update_mode(self, mode_id, value, description):
self.db.execute(Query.UPDATE_MODE, [value, description, mode_id])
def update_acf(self, acf_id, value, description):
self.db.execute(Query.UPDATE_ACF, [value, description, acf_id])
def update_location(self, loc_id, value, description):
self.db.execute(Query.UPDATE_LOCATION, [value, description, loc_id])
def update_documents(self, doc_id, name, description, type, is_preview):
self.db.execute(Query.UPDATE_DOCUMENTS, [name, description, type, is_preview, doc_id])
################################## MARK: INSERT Methods
def insert_signal(self, value, description):
self.db.execute(Query.INSERT_SIGNAL, [value, description])
def insert_frequency(self, value, description):
self.db.execute(Query.INSERT_FREQUENCY, [self.sig_id, value, description])
def insert_bandwidth(self, value, description):
self.db.execute(Query.INSERT_BANDWIDTH, [self.sig_id,value, description])
def insert_modulation(self, value, description):
self.db.execute(Query.INSERT_MODULATION, [self.sig_id,value, description])
def insert_mode(self, value, description):
self.db.execute(Query.INSERT_MODE, [self.sig_id,value, description])
def insert_acf(self, value, description):
self.db.execute(Query.INSERT_ACF, [self.sig_id,value, description])
def insert_location(self, value, description):
self.db.execute(Query.INSERT_LOCATION, [self.sig_id,value, description])
def insert_category(self, clb_id):
self.db.execute(Query.INSERT_CATEGORY, [self.sig_id, clb_id])
def insert_document(self, doc_lst):
row_id = self.db.execute(Query.INSERT_DOCUMENTS, [self.sig_id] + doc_lst[1:], True)[1]
return row_id
################################## MARK: DELETE Methods
def delete_signal(self):
self.db.execute(Query.DELETE_SIGNAL, [self.sig_id])
def delete_frequency(self, freq_id):
self.db.execute(Query.DELETE_FREQUENCY, [freq_id])
def delete_bandwidth(self, band_id):
self.db.execute(Query.DELETE_BANDWIDTH, [band_id])
def delete_modulation(self, modu_id):
self.db.execute(Query.DELETE_MODULATION, [modu_id])
def delete_mode(self, mode_id):
self.db.execute(Query.DELETE_MODE, [mode_id])
def delete_acf(self, acf_id):
self.db.execute(Query.DELETE_ACF, [acf_id])
def delete_location(self, loc_id):
self.db.execute(Query.DELETE_LOCATION, [loc_id])
def delete_document(self, doc_id):
self.db.execute(Query.DELETE_DOCUMENT, [doc_id])
def delete_category(self, cat_id):
self.db.execute(Query.DELETE_CATEGORY, [cat_id])

View File

@@ -0,0 +1,91 @@
import os
import platform
import subprocess
import hashlib
from shutil import rmtree, copyfile, make_archive, unpack_archive
from pathlib import Path
from artemis.utils.constants import Constants, Messages
def is_windows():
return platform.system() == 'Windows'
def is_macos():
return platform.system() == 'Darwin'
def is_linux():
return platform.system() == 'Linux'
def open_file(file_path, timeout=3):
try:
if is_windows():
os.startfile(file_path)
elif is_macos():
subprocess.call(['open', file_path], timeout=timeout)
elif is_linux():
subprocess.call(['xdg-open', file_path], timeout=timeout)
else:
return
except FileNotFoundError:
raise Exception(Messages.FILE_NOT_FOUND_ERR_MSG)
except Exception as e:
raise Exception(Messages.GENERIC_ERROR_MSG.format(e))
def open_directory(directory, timeout=3):
if is_windows():
subprocess.call(['explorer', str(Path(directory))], timeout=timeout)
elif is_macos():
subprocess.call(['open', directory], timeout=timeout)
elif is_linux():
subprocess.call(['xdg-open', directory], timeout=timeout)
else:
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):
copyfile(src_file_path, dst_file_path)
def delete_file(file_path):
if os.path.exists(file_path):
os.remove(file_path)
def pack_db(save_path, db_dir):
make_archive(save_path, 'tar', db_dir.resolve().as_posix())
def unpack_db(tar_path, db_dir_name):
db_dir = Constants.DB_DIR / db_dir_name
unpack_archive(tar_path, db_dir, 'tar')
def match_hash(data, reference_hash):
""" Check whether the checksum of 'data' match the reference one.
Args:
data (str): Path of the file to be checked
reference_hash (str): Reference SHA-256 hash
"""
if reference_hash is None:
raise ValueError("ERROR: Invalid hash code.")
code = hashlib.sha256()
b = bytearray(128*1024)
mv = memoryview(b)
with open(data, 'rb', buffering=0) as f:
while n := f.readinto(mv):
code.update(mv[:n])
return code.hexdigest() == reference_hash

21
artemis/utils/ui_utils.py Normal file
View File

@@ -0,0 +1,21 @@
import os
from artemis.utils.sys_utils import is_windows, is_linux, is_macos
from artemis.utils.config_utils import CONFIGURE_QT
def set_ui():
os.environ['QT_QUICK_CONTROLS_STYLE'] = CONFIGURE_QT.get_or_default('Controls', 'style', 'Material')
os.environ['QT_QUICK_CONTROLS_MATERIAL_VARIANT'] = CONFIGURE_QT.get_or_default('Material', 'variant', 'Dense')
os.environ['QT_QUICK_CONTROLS_MATERIAL_THEME'] = CONFIGURE_QT.get_or_default('Material', 'theme', 'System')
os.environ['QT_QUICK_CONTROLS_MATERIAL_ACCENT'] = CONFIGURE_QT.get_or_default('Material', 'accent', 'Green')
if is_windows():
os.environ['QSG_RHI_BACKEND'] = 'opengl'
if is_linux():
os.environ['GDK_BACKEND'] = 'x11'
os.environ['QT_QPA_PLATFORM'] = 'xcb'
os.environ['QT_ENABLE_GLYPH_CACHE_WORKAROUND'] = '1'
os.environ['QML_USE_GLYPHCACHE_WORKAROUND'] = '1'