Read-only ops are separated in different standard and OS dependent folders from read-write ones (fixed #43), bump Nuitka 2.3

This commit is contained in:
Marco Dalla Tiezza
2024-06-04 19:25:12 +02:00
parent 16e2668fe9
commit 4e7ebcc2f5
16 changed files with 139 additions and 89 deletions

View File

@@ -11,14 +11,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

View File

@@ -4,9 +4,9 @@ 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.path_utils import DATA_DIR
from artemis.utils.network_utils import NetworkManager 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
@@ -69,8 +69,6 @@ class UIArtemis(QObject):
self.network_manager = NetworkManager(self) self.network_manager = NetworkManager(self)
check_data_dir()
def _connect(self): def _connect(self):
# QML > Python connections # QML > Python connections
@@ -277,7 +275,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 +298,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,

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

@@ -3,9 +3,9 @@ from PySide6.QtCore import QObject, Slot, Signal, QUrl, QSaveFile, QDir, QIODevi
from PySide6.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager from PySide6.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager
from artemis.utils.config_utils import * from artemis.utils.config_utils import *
from artemis.utils.sys_utils import delete_file, match_hash, unpack_db from artemis.utils.sys_utils import delete_file, delete_dir, match_hash, unpack_tar
from artemis.utils.constants import Messages from artemis.utils.constants import Messages
from artemis.utils.sys_utils import delete_db_dir from artemis.utils.path_utils import DATA_DIR
class UIDownloader(QObject): class UIDownloader(QObject):
@@ -42,7 +42,7 @@ class UIDownloader(QObject):
the attributes of the UpdatesController class the attributes of the UpdatesController class
""" """
url_file = QUrl(self._parent.network_manager.remote_db_url) url_file = QUrl(self._parent.network_manager.remote_db_url)
dest_path = QDir(Constants.DB_DIR) dest_path = QDir(DATA_DIR)
self.dest_file = dest_path.filePath(url_file.fileName()) self.dest_file = dest_path.filePath(url_file.fileName())
self.file = QSaveFile(self.dest_file) self.file = QSaveFile(self.dest_file)
@@ -97,8 +97,8 @@ class UIDownloader(QObject):
if match_hash(self.dest_file, self._parent.network_manager.remote_db_hash): if match_hash(self.dest_file, self._parent.network_manager.remote_db_hash):
self._label_progress.setProperty("text", "Unpacking archive...") self._label_progress.setProperty("text", "Unpacking archive...")
delete_db_dir('SigID') delete_dir(DATA_DIR / 'SigID')
unpack_db(self.dest_file, 'SigID') unpack_tar(self.dest_file, DATA_DIR / 'SigID')
delete_file(self.dest_file) delete_file(self.dest_file)
self._parent.load_db('SigID') self._parent.load_db('SigID')
self.close_ui.emit() self.close_ui.emit()

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

View File

@@ -1,5 +1,7 @@
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):
@@ -29,4 +31,11 @@ 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())
if not (PREFERENCES_DIR / 'qtquickcontrols2.conf').exists():
copy_file(
BASE_DIR / 'config' / 'qtquickcontrols2.conf',
PREFERENCES_DIR / 'qtquickcontrols2.conf'
)
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():
@@ -14,13 +12,6 @@ class Constants():
ORGANIZATION_DOMAIN = 'aresvalley.com' ORGANIZATION_DOMAIN = 'aresvalley.com'
APPLICATION_VERSION = '4.0.0' 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' SQL_NAME = 'data.sqlite'
LATEST_VERSION_URL = 'https://raw.githubusercontent.com/AresValley/Artemis/master/config/release-info.json' LATEST_VERSION_URL = 'https://raw.githubusercontent.com/AresValley/Artemis/master/config/release-info.json'

View File

@@ -6,6 +6,7 @@ from packaging.version import Version
from artemis.utils.constants import Constants, Messages from artemis.utils.constants import Constants, Messages
from artemis.utils.sql_utils import ArtemisDatabase from artemis.utils.sql_utils import ArtemisDatabase
from artemis.utils.sys_utils import is_windows, is_linux, is_macos from artemis.utils.sys_utils import is_windows, is_linux, is_macos
from artemis.utils.path_utils import DATA_DIR
class NetworkManager: class NetworkManager:
@@ -14,7 +15,7 @@ class NetworkManager:
def __init__(self, parent): def __init__(self, parent):
self._parent = parent self._parent = parent
self.sigid_db_path = Constants.DB_DIR / 'sigID' / Constants.SQL_NAME self.sigid_db_path = DATA_DIR / 'SigID' / Constants.SQL_NAME
self.show_popup = False self.show_popup = False
self.db_update = None self.db_update = None
@@ -92,7 +93,7 @@ class NetworkManager:
""" Loads the local database if exists """ Loads the local database if exists
""" """
if os.path.exists(self.sigid_db_path): if os.path.exists(self.sigid_db_path):
local_db = ArtemisDatabase('sigID') local_db = ArtemisDatabase('SigID')
local_db.load() local_db.load()
return local_db return local_db
return None return None

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,37 @@ 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
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 _preference_dir():
""" preference_dir_path = APP_DIR / 'config'
if os.path.exists(Constants.DB_DIR / db_dir_name / Constants.SQL_NAME): if not preference_dir_path.exists():
try: preference_dir_path.mkdir(parents=True)
database = ArtemisDatabase(db_dir_name) return preference_dir_path
database.load()
return True
except Exception as e: BASE_DIR = Path(os.path.dirname(__file__)) / '../..'
# Invalid or corrupted DB APP_DIR = _app_dir()
return False DATA_DIR = _data_dir()
else: PREFERENCES_DIR = _preference_dir()
# The dir is not containing a data.sqlite file
return False

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)

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

@@ -4,7 +4,7 @@ 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 \
@@ -12,8 +12,9 @@ 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 \
--force-stderr-spec="{TEMP}/artemis.err.log" \
--force-stdout-spec="{TEMP}/artemis.out.log" \
--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 \
--include-data-files=./config/qtquickcontrols2.conf=./config/qtquickcontrols2.conf \ --include-data-files=./config/qtquickcontrols2.conf=./config/qtquickcontrols2.conf \

View File

@@ -2,7 +2,7 @@ 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 `
@@ -10,10 +10,10 @@ python -m nuitka app.py `
--follow-imports ` --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 `
--force-stderr-spec=%PROGRAM_BASE%.err.txt ` --force-stderr-spec="{TEMP}\artemis.err.log" `
--force-stdout-spec=%PROGRAM_BASE%.out.txt ` --force-stdout-spec="{TEMP}\artemis.out.log" `
--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 `
--include-data-files=.\config\qtquickcontrols2.conf=.\config\qtquickcontrols2.conf ` --include-data-files=.\config\qtquickcontrols2.conf=.\config\qtquickcontrols2.conf `

View File

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

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`