From 17966471aba795cd3ac2ce8004417ab51c50b942 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Fri, 30 Apr 2021 17:16:02 -0700 Subject: [PATCH] GUI displays "Read Message" button when a receive mode submission includes a message --- cli/onionshare_cli/web/receive_mode.py | 88 +++++++++++------- cli/onionshare_cli/web/web.py | 21 +++-- desktop/src/onionshare/gui_common.py | 4 + .../resources/images/open_message.png | Bin 0 -> 5403 bytes .../src/onionshare/resources/locale/en.json | 3 +- desktop/src/onionshare/tab/mode/__init__.py | 6 ++ desktop/src/onionshare/tab/mode/history.py | 88 ++++++++++++++++-- .../tab/mode/receive_mode/__init__.py | 26 ++++-- desktop/src/onionshare/tab/tab.py | 3 + 9 files changed, 177 insertions(+), 62 deletions(-) create mode 100644 desktop/src/onionshare/resources/images/open_message.png diff --git a/cli/onionshare_cli/web/receive_mode.py b/cli/onionshare_cli/web/receive_mode.py index bdbe0a5f..a12f5456 100644 --- a/cli/onionshare_cli/web/receive_mode.py +++ b/cli/onionshare_cli/web/receive_mode.py @@ -99,7 +99,7 @@ class ReceiveModeWeb: Handle the upload files POST request, though at this point, the files have already been uploaded and saved to their correct locations. """ - text_received = request.includes_text + message_received = request.includes_message files_received = 0 if not self.web.settings.get("receive", "disable_files"): @@ -137,7 +137,7 @@ class ReceiveModeWeb: if ( self.web.settings.get("receive", "webhook_url") is not None and not request.upload_error - and (text_received or files_received) + and (message_received or files_received) ): msg = "" if files_received > 0: @@ -145,7 +145,7 @@ class ReceiveModeWeb: msg += "1 file" else: msg += f"{files_received} files" - if text_received: + if message_received: if msg == "": msg = "A text message" else: @@ -184,7 +184,7 @@ class ReceiveModeWeb: files_msg += f"{filename}, " files_msg = files_msg.rstrip(", ") - if text_received: + if message_received: if files_received > 0: msg = f"Message submitted, uploaded {files_msg}" else: @@ -358,6 +358,7 @@ class ReceiveModeRequest(Request): super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow) self.web = environ["web"] self.stop_q = environ["stop_q"] + self.filename = None # Prevent running the close() method more than once self.closed = False @@ -458,12 +459,13 @@ class ReceiveModeRequest(Request): self.previous_file = None # Is there a text message? - self.includes_text = False + self.includes_message = False if not self.web.settings.get("receive", "disable_text"): text_message = self.form.get("text") if text_message: if text_message.strip() != "": - self.includes_text = True + self.includes_message = True + with open(self.message_filename, "w") as f: f.write(text_message) @@ -472,16 +474,30 @@ class ReceiveModeRequest(Request): "__init__", f"saved message to {self.message_filename}", ) - print(f"\nReceived: {self.message_filename}") + print(f"Received: {self.message_filename}") + # Tell the GUI about the message self.tell_gui_request_started() + self.web.common.log( + "ReceiveModeRequest", + "__init__", + "sending REQUEST_UPLOAD_INCLUDES_MESSAGE to GUI", + ) + self.web.add_request( + self.web.REQUEST_UPLOAD_INCLUDES_MESSAGE, + self.path, + { + "id": self.history_id, + "filename": self.message_filename, + }, + ) def tell_gui_request_started(self): # Tell the GUI about the request if not self.told_gui_about_request: self.web.common.log( "ReceiveModeRequest", - "_get_file_stream", + "tell_gui_request_started", "sending REQUEST_STARTED to GUI", ) self.web.add_request( @@ -490,8 +506,6 @@ class ReceiveModeRequest(Request): { "id": self.history_id, "content_length": self.content_length, - "includes_text": self.includes_text, - "message_filename": self.message_filename, }, ) self.web.receive_mode.uploads_in_progress.append(self.history_id) @@ -536,31 +550,37 @@ class ReceiveModeRequest(Request): if self.upload_request: self.web.common.log("ReceiveModeRequest", "close") - try: - if self.told_gui_about_request: - history_id = self.history_id + if self.told_gui_about_request: + history_id = self.history_id - if ( - not self.web.stop_q.empty() - or not self.progress[self.filename]["complete"] - ): - # Inform the GUI that the upload has canceled - self.web.add_request( - self.web.REQUEST_UPLOAD_CANCELED, - self.path, - {"id": history_id}, - ) - else: - # Inform the GUI that the upload has finished - self.web.add_request( - self.web.REQUEST_UPLOAD_FINISHED, - self.path, - {"id": history_id}, - ) - self.web.receive_mode.uploads_in_progress.remove(history_id) - - except AttributeError: - pass + if not self.web.stop_q.empty() or ( + self.filename in self.progress + and not self.progress[self.filename]["complete"] + ): + # Inform the GUI that the upload has canceled + self.web.common.log( + "ReceiveModeRequest", + "close", + "sending REQUEST_UPLOAD_CANCELED to GUI", + ) + self.web.add_request( + self.web.REQUEST_UPLOAD_CANCELED, + self.path, + {"id": history_id}, + ) + else: + # Inform the GUI that the upload has finished + self.web.common.log( + "ReceiveModeRequest", + "close", + "sending REQUEST_UPLOAD_FINISHED to GUI", + ) + self.web.add_request( + self.web.REQUEST_UPLOAD_FINISHED, + self.path, + {"id": history_id}, + ) + self.web.receive_mode.uploads_in_progress.remove(history_id) # If no files were written to self.receive_mode_dir, delete it if len(os.listdir(self.receive_mode_dir)) == 0: diff --git a/cli/onionshare_cli/web/web.py b/cli/onionshare_cli/web/web.py index e64943e6..da15c23b 100644 --- a/cli/onionshare_cli/web/web.py +++ b/cli/onionshare_cli/web/web.py @@ -64,16 +64,17 @@ class Web: REQUEST_PROGRESS = 2 REQUEST_CANCELED = 3 REQUEST_RATE_LIMIT = 4 - REQUEST_UPLOAD_FILE_RENAMED = 5 - REQUEST_UPLOAD_SET_DIR = 6 - REQUEST_UPLOAD_FINISHED = 7 - REQUEST_UPLOAD_CANCELED = 8 - REQUEST_INDIVIDUAL_FILE_STARTED = 9 - REQUEST_INDIVIDUAL_FILE_PROGRESS = 10 - REQUEST_INDIVIDUAL_FILE_CANCELED = 11 - REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 12 - REQUEST_OTHER = 13 - REQUEST_INVALID_PASSWORD = 14 + REQUEST_UPLOAD_INCLUDES_MESSAGE = 5 + REQUEST_UPLOAD_FILE_RENAMED = 6 + REQUEST_UPLOAD_SET_DIR = 7 + REQUEST_UPLOAD_FINISHED = 8 + REQUEST_UPLOAD_CANCELED = 9 + REQUEST_INDIVIDUAL_FILE_STARTED = 10 + REQUEST_INDIVIDUAL_FILE_PROGRESS = 11 + REQUEST_INDIVIDUAL_FILE_CANCELED = 12 + REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 13 + REQUEST_OTHER = 14 + REQUEST_INVALID_PASSWORD = 15 def __init__(self, common, is_gui, mode_settings, mode="share"): self.common = common diff --git a/desktop/src/onionshare/gui_common.py b/desktop/src/onionshare/gui_common.py index 7703f7a8..1a44a128 100644 --- a/desktop/src/onionshare/gui_common.py +++ b/desktop/src/onionshare/gui_common.py @@ -353,6 +353,10 @@ class GuiCommon: color: #666666; font-size: 11px; }""", + "receive_message_button": """ + QPushButton { + padding: 5px 10px; + }""", # Settings dialog "settings_version": """ QLabel { diff --git a/desktop/src/onionshare/resources/images/open_message.png b/desktop/src/onionshare/resources/images/open_message.png new file mode 100644 index 0000000000000000000000000000000000000000..6712ecf9bd26590fc6004acbd8d31b2bc60b4048 GIT binary patch literal 5403 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 literal 0 HcmV?d00001 diff --git a/desktop/src/onionshare/resources/locale/en.json b/desktop/src/onionshare/resources/locale/en.json index cbd898dc..9a6240a3 100644 --- a/desktop/src/onionshare/resources/locale/en.json +++ b/desktop/src/onionshare/resources/locale/en.json @@ -195,5 +195,6 @@ "settings_error_bundled_tor_broken": "OnionShare could not connect to Tor:\n{}", "gui_rendezvous_cleanup": "Waiting for Tor circuits to close to be sure your files have successfully transferred.\n\nThis might take a few minutes.", "gui_rendezvous_cleanup_quit_early": "Quit Early", - "error_port_not_available": "OnionShare port not available" + "error_port_not_available": "OnionShare port not available", + "history_receive_read_message_button": "Read Message" } \ No newline at end of file diff --git a/desktop/src/onionshare/tab/mode/__init__.py b/desktop/src/onionshare/tab/mode/__init__.py index fe7d6615..567dc123 100644 --- a/desktop/src/onionshare/tab/mode/__init__.py +++ b/desktop/src/onionshare/tab/mode/__init__.py @@ -447,6 +447,12 @@ class Mode(QtWidgets.QWidget): """ pass + def handle_request_upload_includes_message(self, event): + """ + Handle REQUEST_UPLOAD_INCLUDES_MESSAGE event. + """ + pass + def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. diff --git a/desktop/src/onionshare/tab/mode/history.py b/desktop/src/onionshare/tab/mode/history.py index ffd96248..386d2e2e 100644 --- a/desktop/src/onionshare/tab/mode/history.py +++ b/desktop/src/onionshare/tab/mode/history.py @@ -268,6 +268,66 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): subprocess.Popen(["explorer", f"/select,{abs_filename}"]) +class ReceiveHistoryItemMessage(QtWidgets.QWidget): + def __init__( + self, + common, + ): + super(ReceiveHistoryItemMessage, self).__init__() + self.common = common + self.filename = None + + # Read message button + message_pixmap = QtGui.QPixmap.fromImage( + QtGui.QImage(GuiCommon.get_resource_path("images/open_message.png")) + ) + message_icon = QtGui.QIcon(message_pixmap) + self.message_button = QtWidgets.QPushButton( + strings._("history_receive_read_message_button") + ) + self.message_button.setStyleSheet(self.common.gui.css["receive_message_button"]) + self.message_button.clicked.connect(self.open_message) + self.message_button.setIcon(message_icon) + self.message_button.setIconSize(message_pixmap.rect().size()) + + # Layouts + layout = QtWidgets.QHBoxLayout() + layout.addWidget(self.message_button) + layout.addStretch() + self.setLayout(layout) + + self.hide() + + def set_filename(self, new_filename): + self.filename = new_filename + self.show() + + def open_message(self): + """ + Open the message in the operating system's default text editor + """ + self.common.log("ReceiveHistoryItemMessage", "open_message", self.filename) + + # # Linux + # if self.common.platform == "Linux" or self.common.platform == "BSD": + # try: + # # If nautilus is available, open it + # subprocess.Popen(["xdg-open", self.dir]) + # except Exception: + # Alert( + # self.common, + # strings._("gui_open_folder_error").format(abs_filename), + # ) + + # # macOS + # elif self.common.platform == "Darwin": + # subprocess.call(["open", "-R", abs_filename]) + + # # Windows + # elif self.common.platform == "Windows": + # subprocess.Popen(["explorer", f"/select,{abs_filename}"]) + + class ReceiveHistoryItem(HistoryItem): def __init__(self, common, id, content_length): super(ReceiveHistoryItem, self).__init__() @@ -301,6 +361,9 @@ class ReceiveHistoryItem(HistoryItem): self.common.gui.css["downloads_uploads_progress_bar"] ) + # The message widget, if a message was included + self.message = ReceiveHistoryItemMessage(self.common) + # This layout contains file widgets self.files_layout = QtWidgets.QVBoxLayout() self.files_layout.setContentsMargins(0, 0, 0, 0) @@ -311,6 +374,7 @@ class ReceiveHistoryItem(HistoryItem): # Layout layout = QtWidgets.QVBoxLayout() layout.addWidget(self.label) + layout.addWidget(self.message) layout.addWidget(self.progress_bar) layout.addWidget(files_widget) layout.addStretch() @@ -319,17 +383,14 @@ class ReceiveHistoryItem(HistoryItem): # We're also making a dictionary of file widgets, to make them easier to access self.files = {} + def includes_message(self, message_filename): + self.message.set_filename(message_filename) + def update(self, data): """ Using the progress from Web, update the progress bar and file size labels for each file """ - # self.common.log( - # "ReceiveHistoryItem", - # "update", - # f"id={self.id} data[action]={data['action']} files={list(self.files)}", - # ) - if data["action"] == "progress": total_uploaded_bytes = 0 for filename in data["progress"]: @@ -572,6 +633,13 @@ class HistoryItemList(QtWidgets.QScrollArea): if id in self.items: self.items[id].cancel() + def includes_message(self, id, message_filename): + """ + Show message button for receive mode + """ + if id in self.items: + self.items[id].includes_message(message_filename) + def reset(self): """ Reset all items, emptying the list. Override this method. @@ -665,7 +733,7 @@ class History(QtWidgets.QWidget): """ Add a new item. """ - self.common.log("History", "add", f"id: {id}, item: {item}") + self.common.log("History", "add", f"id: {id}") # Hide empty, show not empty self.empty.hide() @@ -686,6 +754,12 @@ class History(QtWidgets.QWidget): """ self.item_list.cancel(id) + def includes_message(self, id, message_filename): + """ + Show the message button + """ + self.item_list.includes_message(id, message_filename) + def reset(self): """ Reset all items. diff --git a/desktop/src/onionshare/tab/mode/receive_mode/__init__.py b/desktop/src/onionshare/tab/mode/receive_mode/__init__.py index 6056b1db..4dd2980c 100644 --- a/desktop/src/onionshare/tab/mode/receive_mode/__init__.py +++ b/desktop/src/onionshare/tab/mode/receive_mode/__init__.py @@ -78,27 +78,24 @@ class ReceiveMode(Mode): data_dir_layout.addWidget(data_dir_button) self.mode_settings_widget.mode_specific_layout.addLayout(data_dir_layout) - # Disable text + # Disable text or files self.disable_text_checkbox = self.settings.get("receive", "disable_files") self.disable_text_checkbox = QtWidgets.QCheckBox() self.disable_text_checkbox.clicked.connect(self.disable_text_checkbox_clicked) self.disable_text_checkbox.setText( strings._("mode_settings_receive_disable_text_checkbox") ) - self.mode_settings_widget.mode_specific_layout.addWidget( - self.disable_text_checkbox - ) - - # Disable files self.disable_files_checkbox = self.settings.get("receive", "disable_files") self.disable_files_checkbox = QtWidgets.QCheckBox() self.disable_files_checkbox.clicked.connect(self.disable_files_checkbox_clicked) self.disable_files_checkbox.setText( strings._("mode_settings_receive_disable_files_checkbox") ) - self.mode_settings_widget.mode_specific_layout.addWidget( - self.disable_files_checkbox - ) + disable_layout = QtWidgets.QHBoxLayout() + disable_layout.addWidget(self.disable_text_checkbox) + disable_layout.addWidget(self.disable_files_checkbox) + disable_layout.addStretch() + self.mode_settings_widget.mode_specific_layout.addLayout(disable_layout) # Webhook URL webhook_url = self.settings.get("receive", "webhook_url") @@ -344,8 +341,11 @@ class ReceiveMode(Mode): Handle REQUEST_STARTED event. """ item = ReceiveHistoryItem( - self.common, event["data"]["id"], event["data"]["content_length"] + self.common, + event["data"]["id"], + event["data"]["content_length"], ) + self.history.add(event["data"]["id"], item) self.toggle_history.update_indicator(True) self.history.in_progress_count += 1 @@ -365,6 +365,12 @@ class ReceiveMode(Mode): {"action": "progress", "progress": event["data"]["progress"]}, ) + def handle_request_upload_includes_message(self, event): + """ + Handle REQUEST_UPLOAD_INCLUDES_MESSAGE event. + """ + self.history.includes_message(event["data"]["id"], event["data"]["filename"]) + def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. diff --git a/desktop/src/onionshare/tab/tab.py b/desktop/src/onionshare/tab/tab.py index 2d4e164c..3d88ded5 100644 --- a/desktop/src/onionshare/tab/tab.py +++ b/desktop/src/onionshare/tab/tab.py @@ -540,6 +540,9 @@ class Tab(QtWidgets.QWidget): elif event["type"] == Web.REQUEST_CANCELED: mode.handle_request_canceled(event) + elif event["type"] == Web.REQUEST_UPLOAD_INCLUDES_MESSAGE: + mode.handle_request_upload_includes_message(event) + elif event["type"] == Web.REQUEST_UPLOAD_FILE_RENAMED: mode.handle_request_upload_file_renamed(event)