def ask_if_download(self): """Check if the database is at its latest version. If a new database is available automatically start the download. If not ask if should download it anyway. If already downloading do nothing. Handle possible connection errors. """ if not self.download_window.isVisible(): db_path = os.path.join(Constants.DATA_FOLDER, Database.NAME) try: with open(db_path, "rb") as file_db: db = file_db.read() except Exception: self.download_db() else: try: is_checksum_ok = checksum_ok(db, ChecksumWhat.DB) except Exception: pop_up(self, title=Messages.NO_CONNECTION, text=Messages.NO_CONNECTION_MSG).show() else: if not is_checksum_ok: self.download_db() else: answer = pop_up(self, title=Messages.DB_UP_TO_DATE, text=Messages.DB_UP_TO_DATE_MSG, informative_text=Messages.DOWNLOAD_ANYWAY_QUESTION, is_question=True, default_btn=QMessageBox.No).exec() if answer == QMessageBox.Yes: self.download_db()
def _detect_themes(self): """Detect all available themes. 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) if os.path.exists(ThemeConstants.FOLDER): for theme_folder in sorted(os.listdir(ThemeConstants.FOLDER)): relative_folder = os.path.join(ThemeConstants.FOLDER, theme_folder) if os.path.isdir(os.path.abspath(relative_folder)): relative_folder = os.path.join(ThemeConstants.FOLDER, theme_folder) themes.append(relative_folder) for theme_path in themes: theme_name = '&' + self._pretty_name(os.path.basename(theme_path)) new_theme = ag.addAction( QAction( theme_name, self._owner, checkable=True ) ) self._owner.menu_themes.addAction(new_theme) self._theme_names[theme_name.lstrip('&')] = new_theme new_theme.triggered.connect(partial(self._apply, theme_path)) else: pop_up(self._owner, title=ThemeConstants.THEME_FOLDER_NOT_FOUND, text=ThemeConstants.MISSING_THEME_FOLDER).show()
def __init__(self): """Initialize the window.""" super().__init__() self.setupUi(self) self.setWindowFlags( # Qt.Window | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint | Qt.WindowStaysOnTopHint ) self._no_internet_msg = pop_up(self, title=Messages.NO_CONNECTION, text=Messages.NO_CONNECTION_MSG, connection=self.close) self._bad_db_download_msg = pop_up(self, title=Messages.BAD_DOWNLOAD, text=Messages.BAD_DOWNLOAD_MSG, connection=self.close) self._slow_conn_msg = pop_up(self, title=Messages.SLOW_CONN, text=Messages.SLOW_CONN_MSG, connection=self.close) self._download_thread = DownloadThread() self._download_thread.finished.connect(self._wait_close) self._download_thread.progress.connect(self._display_progress) self._download_thread.speed_progress.connect(self._display_speed) self.closed.connect(self._download_thread.set_exit) self.cancel_btn.clicked.connect(self._terminate_process)
def start(self): """Start the theme manager.""" self._detect_themes() if os.path.exists(ThemeConstants.CURRENT_THEME_FILE): with open(ThemeConstants.CURRENT_THEME_FILE, "r") as current_theme_path: theme_path = current_theme_path.read() theme_name = self._pretty_name(os.path.basename(theme_path)) try: self._theme_names[theme_name].setChecked(True) except Exception: pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND, text=ThemeConstants.MISSING_THEME).show() else: self._apply(theme_path) else: try: self._theme_names[self._pretty_name( ThemeConstants.DEFAULT)].setChecked(True) except Exception: pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND, text=ThemeConstants.MISSING_THEME).show() else: self._apply(ThemeConstants.DEFAULT_THEME_PATH)
def start_verify_software_version(self): if not IS_BINARY: pop_up(self._owner, title=Messages.FEATURE_NOT_AVAILABLE, text=Messages.SCRIPT_NOT_UPDATE).show() return if not self._download_window.isVisible(): self._updates_thread.start()
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: pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND, text=ThemeConstants.MISSING_THEME).show() else: self._apply(ThemeConstants.DEFAULT_THEME_PATH)
def apply_default_theme(self): """Apply the default theme if no theme is set or the theme name is invalid.""" 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 update_forecast(self, status_ok): """Update the 3-day forecast screen after a successful download. If the download was not successful throw a warning. In any case remove the downloaded data. """ self.update_forecast_bar.set_idle() if status_ok: self.forecast_data.update_all_labels() elif not self.closing: pop_up(self, title=Messages.BAD_DOWNLOAD, text=Messages.BAD_DOWNLOAD_MSG).show() self.forecast_data.remove_data()
def _verify_software_version(self, success): """Verify if there is a new software version. Otherwise notify the user that the software is up to date.""" if not self._download_window.isVisible(): if success: new_version_found = self._check_new_version() if not new_version_found: pop_up(self._owner, title=Messages.UP_TO_DATE, text=Messages.UP_TO_DATE_MSG).show() else: pop_up(self._owner, title=Messages.NO_CONNECTION, text=Messages.NO_CONNECTION_MSG).show()
def _check_new_version(self): """Check whether there is a new software version available. Does something only if the running program is a compiled version.""" if not IS_BINARY: return None latest_version = self.version_controller.software.version if latest_version is None: return False if latest_version == self._current_version: return False answer = pop_up( self._owner, title=Messages.NEW_VERSION_AVAILABLE, text=Messages.NEW_VERSION_MSG(latest_version), informative_text=Messages.DOWNLOAD_SUGG_MSG, is_question=True, ).exec() if answer == QMessageBox.Yes: if IS_MAC: webbrowser.open(self.version_controller.software.url) else: updater = QProcess() command = Constants.UPDATER_SOFTWARE + " " + \ self.version_controller.software.url + \ " " + self.version_controller.software.hash_code + \ " " + str(self.version_controller.software.size) try: updater.startDetached(command) except BaseException: logging.error("Unable to start updater") pass else: qApp.quit() return True
def _check_updater_version(self): """Check is a new version of the updater is available. 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 None latest_updater_version = self.version_controller.updater.version try: with sp.Popen( [Constants.UPDATER_SOFTWARE, "--version"], encoding="UTF-8", stdout=sp.PIPE, stderr=sp.STDOUT, stdin=sp. DEVNULL # Needed to avoid OsError: [WinError 6] The handle is invalid. ) 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 None if updater_version != latest_updater_version: answer = pop_up( self._owner, title=Messages.UPDATES_AVAILABALE, text=Messages.UPDATES_MSG, is_question=True, ).exec() if answer == QMessageBox.Yes: self._download_window.activate( get_download_target(DownloadTarget.UPDATER, self.version_controller.updater))
def _apply(self, theme_path): """Apply the selected theme. Refresh all relevant widgets. Display a QMessageBox if the theme is not found.""" self._theme_path = theme_path if os.path.exists(theme_path): if self._theme_path != self._current_theme: self._change() self._owner.display_specs( item=self._owner.signals_list.currentItem(), previous_item=None) self._owner.filters.refresh() self._owner.audio_widget.refresh(self._owner.active_color, self._owner.inactive_color) self._owner.spaceweather_screen.refreshable_labels.refresh() else: pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND, text=ThemeConstants.MISSING_THEME).show()
def load_db(self): """Load the database from file. Populate the signals list and set the total number of signals. Handle possible missing file error. """ try: self.db = read_csv( os.path.join(Constants.DATA_FOLDER, Database.NAME), sep=Database.DELIMITER, header=None, index_col=0, dtype={name: str for name in Database.STRINGS}, names=Database.NAMES ) except FileNotFoundError: self.search_bar.setDisabled(True) answer = pop_up(self, title=Messages.NO_DB, text=Messages.NO_DB_AVAIL, informative_text=Messages.DOWNLOAD_NOW_QUESTION, is_question=True).exec() if answer == QMessageBox.Yes: self.download_db() else: # Avoid a crash if there are duplicated signals self.db = self.db.groupby(level=0).first() self.signal_names = self.db.index self.total_signals = len(self.signal_names) self.db.fillna(Constants.UNKNOWN, inplace=True) self.db[Signal.ACF] = ACFValue.list_from_series(self.db[Signal.ACF]) self.db[Signal.WIKI_CLICKED] = False self.update_status_tip(self.total_signals) self.signals_list.clear() self.signals_list.addItems(self.signal_names) self.signals_list.setCurrentItem(None) self.modulation_list.addItems( self.collect_list( Signal.MODULATION ) ) self.locations_list.addItems( self.collect_list( Signal.LOCATION ) )
def check_db_ver(self): """Check if the database is at its latest version. If a new database version is available, ask if it should be downloaded. If a new database version is not available display a message. If already downloading do nothing. Handle possible connection errors. """ if not self.download_window.isVisible(): db_path = os.path.join(Constants.DATA_FOLDER, Database.NAME) answer = None try: with open(db_path, "rb") as file_db: db = file_db.read() except Exception: answer = pop_up( self, title=Messages.NO_DB, text=Messages.NO_DB_AVAIL, informative_text=Messages.DOWNLOAD_NOW_QUESTION, is_question=True).exec() if answer == QMessageBox.Yes: self.download_db() else: try: is_checksum_ok = checksum_ok(db, get_db_hash_code()) except Exception: pop_up(self, title=Messages.NO_CONNECTION, text=Messages.NO_CONNECTION_MSG).show() else: if is_checksum_ok: pop_up(self, title=Messages.UP_TO_DATE, text=Messages.UP_TO_DATE_MSG).show() else: answer = pop_up( self, title=Messages.DB_NEW_VER, text=Messages.DB_NEW_VER_MSG, informative_text=Messages.DOWNLOAD_NOW_QUESTION, is_question=True).exec() if answer == QMessageBox.Yes: self.download_db()
def _change(self): """Change the current theme. Apply the stylesheet and set active and inactive colors. Set all the new images needed. Save the new current theme on file.""" theme_name = os.path.basename(self._theme_path) + ThemeConstants.EXTENSION try: with open(os.path.join(self._theme_path, theme_name), "r") as stylesheet: style = stylesheet.read() self._owner.setStyleSheet(style) self._owner.download_window.setStyleSheet(style) except FileNotFoundError: pop_up(self._owner, title=ThemeConstants.THEME_NOT_FOUND, text=ThemeConstants.MISSING_THEME).show() else: icons_path = os.path.join(self._theme_path, ThemeConstants.ICONS_FOLDER) path_to_search_label = os.path.join(icons_path, Constants.SEARCH_LABEL_IMG) if os.path.exists(path_to_search_label): path = path_to_search_label else: path = ThemeConstants.DEFAULT_SEARCH_LABEL_PATH self._owner.search_label.setPixmap(QPixmap(path)) self._owner.modulation_search_label.setPixmap(QPixmap(path)) self._owner.location_search_label.setPixmap(QPixmap(path)) self._owner.search_label.setScaledContents(True) self._owner.modulation_search_label.setScaledContents(True) self._owner.location_search_label.setScaledContents(True) path_to_volume_label = os.path.join(icons_path, Constants.VOLUME_LABEL_IMG) if os.path.exists(path_to_volume_label): path = path_to_volume_label else: path = ThemeConstants.DEFAULT_VOLUME_LABEL_PATH self._owner.volume_label.setPixmap(QPixmap(path)) self._owner.volume_label.setScaledContents(True) path_to_colors = os.path.join(self._theme_path, ThemeConstants.COLORS) active_color_ok = False inactive_color_ok = False switch_on_color_ok = False switch_off_color_ok = False text_color_ok = False if os.path.exists(path_to_colors): with open(path_to_colors, "r") as colors_file: color_handler = _ColorsHandler.from_file(colors_file.read()) if color_handler is not None: for color in color_handler.simple_color_list: if color.quality == Constants.ACTIVE: self._owner.active_color = color.color_str active_color_ok = True if color.quality == Constants.INACTIVE: self._owner.inactive_color = color.color_str inactive_color_ok = True if color.quality == Constants.TEXT_COLOR: text_color_ok = True self._space_weather_labels.set( "text_color", color.color_str ) for color in color_handler.double_color_list: if color.quality == Constants.LABEL_ON_COLOR: switch_on_color_ok = True self._space_weather_labels.set( "switch_on_colors", color.color_list ) if color.quality == Constants.LABEL_OFF_COLOR: switch_off_color_ok = True self._space_weather_labels.set( "switch_off_colors", color.color_list ) if not (active_color_ok and inactive_color_ok): self._owner.active_color = ThemeConstants.DEFAULT_ACTIVE_COLOR self._owner.inactive_color = ThemeConstants.DEFAULT_INACTIVE_COLOR if not (switch_on_color_ok and switch_off_color_ok): self._space_weather_labels.set( "switch_on_colors", ThemeConstants.DEFAULT_ON_COLORS ) self._space_weather_labels.set( "switch_off_colors", ThemeConstants.DEFAULT_OFF_COLORS ) if not text_color_ok: self._space_weather_labels.set( "text_color", ThemeConstants.DEFAULT_TEXT_COLOR ) self._current_theme = self._theme_path try: with open(ThemeConstants.CURRENT_THEME_FILE, "w") as current_theme: current_theme.write(self._theme_path) except Exception: pass
def _update_space_weather(self, status_ok): """Update the space weather screen after a successful download. If the download was not successful throw a warning. In any case remove the downloaded data. """ self._owner.update_now_bar.set_idle() if status_ok: 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("<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: self._owner.peak_flux_lbl.setText(format_text("B", 7)) elif xray_long >= 1e-6 and xray_long < 1e-5: self._owner.peak_flux_lbl.setText(format_text("C", 6)) elif xray_long >= 1e-5 and xray_long < 1e-4: self._owner.peak_flux_lbl.setText(format_text("M", 5)) elif xray_long >= 1e-4: self._owner.peak_flux_lbl.setText(format_text("X", 4)) elif xray_long == -1.00e+05: self._owner.peak_flux_lbl.setText("No Data") if xray_long < 1e-5 and xray_long != -1.00e+05: self._switchable_r_labels.switch_on(self._owner.r0_now_lbl) elif xray_long >= 1e-5 and xray_long < 5e-5: self._switchable_r_labels.switch_on(self._owner.r1_now_lbl) elif xray_long >= 5e-5 and xray_long < 1e-4: self._switchable_r_labels.switch_on(self._owner.r2_now_lbl) elif xray_long >= 1e-4 and xray_long < 1e-3: self._switchable_r_labels.switch_on(self._owner.r3_now_lbl) elif xray_long >= 1e-3 and xray_long < 2e-3: self._switchable_r_labels.switch_on(self._owner.r4_now_lbl) elif xray_long >= 2e-3: self._switchable_r_labels.switch_on(self._owner.r5_now_lbl) elif xray_long == -1.00e+05: self._switchable_r_labels.switch_off_all() 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: self._switchable_s_labels.switch_on(self._owner.s1_now_lbl) elif pro10 >= 100 and pro10 < 1000: self._switchable_s_labels.switch_on(self._owner.s2_now_lbl) elif pro10 >= 1000 and pro10 < 10000: self._switchable_s_labels.switch_on(self._owner.s3_now_lbl) elif pro10 >= 10000 and pro10 < 100000: self._switchable_s_labels.switch_on(self._owner.s4_now_lbl) elif pro10 >= 100000: self._switchable_s_labels.switch_on(self._owner.s5_now_lbl) elif pro10 == -1.00e+05: self._switchable_s_labels.switch_off_all() k_index = int( self._owner.space_weather_data.ak_index[8][11].replace( '.', '')) self._owner.k_index_lbl.setText(str(k_index)) 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: self._switchable_g_now_labels.switch_on( self._owner.g0_now_lbl) self._k_storm_labels.switch_on(self._owner.k_inactive_lbl) self._owner.expected_noise_lbl.setText( " S0 - S1 (<-120 dBm) ") elif k_index == 1: self._switchable_g_now_labels.switch_on( self._owner.g0_now_lbl) self._k_storm_labels.switch_on( self._owner.k_very_quiet_lbl) self._owner.expected_noise_lbl.setText( " S0 - S1 (<-120 dBm) ") elif k_index == 2: self._switchable_g_now_labels.switch_on( self._owner.g0_now_lbl) self._k_storm_labels.switch_on(self._owner.k_quiet_lbl) self._owner.expected_noise_lbl.setText( " S1 - S2 (-115 dBm) ") elif k_index == 3: self._switchable_g_now_labels.switch_on( self._owner.g0_now_lbl) self._k_storm_labels.switch_on(self._owner.k_unsettled_lbl) self._owner.expected_noise_lbl.setText( " S2 - S3 (-110 dBm) ") elif k_index == 4: self._switchable_g_now_labels.switch_on( self._owner.g0_now_lbl) self._k_storm_labels.switch_on(self._owner.k_active_lbl) self._owner.expected_noise_lbl.setText( " S3 - S4 (-100 dBm) ") elif k_index == 5: self._switchable_g_now_labels.switch_on( self._owner.g1_now_lbl) self._k_storm_labels.switch_on(self._owner.k_min_storm_lbl) self._owner.expected_noise_lbl.setText( " S4 - S6 (-90 dBm) ") elif k_index == 6: self._switchable_g_now_labels.switch_on( self._owner.g2_now_lbl) self._k_storm_labels.switch_on(self._owner.k_maj_storm_lbl) self._owner.expected_noise_lbl.setText( " S6 - S9 (-80 dBm) ") elif k_index == 7: self._switchable_g_now_labels.switch_on( self._owner.g3_now_lbl) self._k_storm_labels.switch_on(self._owner.k_sev_storm_lbl) self._owner.expected_noise_lbl.setText( " S9 - S20 (>-60 dBm) ") elif k_index == 8: self._switchable_g_now_labels.switch_on( self._owner.g4_now_lbl) self._k_storm_labels.switch_on( self._owner.k_very_sev_storm_lbl) self._owner.expected_noise_lbl.setText( " S20 - S30 (>-60 dBm) ") elif k_index == 9: self._switchable_g_now_labels.switch_on( self._owner.g5_now_lbl) self._k_storm_labels.switch_on( self._owner.k_ex_sev_storm_lbl) self._owner.expected_noise_lbl.setText( " S30+ (>>-60 dBm) ") self._owner.expected_noise_lbl.switch_on() if a_index >= 0 and a_index < 8: self._a_storm_labels.switch_on(self._owner.a_quiet_lbl) elif a_index >= 8 and a_index < 16: self._a_storm_labels.switch_on(self._owner.a_unsettled_lbl) elif a_index >= 16 and a_index < 30: self._a_storm_labels.switch_on(self._owner.a_active_lbl) elif a_index >= 30 and a_index < 50: self._a_storm_labels.switch_on(self._owner.a_min_storm_lbl) elif a_index >= 50 and a_index < 100: self._a_storm_labels.switch_on(self._owner.a_maj_storm_lbl) elif a_index >= 100 and a_index < 400: 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 = 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: self._switchable_g_today_labels.switch_on( self._owner.g0_today_lbl) elif k_index_24_hmax == 2: self._switchable_g_today_labels.switch_on( self._owner.g0_today_lbl) elif k_index_24_hmax == 3: self._switchable_g_today_labels.switch_on( self._owner.g0_today_lbl) elif k_index_24_hmax == 4: self._switchable_g_today_labels.switch_on( self._owner.g0_today_lbl) elif k_index_24_hmax == 5: self._switchable_g_today_labels.switch_on( self._owner.g1_today_lbl) elif k_index_24_hmax == 6: self._switchable_g_today_labels.switch_on( self._owner.g2_today_lbl) elif k_index_24_hmax == 7: self._switchable_g_today_labels.switch_on( self._owner.g3_today_lbl) elif k_index_24_hmax == 8: self._switchable_g_today_labels.switch_on( self._owner.g4_today_lbl) elif k_index_24_hmax == 9: self._switchable_g_today_labels.switch_on( self._owner.g5_today_lbl) val = int( self._owner.space_weather_data.ak_index[7][2].replace( '.', '')) self._owner.sfi_lbl.setText(f"{val}") 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}") for label, pixmap in zip( self.space_weather_labels, self._owner.space_weather_data.images): 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() self._owner.space_weather_data.remove_data()