From e21084ea3fbbe14b86ecbc3a653f19171f1c8cca Mon Sep 17 00:00:00 2001 From: Allie Crevier Date: Fri, 24 May 2019 13:24:07 -0700 Subject: [PATCH] move sqlalchemy session management out of gui --- securedrop_client/app.py | 2 +- securedrop_client/gui/main.py | 5 ++- securedrop_client/gui/widgets.py | 54 +++++++++++--------------------- securedrop_client/logic.py | 9 ++++-- 4 files changed, 29 insertions(+), 41 deletions(-) diff --git a/securedrop_client/app.py b/securedrop_client/app.py index fbf9a7d47..098bc18cf 100644 --- a/securedrop_client/app.py +++ b/securedrop_client/app.py @@ -186,7 +186,7 @@ def start_app(args, qt_args) -> None: session_maker = make_session_maker(args.sdc_home) - gui = Window(session_maker) + gui = Window() app.setWindowIcon(load_icon(gui.icon)) app.setStyleSheet(load_css('sdclient.css')) diff --git a/securedrop_client/gui/main.py b/securedrop_client/gui/main.py index f02b63284..37426d29a 100644 --- a/securedrop_client/gui/main.py +++ b/securedrop_client/gui/main.py @@ -25,7 +25,6 @@ from typing import Dict, List, Optional # noqa: F401 from PyQt5.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, QDesktopWidget, \ QApplication -from sqlalchemy.orm import scoped_session from securedrop_client import __version__ from securedrop_client.db import Source @@ -44,7 +43,7 @@ class Window(QMainWindow): icon = 'icon.png' - def __init__(self, session_maker: scoped_session) -> None: + def __init__(self) -> None: """ Create the default start state. The window contains a root widget into which is placed: @@ -69,7 +68,7 @@ def __init__(self, session_maker: scoped_session) -> None: layout.setSpacing(0) self.main_pane.setLayout(layout) self.left_pane = LeftPane() - self.main_view = MainView(session_maker, self.main_pane) + self.main_view = MainView(self.main_pane) layout.addWidget(self.left_pane, 1) layout.addWidget(self.main_view, 8) diff --git a/securedrop_client/gui/widgets.py b/securedrop_client/gui/widgets.py index 1eb23c169..459056e9a 100644 --- a/securedrop_client/gui/widgets.py +++ b/securedrop_client/gui/widgets.py @@ -29,7 +29,6 @@ from PyQt5.QtWidgets import QListWidget, QLabel, QWidget, QListWidgetItem, QHBoxLayout, \ QPushButton, QVBoxLayout, QLineEdit, QScrollArea, QDialog, QAction, QMenu, QMessageBox, \ QToolButton, QSizePolicy, QTextEdit, QStatusBar, QGraphicsDropShadowEffect -from sqlalchemy.orm import scoped_session from securedrop_client.db import Source, Message, File, Reply from securedrop_client.storage import source_exists @@ -588,9 +587,8 @@ class MainView(QWidget): } ''' - def __init__(self, session_maker: scoped_session, parent: QObject): + def __init__(self, parent: QObject): super().__init__(parent) - self.session_maker = session_maker self.setStyleSheet(self.CSS) @@ -633,11 +631,7 @@ def on_source_changed(self): source = self.source_list.get_current_source() if source: - conversation_wrapper = SourceConversationWrapper( - self.session_maker, - source, - self.controller, - ) + conversation_wrapper = SourceConversationWrapper(source, self.controller) self.set_conversation(conversation_wrapper) else: self.clear_conversation() @@ -1282,7 +1276,6 @@ class FileWidget(QWidget): def __init__( self, - session_maker: scoped_session, file_uuid: str, controller: Controller, file_ready_signal: pyqtBoundSignal, @@ -1291,35 +1284,23 @@ def __init__( Given some text and a reference to the controller, make something to display a file. """ super().__init__() - self.session_maker = session_maker self.controller = controller - self.file_uuid = file_uuid - self.file_is_downloaded = False # default to `False`, value updated in `update()` + self.file = self.controller.get_file(file_uuid) self.layout = QHBoxLayout() self.update() self.setLayout(self.layout) - file_ready_signal.connect(self._on_file_download, type=Qt.QueuedConnection) + file_ready_signal.connect(self._on_file_downloaded, type=Qt.QueuedConnection) def update(self) -> None: icon = QLabel() icon.setPixmap(load_image('file.png')) - session = self.session_maker() - - # we have to query to get the object we want - file_ = session.query(File).filter_by(uuid=self.file_uuid).one() - # and then force a refresh because SQLAlchemy might have a copy of this object - # in this thread already that isn't up to date - session.refresh(file_) - - self.file_is_downloaded = file_.is_downloaded - - if self.file_is_downloaded: + if self.file.is_downloaded: description = QLabel("Open") else: - human_filesize = humanize_filesize(file_.size) + human_filesize = humanize_filesize(self.file.size) description = QLabel("Download ({})".format(human_filesize)) self.layout.addWidget(icon) @@ -1333,8 +1314,12 @@ def clear(self) -> None: child.widget().deleteLater() @pyqtSlot(str) - def _on_file_download(self, file_uuid: str) -> None: - if file_uuid == self.file_uuid: + def _on_file_downloaded(self, file_uuid: str) -> None: + if file_uuid == self.file.uuid: + # update state + self.file = self.controller.get_file(self.file.uuid) + + # update gui self.clear() # delete existing icon and label self.update() # draw modified widget @@ -1343,12 +1328,15 @@ def mouseReleaseEvent(self, e): Handle a completed click via the program logic. The download state of the file distinguishes which function in the logic layer to call. """ - if self.file_is_downloaded: + # update state + self.file = self.controller.get_file(self.file.uuid) + + if self.file.is_downloaded: # Open the already downloaded file. - self.controller.on_file_open(self.file_uuid) + self.controller.on_file_open(self.file.uuid) else: # Download the file. - self.controller.on_submission_download(File, self.file_uuid) + self.controller.on_submission_download(File, self.file.uuid) class ConversationView(QWidget): @@ -1362,12 +1350,10 @@ class ConversationView(QWidget): def __init__( self, - session_maker: scoped_session, source_db_object: Source, controller: Controller, ): super().__init__() - self.session_maker = session_maker self.source = source_db_object self.controller = controller @@ -1419,7 +1405,6 @@ def add_file(self, source_db_object, submission_db_object): """ self.conversation_layout.addWidget( FileWidget( - self.session_maker, submission_db_object.uuid, self.controller, self.controller.file_ready, @@ -1494,7 +1479,6 @@ class SourceConversationWrapper(QWidget): def __init__( self, - session_maker: scoped_session, source: Source, controller: Controller, ) -> None: @@ -1504,7 +1488,7 @@ def __init__( self.setLayout(layout) self.conversation_title_bar = SourceProfileShortWidget(source, controller) - self.conversation_view = ConversationView(session_maker, source, controller) + self.conversation_view = ConversationView(source, controller) self.reply_box = ReplyBoxWidget(source, controller) layout.addWidget(self.conversation_title_bar, 1) diff --git a/securedrop_client/logic.py b/securedrop_client/logic.py index 704fcf38e..b797edf4f 100644 --- a/securedrop_client/logic.py +++ b/securedrop_client/logic.py @@ -501,8 +501,8 @@ def on_file_open(self, file_db_object): # Once downloaded, submissions are stored in the data directory # with the same filename as the server, except with the .gz.gpg # stripped off. - server_filename = file_db_object.filename - fn_no_ext, _ = os.path.splitext(os.path.splitext(server_filename)[0]) + file = self.get_file(file_uuid) + fn_no_ext, _ = os.path.splitext(os.path.splitext(file.filename)[0]) submission_filepath = os.path.join(self.data_dir, fn_no_ext) if self.proxy: @@ -641,3 +641,8 @@ def on_reply_success(self, result, current_object: Tuple[str, str]) -> None: def on_reply_failure(self, result, current_object: Tuple[str, str]) -> None: source_uuid, reply_uuid = current_object self.reply_failed.emit(reply_uuid) + + def get_file(self, file_uuid: str) -> db.File: + file = storage.get_file(self.session, file_uuid) + self.session.refresh(file) + return file