Skip to content

Commit

Permalink
Merge pull request #216 from freedomofpress/no-flicker
Browse files Browse the repository at this point in the history
Prevent UI flicker on conversation switch / update
  • Loading branch information
redshiftzero authored Jan 10, 2019
2 parents a5f0ae2 + a1ad28b commit 76b86e0
Show file tree
Hide file tree
Showing 14 changed files with 591 additions and 243 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ SHELL := /bin/bash

.PHONY: clean
clean: ## Clean the workspace of generated resources
@rm -rf build dist *.egg-info .coverage .eggs docs/_build .pytest_cache lib htmlcov && \
@rm -rf build dist *.egg-info .coverage .eggs docs/_build .pytest_cache lib htmlcov .cache && \
find . \( -name '*.py[co]' -o -name dropin.cache \) -delete && \
find . \( -name '*.bak' -o -name dropin.cache \) -delete && \
find . \( -name '*.tgz' -o -name dropin.cache \) -delete && \
find . -name __pycache__ -print0 | xargs rm -rf
find . -name __pycache__ -print0 | xargs -0 rm -rf

TESTS ?= tests
TESTOPTS ?= -v
Expand All @@ -34,7 +34,7 @@ check: clean lint test ## Run the full CI test suite
# 6. Format columns with colon as delimiter.
.PHONY: help
help: ## Print this message and exit.
@printf "Makefile for developing and testing SecureDrop.\n"
@printf "Makefile for developing and testing the SecureDrop client.\n"
@printf "Subcommands:\n\n"
@awk 'BEGIN {FS = ":.*?## "} /^[0-9a-zA-Z_-]+:.*?## / {printf "\033[36m%s\033[0m : %s\n", $$1, $$2}' $(MAKEFILE_LIST) \
| sort \
Expand Down
6 changes: 4 additions & 2 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ chmod 0700 "$SDC_HOME" "$GPG_HOME"
echo "Running app with home directory: $SDC_HOME"
echo ""

gpg --homedir "$GPG_HOME" --allow-secret-key-import --import tests/files/securedrop.gpg.asc
gpg --homedir "$GPG_HOME" --allow-secret-key-import --import tests/files/securedrop.gpg.asc &

# create the database and config for local testing
./create_dev_data.py "$SDC_HOME"
./create_dev_data.py "$SDC_HOME" &

wait

exec python -m securedrop_client --sdc-home "$SDC_HOME" --no-proxy $@
3 changes: 3 additions & 0 deletions securedrop_client/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,19 @@ def configure_logging(sdc_home: str) -> None:
log_fmt = ('%(asctime)s - %(name)s:%(lineno)d(%(funcName)s) '
'%(levelname)s: %(message)s')
formatter = logging.Formatter(log_fmt)

# define log handlers such as for rotating log files
handler = TimedRotatingFileHandler(log_file, when='midnight',
backupCount=5, delay=0,
encoding=ENCODING)
handler.setFormatter(formatter)
handler.setLevel(logging.DEBUG)

# set up primary log
log = logging.getLogger()
log.setLevel(logging.DEBUG)
log.addHandler(handler)

# override excepthook to capture a log of catastrophic failures.
sys.excepthook = excepthook

Expand Down
8 changes: 8 additions & 0 deletions securedrop_client/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ def make_engine(home: str):


class Source(Base):

__tablename__ = 'sources'

# TODO - add number_of_docs
id = Column(Integer, primary_key=True)
uuid = Column(String(36), unique=True, nullable=False)
Expand Down Expand Up @@ -66,7 +68,9 @@ def collection(self):


class Submission(Base):

__tablename__ = 'submissions'

id = Column(Integer, primary_key=True)
uuid = Column(String(36), unique=True, nullable=False)
filename = Column(String(255), nullable=False)
Expand Down Expand Up @@ -101,7 +105,9 @@ def __repr__(self):


class Reply(Base):

__tablename__ = 'replies'

id = Column(Integer, primary_key=True)
uuid = Column(String(36), unique=True, nullable=False)
source_id = Column(Integer, ForeignKey('sources.id'))
Expand Down Expand Up @@ -133,7 +139,9 @@ def __repr__(self):


class User(Base):

__tablename__ = 'users'

id = Column(Integer, primary_key=True)
uuid = Column(String(36), unique=True, nullable=False)
username = Column(String(255), nullable=False, unique=True)
Expand Down
68 changes: 26 additions & 42 deletions securedrop_client/gui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QDesktopWidget, QStatusBar
from securedrop_client import __version__
from securedrop_client.gui.widgets import (ToolBar, MainView, LoginDialog,
ConversationView,
SourceProfileShortWidget)
SourceConversationWrapper)
from securedrop_client.resources import load_icon
from securedrop_client.storage import get_data

logger = logging.getLogger(__name__)

Expand All @@ -53,19 +51,29 @@ def __init__(self, sdc_home: str):
self.sdc_home = sdc_home
self.setWindowTitle(_("SecureDrop Client {}").format(__version__))
self.setWindowIcon(load_icon(self.icon))

self.widget = QWidget()
widget_layout = QVBoxLayout()
self.widget.setLayout(widget_layout)
self.setCentralWidget(self.widget)

self.tool_bar = ToolBar(self.widget)

self.main_view = MainView(self.widget)
self.main_view.source_list.itemSelectionChanged.connect(self.on_source_changed)

widget_layout.addWidget(self.tool_bar, 1)
widget_layout.addWidget(self.main_view, 6)
self.setCentralWidget(self.widget)
self.current_source = None # Tracks which source is shown

# Cache a dict of source.uuid -> SourceConversationWrapper
# We do this to not create/destroy widgets constantly (because it causes UI "flicker")
self.conversations = {}
self.show()

# Tracks which source is shown
self.current_source = None

self.autosize_window()
self.show()

def setup(self, controller):
"""
Expand All @@ -74,9 +82,11 @@ def setup(self, controller):
"""
self.controller = controller # Reference the Client logic instance.
self.tool_bar.setup(self, controller)

self.status_bar = QStatusBar(self)
self.setStatusBar(self.status_bar)
self.set_status('Started SecureDrop Client. Please sign in.', 20000)

self.login_dialog = LoginDialog(self)
self.main_view.setup(self.controller)

Expand Down Expand Up @@ -156,47 +166,21 @@ def on_source_changed(self):
self.current_source = source_widget.source
self.show_conversation_for(self.current_source)

def add_item_content_or(self, adder, item, default):
"""
Private helper function to add correct message to conversation widgets
"""
if item.is_downloaded is False:
adder(default)
else:
adder(get_data(self.sdc_home, item.filename))

def show_conversation_for(self, source):
"""
Show conversation of messages and replies between a source and
journalists.
"""
conversation = ConversationView(self)
conversation.setup(self.controller)
conversation.add_message('Source name: {}'.format(
source.journalist_designation))

# Display each conversation item in the source collection.
for conversation_item in source.collection:

if conversation_item.filename.endswith('msg.gpg'):
self.add_item_content_or(conversation.add_message,
conversation_item,
"<Message not yet downloaded>")
elif conversation_item.filename.endswith('reply.gpg'):
self.add_item_content_or(conversation.add_reply,
conversation_item,
"<Reply not yet downloaded>")
else:
conversation.add_file(source, conversation_item)

container = QWidget()
layout = QVBoxLayout()
container.setLayout(layout)
source_profile = SourceProfileShortWidget(source, self.controller)

layout.addWidget(source_profile)
layout.addWidget(conversation)
self.main_view.update_view(container)

conversation_container = self.conversations.get(source.uuid, None)

if conversation_container is None:
conversation_container = SourceConversationWrapper(source,
self.sdc_home,
self.controller)
self.conversations[source.uuid] = conversation_container

self.main_view.set_conversation(conversation_container)

def set_status(self, message, duration=5000):
"""
Expand Down
Loading

0 comments on commit 76b86e0

Please sign in to comment.