diff --git a/CHANGELOG.md b/CHANGELOG.md index 41d1f41..d0d113a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,14 +11,19 @@ - All signal parameters (such as frequency, modulation, location, etc.) are now followed by a description - Databases can be exported/imported for easy sharing - 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 - 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 - 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 ### Fixed diff --git a/artemis/ui/artemis.py b/artemis/ui/artemis.py index bc8dbad..5a3608e 100644 --- a/artemis/ui/artemis.py +++ b/artemis/ui/artemis.py @@ -4,9 +4,9 @@ from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtCore import QObject, Slot, Signal 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.path_utils import check_data_dir +from artemis.utils.path_utils import DATA_DIR from artemis.utils.network_utils import NetworkManager from artemis.utils.generic_utils import generate_filter_query from artemis.utils.path_utils import normalize_dialog_path @@ -69,8 +69,6 @@ class UIArtemis(QObject): self.network_manager = NetworkManager(self) - check_data_dir() - def _connect(self): # QML > Python connections @@ -277,7 +275,7 @@ class UIArtemis(QObject): """ try: 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( Messages.DIALOG_TYPE_INFO, Messages.GENERIC_SUCCESS, @@ -300,7 +298,8 @@ class UIArtemis(QObject): """ try: 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( Messages.DIALOG_TYPE_INFO, Messages.GENERIC_SUCCESS, diff --git a/artemis/ui/dbmanager.py b/artemis/ui/dbmanager.py index cc6cd5b..f1df752 100644 --- a/artemis/ui/dbmanager.py +++ b/artemis/ui/dbmanager.py @@ -1,10 +1,13 @@ +import os + from PySide6.QtQml import QQmlApplicationEngine 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.sql_utils import ArtemisDatabase from artemis.utils.constants import Constants +from artemis.utils.sys_utils import delete_dir class UIdbmanager(QObject): @@ -67,7 +70,7 @@ class UIdbmanager(QObject): self._parent.lock_menu.emit(True) self._parent.clear_list.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() @@ -85,10 +88,10 @@ class UIdbmanager(QObject): return a dictionary containing only the valid ones with a summary """ 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: - if valid_db(db_dir_name): + if self._valid_db(db_dir_name): database = ArtemisDatabase(db_dir_name) database.load() valid_db_list.append( @@ -103,3 +106,22 @@ class UIdbmanager(QObject): ) 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 diff --git a/artemis/ui/downloader.py b/artemis/ui/downloader.py index acc50b1..7686dec 100644 --- a/artemis/ui/downloader.py +++ b/artemis/ui/downloader.py @@ -3,9 +3,9 @@ from PySide6.QtCore import QObject, Slot, Signal, QUrl, QSaveFile, QDir, QIODevi 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.sys_utils import delete_file, delete_dir, match_hash, unpack_tar 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): @@ -42,7 +42,7 @@ class UIDownloader(QObject): the attributes of the UpdatesController class """ 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.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): self._label_progress.setProperty("text", "Unpacking archive...") - delete_db_dir('SigID') - unpack_db(self.dest_file, 'SigID') + delete_dir(DATA_DIR / 'SigID') + unpack_tar(self.dest_file, DATA_DIR / 'SigID') delete_file(self.dest_file) self._parent.load_db('SigID') self.close_ui.emit() diff --git a/artemis/ui/signaleditor.py b/artemis/ui/signaleditor.py index 867ebb8..67c7a72 100644 --- a/artemis/ui/signaleditor.py +++ b/artemis/ui/signaleditor.py @@ -4,6 +4,7 @@ from PySide6.QtCore import QObject, Signal, Slot from artemis.utils.path_utils import * from artemis.utils.generic_utils import * from artemis.utils.sql_utils import ArtemisSignal +from artemis.utils.sys_utils import delete_file class UIsignaleditor(QObject): diff --git a/artemis/utils/config_utils.py b/artemis/utils/config_utils.py index a0c2a08..d3d0810 100644 --- a/artemis/utils/config_utils.py +++ b/artemis/utils/config_utils.py @@ -1,5 +1,7 @@ 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): @@ -29,4 +31,11 @@ class Config(ConfigParser): 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()) + +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()) diff --git a/artemis/utils/constants.py b/artemis/utils/constants.py index d3d47ec..67fc128 100644 --- a/artemis/utils/constants.py +++ b/artemis/utils/constants.py @@ -1,9 +1,7 @@ -import os import locale import sys from PySide6.QtCore import qVersion -from pathlib import Path class Constants(): @@ -14,13 +12,6 @@ class Constants(): 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' LATEST_VERSION_URL = 'https://raw.githubusercontent.com/AresValley/Artemis/master/config/release-info.json' diff --git a/artemis/utils/network_utils.py b/artemis/utils/network_utils.py index 7612168..6198b31 100644 --- a/artemis/utils/network_utils.py +++ b/artemis/utils/network_utils.py @@ -6,6 +6,7 @@ from packaging.version import Version from artemis.utils.constants import Constants, Messages from artemis.utils.sql_utils import ArtemisDatabase from artemis.utils.sys_utils import is_windows, is_linux, is_macos +from artemis.utils.path_utils import DATA_DIR class NetworkManager: @@ -14,7 +15,7 @@ class NetworkManager: def __init__(self, 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.db_update = None @@ -92,7 +93,7 @@ class NetworkManager: """ Loads the local database if exists """ if os.path.exists(self.sigid_db_path): - local_db = ArtemisDatabase('sigID') + local_db = ArtemisDatabase('SigID') local_db.load() return local_db return None diff --git a/artemis/utils/path_utils.py b/artemis/utils/path_utils.py index 861f756..c927ef7 100644 --- a/artemis/utils/path_utils.py +++ b/artemis/utils/path_utils.py @@ -1,14 +1,8 @@ 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) +from artemis.utils.sys_utils import is_windows, is_linux, is_macos def normalize_dialog_path(path): @@ -19,38 +13,37 @@ def normalize_dialog_path(path): return norm_path -def logs_dir(): +def _app_dir(): 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(): - 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(): - 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: - logs_dir_path = Constants.LOGS_DIR + app_dir_path = BASE_DIR - if not logs_dir_path.exists(): - logs_dir_path.mkdir(parents=True) + if not app_dir_path.exists(): + app_dir_path.mkdir(parents=True) - return logs_dir_path + return app_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. +def _data_dir(): + data_dir_path = APP_DIR / 'data' + if not data_dir_path.exists(): + data_dir_path.mkdir(parents=True) + return data_dir_path - 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 + +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() +PREFERENCES_DIR = _preference_dir() diff --git a/artemis/utils/sql_utils.py b/artemis/utils/sql_utils.py index 767700c..89ba777 100644 --- a/artemis/utils/sql_utils.py +++ b/artemis/utils/sql_utils.py @@ -1,13 +1,14 @@ -import sqlite3 import os +import sqlite3 from PySide6.QtCore import QUrl from operator import itemgetter from datetime import datetime +from contextlib import closing from artemis.utils.constants import Query, Constants -from artemis.utils.generic_utils import * -from contextlib import closing +from artemis.utils.path_utils import DATA_DIR +from artemis.utils.generic_utils import format_frequency class Database(): @@ -51,7 +52,7 @@ class ArtemisDatabase(Database): def __init__(self, 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.media_dir = self.db_dir / 'media' super().__init__(self.sql_path) diff --git a/artemis/utils/sys_utils.py b/artemis/utils/sys_utils.py index 649dcea..e1f3ab3 100644 --- a/artemis/utils/sys_utils.py +++ b/artemis/utils/sys_utils.py @@ -6,7 +6,7 @@ import hashlib from shutil import rmtree, copyfile, make_archive, unpack_archive from pathlib import Path -from artemis.utils.constants import Constants, Messages +from artemis.utils.constants import Messages def is_windows(): @@ -46,29 +46,38 @@ def open_directory(directory, timeout=3): 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_dir(dir_path): + if os.path.exists(dir_path): + rmtree(dir_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 make_tar(save_path, origin_path): + """ 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): - db_dir = Constants.DB_DIR / db_dir_name - unpack_archive(tar_path, db_dir, 'tar') +def unpack_tar(tar_path, destination_path): + """ Unpack a tar archive in a folder + + 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): diff --git a/building/Linux/build_linux.sh b/building/Linux/build_linux.sh index 446d59a..55bed39 100644 --- a/building/Linux/build_linux.sh +++ b/building/Linux/build_linux.sh @@ -4,7 +4,7 @@ echo "Building Linux target ..." echo "Installing requirements ..." pip install -r requirements.txt -pip install nuitka +pip install nuitka==2.3 echo "Building with Nuitka ..." python -m nuitka app.py \ @@ -12,8 +12,9 @@ python -m nuitka app.py \ --follow-imports \ --show-modules \ --assume-yes-for-downloads \ - --disable-console \ --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-data-files=./artemis/resources.py=./artemis/resources.py \ --include-data-files=./config/qtquickcontrols2.conf=./config/qtquickcontrols2.conf \ diff --git a/building/Windows/build_windows.ps1 b/building/Windows/build_windows.ps1 index bedf32c..6334db8 100644 --- a/building/Windows/build_windows.ps1 +++ b/building/Windows/build_windows.ps1 @@ -2,7 +2,7 @@ Write-Output "Building Windows target" Write-Output "Installing requirements ..." pip install -r requirements.txt -pip install nuitka +pip install nuitka==2.3 Write-Output "Building with Nuitka ..." python -m nuitka app.py ` @@ -10,10 +10,10 @@ python -m nuitka app.py ` --follow-imports ` --show-modules ` --assume-yes-for-downloads ` - --disable-console ` + --windows-console-mode=disable ` --enable-plugin=pyside6 ` - --force-stderr-spec=%PROGRAM_BASE%.err.txt ` - --force-stdout-spec=%PROGRAM_BASE%.out.txt ` + --force-stderr-spec="{TEMP}\artemis.err.log" ` + --force-stdout-spec="{TEMP}\artemis.out.log" ` --include-qt-plugins=sensible,styles,qml,multimedia ` --include-data-files=.\artemis\resources.py=.\artemis\resources.py ` --include-data-files=.\config\qtquickcontrols2.conf=.\config\qtquickcontrols2.conf ` diff --git a/building/Windows/windows_installer.iss b/building/Windows/windows_installer.iss index 54bafdf..f61f737 100644 --- a/building/Windows/windows_installer.iss +++ b/building/Windows/windows_installer.iss @@ -42,3 +42,7 @@ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de [Run] 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" diff --git a/building/macOS/build_macos.sh b/building/macOS/build_macos.sh index 1f160ef..98c9e84 100644 --- a/building/macOS/build_macos.sh +++ b/building/macOS/build_macos.sh @@ -4,7 +4,7 @@ echo "Building maacOS target ..." echo "Installing requirements ..." pip install -r requirements.txt -pip install nuitka imageio +pip install nuitka==2.3 imageio echo "Building with Nuitka ..." python -m nuitka app.py \ @@ -12,7 +12,6 @@ python -m nuitka app.py \ --follow-imports \ --show-modules \ --assume-yes-for-downloads \ - --disable-console \ --enable-plugin=pyside6 \ --include-qt-plugins=sensible,styles,qml,multimedia \ --include-data-files=./artemis/resources.py=./artemis/resources.py \ diff --git a/docs/run_from_source.md b/docs/run_from_source.md index 36b3b20..aec075d 100644 --- a/docs/run_from_source.md +++ b/docs/run_from_source.md @@ -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 ``` + +## 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`