diff options
Diffstat (limited to 'onionshare_gui/mode')
-rw-r--r-- | onionshare_gui/mode/__init__.py | 161 | ||||
-rw-r--r-- | onionshare_gui/mode/file_selection.py (renamed from onionshare_gui/mode/share_mode/file_selection.py) | 93 | ||||
-rw-r--r-- | onionshare_gui/mode/history.py | 371 | ||||
-rw-r--r-- | onionshare_gui/mode/receive_mode/__init__.py | 107 | ||||
-rw-r--r-- | onionshare_gui/mode/share_mode/__init__.py | 92 | ||||
-rw-r--r-- | onionshare_gui/mode/share_mode/threads.py | 21 | ||||
-rw-r--r-- | onionshare_gui/mode/website_mode/__init__.py | 266 |
7 files changed, 871 insertions, 240 deletions
diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py index 8f5ff32b..04709dc2 100644 --- a/onionshare_gui/mode/__init__.py +++ b/onionshare_gui/mode/__init__.py @@ -22,15 +22,19 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings from onionshare.common import AutoStopTimer +from .history import IndividualFileHistoryItem + from ..server_status import ServerStatus from ..threads import OnionThread -from ..threads import AutoStartTimer +from ..threads import AutoStartTimer from ..widgets import Alert + class Mode(QtWidgets.QWidget): """ - The class that ShareMode and ReceiveMode inherit from. + The class that all modes inherit from """ + start_server_finished = QtCore.pyqtSignal() stop_server_finished = QtCore.pyqtSignal() starting_server_step2 = QtCore.pyqtSignal() @@ -39,7 +43,17 @@ class Mode(QtWidgets.QWidget): starting_server_early = QtCore.pyqtSignal() set_server_active = QtCore.pyqtSignal(bool) - def __init__(self, common, qtapp, app, status_bar, server_status_label, system_tray, filenames=None, local_only=False): + def __init__( + self, + common, + qtapp, + app, + status_bar, + server_status_label, + system_tray, + filenames=None, + local_only=False, + ): super(Mode, self).__init__() self.common = common self.qtapp = qtapp @@ -63,7 +77,9 @@ class Mode(QtWidgets.QWidget): self.startup_thread = None # Server status - self.server_status = ServerStatus(self.common, self.qtapp, self.app, None, self.local_only) + self.server_status = ServerStatus( + self.common, self.qtapp, self.app, None, self.local_only + ) self.server_status.server_started.connect(self.start_server) self.server_status.server_stopped.connect(self.stop_server) self.server_status.server_canceled.connect(self.cancel_server) @@ -96,16 +112,26 @@ class Mode(QtWidgets.QWidget): """ Returns a human-friendly time delta from given seconds. """ - days = secs//86400 - hours = (secs - days*86400)//3600 - minutes = (secs - days*86400 - hours*3600)//60 - seconds = secs - days*86400 - hours*3600 - minutes*60 + days = secs // 86400 + hours = (secs - days * 86400) // 3600 + minutes = (secs - days * 86400 - hours * 3600) // 60 + seconds = secs - days * 86400 - hours * 3600 - minutes * 60 if not seconds: - seconds = '0' - result = ("{0}{1}, ".format(days, strings._('days_first_letter')) if days else "") + \ - ("{0}{1}, ".format(hours, strings._('hours_first_letter')) if hours else "") + \ - ("{0}{1}, ".format(minutes, strings._('minutes_first_letter')) if minutes else "") + \ - "{0}{1}".format(seconds, strings._('seconds_first_letter')) + seconds = "0" + result = ( + ("{0}{1}, ".format(days, strings._("days_first_letter")) if days else "") + + ( + "{0}{1}, ".format(hours, strings._("hours_first_letter")) + if hours + else "" + ) + + ( + "{0}{1}, ".format(minutes, strings._("minutes_first_letter")) + if minutes + else "" + ) + + "{0}{1}".format(seconds, strings._("seconds_first_letter")) + ) return result @@ -118,25 +144,45 @@ class Mode(QtWidgets.QWidget): if self.server_status.autostart_timer_datetime: now = QtCore.QDateTime.currentDateTime() if self.server_status.local_only: - seconds_remaining = now.secsTo(self.server_status.autostart_timer_widget.dateTime()) + seconds_remaining = now.secsTo( + self.server_status.autostart_timer_widget.dateTime() + ) else: - seconds_remaining = now.secsTo(self.server_status.autostart_timer_datetime.replace(second=0, microsecond=0)) + seconds_remaining = now.secsTo( + self.server_status.autostart_timer_datetime.replace( + second=0, microsecond=0 + ) + ) # Update the server button if seconds_remaining > 0: - self.server_status.server_button.setText(strings._('gui_waiting_to_start').format(self.human_friendly_time(seconds_remaining))) + self.server_status.server_button.setText( + strings._("gui_waiting_to_start").format( + self.human_friendly_time(seconds_remaining) + ) + ) else: - self.server_status.server_button.setText(strings._('gui_please_wait')) + self.server_status.server_button.setText( + strings._("gui_please_wait") + ) # If the auto-stop timer has stopped, stop the server if self.server_status.status == ServerStatus.STATUS_STARTED: - if self.app.autostop_timer_thread and self.common.settings.get('autostop_timer'): + if self.app.autostop_timer_thread and self.common.settings.get( + "autostop_timer" + ): if self.autostop_timer_datetime_delta > 0: now = QtCore.QDateTime.currentDateTime() - seconds_remaining = now.secsTo(self.server_status.autostop_timer_datetime) + seconds_remaining = now.secsTo( + self.server_status.autostop_timer_datetime + ) # Update the server button server_button_text = self.get_stop_server_autostop_timer_text() - self.server_status.server_button.setText(server_button_text.format(self.human_friendly_time(seconds_remaining))) + self.server_status.server_button.setText( + server_button_text.format( + self.human_friendly_time(seconds_remaining) + ) + ) self.status_bar.clearMessage() if not self.app.autostop_timer_thread.is_alive(): @@ -166,22 +212,22 @@ class Mode(QtWidgets.QWidget): Start the onionshare server. This uses multiple threads to start the Tor onion server and the web app. """ - self.common.log('Mode', 'start_server') + self.common.log("Mode", "start_server") self.start_server_custom() self.set_server_active.emit(True) - self.app.set_stealth(self.common.settings.get('use_stealth')) + self.app.set_stealth(self.common.settings.get("use_stealth")) # Clear the status bar self.status_bar.clearMessage() - self.server_status_label.setText('') + self.server_status_label.setText("") # Ensure we always get a new random port each time we might launch an OnionThread self.app.port = None # Start the onion thread. If this share was scheduled for a future date, - # the OnionThread will start and exit 'early' to obtain the port, slug + # the OnionThread will start and exit 'early' to obtain the port, password # and onion address, but it will not start the WebThread yet. if self.server_status.autostart_timer_datetime: self.start_onion_thread(obtain_onion_early=True) @@ -190,7 +236,7 @@ class Mode(QtWidgets.QWidget): # If scheduling a share, delay starting the real share if self.server_status.autostart_timer_datetime: - self.common.log('Mode', 'start_server', 'Starting auto-start timer') + self.common.log("Mode", "start_server", "Starting auto-start timer") self.startup_thread = AutoStartTimer(self) # Once the timer has finished, start the real share, with a WebThread self.startup_thread.success.connect(self.start_scheduled_service) @@ -199,7 +245,7 @@ class Mode(QtWidgets.QWidget): self.startup_thread.start() def start_onion_thread(self, obtain_onion_early=False): - self.common.log('Mode', 'start_server', 'Starting an onion thread') + self.common.log("Mode", "start_server", "Starting an onion thread") self.obtain_onion_early = obtain_onion_early self.onion_thread = OnionThread(self) self.onion_thread.success.connect(self.starting_server_step2.emit) @@ -211,7 +257,7 @@ class Mode(QtWidgets.QWidget): # We start a new OnionThread with the saved scheduled key from settings self.common.settings.load() self.obtain_onion_early = obtain_onion_early - self.common.log('Mode', 'start_server', 'Starting a scheduled onion thread') + self.common.log("Mode", "start_server", "Starting a scheduled onion thread") self.onion_thread = OnionThread(self) self.onion_thread.success.connect(self.starting_server_step2.emit) self.onion_thread.error.connect(self.starting_server_error.emit) @@ -235,7 +281,7 @@ class Mode(QtWidgets.QWidget): """ Step 2 in starting the onionshare server. """ - self.common.log('Mode', 'start_server_step2') + self.common.log("Mode", "start_server_step2") self.start_server_step2_custom() @@ -255,22 +301,28 @@ class Mode(QtWidgets.QWidget): """ Step 3 in starting the onionshare server. """ - self.common.log('Mode', 'start_server_step3') + self.common.log("Mode", "start_server_step3") self.start_server_step3_custom() - if self.common.settings.get('autostop_timer'): + if self.common.settings.get("autostop_timer"): # Convert the date value to seconds between now and then now = QtCore.QDateTime.currentDateTime() - self.autostop_timer_datetime_delta = now.secsTo(self.server_status.autostop_timer_datetime) + self.autostop_timer_datetime_delta = now.secsTo( + self.server_status.autostop_timer_datetime + ) # Start the auto-stop timer if self.autostop_timer_datetime_delta > 0: - self.app.autostop_timer_thread = AutoStopTimer(self.common, self.autostop_timer_datetime_delta) + self.app.autostop_timer_thread = AutoStopTimer( + self.common, self.autostop_timer_datetime_delta + ) self.app.autostop_timer_thread.start() # The auto-stop timer has actually already passed since the user clicked Start. Probably the Onion service took too long to start. else: self.stop_server() - self.start_server_error(strings._('gui_server_started_after_autostop_timer')) + self.start_server_error( + strings._("gui_server_started_after_autostop_timer") + ) def start_server_step3_custom(self): """ @@ -282,7 +334,7 @@ class Mode(QtWidgets.QWidget): """ If there's an error when trying to start the onion service """ - self.common.log('Mode', 'start_server_error') + self.common.log("Mode", "start_server_error") Alert(self.common, error, QtWidgets.QMessageBox.Warning) self.set_server_active.emit(False) @@ -303,16 +355,16 @@ class Mode(QtWidgets.QWidget): """ self.cancel_server_custom() if self.startup_thread: - self.common.log('Mode', 'cancel_server: quitting startup thread') + self.common.log("Mode", "cancel_server: quitting startup thread") self.startup_thread.canceled = True self.app.onion.scheduled_key = None self.app.onion.scheduled_auth_cookie = None self.startup_thread.quit() if self.onion_thread: - self.common.log('Mode', 'cancel_server: quitting onion thread') + self.common.log("Mode", "cancel_server: quitting onion thread") self.onion_thread.quit() if self.web_thread: - self.common.log('Mode', 'cancel_server: quitting web thread') + self.common.log("Mode", "cancel_server: quitting web thread") self.web_thread.quit() self.stop_server() @@ -326,7 +378,7 @@ class Mode(QtWidgets.QWidget): """ Stop the onionshare server. """ - self.common.log('Mode', 'stop_server') + self.common.log("Mode", "stop_server") if self.server_status.status != ServerStatus.STATUS_STOPPED: try: @@ -380,7 +432,9 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_RATE_LIMIT event. """ self.stop_server() - Alert(self.common, strings._('error_rate_limit'), QtWidgets.QMessageBox.Critical) + Alert( + self.common, strings._("error_rate_limit"), QtWidgets.QMessageBox.Critical + ) def handle_request_progress(self, event): """ @@ -417,3 +471,32 @@ class Mode(QtWidgets.QWidget): Handle REQUEST_UPLOAD_CANCELED event. """ pass + + def handle_request_individual_file_started(self, event): + """ + Handle REQUEST_INDVIDIDUAL_FILES_STARTED event. + Used in both Share and Website modes, so implemented here. + """ + self.toggle_history.update_indicator(True) + self.history.requests_count += 1 + self.history.update_requests() + + item = IndividualFileHistoryItem(self.common, event["data"], event["path"]) + self.history.add(event["data"]["id"], item) + + def handle_request_individual_file_progress(self, event): + """ + Handle REQUEST_INDVIDIDUAL_FILES_PROGRESS event. + Used in both Share and Website modes, so implemented here. + """ + self.history.update(event["data"]["id"], event["data"]["bytes"]) + + if self.server_status.status == self.server_status.STATUS_STOPPED: + self.history.cancel(event["data"]["id"]) + + def handle_request_individual_file_canceled(self, event): + """ + Handle REQUEST_INDVIDIDUAL_FILES_CANCELED event. + Used in both Share and Website modes, so implemented here. + """ + self.history.cancel(event["data"]["id"]) diff --git a/onionshare_gui/mode/share_mode/file_selection.py b/onionshare_gui/mode/file_selection.py index 0d4229fe..c505dc03 100644 --- a/onionshare_gui/mode/share_mode/file_selection.py +++ b/onionshare_gui/mode/file_selection.py @@ -22,13 +22,15 @@ from PyQt5 import QtCore, QtWidgets, QtGui from onionshare import strings -from ...widgets import Alert, AddFileDialog +from ..widgets import Alert, AddFileDialog + class DropHereLabel(QtWidgets.QLabel): """ When there are no files or folders in the FileList yet, display the 'drop files here' message and graphic. """ + def __init__(self, common, parent, image=False): self.parent = parent super(DropHereLabel, self).__init__(parent=parent) @@ -39,10 +41,16 @@ class DropHereLabel(QtWidgets.QLabel): self.setAlignment(QtCore.Qt.AlignCenter) if image: - self.setPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/logo_transparent.png')))) + self.setPixmap( + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/logo_transparent.png") + ) + ) + ) else: - self.setText(strings._('gui_drag_and_drop')) - self.setStyleSheet(self.common.css['share_file_selection_drop_here_label']) + self.setText(strings._("gui_drag_and_drop")) + self.setStyleSheet(self.common.css["share_file_selection_drop_here_label"]) self.hide() @@ -57,6 +65,7 @@ class DropCountLabel(QtWidgets.QLabel): While dragging files over the FileList, this counter displays the number of files you're dragging. """ + def __init__(self, common, parent): self.parent = parent super(DropCountLabel, self).__init__(parent=parent) @@ -65,8 +74,8 @@ class DropCountLabel(QtWidgets.QLabel): self.setAcceptDrops(True) self.setAlignment(QtCore.Qt.AlignCenter) - self.setText(strings._('gui_drag_and_drop')) - self.setStyleSheet(self.common.css['share_file_selection_drop_count_label']) + self.setText(strings._("gui_drag_and_drop")) + self.setStyleSheet(self.common.css["share_file_selection_drop_count_label"]) self.hide() def dragEnterEvent(self, event): @@ -78,6 +87,7 @@ class FileList(QtWidgets.QListWidget): """ The list of files and folders in the GUI. """ + files_dropped = QtCore.pyqtSignal() files_updated = QtCore.pyqtSignal() @@ -139,7 +149,7 @@ class FileList(QtWidgets.QListWidget): if self.count() > 0: # Add and delete an empty item, to force all items to get redrawn # This is ugly, but the only way I could figure out how to proceed - item = QtWidgets.QListWidgetItem('fake item') + item = QtWidgets.QListWidgetItem("fake item") self.addItem(item) self.takeItem(self.row(item)) self.update() @@ -149,21 +159,27 @@ class FileList(QtWidgets.QListWidget): # and extend based on the overall width minus that amount. for index in range(self.count()): metrics = QtGui.QFontMetrics(self.item(index).font()) - elided = metrics.elidedText(self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200) + elided = metrics.elidedText( + self.item(index).basename, QtCore.Qt.ElideRight, self.width() - 200 + ) self.item(index).setText(elided) - def dragEnterEvent(self, event): """ dragEnterEvent for dragging files and directories into the widget. """ if event.mimeData().hasUrls: - self.setStyleSheet(self.common.css['share_file_list_drag_enter']) + self.setStyleSheet(self.common.css["share_file_list_drag_enter"]) count = len(event.mimeData().urls()) - self.drop_count.setText('+{}'.format(count)) + self.drop_count.setText("+{}".format(count)) size_hint = self.drop_count.sizeHint() - self.drop_count.setGeometry(self.width() - size_hint.width() - 10, self.height() - size_hint.height() - 10, size_hint.width(), size_hint.height()) + self.drop_count.setGeometry( + self.width() - size_hint.width() - 10, + self.height() - size_hint.height() - 10, + size_hint.width(), + size_hint.height(), + ) self.drop_count.show() event.accept() else: @@ -173,7 +189,7 @@ class FileList(QtWidgets.QListWidget): """ dragLeaveEvent for dragging files and directories into the widget. """ - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.setStyleSheet(self.common.css["share_file_list_drag_leave"]) self.drop_count.hide() event.accept() self.update() @@ -201,7 +217,7 @@ class FileList(QtWidgets.QListWidget): else: event.ignore() - self.setStyleSheet(self.common.css['share_file_list_drag_leave']) + self.setStyleSheet(self.common.css["share_file_list_drag_leave"]) self.drop_count.hide() self.files_dropped.emit() @@ -238,12 +254,14 @@ class FileList(QtWidgets.QListWidget): # Item's filename attribute and size labels item.filename = filename item_size = QtWidgets.QLabel(size_readable) - item_size.setStyleSheet(self.common.css['share_file_list_item_size']) + item_size.setStyleSheet(self.common.css["share_file_list_item_size"]) - item.basename = os.path.basename(filename.rstrip('/')) + item.basename = os.path.basename(filename.rstrip("/")) # Use the basename as the method with which to sort the list metrics = QtGui.QFontMetrics(item.font()) - elided = metrics.elidedText(item.basename, QtCore.Qt.ElideRight, self.sizeHint().width()) + elided = metrics.elidedText( + item.basename, QtCore.Qt.ElideRight, self.sizeHint().width() + ) item.setData(QtCore.Qt.DisplayRole, elided) # Item's delete button @@ -255,9 +273,13 @@ class FileList(QtWidgets.QListWidget): item.item_button = QtWidgets.QPushButton() item.item_button.setDefault(False) item.item_button.setFlat(True) - item.item_button.setIcon( QtGui.QIcon(self.common.get_resource_path('images/file_delete.png')) ) + item.item_button.setIcon( + QtGui.QIcon(self.common.get_resource_path("images/file_delete.png")) + ) item.item_button.clicked.connect(delete_item) - item.item_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + item.item_button.setSizePolicy( + QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed + ) # Item info widget, with a white background item_info_layout = QtWidgets.QHBoxLayout() @@ -265,7 +287,7 @@ class FileList(QtWidgets.QListWidget): item_info_layout.addWidget(item_size) item_info_layout.addWidget(item.item_button) item_info = QtWidgets.QWidget() - item_info.setObjectName('item-info') + item_info.setObjectName("item-info") item_info.setLayout(item_info_layout) # Create the item's widget and layouts @@ -288,6 +310,7 @@ class FileSelection(QtWidgets.QVBoxLayout): The list of files and folders in the GUI, as well as buttons to add and delete the files and folders. """ + def __init__(self, common, parent): super(FileSelection, self).__init__() @@ -303,21 +326,21 @@ class FileSelection(QtWidgets.QVBoxLayout): self.file_list.files_updated.connect(self.update) # Buttons - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": # The macOS sandbox makes it so the Mac version needs separate add files # and folders buttons, in order to use native file selection dialogs - self.add_files_button = QtWidgets.QPushButton(strings._('gui_add_files')) + self.add_files_button = QtWidgets.QPushButton(strings._("gui_add_files")) self.add_files_button.clicked.connect(self.add_files) - self.add_folder_button = QtWidgets.QPushButton(strings._('gui_add_folder')) + self.add_folder_button = QtWidgets.QPushButton(strings._("gui_add_folder")) self.add_folder_button.clicked.connect(self.add_folder) else: - self.add_button = QtWidgets.QPushButton(strings._('gui_add')) + self.add_button = QtWidgets.QPushButton(strings._("gui_add")) self.add_button.clicked.connect(self.add) - self.delete_button = QtWidgets.QPushButton(strings._('gui_delete')) + self.delete_button = QtWidgets.QPushButton(strings._("gui_delete")) self.delete_button.clicked.connect(self.delete) button_layout = QtWidgets.QHBoxLayout() button_layout.addStretch() - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": button_layout.addWidget(self.add_files_button) button_layout.addWidget(self.add_folder_button) else: @@ -336,14 +359,14 @@ class FileSelection(QtWidgets.QVBoxLayout): """ # All buttons should be hidden if the server is on if self.server_on: - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": self.add_files_button.hide() self.add_folder_button.hide() else: self.add_button.hide() self.delete_button.hide() else: - if self.common.platform == 'Darwin': + if self.common.platform == "Darwin": self.add_files_button.show() self.add_folder_button.show() else: @@ -362,7 +385,7 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add button clicked. """ - file_dialog = AddFileDialog(self.common, caption=strings._('gui_choose_items')) + file_dialog = AddFileDialog(self.common, caption=strings._("gui_choose_items")) if file_dialog.exec_() == QtWidgets.QDialog.Accepted: for filename in file_dialog.selectedFiles(): self.file_list.add_file(filename) @@ -374,7 +397,9 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add files button clicked. """ - files = QtWidgets.QFileDialog.getOpenFileNames(self.parent, caption=strings._('gui_choose_items')) + files = QtWidgets.QFileDialog.getOpenFileNames( + self.parent, caption=strings._("gui_choose_items") + ) filenames = files[0] for filename in filenames: self.file_list.add_file(filename) @@ -383,9 +408,11 @@ class FileSelection(QtWidgets.QVBoxLayout): """ Add folder button clicked. """ - filename = QtWidgets.QFileDialog.getExistingDirectory(self.parent, - caption=strings._('gui_choose_items'), - options=QtWidgets.QFileDialog.ShowDirsOnly) + filename = QtWidgets.QFileDialog.getExistingDirectory( + self.parent, + caption=strings._("gui_choose_items"), + options=QtWidgets.QFileDialog.ShowDirsOnly, + ) self.file_list.add_file(filename) def delete(self): diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py index 1546cb68..85eec7e4 100644 --- a/onionshare_gui/mode/history.py +++ b/onionshare_gui/mode/history.py @@ -31,6 +31,7 @@ class HistoryItem(QtWidgets.QWidget): """ The base history item """ + STATUS_STARTED = 0 STATUS_FINISHED = 1 STATUS_CANCELED = 2 @@ -49,34 +50,42 @@ class HistoryItem(QtWidgets.QWidget): When an item finishes, returns a string displaying the start/end datetime range. started is a datetime object. """ - return self._get_label_text('gui_all_modes_transfer_finished', 'gui_all_modes_transfer_finished_range', started) + return self._get_label_text( + "gui_all_modes_transfer_finished", + "gui_all_modes_transfer_finished_range", + started, + ) def get_canceled_label_text(self, started): """ When an item is canceled, returns a string displaying the start/end datetime range. started is a datetime object. """ - return self._get_label_text('gui_all_modes_transfer_canceled', 'gui_all_modes_transfer_canceled_range', started) + return self._get_label_text( + "gui_all_modes_transfer_canceled", + "gui_all_modes_transfer_canceled_range", + started, + ) def _get_label_text(self, string_name, string_range_name, started): """ Return a string that contains a date, or date range. """ ended = datetime.now() - if started.year == ended.year and started.month == ended.month and started.day == ended.day: + if ( + started.year == ended.year + and started.month == ended.month + and started.day == ended.day + ): if started.hour == ended.hour and started.minute == ended.minute: - text = strings._(string_name).format( - started.strftime("%b %d, %I:%M%p") - ) + text = strings._(string_name).format(started.strftime("%b %d, %I:%M%p")) else: text = strings._(string_range_name).format( - started.strftime("%b %d, %I:%M%p"), - ended.strftime("%I:%M%p") + started.strftime("%b %d, %I:%M%p"), ended.strftime("%I:%M%p") ) else: text = strings._(string_range_name).format( - started.strftime("%b %d, %I:%M%p"), - ended.strftime("%b %d, %I:%M%p") + started.strftime("%b %d, %I:%M%p"), ended.strftime("%b %d, %I:%M%p") ) return text @@ -85,6 +94,7 @@ class ShareHistoryItem(HistoryItem): """ Download history item, for share mode """ + def __init__(self, common, id, total_bytes): super(ShareHistoryItem, self).__init__() self.common = common @@ -97,7 +107,11 @@ class ShareHistoryItem(HistoryItem): self.status = HistoryItem.STATUS_STARTED # Label - self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started_dt.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel( + strings._("gui_all_modes_transfer_started").format( + self.started_dt.strftime("%b %d, %I:%M%p") + ) + ) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -107,7 +121,9 @@ class ShareHistoryItem(HistoryItem): self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(total_bytes) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) self.progress_bar.total_bytes = total_bytes # Layout @@ -124,8 +140,9 @@ class ShareHistoryItem(HistoryItem): self.progress_bar.setValue(downloaded_bytes) if downloaded_bytes == self.progress_bar.total_bytes: - pb_fmt = strings._('gui_all_modes_progress_complete').format( - self.common.format_seconds(time.time() - self.started)) + pb_fmt = strings._("gui_all_modes_progress_complete").format( + self.common.format_seconds(time.time() - self.started) + ) # Change the label self.label.setText(self.get_finished_label_text(self.started_dt)) @@ -137,24 +154,26 @@ class ShareHistoryItem(HistoryItem): # Wait a couple of seconds for the download rate to stabilize. # This prevents a "Windows copy dialog"-esque experience at # the beginning of the download. - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(downloaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(downloaded_bytes) + ) else: - pb_fmt = strings._('gui_all_modes_progress_eta').format( + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(downloaded_bytes), - self.estimated_time_remaining) + self.estimated_time_remaining, + ) self.progress_bar.setFormat(pb_fmt) def cancel(self): - self.progress_bar.setFormat(strings._('gui_canceled')) + self.progress_bar.setFormat(strings._("gui_canceled")) self.status = HistoryItem.STATUS_CANCELED @property def estimated_time_remaining(self): - return self.common.estimated_time_remaining(self.downloaded_bytes, - self.total_bytes, - self.started) + return self.common.estimated_time_remaining( + self.downloaded_bytes, self.total_bytes, self.started + ) class ReceiveHistoryItemFile(QtWidgets.QWidget): @@ -162,7 +181,9 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): super(ReceiveHistoryItemFile, self).__init__() self.common = common - self.common.log('ReceiveHistoryItemFile', '__init__', 'filename: {}'.format(filename)) + self.common.log( + "ReceiveHistoryItemFile", "__init__", "filename: {}".format(filename) + ) self.filename = filename self.dir = None @@ -174,11 +195,13 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): # File size label self.filesize_label = QtWidgets.QLabel() - self.filesize_label.setStyleSheet(self.common.css['receive_file_size']) + self.filesize_label.setStyleSheet(self.common.css["receive_file_size"]) self.filesize_label.hide() # Folder button - folder_pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/open_folder.png'))) + folder_pixmap = QtGui.QPixmap.fromImage( + QtGui.QImage(self.common.get_resource_path("images/open_folder.png")) + ) folder_icon = QtGui.QIcon(folder_pixmap) self.folder_button = QtWidgets.QPushButton() self.folder_button.clicked.connect(self.open_folder) @@ -213,29 +236,37 @@ class ReceiveHistoryItemFile(QtWidgets.QWidget): """ Open the downloads folder, with the file selected, in a cross-platform manner """ - self.common.log('ReceiveHistoryItemFile', 'open_folder') + self.common.log("ReceiveHistoryItemFile", "open_folder") if not self.dir: - self.common.log('ReceiveHistoryItemFile', 'open_folder', "dir has not been set yet, can't open folder") + self.common.log( + "ReceiveHistoryItemFile", + "open_folder", + "dir has not been set yet, can't open folder", + ) return abs_filename = os.path.join(self.dir, self.filename) # Linux - if self.common.platform == 'Linux' or self.common.platform == 'BSD': + if self.common.platform == "Linux" or self.common.platform == "BSD": try: # If nautilus is available, open it - subprocess.Popen(['nautilus', abs_filename]) + subprocess.Popen(["nautilus", abs_filename]) except: - Alert(self.common, strings._('gui_open_folder_error_nautilus').format(abs_filename)) + Alert( + self.common, + strings._("gui_open_folder_error_nautilus").format(abs_filename), + ) # macOS - elif self.common.platform == 'Darwin': - subprocess.call(['open', '-R', abs_filename]) + elif self.common.platform == "Darwin": + subprocess.call(["open", "-R", abs_filename]) # Windows - elif self.common.platform == 'Windows': - subprocess.Popen(['explorer', '/select,{}'.format(abs_filename)]) + elif self.common.platform == "Windows": + subprocess.Popen(["explorer", "/select,{}".format(abs_filename)]) + class ReceiveHistoryItem(HistoryItem): def __init__(self, common, id, content_length): @@ -247,7 +278,11 @@ class ReceiveHistoryItem(HistoryItem): self.status = HistoryItem.STATUS_STARTED # Label - self.label = QtWidgets.QLabel(strings._('gui_all_modes_transfer_started').format(self.started.strftime("%b %d, %I:%M%p"))) + self.label = QtWidgets.QLabel( + strings._("gui_all_modes_transfer_started").format( + self.started.strftime("%b %d, %I:%M%p") + ) + ) # Progress bar self.progress_bar = QtWidgets.QProgressBar() @@ -256,13 +291,15 @@ class ReceiveHistoryItem(HistoryItem): self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) self.progress_bar.setMinimum(0) self.progress_bar.setValue(0) - self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar']) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) # This layout contains file widgets self.files_layout = QtWidgets.QVBoxLayout() self.files_layout.setContentsMargins(0, 0, 0, 0) files_widget = QtWidgets.QWidget() - files_widget.setStyleSheet(self.common.css['receive_file']) + files_widget.setStyleSheet(self.common.css["receive_file"]) files_widget.setLayout(self.files_layout) # Layout @@ -281,10 +318,10 @@ class ReceiveHistoryItem(HistoryItem): Using the progress from Web, update the progress bar and file size labels for each file """ - if data['action'] == 'progress': + if data["action"] == "progress": total_uploaded_bytes = 0 - for filename in data['progress']: - total_uploaded_bytes += data['progress'][filename]['uploaded_bytes'] + for filename in data["progress"]: + total_uploaded_bytes += data["progress"][filename]["uploaded_bytes"] # Update the progress bar self.progress_bar.setMaximum(self.content_length) @@ -292,35 +329,39 @@ class ReceiveHistoryItem(HistoryItem): elapsed = datetime.now() - self.started if elapsed.seconds < 10: - pb_fmt = strings._('gui_all_modes_progress_starting').format( - self.common.human_readable_filesize(total_uploaded_bytes)) + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(total_uploaded_bytes) + ) else: estimated_time_remaining = self.common.estimated_time_remaining( - total_uploaded_bytes, - self.content_length, - self.started.timestamp()) - pb_fmt = strings._('gui_all_modes_progress_eta').format( + total_uploaded_bytes, self.content_length, self.started.timestamp() + ) + pb_fmt = strings._("gui_all_modes_progress_eta").format( self.common.human_readable_filesize(total_uploaded_bytes), - estimated_time_remaining) + estimated_time_remaining, + ) # Using list(progress) to avoid "RuntimeError: dictionary changed size during iteration" - for filename in list(data['progress']): + for filename in list(data["progress"]): # Add a new file if needed if filename not in self.files: self.files[filename] = ReceiveHistoryItemFile(self.common, filename) self.files_layout.addWidget(self.files[filename]) # Update the file - self.files[filename].update(data['progress'][filename]['uploaded_bytes'], data['progress'][filename]['complete']) + self.files[filename].update( + data["progress"][filename]["uploaded_bytes"], + data["progress"][filename]["complete"], + ) - elif data['action'] == 'rename': - self.files[data['old_filename']].rename(data['new_filename']) - self.files[data['new_filename']] = self.files.pop(data['old_filename']) + elif data["action"] == "rename": + self.files[data["old_filename"]].rename(data["new_filename"]) + self.files[data["new_filename"]] = self.files.pop(data["old_filename"]) - elif data['action'] == 'set_dir': - self.files[data['filename']].set_dir(data['dir']) + elif data["action"] == "set_dir": + self.files[data["filename"]].set_dir(data["dir"]) - elif data['action'] == 'finished': + elif data["action"] == "finished": # Change the status self.status = HistoryItem.STATUS_FINISHED @@ -330,7 +371,7 @@ class ReceiveHistoryItem(HistoryItem): # Change the label self.label.setText(self.get_finished_label_text(self.started)) - elif data['action'] == 'canceled': + elif data["action"] == "canceled": # Change the status self.status = HistoryItem.STATUS_CANCELED @@ -341,10 +382,128 @@ class ReceiveHistoryItem(HistoryItem): self.label.setText(self.get_canceled_label_text(self.started)) +class IndividualFileHistoryItem(HistoryItem): + """ + Individual file history item, for share mode viewing of individual files + """ + + def __init__(self, common, data, path): + super(IndividualFileHistoryItem, self).__init__() + self.status = HistoryItem.STATUS_STARTED + self.common = common + + self.id = id + self.path = path + self.total_bytes = 0 + self.downloaded_bytes = 0 + self.started = time.time() + self.started_dt = datetime.fromtimestamp(self.started) + self.status = HistoryItem.STATUS_STARTED + + self.directory_listing = "directory_listing" in data + + # Labels + self.timestamp_label = QtWidgets.QLabel( + self.started_dt.strftime("%b %d, %I:%M%p") + ) + self.timestamp_label.setStyleSheet( + self.common.css["history_individual_file_timestamp_label"] + ) + self.path_label = QtWidgets.QLabel("{}".format(self.path)) + self.status_code_label = QtWidgets.QLabel() + + # Progress bar + self.progress_bar = QtWidgets.QProgressBar() + self.progress_bar.setTextVisible(True) + self.progress_bar.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.progress_bar.setAlignment(QtCore.Qt.AlignHCenter) + self.progress_bar.setValue(0) + self.progress_bar.setStyleSheet( + self.common.css["downloads_uploads_progress_bar"] + ) + + # Text layout + labels_layout = QtWidgets.QHBoxLayout() + labels_layout.addWidget(self.timestamp_label) + labels_layout.addWidget(self.path_label) + labels_layout.addWidget(self.status_code_label) + labels_layout.addStretch() + + # Layout + layout = QtWidgets.QVBoxLayout() + layout.addLayout(labels_layout) + layout.addWidget(self.progress_bar) + self.setLayout(layout) + + # Is a status code already sent? + if "status_code" in data: + self.status_code_label.setText("{}".format(data["status_code"])) + if data["status_code"] >= 200 and data["status_code"] < 300: + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_2xx"] + ) + if data["status_code"] >= 400 and data["status_code"] < 500: + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_4xx"] + ) + self.status = HistoryItem.STATUS_FINISHED + self.progress_bar.hide() + return + + else: + self.total_bytes = data["filesize"] + self.progress_bar.setMinimum(0) + self.progress_bar.setMaximum(data["filesize"]) + self.progress_bar.total_bytes = data["filesize"] + + # Start at 0 + self.update(0) + + def update(self, downloaded_bytes): + self.downloaded_bytes = downloaded_bytes + + self.progress_bar.setValue(downloaded_bytes) + if downloaded_bytes == self.progress_bar.total_bytes: + self.status_code_label.setText("200") + self.status_code_label.setStyleSheet( + self.common.css["history_individual_file_status_code_label_2xx"] + ) + self.progress_bar.hide() + self.status = HistoryItem.STATUS_FINISHED + + else: + elapsed = time.time() - self.started + if elapsed < 10: + # Wait a couple of seconds for the download rate to stabilize. + # This prevents a "Windows copy dialog"-esque experience at + # the beginning of the download. + pb_fmt = strings._("gui_all_modes_progress_starting").format( + self.common.human_readable_filesize(downloaded_bytes) + ) + else: + pb_fmt = strings._("gui_all_modes_progress_eta").format( + self.common.human_readable_filesize(downloaded_bytes), + self.estimated_time_remaining, + ) + + self.progress_bar.setFormat(pb_fmt) + + def cancel(self): + self.progress_bar.setFormat(strings._("gui_canceled")) + self.status = HistoryItem.STATUS_CANCELED + + @property + def estimated_time_remaining(self): + return self.common.estimated_time_remaining( + self.downloaded_bytes, self.total_bytes, self.started + ) + + class HistoryItemList(QtWidgets.QScrollArea): """ List of items """ + def __init__(self, common): super(HistoryItemList, self).__init__() self.common = common @@ -404,45 +563,53 @@ class HistoryItemList(QtWidgets.QScrollArea): Reset all items, emptying the list. Override this method. """ for key, item in self.items.copy().items(): - if item.status != HistoryItem.STATUS_STARTED: - self.items_layout.removeWidget(item) - item.close() - del self.items[key] + self.items_layout.removeWidget(item) + item.close() + del self.items[key] + class History(QtWidgets.QWidget): """ A history of what's happened so far in this mode. This contains an internal object full of a scrollable list of items. """ - def __init__(self, common, empty_image, empty_text, header_text): + + def __init__(self, common, empty_image, empty_text, header_text, mode=""): super(History, self).__init__() self.common = common + self.mode = mode self.setMinimumWidth(350) # In progress and completed counters self.in_progress_count = 0 self.completed_count = 0 + self.requests_count = 0 - # In progress and completed labels + # In progress, completed, and requests labels self.in_progress_label = QtWidgets.QLabel() - self.in_progress_label.setStyleSheet(self.common.css['mode_info_label']) + self.in_progress_label.setStyleSheet(self.common.css["mode_info_label"]) self.completed_label = QtWidgets.QLabel() - self.completed_label.setStyleSheet(self.common.css['mode_info_label']) + self.completed_label.setStyleSheet(self.common.css["mode_info_label"]) + self.requests_label = QtWidgets.QLabel() + self.requests_label.setStyleSheet(self.common.css["mode_info_label"]) # Header self.header_label = QtWidgets.QLabel(header_text) - self.header_label.setStyleSheet(self.common.css['downloads_uploads_label']) - clear_button = QtWidgets.QPushButton(strings._('gui_all_modes_clear_history')) - clear_button.setStyleSheet(self.common.css['downloads_uploads_clear']) - clear_button.setFlat(True) - clear_button.clicked.connect(self.reset) + self.header_label.setStyleSheet(self.common.css["downloads_uploads_label"]) + self.clear_button = QtWidgets.QPushButton( + strings._("gui_all_modes_clear_history") + ) + self.clear_button.setStyleSheet(self.common.css["downloads_uploads_clear"]) + self.clear_button.setFlat(True) + self.clear_button.clicked.connect(self.reset) header_layout = QtWidgets.QHBoxLayout() header_layout.addWidget(self.header_label) header_layout.addStretch() header_layout.addWidget(self.in_progress_label) header_layout.addWidget(self.completed_label) - header_layout.addWidget(clear_button) + header_layout.addWidget(self.requests_label) + header_layout.addWidget(self.clear_button) # When there are no items self.empty_image = QtWidgets.QLabel() @@ -450,14 +617,14 @@ class History(QtWidgets.QWidget): self.empty_image.setPixmap(empty_image) self.empty_text = QtWidgets.QLabel(empty_text) self.empty_text.setAlignment(QtCore.Qt.AlignCenter) - self.empty_text.setStyleSheet(self.common.css['downloads_uploads_empty_text']) + self.empty_text.setStyleSheet(self.common.css["downloads_uploads_empty_text"]) empty_layout = QtWidgets.QVBoxLayout() empty_layout.addStretch() empty_layout.addWidget(self.empty_image) empty_layout.addWidget(self.empty_text) empty_layout.addStretch() self.empty = QtWidgets.QWidget() - self.empty.setStyleSheet(self.common.css['downloads_uploads_empty']) + self.empty.setStyleSheet(self.common.css["downloads_uploads_empty"]) self.empty.setLayout(empty_layout) # When there are items @@ -482,7 +649,7 @@ class History(QtWidgets.QWidget): """ Add a new item. """ - self.common.log('History', 'add', 'id: {}, item: {}'.format(id, item)) + self.common.log("History", "add", "id: {}, item: {}".format(id, item)) # Hide empty, show not empty self.empty.hide() @@ -520,27 +687,56 @@ class History(QtWidgets.QWidget): self.completed_count = 0 self.update_completed() + # Reset web requests counter + self.requests_count = 0 + self.update_requests() + def update_completed(self): """ Update the 'completed' widget. """ if self.completed_count == 0: - image = self.common.get_resource_path('images/share_completed_none.png') + image = self.common.get_resource_path("images/history_completed_none.png") else: - image = self.common.get_resource_path('images/share_completed.png') - self.completed_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.completed_count)) - self.completed_label.setToolTip(strings._('history_completed_tooltip').format(self.completed_count)) + image = self.common.get_resource_path("images/history_completed.png") + self.completed_label.setText( + '<img src="{0:s}" /> {1:d}'.format(image, self.completed_count) + ) + self.completed_label.setToolTip( + strings._("history_completed_tooltip").format(self.completed_count) + ) def update_in_progress(self): """ Update the 'in progress' widget. """ if self.in_progress_count == 0: - image = self.common.get_resource_path('images/share_in_progress_none.png') + image = self.common.get_resource_path("images/history_in_progress_none.png") else: - image = self.common.get_resource_path('images/share_in_progress.png') - self.in_progress_label.setText('<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count)) - self.in_progress_label.setToolTip(strings._('history_in_progress_tooltip').format(self.in_progress_count)) + image = self.common.get_resource_path("images/history_in_progress.png") + + self.in_progress_label.setText( + '<img src="{0:s}" /> {1:d}'.format(image, self.in_progress_count) + ) + self.in_progress_label.setToolTip( + strings._("history_in_progress_tooltip").format(self.in_progress_count) + ) + + def update_requests(self): + """ + Update the 'web requests' widget. + """ + if self.requests_count == 0: + image = self.common.get_resource_path("images/history_requests_none.png") + else: + image = self.common.get_resource_path("images/history_requests.png") + + self.requests_label.setText( + '<img src="{0:s}" /> {1:d}'.format(image, self.requests_count) + ) + self.requests_label.setToolTip( + strings._("history_requests_tooltip").format(self.requests_count) + ) class ToggleHistory(QtWidgets.QPushButton): @@ -548,6 +744,7 @@ class ToggleHistory(QtWidgets.QPushButton): Widget for toggling showing or hiding the history, as well as keeping track of the indicator counter if it's hidden """ + def __init__(self, common, current_mode, history_widget, icon, selected_icon): super(ToggleHistory, self).__init__() self.common = common @@ -567,13 +764,15 @@ class ToggleHistory(QtWidgets.QPushButton): # Keep track of indicator self.indicator_count = 0 self.indicator_label = QtWidgets.QLabel(parent=self) - self.indicator_label.setStyleSheet(self.common.css['download_uploads_indicator']) + self.indicator_label.setStyleSheet( + self.common.css["download_uploads_indicator"] + ) self.update_indicator() def update_indicator(self, increment=False): """ Update the display of the indicator count. If increment is True, then - only increment the counter if Downloads is hidden. + only increment the counter if History is hidden. """ if increment and not self.history_widget.isVisible(): self.indicator_count += 1 @@ -584,14 +783,16 @@ class ToggleHistory(QtWidgets.QPushButton): self.indicator_label.hide() else: size = self.indicator_label.sizeHint() - self.indicator_label.setGeometry(35-size.width(), 0, size.width(), size.height()) + self.indicator_label.setGeometry( + 35 - size.width(), 0, size.width(), size.height() + ) self.indicator_label.show() def toggle_clicked(self): """ Toggle showing and hiding the history widget """ - self.common.log('ToggleHistory', 'toggle_clicked') + self.common.log("ToggleHistory", "toggle_clicked") if self.history_widget.isVisible(): self.history_widget.hide() diff --git a/onionshare_gui/mode/receive_mode/__init__.py b/onionshare_gui/mode/receive_mode/__init__.py index 4c0b49ba..a0507949 100644 --- a/onionshare_gui/mode/receive_mode/__init__.py +++ b/onionshare_gui/mode/receive_mode/__init__.py @@ -25,19 +25,21 @@ from onionshare.web import Web from ..history import History, ToggleHistory, ReceiveHistoryItem from .. import Mode + class ReceiveMode(Mode): """ Parts of the main window UI for receiving files. """ + def init(self): """ Custom initialization for ReceiveMode. """ # Create the Web object - self.web = Web(self.common, True, 'receive') + self.web = Web(self.common, True, "receive") # Server status - self.server_status.set_mode('receive') + self.server_status.set_mode("receive") self.server_status.server_started_finished.connect(self.update_primary_action) self.server_status.server_stopped.connect(self.update_primary_action) self.server_status.server_canceled.connect(self.update_primary_action) @@ -49,21 +51,31 @@ class ReceiveMode(Mode): # Upload history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/receive_icon_transparent.png'))), - strings._('gui_receive_mode_no_files'), - strings._('gui_all_modes_history') + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/receive_icon_transparent.png") + ) + ), + strings._("gui_receive_mode_no_files"), + strings._("gui_all_modes_history"), ) self.history.hide() # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/receive_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon( + self.common.get_resource_path("images/receive_icon_toggle.png") + ), + QtGui.QIcon( + self.common.get_resource_path("images/receive_icon_toggle_selected.png") + ), ) # Receive mode warning - receive_warning = QtWidgets.QLabel(strings._('gui_receive_mode_warning')) + receive_warning = QtWidgets.QLabel(strings._("gui_receive_mode_warning")) receive_warning.setMinimumHeight(80) receive_warning.setWordWrap(True) @@ -90,20 +102,25 @@ class ReceiveMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_receive_stop_server_autostop_timer') + return strings._("gui_receive_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ The auto-stop timer expired, should we stop the server? Returns a bool """ # If there were no attempts to upload files, or all uploads are done, we can stop - if self.web.receive_mode.upload_count == 0 or not self.web.receive_mode.uploads_in_progress: + if ( + self.web.receive_mode.cur_history_id == 0 + or not self.web.receive_mode.uploads_in_progress + ): self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True # An upload is probably still running - hold off on stopping the share, but block new shares. else: - self.server_status_label.setText(strings._('gui_receive_mode_autostop_timer_waiting')) + self.server_status_label.setText( + strings._("gui_receive_mode_autostop_timer_waiting") + ) self.web.receive_mode.can_upload = False return False @@ -112,8 +129,8 @@ class ReceiveMode(Mode): Starting the server. """ # Reset web counters - self.web.receive_mode.upload_count = 0 - self.web.error404_count = 0 + self.web.receive_mode.cur_history_id = 0 + self.web.reset_invalid_passwords() # Hide and reset the uploads if we have previously shared self.reset_info_counters() @@ -136,56 +153,68 @@ class ReceiveMode(Mode): """ Handle REQUEST_LOAD event. """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) + self.system_tray.showMessage( + strings._("systray_page_loaded_title"), + strings._("systray_page_loaded_message"), + ) def handle_request_started(self, event): """ Handle REQUEST_STARTED event. """ - item = ReceiveHistoryItem(self.common, event["data"]["id"], event["data"]["content_length"]) + item = ReceiveHistoryItem( + 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 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_receive_started_title'), strings._('systray_receive_started_message')) + self.system_tray.showMessage( + strings._("systray_receive_started_title"), + strings._("systray_receive_started_message"), + ) def handle_request_progress(self, event): """ Handle REQUEST_PROGRESS event. """ - self.history.update(event["data"]["id"], { - 'action': 'progress', - 'progress': event["data"]["progress"] - }) + self.history.update( + event["data"]["id"], + {"action": "progress", "progress": event["data"]["progress"]}, + ) def handle_request_upload_file_renamed(self, event): """ Handle REQUEST_UPLOAD_FILE_RENAMED event. """ - self.history.update(event["data"]["id"], { - 'action': 'rename', - 'old_filename': event["data"]["old_filename"], - 'new_filename': event["data"]["new_filename"] - }) + self.history.update( + event["data"]["id"], + { + "action": "rename", + "old_filename": event["data"]["old_filename"], + "new_filename": event["data"]["new_filename"], + }, + ) def handle_request_upload_set_dir(self, event): """ Handle REQUEST_UPLOAD_SET_DIR event. """ - self.history.update(event["data"]["id"], { - 'action': 'set_dir', - 'filename': event["data"]["filename"], - 'dir': event["data"]["dir"] - }) + self.history.update( + event["data"]["id"], + { + "action": "set_dir", + "filename": event["data"]["filename"], + "dir": event["data"]["dir"], + }, + ) def handle_request_upload_finished(self, event): """ Handle REQUEST_UPLOAD_FINISHED event. """ - self.history.update(event["data"]["id"], { - 'action': 'finished' - }) + self.history.update(event["data"]["id"], {"action": "finished"}) self.history.completed_count += 1 self.history.in_progress_count -= 1 self.history.update_completed() @@ -195,9 +224,7 @@ class ReceiveMode(Mode): """ Handle REQUEST_UPLOAD_CANCELED event. """ - self.history.update(event["data"]["id"], { - 'action': 'canceled' - }) + self.history.update(event["data"]["id"], {"action": "canceled"}) self.history.in_progress_count -= 1 self.history.update_in_progress() @@ -212,6 +239,8 @@ class ReceiveMode(Mode): Set the info counters back to zero. """ self.history.reset() + self.toggle_history.indicator_count = 0 + self.toggle_history.update_indicator() def update_primary_action(self): - self.common.log('ReceiveMode', 'update_primary_action') + self.common.log("ReceiveMode", "update_primary_action") diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py index 6cb50b2b..d0cc6a04 100644 --- a/onionshare_gui/mode/share_mode/__init__.py +++ b/onionshare_gui/mode/share_mode/__init__.py @@ -25,7 +25,7 @@ from onionshare.onion import * from onionshare.common import Common from onionshare.web import Web -from .file_selection import FileSelection +from ..file_selection import FileSelection from .threads import CompressThread from .. import Mode from ..history import History, ToggleHistory, ShareHistoryItem @@ -36,6 +36,7 @@ class ShareMode(Mode): """ Parts of the main window UI for sharing files. """ + def init(self): """ Custom initialization for ReceiveMode. @@ -44,7 +45,7 @@ class ShareMode(Mode): self.compress_thread = None # Create the Web object - self.web = Web(self.common, True, 'share') + self.web = Web(self.common, True, "share") # File selection self.file_selection = FileSelection(self.common, self) @@ -53,7 +54,7 @@ class ShareMode(Mode): self.file_selection.file_list.add_file(filename) # Server status - self.server_status.set_mode('share', self.file_selection) + self.server_status.set_mode("share", self.file_selection) self.server_status.server_started.connect(self.file_selection.server_started) self.server_status.server_stopped.connect(self.file_selection.server_stopped) self.server_status.server_stopped.connect(self.update_primary_action) @@ -68,15 +69,19 @@ class ShareMode(Mode): # Filesize warning self.filesize_warning = QtWidgets.QLabel() self.filesize_warning.setWordWrap(True) - self.filesize_warning.setStyleSheet(self.common.css['share_filesize_warning']) + self.filesize_warning.setStyleSheet(self.common.css["share_filesize_warning"]) self.filesize_warning.hide() # Download history self.history = History( self.common, - QtGui.QPixmap.fromImage(QtGui.QImage(self.common.get_resource_path('images/share_icon_transparent.png'))), - strings._('gui_share_mode_no_files'), - strings._('gui_all_modes_history') + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/share_icon_transparent.png") + ) + ), + strings._("gui_share_mode_no_files"), + strings._("gui_all_modes_history"), ) self.history.hide() @@ -86,9 +91,13 @@ class ShareMode(Mode): # Toggle history self.toggle_history = ToggleHistory( - self.common, self, self.history, - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle.png')), - QtGui.QIcon(self.common.get_resource_path('images/share_icon_toggle_selected.png')) + self.common, + self, + self.history, + QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")), + QtGui.QIcon( + self.common.get_resource_path("images/share_icon_toggle_selected.png") + ), ) # Top bar @@ -125,20 +134,22 @@ class ShareMode(Mode): """ Return the string to put on the stop server button, if there's an auto-stop timer """ - return strings._('gui_share_stop_server_autostop_timer') + return strings._("gui_share_stop_server_autostop_timer") def autostop_timer_finished_should_stop_server(self): """ The auto-stop timer expired, should we stop the server? Returns a bool """ # If there were no attempts to download the share, or all downloads are done, we can stop - if self.web.share_mode.download_count == 0 or self.web.done: + if self.web.share_mode.cur_history_id == 0 or self.web.done: self.server_status.stop_server() - self.server_status_label.setText(strings._('close_on_autostop_timer')) + self.server_status_label.setText(strings._("close_on_autostop_timer")) return True # A download is probably still running - hold off on stopping the share else: - self.server_status_label.setText(strings._('gui_share_mode_autostop_timer_waiting')) + self.server_status_label.setText( + strings._("gui_share_mode_autostop_timer_waiting") + ) return False def start_server_custom(self): @@ -146,8 +157,8 @@ class ShareMode(Mode): Starting the server. """ # Reset web counters - self.web.share_mode.download_count = 0 - self.web.error404_count = 0 + self.web.share_mode.cur_history_id = 0 + self.web.reset_invalid_passwords() # Hide and reset the downloads if we have previously shared self.reset_info_counters() @@ -162,7 +173,9 @@ class ShareMode(Mode): for index in range(self.file_selection.file_list.count()): self.filenames.append(self.file_selection.file_list.item(index).filename) - self._zip_progress_bar.total_files_size = ShareMode._compute_total_size(self.filenames) + self._zip_progress_bar.total_files_size = ShareMode._compute_total_size( + self.filenames + ) self.status_bar.insertWidget(0, self._zip_progress_bar) # prepare the files for sending in a new thread @@ -216,7 +229,7 @@ class ShareMode(Mode): Stop the compression thread on cancel """ if self.compress_thread: - self.common.log('ShareMode', 'cancel_server: quitting compress thread') + self.common.log("ShareMode", "cancel_server: quitting compress thread") self.compress_thread.quit() def handle_tor_broke_custom(self): @@ -225,12 +238,6 @@ class ShareMode(Mode): """ self.primary_action.hide() - def handle_request_load(self, event): - """ - Handle REQUEST_LOAD event. - """ - self.system_tray.showMessage(strings._('systray_page_loaded_title'), strings._('systray_page_loaded_message')) - def handle_request_started(self, event): """ Handle REQUEST_STARTED event. @@ -246,7 +253,10 @@ class ShareMode(Mode): self.history.in_progress_count += 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_share_started_title'), strings._('systray_share_started_message')) + self.system_tray.showMessage( + strings._("systray_share_started_title"), + strings._("systray_share_started_message"), + ) def handle_request_progress(self, event): """ @@ -256,7 +266,10 @@ class ShareMode(Mode): # Is the download complete? if event["data"]["bytes"] == self.web.share_mode.filesize: - self.system_tray.showMessage(strings._('systray_share_completed_title'), strings._('systray_share_completed_message')) + self.system_tray.showMessage( + strings._("systray_share_completed_title"), + strings._("systray_share_completed_message"), + ) # Update completed and in progress labels self.history.completed_count += 1 @@ -265,10 +278,10 @@ class ShareMode(Mode): self.history.update_in_progress() # Close on finish? - if self.common.settings.get('close_after_first_download'): + if self.common.settings.get("close_after_first_download"): self.server_status.stop_server() self.status_bar.clearMessage() - self.server_status_label.setText(strings._('closing_automatically')) + self.server_status_label.setText(strings._("closing_automatically")) else: if self.server_status.status == self.server_status.STATUS_STOPPED: self.history.cancel(event["data"]["id"]) @@ -284,7 +297,10 @@ class ShareMode(Mode): # Update in progress count self.history.in_progress_count -= 1 self.history.update_in_progress() - self.system_tray.showMessage(strings._('systray_share_canceled_title'), strings._('systray_share_canceled_message')) + self.system_tray.showMessage( + strings._("systray_share_canceled_title"), + strings._("systray_share_canceled_message"), + ) def on_reload_settings(self): """ @@ -296,7 +312,7 @@ class ShareMode(Mode): self.info_label.show() def update_primary_action(self): - self.common.log('ShareMode', 'update_primary_action') + self.common.log("ShareMode", "update_primary_action") # Show or hide primary action layout file_count = self.file_selection.file_list.count() @@ -312,9 +328,15 @@ class ShareMode(Mode): total_size_readable = self.common.human_readable_filesize(total_size_bytes) if file_count > 1: - self.info_label.setText(strings._('gui_file_info').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info").format(file_count, total_size_readable) + ) else: - self.info_label.setText(strings._('gui_file_info_single').format(file_count, total_size_readable)) + self.info_label.setText( + strings._("gui_file_info_single").format( + file_count, total_size_readable + ) + ) else: self.primary_action.hide() @@ -325,6 +347,8 @@ class ShareMode(Mode): Set the info counters back to zero. """ self.history.reset() + self.toggle_history.indicator_count = 0 + self.toggle_history.update_indicator() @staticmethod def _compute_total_size(filenames): @@ -347,8 +371,8 @@ class ZipProgressBar(QtWidgets.QProgressBar): self.setMaximumHeight(20) self.setMinimumWidth(200) self.setValue(0) - self.setFormat(strings._('zip_progress_bar_format')) - self.setStyleSheet(self.common.css['share_zip_progess_bar']) + self.setFormat(strings._("zip_progress_bar_format")) + self.setStyleSheet(self.common.css["share_zip_progess_bar"]) self._total_files_size = total_files_size self._processed_size = 0 diff --git a/onionshare_gui/mode/share_mode/threads.py b/onionshare_gui/mode/share_mode/threads.py index 24e2c242..414c7be1 100644 --- a/onionshare_gui/mode/share_mode/threads.py +++ b/onionshare_gui/mode/share_mode/threads.py @@ -24,13 +24,14 @@ class CompressThread(QtCore.QThread): """ Compresses files to be shared """ + success = QtCore.pyqtSignal() error = QtCore.pyqtSignal(str) def __init__(self, mode): super(CompressThread, self).__init__() self.mode = mode - self.mode.common.log('CompressThread', '__init__') + self.mode.common.log("CompressThread", "__init__") # prepare files to share def set_processed_size(self, x): @@ -38,21 +39,21 @@ class CompressThread(QtCore.QThread): self.mode._zip_progress_bar.update_processed_size_signal.emit(x) def run(self): - self.mode.common.log('CompressThread', 'run') + self.mode.common.log("CompressThread", "run") try: - if self.mode.web.share_mode.set_file_info(self.mode.filenames, processed_size_callback=self.set_processed_size): - self.success.emit() - else: - # Cancelled - pass - - self.mode.app.cleanup_filenames += self.mode.web.share_mode.cleanup_filenames + self.mode.web.share_mode.set_file_info( + self.mode.filenames, processed_size_callback=self.set_processed_size + ) + self.success.emit() + self.mode.app.cleanup_filenames += ( + self.mode.web.share_mode.cleanup_filenames + ) except OSError as e: self.error.emit(e.strerror) def cancel(self): - self.mode.common.log('CompressThread', 'cancel') + self.mode.common.log("CompressThread", "cancel") # Let the Web and ZipWriter objects know that we're canceling compression early self.mode.web.cancel_compression = True diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py new file mode 100644 index 00000000..8cd2eca6 --- /dev/null +++ b/onionshare_gui/mode/website_mode/__init__.py @@ -0,0 +1,266 @@ +# -*- coding: utf-8 -*- +""" +OnionShare | https://onionshare.org/ + +Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +import os +import random +import string + +from PyQt5 import QtCore, QtWidgets, QtGui + +from onionshare import strings +from onionshare.onion import * +from onionshare.common import Common +from onionshare.web import Web + +from ..file_selection import FileSelection +from .. import Mode +from ..history import History, ToggleHistory +from ...widgets import Alert + + +class WebsiteMode(Mode): + """ + Parts of the main window UI for sharing files. + """ + + success = QtCore.pyqtSignal() + error = QtCore.pyqtSignal(str) + + def init(self): + """ + Custom initialization for ReceiveMode. + """ + # Create the Web object + self.web = Web(self.common, True, "website") + + # File selection + self.file_selection = FileSelection(self.common, self) + if self.filenames: + for filename in self.filenames: + self.file_selection.file_list.add_file(filename) + + # Server status + self.server_status.set_mode("website", self.file_selection) + self.server_status.server_started.connect(self.file_selection.server_started) + self.server_status.server_stopped.connect(self.file_selection.server_stopped) + self.server_status.server_stopped.connect(self.update_primary_action) + self.server_status.server_canceled.connect(self.file_selection.server_stopped) + self.server_status.server_canceled.connect(self.update_primary_action) + self.file_selection.file_list.files_updated.connect(self.server_status.update) + self.file_selection.file_list.files_updated.connect(self.update_primary_action) + # Tell server_status about web, then update + self.server_status.web = self.web + self.server_status.update() + + # Filesize warning + self.filesize_warning = QtWidgets.QLabel() + self.filesize_warning.setWordWrap(True) + self.filesize_warning.setStyleSheet(self.common.css["share_filesize_warning"]) + self.filesize_warning.hide() + + # Download history + self.history = History( + self.common, + QtGui.QPixmap.fromImage( + QtGui.QImage( + self.common.get_resource_path("images/share_icon_transparent.png") + ) + ), + strings._("gui_website_mode_no_files"), + strings._("gui_all_modes_history"), + "website", + ) + self.history.in_progress_label.hide() + self.history.completed_label.hide() + self.history.hide() + + # Info label + self.info_label = QtWidgets.QLabel() + self.info_label.hide() + + # Toggle history + self.toggle_history = ToggleHistory( + self.common, + self, + self.history, + QtGui.QIcon(self.common.get_resource_path("images/share_icon_toggle.png")), + QtGui.QIcon( + self.common.get_resource_path("images/share_icon_toggle_selected.png") + ), + ) + + # Top bar + top_bar_layout = QtWidgets.QHBoxLayout() + top_bar_layout.addWidget(self.info_label) + top_bar_layout.addStretch() + top_bar_layout.addWidget(self.toggle_history) + + # Primary action layout + self.primary_action_layout.addWidget(self.filesize_warning) + self.primary_action.hide() + self.update_primary_action() + + # Main layout + self.main_layout = QtWidgets.QVBoxLayout() + self.main_layout.addLayout(top_bar_layout) + self.main_layout.addLayout(self.file_selection) + self.main_layout.addWidget(self.primary_action) + self.main_layout.addWidget(self.min_width_widget) + + # Wrapper layout + self.wrapper_layout = QtWidgets.QHBoxLayout() + self.wrapper_layout.addLayout(self.main_layout) + self.wrapper_layout.addWidget(self.history, stretch=1) + self.setLayout(self.wrapper_layout) + + # Always start with focus on file selection + self.file_selection.setFocus() + + def get_stop_server_autostop_timer_text(self): + """ + Return the string to put on the stop server button, if there's an auto-stop timer + """ + return strings._("gui_share_stop_server_autostop_timer") + + def autostop_timer_finished_should_stop_server(self): + """ + The auto-stop timer expired, should we stop the server? Returns a bool + """ + + self.server_status.stop_server() + self.server_status_label.setText(strings._("close_on_autostop_timer")) + return True + + def start_server_custom(self): + """ + Starting the server. + """ + # Reset web counters + self.web.website_mode.visit_count = 0 + self.web.reset_invalid_passwords() + + # Hide and reset the downloads if we have previously shared + self.reset_info_counters() + + def start_server_step2_custom(self): + """ + Step 2 in starting the server. Zipping up files. + """ + self.filenames = [] + for index in range(self.file_selection.file_list.count()): + self.filenames.append(self.file_selection.file_list.item(index).filename) + + # Continue + self.starting_server_step3.emit() + self.start_server_finished.emit() + + def start_server_step3_custom(self): + """ + Step 3 in starting the server. Display large filesize + warning, if applicable. + """ + self.web.website_mode.set_file_info(self.filenames) + self.success.emit() + + def start_server_error_custom(self): + """ + Start server error. + """ + if self._zip_progress_bar is not None: + self.status_bar.removeWidget(self._zip_progress_bar) + self._zip_progress_bar = None + + def stop_server_custom(self): + """ + Stop server. + """ + + self.filesize_warning.hide() + self.history.completed_count = 0 + self.file_selection.file_list.adjustSize() + + def cancel_server_custom(self): + """ + Log that the server has been cancelled + """ + self.common.log("WebsiteMode", "cancel_server") + + def handle_tor_broke_custom(self): + """ + Connection to Tor broke. + """ + self.primary_action.hide() + + def on_reload_settings(self): + """ + If there were some files listed for sharing, we should be ok to re-enable + the 'Start Sharing' button now. + """ + if self.server_status.file_selection.get_num_files() > 0: + self.primary_action.show() + self.info_label.show() + + def update_primary_action(self): + self.common.log("WebsiteMode", "update_primary_action") + + # Show or hide primary action layout + file_count = self.file_selection.file_list.count() + if file_count > 0: + self.primary_action.show() + self.info_label.show() + + # Update the file count in the info label + total_size_bytes = 0 + for index in range(self.file_selection.file_list.count()): + item = self.file_selection.file_list.item(index) + total_size_bytes += item.size_bytes + total_size_readable = self.common.human_readable_filesize(total_size_bytes) + + if file_count > 1: + self.info_label.setText( + strings._("gui_file_info").format(file_count, total_size_readable) + ) + else: + self.info_label.setText( + strings._("gui_file_info_single").format( + file_count, total_size_readable + ) + ) + + else: + self.primary_action.hide() + self.info_label.hide() + + def reset_info_counters(self): + """ + Set the info counters back to zero. + """ + self.history.reset() + self.toggle_history.indicator_count = 0 + self.toggle_history.update_indicator() + + @staticmethod + def _compute_total_size(filenames): + total_size = 0 + for filename in filenames: + if os.path.isfile(filename): + total_size += os.path.getsize(filename) + if os.path.isdir(filename): + total_size += Common.dir_size(filename) + return total_size |