Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84dc68dd55 | ||
|
|
cfd302d3ca | ||
|
|
3c6658d19d | ||
|
|
5af0faaa65 | ||
|
|
ce2cfdc76a | ||
|
|
6e0a161b89 | ||
|
|
940c6a0d58 | ||
|
|
194b5c8fb8 | ||
|
|
4e1b3f24c5 | ||
|
|
eaeb51de65 | ||
|
|
995696f11a | ||
|
|
7503b6bb14 | ||
|
|
b867ca849d | ||
|
|
ab32fbbf98 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,4 @@
|
||||
__pycache__
|
||||
__PYCache__
|
||||
Data
|
||||
src/themes/__current_theme
|
||||
designer.bat
|
||||
@@ -8,3 +8,4 @@ launch.bat
|
||||
spec_files/**/output
|
||||
*.txt
|
||||
*.json
|
||||
info.log
|
||||
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -4,11 +4,23 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
The first release is [3.0.0] because this is actually the third major version (completely rewritten) of the software.
|
||||
|
||||
## [Unreleased]
|
||||
...
|
||||
|
||||
## [3.2.1] - 2020-04-25
|
||||
### Added
|
||||
- Add some basic logging to the application. Also for severe errors, track them in info.log file in local folder.
|
||||
- Add Raspberry PI support ([#18](https://github.com/AresValley/Artemis/pull/18), [#20](https://github.com/AresValley/Artemis/pull/20))
|
||||
|
||||
### Fixed
|
||||
- Support new `JSON` format for some forecast data ([#21](https://github.com/AresValley/Artemis/pull/14)).
|
||||
- Fixed categorization for very low x-ray flux according to NOAA format.
|
||||
- Remove the `exclusive` parameter in a PyQt function ([#16](https://github.com/AresValley/Artemis/pull/16)).
|
||||
|
||||
|
||||
## [3.2.0] - 2019-12-14
|
||||
|
||||
### Added
|
||||
- The default font can be changed ([#14](https://github.com/AresValley/Artemis/pull/14))
|
||||
- The default font can be changed ([#14](https://github.com/AresValley/Artemis/pull/14)).
|
||||
- Move `Themes` into `Settings`.
|
||||
- Better settings management in `settings.json`.
|
||||
|
||||
@@ -44,7 +56,8 @@ First release.
|
||||
|
||||
|
||||
<!-- Links definitions -->
|
||||
[Unreleased]: https://github.com/AresValley/Artemis/compare/v3.2.0...HEAD
|
||||
[Unreleased]: https://github.com/AresValley/Artemis/compare/v3.2.1...HEAD
|
||||
[3.2.1]: https://github.com/AresValley/Artemis/compare/v3.2.0...v3.2.1
|
||||
[3.2.0]: https://github.com/AresValley/Artemis/compare/v3.1.0...v3.2.0
|
||||
[3.1.0]: https://github.com/AresValley/Artemis/compare/v3.0.1...v3.1.0
|
||||
[3.0.1]: https://github.com/AresValley/Artemis/compare/v3.0.0...v3.0.1
|
||||
|
||||
@@ -147,7 +147,7 @@ The only folder with the pre-built package is the `themes` one. In this way the
|
||||
Some of the available themes were adapted from https://github.com/GTRONICK/QSS.
|
||||
|
||||
## License
|
||||
This program (ARTEMIS 3, 2014-2019) is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
This program (ARTEMIS 3, 2014-2020) is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
@@ -159,5 +159,6 @@ You should have received a copy of the GNU General Public License along with thi
|
||||
* **Paolo Romani (IZ1MLL)** - *Lead β Tester, RF specialist*
|
||||
* **Carl Colena** - *Sigidwiki admin, β Tester, Signals expert*
|
||||
* [**Marco Bortoli**](https://github.com/marbort "GitHub profile") - *macOS deployment, β Tester*
|
||||
* [**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*
|
||||
* [**Francesco Capostagno**](https://github.com/fcapostagno "GitHub profile"), **Luca**, **Pietro** - *β Tester*
|
||||
|
||||
@@ -28,14 +28,13 @@ class ACFValue:
|
||||
self._description = ""
|
||||
self._value = value
|
||||
self._string = self._value
|
||||
try:
|
||||
if self._value.isdigit():
|
||||
self.numeric_value = float(self._value)
|
||||
except Exception:
|
||||
self.is_numeric = False
|
||||
self.numeric_value = 0.0
|
||||
else:
|
||||
self.is_numeric = True
|
||||
self._string += " ms"
|
||||
else:
|
||||
self.is_numeric = False
|
||||
self.numeric_value = 0.0
|
||||
|
||||
@classmethod
|
||||
def list_from_series(cls, series):
|
||||
|
||||
@@ -5,6 +5,7 @@ import webbrowser
|
||||
import os
|
||||
import sys
|
||||
from time import sleep, time
|
||||
import logging
|
||||
|
||||
from pandas import read_csv
|
||||
|
||||
@@ -58,11 +59,12 @@ from downloadtargetfactory import get_download_target
|
||||
from settings import Settings
|
||||
from updatescontroller import UpdatesController
|
||||
from urlbutton import UrlButton
|
||||
import loggingconf # noqa 401
|
||||
|
||||
# import default_imgs_rc
|
||||
|
||||
|
||||
__LATEST_VERSION__ = "3.2.0"
|
||||
__LATEST_VERSION__ = "3.2.1"
|
||||
|
||||
if IS_BINARY:
|
||||
__VERSION__ = __LATEST_VERSION__
|
||||
@@ -256,6 +258,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
|
||||
font.setUnderline(self.settings.font['underline'])
|
||||
self.apply_font(font)
|
||||
except Exception: # Invalid font
|
||||
logging.warning("Invalid Font in settings.json")
|
||||
pass
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -357,6 +360,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
|
||||
try:
|
||||
webbrowser.open(Constants.GFD_SITE + query.lower())
|
||||
except Exception:
|
||||
logging.error("Cannot open browser")
|
||||
pass
|
||||
|
||||
def set_initial_size(self):
|
||||
@@ -445,7 +449,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
|
||||
else:
|
||||
try:
|
||||
is_checksum_ok = checksum_ok(db, get_db_hash_code())
|
||||
except Exception:
|
||||
except ValueError as e:
|
||||
logging.info(e)
|
||||
pop_up(self, title=Messages.NO_CONNECTION,
|
||||
text=Messages.NO_CONNECTION_MSG).show()
|
||||
else:
|
||||
@@ -485,7 +490,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
|
||||
else:
|
||||
try:
|
||||
is_checksum_ok = checksum_ok(db, get_db_hash_code())
|
||||
except Exception:
|
||||
except ValueError as e:
|
||||
logging.info(e)
|
||||
pop_up(self, title=Messages.NO_CONNECTION,
|
||||
text=Messages.NO_CONNECTION_MSG).show()
|
||||
else:
|
||||
|
||||
@@ -12,6 +12,7 @@ class SupportedOs:
|
||||
WINDOWS = "windows"
|
||||
LINUX = "linux"
|
||||
MAC = "mac"
|
||||
RASPBIAN = "raspberry"
|
||||
|
||||
|
||||
class Ftype:
|
||||
@@ -113,8 +114,8 @@ class Constants:
|
||||
UPDATING_STR = "Updating..."
|
||||
ACF_DOCS = "https://aresvalley.com/documentation/"
|
||||
FORECAST_PROBABILITIES = "https://services.swpc.noaa.gov/text/sgarf.txt"
|
||||
SPACE_WEATHER_XRAY = "https://services.swpc.noaa.gov/text/goes-xray-flux-primary.txt"
|
||||
SPACE_WEATHER_PROT_EL = "https://services.swpc.noaa.gov/text/goes-particle-flux-primary.txt"
|
||||
SPACE_WEATHER_XRAY = "https://services.swpc.noaa.gov/json/goes/primary/xrays-1-day.json"
|
||||
SPACE_WEATHER_PROT_EL = "https://services.swpc.noaa.gov/json/goes/primary/integral-protons-1-day.json"
|
||||
SPACE_WEATHER_AK_INDEX = "https://services.swpc.noaa.gov/text/wwv.txt"
|
||||
SPACE_WEATHER_SGAS = "https://services.swpc.noaa.gov/text/sgas.txt"
|
||||
SPACE_WEATHER_GEO_STORM = "https://services.swpc.noaa.gov/text/3-day-forecast.txt"
|
||||
@@ -209,6 +210,8 @@ class Messages:
|
||||
NEW_VERSION_AVAILABLE = "New software version"
|
||||
NEW_VERSION_MSG = lambda v: f"The software version {v} is available." # noqa: E731
|
||||
DOWNLOAD_SUGG_MSG = "Download new version now?"
|
||||
SCREEN_UPDATE_FAIL = "Unable to update the data"
|
||||
SCREEN_UPDATE_FAIL_MSG = "Downloaded data currupted or invalid"
|
||||
|
||||
|
||||
class ThemeConstants:
|
||||
|
||||
@@ -49,6 +49,7 @@ class _TarExtractor:
|
||||
EXTRACTORS = {
|
||||
SupportedOs.WINDOWS: _ZipExtractor,
|
||||
SupportedOs.LINUX: _TarExtractor,
|
||||
SupportedOs.RASPBIAN: _TarExtractor,
|
||||
# No extractor for MacOs, just download the file through the browser.
|
||||
}
|
||||
|
||||
|
||||
43
src/loggingconf.py
Normal file
43
src/loggingconf.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import logging
|
||||
import logging.config
|
||||
from constants import __BASE_FOLDER__
|
||||
import os.path
|
||||
|
||||
"""Import the module to initialize the logging configuration.
|
||||
|
||||
It is imported only for its side effects."""
|
||||
|
||||
|
||||
_LOGGING_CONFIG = {
|
||||
'version': 1,
|
||||
'formatters': {
|
||||
'general': {
|
||||
'format': '%(asctime)s::%(levelname)s::%(module)s::%(funcName)s::%(message)s',
|
||||
'datefmt': '%d/%m/%Y %I:%M:%S %p',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'INFO',
|
||||
'formatter': 'general',
|
||||
'class': 'logging.StreamHandler',
|
||||
'stream': 'ext://sys.stdout',
|
||||
},
|
||||
'file': {
|
||||
'class': 'logging.FileHandler',
|
||||
'level': 'ERROR',
|
||||
'filename': os.path.join(__BASE_FOLDER__, 'info.log'),
|
||||
'mode': 'w',
|
||||
'encoding': 'utf8',
|
||||
'formatter': 'general',
|
||||
},
|
||||
},
|
||||
'root': {
|
||||
'level': 'DEBUG',
|
||||
'handlers': ['console', 'file'],
|
||||
},
|
||||
# Add loggers if required
|
||||
# 'loggers': {}
|
||||
}
|
||||
|
||||
logging.config.dictConfig(_LOGGING_CONFIG)
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import platform
|
||||
from constants import SupportedOs
|
||||
|
||||
|
||||
@@ -20,6 +21,7 @@ def _is_linux_os():
|
||||
IS_MAC = _is_mac_os()
|
||||
IS_LINUX = _is_linux_os()
|
||||
IS_WINDOWS = _is_win_os()
|
||||
IS_RASPBIAN = IS_LINUX and 'arm' in platform.machine().lower()
|
||||
|
||||
|
||||
def get_os():
|
||||
@@ -27,8 +29,10 @@ def get_os():
|
||||
if IS_WINDOWS:
|
||||
return SupportedOs.WINDOWS
|
||||
elif IS_LINUX:
|
||||
if IS_RASPBIAN:
|
||||
return SupportedOs.RASPBIAN
|
||||
return SupportedOs.LINUX
|
||||
elif IS_MAC:
|
||||
return SupportedOs.MAC
|
||||
else:
|
||||
raise Exception("ERROR: OS not recognized.")
|
||||
return None
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os.path
|
||||
from constants import Constants
|
||||
import json
|
||||
import logging
|
||||
|
||||
|
||||
class Settings:
|
||||
@@ -16,7 +17,8 @@ class Settings:
|
||||
try:
|
||||
with open(Constants.SETTINGS_FILE, 'r') as settings_file:
|
||||
self._dct = json.load(settings_file)
|
||||
except Exception:
|
||||
except FileNotFoundError:
|
||||
logging.info("No settings.json file")
|
||||
pass # Invalid file.
|
||||
|
||||
def save(self, **kwargs):
|
||||
@@ -38,7 +40,4 @@ class Settings:
|
||||
"""Return the corresponding setting.
|
||||
|
||||
Return None if there is no such setting yet."""
|
||||
try:
|
||||
return self._dct[attr]
|
||||
except Exception:
|
||||
return None
|
||||
return self._dct.get(attr, None)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import logging
|
||||
import webbrowser
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
from constants import Constants, Messages
|
||||
from switchable_label import SwitchableLabelsIterable
|
||||
from weatherdata import SpaceWeatherData
|
||||
from utilities import safe_cast, pop_up
|
||||
from utilities import pop_up
|
||||
|
||||
|
||||
class SpaceWeatherManager(QObject):
|
||||
@@ -136,13 +137,14 @@ class SpaceWeatherManager(QObject):
|
||||
"""
|
||||
self._owner.update_now_bar.set_idle()
|
||||
if status_ok:
|
||||
xray_long = safe_cast(self._owner.space_weather_data.xray[-1][7], float)
|
||||
try:
|
||||
xray_long = float(self._owner.space_weather_data.xray)
|
||||
|
||||
def format_text(letter, power):
|
||||
return letter + f"{xray_long * 10**power:.1f}"
|
||||
|
||||
if xray_long < 1e-8 and xray_long != -1.00e+05:
|
||||
self._owner.peak_flux_lbl.setText(format_text("<A", 8))
|
||||
self._owner.peak_flux_lbl.setText("<A0.0")
|
||||
elif xray_long >= 1e-8 and xray_long < 1e-7:
|
||||
self._owner.peak_flux_lbl.setText(format_text("A", 8))
|
||||
elif xray_long >= 1e-7 and xray_long < 1e-6:
|
||||
@@ -171,7 +173,7 @@ class SpaceWeatherManager(QObject):
|
||||
elif xray_long == -1.00e+05:
|
||||
self._switchable_r_labels.switch_off_all()
|
||||
|
||||
pro10 = safe_cast(self._owner.space_weather_data.prot_el[-1][8], float)
|
||||
pro10 = float(self._owner.space_weather_data.prot_el)
|
||||
if pro10 < 10 and pro10 != -1.00e+05:
|
||||
self._switchable_s_labels.switch_on(self._owner.s0_now_lbl)
|
||||
elif pro10 >= 10 and pro10 < 100:
|
||||
@@ -187,13 +189,9 @@ class SpaceWeatherManager(QObject):
|
||||
elif pro10 == -1.00e+05:
|
||||
self._switchable_s_labels.switch_off_all()
|
||||
|
||||
k_index = safe_cast(
|
||||
self._owner.space_weather_data.ak_index[8][11].replace('.', ''), int
|
||||
)
|
||||
k_index = int(self._owner.space_weather_data.ak_index[8][11].replace('.', ''))
|
||||
self._owner.k_index_lbl.setText(str(k_index))
|
||||
a_index = safe_cast(
|
||||
self._owner.space_weather_data.ak_index[7][7].replace('.', ''), int
|
||||
)
|
||||
a_index = int(self._owner.space_weather_data.ak_index[7][7].replace('.', ''))
|
||||
self._owner.a_index_lbl.setText(str(a_index))
|
||||
|
||||
if k_index == 0:
|
||||
@@ -252,9 +250,7 @@ class SpaceWeatherManager(QObject):
|
||||
self._a_storm_labels.switch_on(self._owner.a_sev_storm_lbl)
|
||||
|
||||
index = self._owner.space_weather_data.geo_storm[6].index("was") + 1
|
||||
k_index_24_hmax = safe_cast(
|
||||
self._owner.space_weather_data.geo_storm[6][index], int
|
||||
)
|
||||
k_index_24_hmax = int(self._owner.space_weather_data.geo_storm[6][index])
|
||||
if k_index_24_hmax == 0:
|
||||
self._switchable_g_today_labels.switch_on(self._owner.g0_today_lbl)
|
||||
elif k_index_24_hmax == 1:
|
||||
@@ -276,13 +272,10 @@ class SpaceWeatherManager(QObject):
|
||||
elif k_index_24_hmax == 9:
|
||||
self._switchable_g_today_labels.switch_on(self._owner.g5_today_lbl)
|
||||
|
||||
val = safe_cast(
|
||||
self._owner.space_weather_data.ak_index[7][2].replace('.', ''), int
|
||||
)
|
||||
val = int(self._owner.space_weather_data.ak_index[7][2].replace('.', ''))
|
||||
self._owner.sfi_lbl.setText(f"{val}")
|
||||
val = safe_cast(
|
||||
[x[4] for x in self._owner.space_weather_data.sgas
|
||||
if "SSN" in x][0], int
|
||||
val = int(
|
||||
[x[4] for x in self._owner.space_weather_data.sgas if "SSN" in x][0]
|
||||
)
|
||||
self._owner.sn_lbl.setText(f"{val:d}")
|
||||
|
||||
@@ -291,6 +284,14 @@ class SpaceWeatherManager(QObject):
|
||||
label.pixmap = pixmap
|
||||
label.make_transparent()
|
||||
label.apply_pixmap()
|
||||
except Exception as e: # This is a mess, so log an error and give up
|
||||
logging.error(f"Forecast update failure: {e}")
|
||||
pop_up(
|
||||
self._owner,
|
||||
title=Messages.SCREEN_UPDATE_FAIL,
|
||||
text=Messages.SCREEN_UPDATE_FAIL_MSG
|
||||
).show()
|
||||
|
||||
elif not self._owner.closing:
|
||||
pop_up(self._owner, title=Messages.BAD_DOWNLOAD,
|
||||
text=Messages.BAD_DOWNLOAD_MSG).show()
|
||||
|
||||
@@ -140,7 +140,7 @@ class ThemeManager:
|
||||
Connect all the actions to change the theme.
|
||||
Display a QMessageBox if the theme folder is not found."""
|
||||
themes = []
|
||||
ag = QActionGroup(self._owner, exclusive=True)
|
||||
ag = QActionGroup(self._owner)
|
||||
themes_menu = self._owner.settings_menu.addMenu("Themes")
|
||||
if not os.path.exists(ThemeConstants.FOLDER):
|
||||
pop_up(self._owner, title=ThemeConstants.THEME_FOLDER_NOT_FOUND,
|
||||
@@ -272,17 +272,15 @@ class ThemeManager:
|
||||
|
||||
def apply_default_theme(self):
|
||||
"""Apply the default theme if no theme is set or the theme name is invalid."""
|
||||
try:
|
||||
self._theme_names[
|
||||
self._pretty_name(ThemeConstants.DEFAULT)
|
||||
].setChecked(True)
|
||||
except Exception:
|
||||
pretty_name = self._theme_names.get(self._pretty_name(ThemeConstants.DEFAULT), None)
|
||||
if pretty_name is None:
|
||||
pop_up(
|
||||
self._owner,
|
||||
title=ThemeConstants.THEME_NOT_FOUND,
|
||||
text=ThemeConstants.MISSING_THEME
|
||||
).show()
|
||||
else:
|
||||
pretty_name.setChecked(True)
|
||||
self._apply(ThemeConstants.DEFAULT_THEME_PATH)
|
||||
|
||||
def start(self):
|
||||
@@ -291,11 +289,11 @@ class ThemeManager:
|
||||
if self._owner.settings.theme is not None:
|
||||
theme_path = os.path.join(ThemeConstants.FOLDER, self._owner.settings.theme)
|
||||
theme_name = self._pretty_name(os.path.basename(theme_path))
|
||||
try:
|
||||
self._theme_names[theme_name].setChecked(True)
|
||||
except Exception:
|
||||
theme = self._theme_names.get(theme_name, None)
|
||||
if theme is None:
|
||||
self.apply_default_theme()
|
||||
else:
|
||||
theme.setChecked(True)
|
||||
self._apply(theme_path, save=False)
|
||||
else:
|
||||
self.apply_default_theme()
|
||||
|
||||
@@ -7,7 +7,7 @@ from time import perf_counter
|
||||
import aiohttp
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
|
||||
from constants import Constants
|
||||
from utilities import checksum_ok
|
||||
from utilities import checksum_ok, get_file_extension
|
||||
from web_utilities import (
|
||||
get_cacert_file,
|
||||
get_pool_manager,
|
||||
@@ -156,7 +156,7 @@ class DownloadThread(BaseDownloadThread):
|
||||
"""Verify the checksum of the downloaded data and set the status accordingly."""
|
||||
try:
|
||||
is_checksum_ok = checksum_ok(raw_data, self._target.hash_code)
|
||||
except Exception: # Invalid hash code.
|
||||
except ValueError: # Invalid hash code.
|
||||
self.status = ThreadStatus.NO_CONNECTION_ERR
|
||||
return True
|
||||
else:
|
||||
@@ -234,7 +234,7 @@ class UpdateSpaceWeatherThread(BaseDownloadThread):
|
||||
"""Download the data conteining the information of a specific property."""
|
||||
link = getattr(Constants, "SPACE_WEATHER_" + property_name.upper())
|
||||
data = await _download_resource(session, link)
|
||||
setattr(self._space_weather_data, property_name, str(data, 'utf-8'))
|
||||
self._space_weather_data.set_property(property_name, data, get_file_extension(link))
|
||||
|
||||
async def _download_image(self, session, n):
|
||||
"""Download the data corresponding the n-th image displayed in the screen."""
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import subprocess as sp
|
||||
import webbrowser
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, QProcess
|
||||
@@ -99,6 +100,7 @@ class UpdatesController(QObject):
|
||||
try:
|
||||
updater.startDetached(command)
|
||||
except BaseException:
|
||||
logging.error("Unable to start updater")
|
||||
pass
|
||||
else:
|
||||
qApp.quit()
|
||||
@@ -110,7 +112,7 @@ class UpdatesController(QObject):
|
||||
If so, ask to download the new version.
|
||||
If the software is not a compiled version, the function is a NOP."""
|
||||
if not IS_BINARY or IS_MAC:
|
||||
return
|
||||
return None
|
||||
latest_updater_version = self.version_controller.updater.version
|
||||
try:
|
||||
with sp.Popen(
|
||||
@@ -122,9 +124,10 @@ class UpdatesController(QObject):
|
||||
) as proc:
|
||||
updater_version = proc.stdout.read().rstrip("\r\n") # Strip any possible newline, to be sure.
|
||||
except Exception:
|
||||
logging.error("Unable to query the updater")
|
||||
updater_version = latest_updater_version
|
||||
if latest_updater_version is None:
|
||||
return
|
||||
return None
|
||||
if updater_version != latest_updater_version:
|
||||
answer = pop_up(
|
||||
self._owner,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
from functools import partial
|
||||
import hashlib
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
@@ -30,12 +31,12 @@ class UniqueMessageBox(QMessageBox):
|
||||
"""Overrides QMessageBox.exec. Call the parent method if there are no
|
||||
other instances executing exec; also set the current font.
|
||||
Otherwise return None,"""
|
||||
if UniqueMessageBox._open_message:
|
||||
if self.__class__._open_message:
|
||||
return None
|
||||
self.setFont(self._font)
|
||||
UniqueMessageBox._open_message = True
|
||||
self.__class__._open_message = True
|
||||
answer = super().exec()
|
||||
UniqueMessageBox._open_message = False
|
||||
self.__class__._open_message = False
|
||||
return answer
|
||||
|
||||
def show(self):
|
||||
@@ -107,7 +108,7 @@ def checksum_ok(data, reference_hash_code):
|
||||
|
||||
Expects a sha256 code as argument."""
|
||||
if reference_hash_code is None:
|
||||
raise Exception("ERROR: Invalid hash code.")
|
||||
raise ValueError("ERROR: Invalid hash code.")
|
||||
code = hashlib.sha256()
|
||||
code.update(data)
|
||||
return code.hexdigest() == reference_hash_code
|
||||
@@ -192,6 +193,25 @@ def safe_cast(value, cast_type, default=-1):
|
||||
try:
|
||||
r = cast_type(value)
|
||||
except Exception:
|
||||
logging.error("Cast type failure")
|
||||
r = default
|
||||
finally:
|
||||
return r
|
||||
|
||||
|
||||
def get_file_extension(file):
|
||||
"""Return the extension of a file. Return None if there is not such property."""
|
||||
components = file.split('.')
|
||||
if len(components) > 1:
|
||||
return components[-1]
|
||||
return None
|
||||
|
||||
|
||||
def get_value_from_list_of_dicts(iterable, callable_ok, key_value):
|
||||
"""Return a value from a dict inside a list of dicts.
|
||||
|
||||
The iterable is reversed first, then the value corresponding to the key key_value
|
||||
is returned from the first dict for which callable_ok(dict) returns True"""
|
||||
for d in reversed(iterable):
|
||||
if callable_ok(d):
|
||||
return d[key_value]
|
||||
|
||||
@@ -61,16 +61,25 @@ def _download_versions_file():
|
||||
"size": ...
|
||||
}
|
||||
}
|
||||
"raspberry": {
|
||||
"software": {
|
||||
"version": "...",
|
||||
"url": "...",
|
||||
"hash_code": "...",
|
||||
"size": ...
|
||||
},
|
||||
"updater": {
|
||||
"version": "...",
|
||||
"url": "...",
|
||||
"hash_code": "...",
|
||||
"size": ...
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
version_dict = json.load(
|
||||
return json.load(
|
||||
BytesIO(download_file(Constants.VERSION_LINK))
|
||||
)[get_os()]
|
||||
except Exception:
|
||||
return None
|
||||
else:
|
||||
return version_dict
|
||||
).get(get_os(), None)
|
||||
|
||||
|
||||
class VersionController:
|
||||
@@ -80,7 +89,6 @@ class VersionController:
|
||||
|
||||
def __init__(self, dct=None):
|
||||
"""Initialize the dictionary"""
|
||||
super().__init__()
|
||||
self._dct = dct
|
||||
|
||||
def __getattr__(self, attr):
|
||||
@@ -89,11 +97,9 @@ class VersionController:
|
||||
if self._dct is None:
|
||||
if not self.update():
|
||||
return None
|
||||
try:
|
||||
dct_element = self._dct[attr]
|
||||
except Exception("ERROR: Invalid attribute!"):
|
||||
dct_element = self._dct.get(attr, None)
|
||||
if dct_element is None:
|
||||
return None
|
||||
else:
|
||||
if isinstance(dct_element, dict):
|
||||
setattr(self, attr, type(self)(dct_element))
|
||||
else:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
|
||||
@@ -9,7 +11,7 @@ from threads import (
|
||||
)
|
||||
from constants import Constants
|
||||
from switchable_label import MultiColorSwitchableLabel
|
||||
from utilities import safe_cast
|
||||
from utilities import safe_cast, get_value_from_list_of_dicts
|
||||
|
||||
|
||||
class _BaseWeatherData(QObject):
|
||||
@@ -89,12 +91,35 @@ class SpaceWeatherData(_BaseWeatherData):
|
||||
"""Override _BaseWeatherData._parse_data.
|
||||
|
||||
Set all the data."""
|
||||
self.xray = self._double_split(self.xray)
|
||||
self.prot_el = self._double_split(self.prot_el)
|
||||
if self.xray is not None:
|
||||
self.xray = get_value_from_list_of_dicts(
|
||||
self.xray,
|
||||
lambda d: d["energy"] == "0.1-0.8nm",
|
||||
"flux"
|
||||
)
|
||||
if self.prot_el is not None:
|
||||
self.prot_el = get_value_from_list_of_dicts(
|
||||
self.prot_el,
|
||||
lambda d: d["energy"] == ">=10 MeV",
|
||||
"flux"
|
||||
)
|
||||
if self.ak_index is not None:
|
||||
self.ak_index = self._double_split(self.ak_index)
|
||||
if self.sgas is not None:
|
||||
self.sgas = self._double_split(self.sgas)
|
||||
if self.geo_storm is not None:
|
||||
self.geo_storm = self._double_split(self.geo_storm)
|
||||
|
||||
def set_property(self, property_name, data, extension):
|
||||
"""Set a property to the object. Format the data based on the extension."""
|
||||
if extension == 'txt':
|
||||
setattr(self, property_name, str(data, 'utf-8'))
|
||||
elif extension == 'json':
|
||||
setattr(self, property_name, json.loads(data))
|
||||
else:
|
||||
logging.error("Invalid file extension")
|
||||
setattr(self, property_name, None)
|
||||
|
||||
def remove_data(self):
|
||||
"""Remove the reference to all the data."""
|
||||
self.xray = ''
|
||||
@@ -347,6 +372,7 @@ class ForecastData(_BaseWeatherData):
|
||||
self._set_dates(forecast, rows["solar_row"])
|
||||
self._set_labels_values(labels_table)
|
||||
except Exception:
|
||||
logging.error("Update ForecastData failure")
|
||||
pass
|
||||
|
||||
def remove_data(self):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import urllib3
|
||||
@@ -37,15 +38,21 @@ def _download_multiline_file_as_list(url=Database.LINK_REF):
|
||||
The downloaded file is a csv file with columns (last version == last line):
|
||||
data.zip_SHA256 | db.csv_SHA256 | Version | Creation_date"""
|
||||
try:
|
||||
f = download_file(url, encoding="UTF-8").splitlines()[-1].split(Database.DELIMITER)
|
||||
return download_file(url, encoding="UTF-8").splitlines()[-1].split(Database.DELIMITER)
|
||||
except Exception:
|
||||
logging.error("Database metadata download failure")
|
||||
return None
|
||||
return f
|
||||
|
||||
|
||||
def get_folder_hash_code():
|
||||
return _download_multiline_file_as_list()[0]
|
||||
f = _download_multiline_file_as_list()
|
||||
if f is not None:
|
||||
return f[0]
|
||||
return None
|
||||
|
||||
|
||||
def get_db_hash_code():
|
||||
return _download_multiline_file_as_list()[1]
|
||||
f = _download_multiline_file_as_list()
|
||||
if f is not None:
|
||||
return f[1]
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user