Skip to content

Commit

Permalink
use deletion signal for SourceWidget
Browse files Browse the repository at this point in the history
  • Loading branch information
Allie Crevier committed Nov 23, 2021
1 parent 3818359 commit eb391f7
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 10 deletions.
60 changes: 50 additions & 10 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1174,7 +1174,7 @@ class SourceWidget(QWidget):
SOURCE_PREVIEW_CSS = load_css("source_preview.css")
SOURCE_TIMESTAMP_CSS = load_css("source_timestamp.css")

deleting = False
CONVERSATION_DELETED_TEXT = _("\u2014 All files and messages deleted for this source \u2014")

def __init__(
self,
Expand All @@ -1186,7 +1186,11 @@ def __init__(
super().__init__()

self.controller = controller
self.controller.sync_started.connect(self._on_sync_started)
self.controller.conversation_deleted.connect(self._on_conversation_deleted)
controller.conversation_deletion_successful.connect(
self._on_conversation_deletion_successful
)
self.controller.conversation_deletion_failed.connect(self._on_conversation_deletion_failed)
self.controller.source_deleted.connect(self._on_source_deleted)
self.controller.source_deletion_failed.connect(self._on_source_deletion_failed)
Expand All @@ -1199,6 +1203,11 @@ def __init__(
self.source_uuid = self.source.uuid
self.last_updated = self.source.last_updated
self.selected = False
self.deletion_scheduled_timestamp = datetime.utcnow()
self.sync_started_timestamp = datetime.utcnow()

self.deleting_conversation = False
self.deleting = False

self.setCursor(QCursor(Qt.PointingHandCursor))

Expand Down Expand Up @@ -1274,6 +1283,15 @@ def update(self) -> None:
"""
Updates the displayed values with the current values from self.source.
"""
# If the account or conversation is being deleted, do not update the source widget
if self.deleting or self.deleting_conversation:
return

# If the sync started before the deletion finished, then the sync is stale and we do
# not want to update the source widget.
if self.sync_started_timestamp < self.deletion_scheduled_timestamp:
return

try:
self.controller.session.refresh(self.source)
self.last_updated = self.source.last_updated
Expand All @@ -1295,6 +1313,12 @@ def update(self) -> None:

self.end_deletion()

if self.source.document_count == 0:
self.paperclip.hide()
self.paperclip_disabled.hide()

self.star.update(self.source.is_starred)

# When not authenticated we always show the source as having been seen
self.seen = True if not self.controller.is_authenticated else self.source.seen
self.update_styles()
Expand All @@ -1311,26 +1335,30 @@ def set_snippet(
if source_uuid != self.source_uuid:
return

if self.deleting:
content = ""
elif not self.source.server_collection:
if self.deleting or self.deleting_conversation:
return

if not self.source.server_collection:
if self.source.interaction_count > 0:
# The server only ever increases the interaction
# count, so if it's non-zero but the source collection
# is empty, we know the conversation has been deleted.
content = _("\u2014 All files and messages deleted for this source \u2014")
else:
content = ""
self.set_snippet_while_conversation_deletion_scheduled()
else:
last_activity = self.source.server_collection[-1]
if collection_uuid and collection_uuid != last_activity.uuid:
return

if not content:
content = str(last_activity)
self.preview.setProperty("class", "")
self.preview.setText(content) if content else self.preview.setText(str(last_activity))
self.preview.adjust_preview(self.width())
self.update_styles()

self.preview.setText(content)
def set_snippet_while_conversation_deletion_scheduled(self) -> None:
self.preview.setProperty("class", "conversation_deleted")
self.preview.setText(self.CONVERSATION_DELETED_TEXT)
self.preview.adjust_preview(self.width())
self.update_styles()

def update_styles(self) -> None:
if self.seen:
Expand Down Expand Up @@ -1383,11 +1411,22 @@ def _on_source_selected(self, selected_source_uuid: str) -> None:
self.selected = False
self.update_styles()

@pyqtSlot(datetime)
def _on_sync_started(self, timestamp: datetime) -> None:
self.sync_started_timestamp = timestamp

@pyqtSlot(str)
def _on_conversation_deleted(self, source_uuid: str) -> None:
if self.source_uuid == source_uuid:
self.start_conversation_deletion()

@pyqtSlot(str, datetime)
def _on_conversation_deletion_successful(self, source_uuid: str, timestamp: datetime) -> None:
if self.source_uuid == source_uuid:
self.deletion_scheduled_timestamp = timestamp
self.set_snippet_while_conversation_deletion_scheduled()
self.end_conversation_deletion()

@pyqtSlot(str)
def _on_conversation_deletion_failed(self, source_uuid: str) -> None:
if self.source_uuid == source_uuid:
Expand Down Expand Up @@ -1417,6 +1456,7 @@ def end_conversation_deletion(self) -> None:

def end_deletion(self) -> None:
self.deletion_indicator.stop()
self.update_styles()
self.preview.show()
self.timestamp.show()
self.paperclip_disabled.hide()
Expand Down
51 changes: 51 additions & 0 deletions tests/gui/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,23 @@ def test_SourceWidget__on_source_selected_skips_op_if_already_seen(mocker):
sw.update_styles.assert_called_once_with()


def test_SourceWidget__on_sync_started(mocker):
sw = SourceWidget(mocker.MagicMock(), factory.Source(), mocker.MagicMock(), mocker.MagicMock())
timestamp = datetime.now()
sw._on_sync_started(timestamp)
assert sw.sync_started_timestamp == timestamp


def test_SourceWidget__on_conversation_deletion_successful(mocker):
sw = SourceWidget(mocker.MagicMock(), factory.Source(), mocker.MagicMock(), mocker.MagicMock())
timestamp = datetime.now()
sw._on_conversation_deletion_successful(sw.source.uuid, timestamp)
assert sw.deletion_scheduled_timestamp == timestamp
assert sw.preview.text() == "\u2014 All files and messages deleted for this source \u2014"
assert sw.deleting_conversation is False
assert sw.deletion_indicator.isHidden()


def test_SourceWidget_update_attachment_icon(mocker):
"""
Attachment icon identicates document count
Expand Down Expand Up @@ -1843,6 +1860,40 @@ def test_SourceWidget_update_does_not_raise_exception(mocker):
assert mock_logger.debug.call_count == 1


def test_SourceWidget_update_skips_setting_snippet_if_deletion_in_progress(mocker):
"""
If the source is being deleted, do not update the snippet.
"""
sw = SourceWidget(mocker.MagicMock(), factory.Source(), mocker.MagicMock(), mocker.MagicMock())
sw.deleting = True
sw.set_snippet = mocker.MagicMock()
sw.update()
sw.set_snippet.assert_not_called()


def test_SourceWidget_update_skips_setting_snippet_if_sync_is_stale(mocker):
"""
If the sync started before the source was scheduled for deletion, do not update the snippet.
"""
sw = SourceWidget(mocker.MagicMock(), factory.Source(), mocker.MagicMock(), mocker.MagicMock())
sw.sync_started_timestamp = datetime.now()
sw.deletion_scheduled_timestamp = datetime.now()
sw.set_snippet = mocker.MagicMock()
sw.update()
sw.set_snippet.assert_not_called()


def test_SourceWidget_update_skips_setting_snippet_if_conversation_deletion_in_progress(mocker):
"""
If the source conversation is being deleted, do not update the snippet.
"""
sw = SourceWidget(mocker.MagicMock(), factory.Source(), mocker.MagicMock(), mocker.MagicMock())
sw.deleting_conversation = True
sw.set_snippet = mocker.MagicMock()
sw.update()
sw.set_snippet.assert_not_called()


def test_SourceWidget_set_snippet_draft_only(mocker, session_maker, session, homedir):
"""
Snippets/previews do not include draft messages.
Expand Down

0 comments on commit eb391f7

Please sign in to comment.