Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retry fixing blocked UI on client start. #944

Merged
merged 8 commits into from
Mar 25, 2020
115 changes: 69 additions & 46 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ def setup(self, controller):
"""
self.controller = controller
self.source_list.setup(controller)
self.source_list.delete_source_by_uuid.connect(self.delete_conversation)

def show_sources(self, sources: List[Source]):
"""
Expand All @@ -691,11 +692,7 @@ def show_sources(self, sources: List[Source]):
else:
self.empty_conversation_view.show_no_sources_message()
self.empty_conversation_view.show()

deleted_sources = self.source_list.update(sources)
for source_uuid in deleted_sources:
# Then call the function to remove the wrapper and its children.
self.delete_conversation(source_uuid)
self.source_list.update(sources)

def on_source_changed(self):
"""
Expand Down Expand Up @@ -729,8 +726,6 @@ def delete_conversation(self, source_uuid: str) -> None:
"""
try:
logger.debug('Deleting SourceConversationWrapper for {}'.format(source_uuid))
conversation_wrapper = self.source_conversations[source_uuid]
conversation_wrapper.deleteLater()
del self.source_conversations[source_uuid]
except KeyError:
logger.debug('No SourceConversationWrapper for {} to delete'.format(source_uuid))
Expand Down Expand Up @@ -886,6 +881,8 @@ class SourceList(QListWidget):
}
'''

delete_source_by_uuid = pyqtSignal(str)
redshiftzero marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self):
super().__init__()

Expand All @@ -910,50 +907,76 @@ def setup(self, controller):
self.controller.file_ready.connect(self.set_snippet)
self.controller.file_missing.connect(self.set_snippet)

def update(self, sources: List[Source]) -> List[str]:
"""
Update the list with the passed in list of sources.
"""
# Delete widgets that no longer exist in source list
source_uuids = [source.uuid for source in sources]
deleted_uuids = []
for i in range(self.count()):
list_item = self.item(i)
list_widget = self.itemWidget(list_item)

if list_widget and list_widget.source_uuid not in source_uuids:
if list_item.isSelected():
self.setCurrentItem(None)
del self.source_widgets[list_widget.source_uuid]
deleted_uuids.append(list_widget.source_uuid)
self.takeItem(i)
list_widget.deleteLater()

# Create new widgets for new sources
widget_uuids = [self.itemWidget(self.item(i)).source_uuid for i in range(self.count())]
for source in sources:
if source.uuid in widget_uuids:
try:
self.source_widgets[source.uuid].update()
except sqlalchemy.exc.InvalidRequestError as e:
logger.error(
"Could not update SourceWidget for source %s; deleting it. Error was: %s",
source.uuid,
e
)
deleted_uuids.append(source.uuid)
self.source_widgets[source.uuid].deleteLater()
del self.source_widgets[list_widget.source_uuid]
else:
def update(self, sources: List[Source]):
"""
Reset and update the list with the passed in list of sources.
"""
# A flag to show if a source is currently selected (the current source
# doesn't have to be selected in a QListWidget -- it appears to default
# to whatever is in row 0, whether it's selected or not).
has_source_selected = False
if self.currentRow() > -1:
has_source_selected = self.item(self.currentRow()).isSelected()
current_source = self.get_current_source()
current_source_id = current_source and current_source.id
# Make a copy of the source_widgets that are currently displayed
# so that we can compare which widgets were removed. This means that
# the source was deleted, and we'll return this so we can also
# delete the corresponding SourceConversationWrapper.
existing_source_widgets = self.source_widgets.copy()

# When we call clear() to delete all SourceWidgets, we should
# also clear the source_widgets dict.
self.clear()
self.source_widgets = {}
sources.reverse()
redshiftzero marked this conversation as resolved.
Show resolved Hide resolved
self.add_source(existing_source_widgets, sources, current_source_id,
has_source_selected)

def add_source(self, existing_source_widgets, sources, current_source_id,
sssoleileraaa marked this conversation as resolved.
Show resolved Hide resolved
has_source_selected, slice_size=1):
"""
Add a slice of sources, ensure the currently selected source is
retained and, if necessary, reschedule the addition of more sources.
If no more sources are left, work out the deleted sources and emit
deletion signal for each source to delete.
"""

def schedule_source_management(slice_size=slice_size):
if not sources:
# No more sources to add, discover the deleted sources and
# delete them.
deleted_uuids = list(set(existing_source_widgets.keys()) -
set(self.source_widgets.keys()))
for source_uuid in deleted_uuids:
self.delete_source_by_uuid.emit(source_uuid)
# Nothing more to do.
return
# Process the remaining "slice_size" number of sources.
sources_slice = sources[:slice_size]
for source in sources_slice:
new_source = SourceWidget(self.controller, source)
self.source_widgets[source.uuid] = new_source

list_item = QListWidgetItem()
self.insertItem(0, list_item)
list_item = QListWidgetItem(self)
list_item.setSizeHint(new_source.sizeHint())

self.addItem(list_item)
self.setItemWidget(list_item, new_source)

return deleted_uuids
if source.id == current_source_id and has_source_selected:
self.setCurrentItem(list_item)
# ATTENTION! 32 is an arbitrary number arrived at via
# experimentation. It adds plenty of sources, but doesn't block
# for a noticable amount of time.
new_slice_size = min(32, slice_size * 2)
# Call add_source again for the remaining sources.
self.add_source(existing_source_widgets, sources[slice_size:],
current_source_id, has_source_selected,
new_slice_size)

# Schedule the closure defined above in the next iteration of the
# Qt event loop (thus unblocking the UI).
QTimer.singleShot(1, schedule_source_management)

def get_current_source(self):
source_item = self.currentItem()
Expand Down
Loading