diff --git a/securedrop_client/api_jobs/uploads.py b/securedrop_client/api_jobs/uploads.py index 0654866aad..7d5c392c35 100644 --- a/securedrop_client/api_jobs/uploads.py +++ b/securedrop_client/api_jobs/uploads.py @@ -38,6 +38,8 @@ def call_api(self, api_client: API, session: Session) -> str: """ try: + import time + time.sleep(5) # If the reply has already made it to the server but we didn't get a 201 response back, # then a reply with self.reply_uuid will exist in the replies table. reply_db_object = session.query(Reply).filter_by(uuid=self.reply_uuid).one_or_none() diff --git a/securedrop_client/gui/widgets.py b/securedrop_client/gui/widgets.py index b46dedc192..6dae4540fd 100644 --- a/securedrop_client/gui/widgets.py +++ b/securedrop_client/gui/widgets.py @@ -2976,11 +2976,6 @@ def update_conversation(self, collection: list) -> None: passed into this method in case of a mismatch between where the widget has been and now is in terms of its index in the conversation. """ - # If the sync started before the deletion finished, then the sync is stale and we do - # not want to update the conversation. - if self.sync_started_timestamp < self.deletion_scheduled_timestamp: - return - self.controller.session.refresh(self.source) # Keep a temporary copy of the current conversation so we can delete any @@ -3116,7 +3111,7 @@ def add_reply(self, reply: Union[DraftReply, Reply], sender: User, index: int) - self.scroll.add_widget_to_conversation(index, conversation_item, Qt.AlignRight) self.current_messages[reply.uuid] = conversation_item - def on_reply_sent(self, source_uuid: str, reply_uuid: str, reply_text: str) -> None: + def on_draft_reply_saved(self, source_uuid: str) -> None: """ Add the reply text sent from ReplyBoxWidget to the conversation. """ @@ -3126,7 +3121,6 @@ def on_reply_sent(self, source_uuid: str, reply_uuid: str, reply_text: str) -> N self.update_conversation(self.source.collection) except sqlalchemy.exc.InvalidRequestError as e: logger.debug(e) - self.update_deletion_markers() class SourceConversationWrapper(QWidget): @@ -3175,7 +3169,7 @@ def __init__(self, source: Source, controller: Controller) -> None: layout.addWidget(self.reply_box) # Connect reply_box to conversation_view - self.reply_box.reply_sent.connect(self.conversation_view.on_reply_sent) + self.reply_box.draft_reply_saved.connect(self.conversation_view.on_draft_reply_saved) self.conversation_view.conversation_updated.connect(self.on_conversation_updated) @pyqtSlot(str) @@ -3268,7 +3262,7 @@ class ReplyBoxWidget(QWidget): A textbox where a journalist can enter a reply. """ - reply_sent = pyqtSignal(str, str, str) + draft_reply_saved = pyqtSignal(str) def __init__(self, source: Source, controller: Controller) -> None: super().__init__() @@ -3364,8 +3358,9 @@ def send_reply(self) -> None: self.text_edit.clearFocus() # Fixes #691 self.text_edit.setText("") reply_uuid = str(uuid4()) - self.controller.send_reply(self.source.uuid, reply_uuid, reply_text) - self.reply_sent.emit(self.source.uuid, reply_uuid, reply_text) + self.controller.add_draft_reply(self.source.uuid, reply_uuid, reply_text) + self.draft_reply_saved.emit(self.source.uuid) + self.controller.send_reply(reply_uuid) @pyqtSlot(bool) def _on_authentication_changed(self, authenticated: bool) -> None: diff --git a/securedrop_client/logic.py b/securedrop_client/logic.py index a764538ddc..5f824ba349 100644 --- a/securedrop_client/logic.py +++ b/securedrop_client/logic.py @@ -1081,18 +1081,10 @@ def delete_conversation(self, source: db.Source) -> None: self.add_job.emit(job) self.conversation_deleted.emit(source.uuid) - @login_required - def send_reply(self, source_uuid: str, reply_uuid: str, message: str) -> None: + def add_draft_reply(self, source_uuid: str, reply_uuid: str, message: str): """ - Send a reply to a source. + Add a draft reply to the database with a PENDING reply status. """ - # If the user account no longer exists, do not send - if not self.authenticated_user: - logger.error("Sender of reply {} has been deleted".format(reply_uuid)) - return - - # Before we send the reply, add the draft to the database with a PENDING - # reply send status. source = self.session.query(db.Source).filter_by(uuid=source_uuid).one() reply_status = ( self.session.query(db.ReplySendStatus) @@ -1111,7 +1103,27 @@ def send_reply(self, source_uuid: str, reply_uuid: str, message: str) -> None: self.session.add(draft_reply) self.session.commit() - job = SendReplyJob(source_uuid, reply_uuid, message, self.gpg) + @login_required + def send_reply(self, reply_uuid: str) -> None: + """ + Send a reply to a source. + """ + # If the user account no longer exists, do not send + if not self.authenticated_user: + logger.error("Sender of reply {} has been deleted".format(reply_uuid)) + return + + # You can only send a reply that has first been saved as a draft, so retrieve + # the draft. + draft_reply = self.session.query(db.DraftReply).filter_by(uuid=reply_uuid).one_or_none() + if not draft_reply: + return + + source = self.session.query(db.Source).filter_by(id=draft_reply.source_id).one_or_none() + if not source: + return + + job = SendReplyJob(source.uuid, draft_reply.uuid, draft_reply.content, self.gpg) job.success_signal.connect(self.on_reply_success, type=Qt.QueuedConnection) job.failure_signal.connect(self.on_reply_failure, type=Qt.QueuedConnection)