diff --git a/neurotic/gui/standalone.py b/neurotic/gui/standalone.py
index 8dbf336..3a9034b 100644
--- a/neurotic/gui/standalone.py
+++ b/neurotic/gui/standalone.py
@@ -12,6 +12,7 @@
import requests
import subprocess
import pkg_resources
+import warnings
from packaging import version
import quantities as pq
@@ -26,6 +27,9 @@
import logging
logger = logging.getLogger(__name__)
+# suppress warning that gdrive token file does not exist
+warnings.filterwarnings('ignore', message='Cannot access .*', module='oauth2client')
+
def open_path_with_default_program(path):
"""
@@ -64,8 +68,9 @@ class MainWindow(QT.QMainWindow):
"""
request_download = QT.pyqtSignal()
- request_check_for_updates = QT.pyqtSignal(bool)
request_load_dataset = QT.pyqtSignal()
+ request_gdrive_authorization = QT.pyqtSignal()
+ request_check_for_updates = QT.pyqtSignal(bool)
def __init__(self, file=None, initial_selection=None, lazy=True, theme='light', ui_scale='medium', support_increased_line_width=False, show_datetime=False):
"""
@@ -118,23 +123,37 @@ def __init__(self, file=None, initial_selection=None, lazy=True, theme='light',
self.loading_label.setFrameStyle(QT.QFrame.Panel | QT.QFrame.Sunken)
self.loading_label.setAlignment(QT.Qt.AlignCenter)
+ # gdrive auth flow label
+ self.gdrive_auth_label = QT.QLabel('Continue Google Drive authorization\nin your web browser')
+ self.gdrive_auth_label.setFrameStyle(QT.QFrame.Panel | QT.QFrame.Sunken)
+ self.gdrive_auth_label.setAlignment(QT.Qt.AlignCenter)
+
# initially stack the metadata selector above the loading label
self.stacked_layout = QT.QStackedLayout()
self.stacked_layout.addWidget(self.metadata_selector) # index 0
self.stacked_layout.addWidget(self.loading_label) # index 1
+ self.stacked_layout.addWidget(self.gdrive_auth_label) # index 2
central_widget = QT.QWidget()
central_widget.setLayout(self.stacked_layout)
self.setCentralWidget(central_widget)
- # create a worker thread for downloading data and checking for updates
+ # create a worker thread for network activity (e.g., downloading data)
self.network_thread = QT.QThread()
self.network_worker = _NetworkWorker(self)
self.network_worker.moveToThread(self.network_thread)
+
+ # set up the network thread to perform data downloads
self.request_download.connect(self.network_worker.download)
self.network_worker.download_finished.connect(self.on_download_finished)
+
+ # set up the network thread to perform checks for updates
self.request_check_for_updates.connect(self.network_worker.get_latest_release_number)
self.network_worker.version_check_finished.connect(self.on_version_check_finished)
+ # set up the network thread to run the Google Drive authorization flow
+ self.request_gdrive_authorization.connect(self.network_worker.authorize_gdrive)
+ self.network_worker.gdrive_authorization_finished.connect(self.on_gdrive_authorization_finished)
+
# create a worker thread for loading datasets
self.load_dataset_thread = QT.QThread()
self.load_dataset_worker = _LoadDatasetWorker(self)
@@ -181,10 +200,10 @@ def create_menus(self):
Construct the menus of the app.
"""
- menu_bar = self.menuBar()
- menu_bar.setNativeMenuBar(False) # disable for macOS, see GH-239
+ self.menu_bar = self.menuBar()
+ self.menu_bar.setNativeMenuBar(False) # disable for macOS, see GH-239
- file_menu = menu_bar.addMenu(self.tr('&File'))
+ file_menu = self.menu_bar.addMenu(self.tr('&File'))
do_open_metadata = file_menu.addAction('&Open metadata')
do_open_metadata.setShortcut('Ctrl+O')
@@ -219,7 +238,7 @@ def create_menus(self):
self.do_launch.setShortcut('Return')
self.do_launch.triggered.connect(self.start_launch)
- options_menu = menu_bar.addMenu(self.tr('&Options'))
+ options_menu = self.menu_bar.addMenu(self.tr('&Options'))
do_toggle_lazy = options_menu.addAction('&Fast loading')
do_toggle_lazy.setStatusTip('Reduces load time and memory usage, disables expensive features like spike detection')
@@ -238,7 +257,7 @@ def create_menus(self):
do_view_global_config_file = options_menu.addAction('View global &config file')
do_view_global_config_file.triggered.connect(self.view_global_config_file)
- appearance_menu = menu_bar.addMenu(self.tr('&Appearance'))
+ appearance_menu = self.menu_bar.addMenu(self.tr('&Appearance'))
ui_scale_group = QT.QActionGroup(appearance_menu)
ui_scale_actions = {}
@@ -269,7 +288,7 @@ def create_menus(self):
do_toggle_support_increased_line_width.setChecked(self.support_increased_line_width)
do_toggle_support_increased_line_width.triggered.connect(self.toggle_support_increased_line_width)
- help_menu = menu_bar.addMenu(self.tr('&Help'))
+ help_menu = self.menu_bar.addMenu(self.tr('&Help'))
self.do_toggle_debug_logging = help_menu.addAction('Show and log &debug messages')
self.do_toggle_debug_logging.setCheckable(True)
@@ -287,8 +306,8 @@ def create_menus(self):
do_open_gdrive_creds_dir = help_menu.addAction('Open Google Drive credentials directory')
do_open_gdrive_creds_dir.triggered.connect(self.open_gdrive_creds_dir)
- do_authorize_gdrive = help_menu.addAction('Request Google Drive authorization now')
- do_authorize_gdrive.triggered.connect(self.authorize_gdrive)
+ self.do_authorize_gdrive = help_menu.addAction('Request Google Drive authorization now')
+ self.do_authorize_gdrive.triggered.connect(self.authorize_gdrive)
do_deauthorize_gdrive = help_menu.addAction('Purge Google Drive authorization token')
do_deauthorize_gdrive.triggered.connect(self.deauthorize_gdrive)
@@ -401,8 +420,7 @@ def start_launch(self):
Load data for the selected dataset in a separate thread.
"""
- self.do_launch.setText('&Launch in progress!')
- self.do_launch.setEnabled(False)
+ self.menu_bar.setEnabled(False)
self.metadata_selector.setEnabled(False)
self.stacked_layout.setCurrentIndex(1) # show loading label
self.load_dataset_thread.start()
@@ -451,8 +469,7 @@ def on_load_dataset_finished(self):
finally:
- self.do_launch.setText('&Launch')
- self.do_launch.setEnabled(True)
+ self.menu_bar.setEnabled(True)
self.metadata_selector.setEnabled(True)
self.stacked_layout.setCurrentIndex(0) # show metadata selector
@@ -540,26 +557,34 @@ def authorize_gdrive(self):
text = 'Completing this process will allow neurotic to ' \
'download files from your Google Drive. A web browser ' \
'will open so that you can log into your Google account ' \
- 'and accept the request for permissions. neurotic ' \
- 'will be unresponsive until this process finishes.' \
+ 'and accept the request for permissions. You will not be ' \
+ 'able to use neurotic until this process finishes.' \
'
Do you want to continue?'
button = QT.QMessageBox.question(self, title, text,
defaultButton=QT.QMessageBox.Yes)
if button == QT.QMessageBox.Yes:
- logger.info(f'Initiating Google Drive authorization flow')
- self.statusBar().showMessage('Check web browser to continue '
- 'authorization', msecs=5000)
- try:
- gdrive_downloader.authorize()
- except Exception as e:
- logger.error(f'Problem during authorization: {e}')
- self.statusBar().showMessage('ERROR: Authorization failed '
- '(see console for details)',
- msecs=5000)
- else:
- logger.info(f'Authorization complete')
- self.statusBar().showMessage('Authorization complete',
- msecs=5000)
+ self.menu_bar.setEnabled(False)
+ self.metadata_selector.setEnabled(False)
+ self.stacked_layout.setCurrentIndex(2) # show gdrive auth label
+ self.network_thread.start()
+ self.request_gdrive_authorization.emit()
+
+ def on_gdrive_authorization_finished(self, success):
+ """
+ Cleanup network thread and display success or failute of Google Drive
+ authorization flow.
+ """
+ self.network_thread.quit()
+ if success:
+ self.statusBar().showMessage('Authorization successful',
+ msecs=5000)
+ else:
+ self.statusBar().showMessage('ERROR: Authorization failed '
+ '(see console for details)',
+ msecs=5000)
+ self.menu_bar.setEnabled(True)
+ self.metadata_selector.setEnabled(True)
+ self.stacked_layout.setCurrentIndex(0) # show metadata selector
def deauthorize_gdrive(self):
"""
@@ -722,6 +747,10 @@ def set_ui_scale(self, size):
font.setPointSize(font_size[size]+4)
self.loading_label.setFont(font)
+ font = self.gdrive_auth_label.font()
+ font.setPointSize(font_size[size]+4)
+ self.gdrive_auth_label.setFont(font)
+
def set_theme(self, theme):
self.theme = theme
@@ -822,10 +851,11 @@ def load(self):
class _NetworkWorker(QT.QObject):
"""
- A thread worker for downloading data files and checking for updates.
+ A thread worker for for network activity (e.g., downloading data)
"""
download_finished = QT.pyqtSignal(bool)
+ gdrive_authorization_finished = QT.pyqtSignal(bool)
version_check_finished = QT.pyqtSignal(str, bool)
def __init__(self, mainwindow):
@@ -851,6 +881,18 @@ def download(self):
finally:
self.download_finished.emit(success)
+ def authorize_gdrive(self):
+ success = False
+ try:
+ logger.info('Initiating Google Drive authorization flow')
+ gdrive_downloader.authorize()
+ success = True
+ logger.info('Authorization successful')
+ except Exception as e:
+ logger.error(f'Problem during authorization: {e}')
+ finally:
+ self.gdrive_authorization_finished.emit(success)
+
def get_latest_release_number(self, show_new_only):
"""
Query GitHub for the version number of the latest release and emit a