summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMicah Lee <micah@micahflee.com>2019-09-03 21:46:32 -0700
committerMicah Lee <micah@micahflee.com>2019-09-03 21:46:32 -0700
commit644b47082a716de9a7e2311b9f3c2a75c4d96fbe (patch)
treef3af6cf0eee7d8f1c7c071c67231851d9db10aea
parentc55925c1ce296ec8fe31bbe1b50c23133eda687a (diff)
downloadonionshare-644b47082a716de9a7e2311b9f3c2a75c4d96fbe.tar.gz
onionshare-644b47082a716de9a7e2311b9f3c2a75c4d96fbe.zip
Start making IndividualFileHistoryItem widgets appear in the history, and make non-GET requests return 405 Method Not Allowed
-rw-r--r--onionshare/web/send_base_mode.py41
-rw-r--r--onionshare/web/web.py32
-rw-r--r--onionshare_gui/mode/__init__.py47
-rw-r--r--onionshare_gui/mode/history.py99
-rw-r--r--onionshare_gui/mode/share_mode/__init__.py15
-rw-r--r--onionshare_gui/mode/website_mode/__init__.py17
-rw-r--r--onionshare_gui/onionshare_gui.py11
-rw-r--r--share/locale/en.json1
-rw-r--r--share/templates/405.html19
9 files changed, 183 insertions, 99 deletions
diff --git a/onionshare/web/send_base_mode.py b/onionshare/web/send_base_mode.py
index a34aedfd..402bc32f 100644
--- a/onionshare/web/send_base_mode.py
+++ b/onionshare/web/send_base_mode.py
@@ -132,18 +132,28 @@ class SendBaseModeWeb:
file_to_download = filesystem_path
filesize = os.path.getsize(filesystem_path)
- # TODO: Tell GUI the download started
- #self.web.add_request(self.web.REQUEST_STARTED, path, {
- # 'id': download_id,
- # 'use_gzip': use_gzip
- #})
+ # Each download has a unique id
+ download_id = self.download_count
+ self.download_count += 1
+
+ path = request.path
+
+ # Tell GUI the individual file started
+ self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_STARTED, path, {
+ 'id': download_id,
+ 'filesize': filesize,
+ 'method': request.method
+ })
+
+ # Only GET requests are allowed, any other method should fail
+ if request.method != "GET":
+ return self.web.error405()
def generate():
chunk_size = 102400 # 100kb
fp = open(file_to_download, 'rb')
done = False
- canceled = False
while not done:
chunk = fp.read(chunk_size)
if chunk == b'':
@@ -152,7 +162,7 @@ class SendBaseModeWeb:
try:
yield chunk
- # TODO: Tell GUI the progress
+ # Tell GUI the progress
downloaded_bytes = fp.tell()
percent = (1.0 * downloaded_bytes / filesize) * 100
if not self.web.is_gui or self.common.platform == 'Linux' or self.common.platform == 'BSD':
@@ -160,20 +170,19 @@ class SendBaseModeWeb:
"\r{0:s}, {1:.2f}% ".format(self.common.human_readable_filesize(downloaded_bytes), percent))
sys.stdout.flush()
- #self.web.add_request(self.web.REQUEST_PROGRESS, path, {
- # 'id': download_id,
- # 'bytes': downloaded_bytes
- # })
+ self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_PROGRESS, path, {
+ 'id': download_id,
+ 'bytes': downloaded_bytes
+ })
done = False
except:
# Looks like the download was canceled
done = True
- canceled = True
- # TODO: Tell the GUI the download has canceled
- #self.web.add_request(self.web.REQUEST_CANCELED, path, {
- # 'id': download_id
- #})
+ # Tell the GUI the individual file was canceled
+ self.web.add_request(self.web.REQUEST_INDIVIDUAL_FILE_CANCELED, path, {
+ 'id': download_id
+ })
fp.close()
diff --git a/onionshare/web/web.py b/onionshare/web/web.py
index 8d5a6af5..5a96b324 100644
--- a/onionshare/web/web.py
+++ b/onionshare/web/web.py
@@ -37,15 +37,18 @@ class Web:
REQUEST_LOAD = 0
REQUEST_STARTED = 1
REQUEST_PROGRESS = 2
- REQUEST_OTHER = 3
- REQUEST_CANCELED = 4
- REQUEST_RATE_LIMIT = 5
- REQUEST_UPLOAD_FILE_RENAMED = 6
- REQUEST_UPLOAD_SET_DIR = 7
- REQUEST_UPLOAD_FINISHED = 8
- REQUEST_UPLOAD_CANCELED = 9
- REQUEST_ERROR_DATA_DIR_CANNOT_CREATE = 10
- REQUEST_INVALID_PASSWORD = 11
+ 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
def __init__(self, common, is_gui, mode='share'):
self.common = common
@@ -193,15 +196,18 @@ class Web:
r = make_response(render_template('401.html', static_url_path=self.static_url_path), 401)
return self.add_security_headers(r)
- def error404(self):
+ def error403(self):
self.add_request(Web.REQUEST_OTHER, request.path)
- r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404)
+ r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403)
return self.add_security_headers(r)
- def error403(self):
+ def error404(self):
self.add_request(Web.REQUEST_OTHER, request.path)
+ r = make_response(render_template('404.html', static_url_path=self.static_url_path), 404)
+ return self.add_security_headers(r)
- r = make_response(render_template('403.html', static_url_path=self.static_url_path), 403)
+ def error405(self):
+ r = make_response(render_template('405.html', static_url_path=self.static_url_path), 405)
return self.add_security_headers(r)
def add_security_headers(self, r):
diff --git a/onionshare_gui/mode/__init__.py b/onionshare_gui/mode/__init__.py
index e92e36f8..b5a95f41 100644
--- a/onionshare_gui/mode/__init__.py
+++ b/onionshare_gui/mode/__init__.py
@@ -22,6 +22,8 @@ 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
@@ -29,7 +31,7 @@ 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()
@@ -417,3 +419,46 @@ 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.
+ """
+ item = IndividualFileHistoryItem(self.common, event["data"], event["path"])
+ self.history.add(event["data"]["id"], item)
+ self.toggle_history.update_indicator(True)
+ self.history.in_progress_count += 1
+ self.history.update_in_progress()
+
+ 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"])
+
+ # Is the download complete?
+ if event["data"]["bytes"] == self.web.share_mode.filesize:
+ # Update completed and in progress labels
+ self.history.completed_count += 1
+ self.history.in_progress_count -= 1
+ self.history.update_completed()
+ self.history.update_in_progress()
+
+ else:
+ if self.server_status.status == self.server_status.STATUS_STOPPED:
+ self.history.cancel(event["data"]["id"])
+ self.history.in_progress_count = 0
+ self.history.update_in_progress()
+
+ 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"])
+
+ # Update in progress count
+ self.history.in_progress_count -= 1
+ self.history.update_in_progress()
diff --git a/onionshare_gui/mode/history.py b/onionshare_gui/mode/history.py
index c2c696fc..a9fbbb36 100644
--- a/onionshare_gui/mode/history.py
+++ b/onionshare_gui/mode/history.py
@@ -345,62 +345,89 @@ class IndividualFileHistoryItem(HistoryItem):
"""
Individual file history item, for share mode viewing of individual files
"""
- def __init__(self, common, path):
+ def __init__(self, common, data, path):
super(IndividualFileHistoryItem, self).__init__()
self.status = HistoryItem.STATUS_STARTED
self.common = common
- self.visited = time.time()
- self.visited_dt = datetime.fromtimestamp(self.visited)
+ self.id = id
+ self.path = path
+ self.method = data['method']
+ self.total_bytes = data['filesize']
+ self.downloaded_bytes = 0
+ self.started = time.time()
+ self.started_dt = datetime.fromtimestamp(self.started)
+ self.status = HistoryItem.STATUS_STARTED
# Labels
- self.timestamp_label = QtWidgets.QLabel(self.visited_dt.strftime("%b %d, %I:%M%p"))
- self.path_viewed_label = QtWidgets.QLabel(strings._('gui_individual_file_download').format(path))
+ self.timestamp_label = QtWidgets.QLabel(self.started_dt.strftime("%b %d, %I:%M%p"))
+ self.method_label = QtWidgets.QLabel("{} {}".format(self.method, self.path))
+ self.status_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.setMinimum(0)
+ self.progress_bar.setMaximum(data['filesize'])
+ self.progress_bar.setValue(0)
+ self.progress_bar.setStyleSheet(self.common.css['downloads_uploads_progress_bar'])
+ self.progress_bar.total_bytes = data['filesize']
+
+ # Text layout
+ labels_layout = QtWidgets.QHBoxLayout()
+ labels_layout.addWidget(self.timestamp_label)
+ labels_layout.addWidget(self.method_label)
+ labels_layout.addWidget(self.status_label)
# Layout
layout = QtWidgets.QVBoxLayout()
- layout.addWidget(self.timestamp_label)
- layout.addWidget(self.path_viewed_label)
+ layout.addLayout(labels_layout)
+ layout.addWidget(self.progress_bar)
self.setLayout(layout)
+ # All non-GET requests are error 405 Method Not Allowed
+ if self.method.lower() != 'get':
+ self.status_label.setText("405")
+ self.progress_bar.hide()
+ else:
+ # Start at 0
+ self.update(0)
- def update(self):
- self.label.setText(self.get_finished_label_text(self.started_dt))
- self.status = HistoryItem.STATUS_FINISHED
-
- def cancel(self):
- self.progress_bar.setFormat(strings._('gui_canceled'))
- self.status = HistoryItem.STATUS_CANCELED
-
-class VisitHistoryItem(HistoryItem):
- """
- Download history item, for share mode
- """
- def __init__(self, common, id, total_bytes):
- super(VisitHistoryItem, self).__init__()
- self.status = HistoryItem.STATUS_STARTED
- self.common = common
-
- self.id = id
- self.visited = time.time()
- self.visited_dt = datetime.fromtimestamp(self.visited)
+ def update(self, downloaded_bytes):
+ self.downloaded_bytes = downloaded_bytes
- # Label
- self.label = QtWidgets.QLabel(strings._('gui_visit_started').format(self.visited_dt.strftime("%b %d, %I:%M%p")))
+ self.progress_bar.setValue(downloaded_bytes)
+ if downloaded_bytes == self.progress_bar.total_bytes:
+ self.progress_bar.hide()
+ self.status = HistoryItem.STATUS_FINISHED
- # Layout
- layout = QtWidgets.QVBoxLayout()
- layout.addWidget(self.label)
- self.setLayout(layout)
+ 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)
- def update(self):
- self.label.setText(self.get_finished_label_text(self.started_dt))
- self.status = HistoryItem.STATUS_FINISHED
+ 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
diff --git a/onionshare_gui/mode/share_mode/__init__.py b/onionshare_gui/mode/share_mode/__init__.py
index 56aa1364..b5da0cd3 100644
--- a/onionshare_gui/mode/share_mode/__init__.py
+++ b/onionshare_gui/mode/share_mode/__init__.py
@@ -225,21 +225,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'))
- if not self.common.settings.get('close_after_first_download') and not event["path"].startswith(('/favicon.ico', '/download', self.web.static_url_path)) and event["path"] != '/':
-
- item = IndividualFileHistoryItem(self.common, event["path"])
-
- self.history.add(0, item)
- self.toggle_history.update_indicator(True)
- self.history.completed_count += 1
- self.history.update_completed()
- self.system_tray.showMessage(strings._('systray_individual_file_downloaded_title'), strings._('systray_individual_file_downloaded_message').format(event["path"]))
-
def handle_request_started(self, event):
"""
Handle REQUEST_STARTED event.
diff --git a/onionshare_gui/mode/website_mode/__init__.py b/onionshare_gui/mode/website_mode/__init__.py
index 8ac88c8c..3d4497f0 100644
--- a/onionshare_gui/mode/website_mode/__init__.py
+++ b/onionshare_gui/mode/website_mode/__init__.py
@@ -30,7 +30,7 @@ from onionshare.web import Web
from ..file_selection import FileSelection
from .. import Mode
-from ..history import History, ToggleHistory, VisitHistoryItem
+from ..history import History, ToggleHistory
from ...widgets import Alert
class WebsiteMode(Mode):
@@ -204,21 +204,6 @@ class WebsiteMode(Mode):
"""
self.system_tray.showMessage(strings._('systray_site_loaded_title'), strings._('systray_site_loaded_message'))
- def handle_request_started(self, event):
- """
- Handle REQUEST_STARTED event.
- """
- if ( (event["path"] == '') or (event["path"].find(".htm") != -1 ) ):
- item = VisitHistoryItem(self.common, event["data"]["id"], 0)
-
- self.history.add(event["data"]["id"], item)
- self.toggle_history.update_indicator(True)
- self.history.completed_count += 1
- self.history.update_completed()
-
- self.system_tray.showMessage(strings._('systray_website_started_title'), strings._('systray_website_started_message'))
-
-
def on_reload_settings(self):
"""
If there were some files listed for sharing, we should be ok to re-enable
diff --git a/onionshare_gui/onionshare_gui.py b/onionshare_gui/onionshare_gui.py
index bed86895..20873bc8 100644
--- a/onionshare_gui/onionshare_gui.py
+++ b/onionshare_gui/onionshare_gui.py
@@ -383,7 +383,7 @@ class OnionShareGui(QtWidgets.QMainWindow):
self.share_mode.server_status.autostart_timer_container.hide()
self.receive_mode.server_status.autostart_timer_container.hide()
self.website_mode.server_status.autostart_timer_container.hide()
-
+
d = SettingsDialog(self.common, self.onion, self.qtapp, self.config, self.local_only)
d.settings_saved.connect(reload_settings)
d.exec_()
@@ -470,6 +470,15 @@ class OnionShareGui(QtWidgets.QMainWindow):
elif event["type"] == Web.REQUEST_UPLOAD_CANCELED:
mode.handle_request_upload_canceled(event)
+ elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_STARTED:
+ mode.handle_request_individual_file_started(event)
+
+ elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_PROGRESS:
+ mode.handle_request_individual_file_progress(event)
+
+ elif event["type"] == Web.REQUEST_INDIVIDUAL_FILE_CANCELED:
+ mode.handle_request_individual_file_canceled(event)
+
if event["type"] == Web.REQUEST_ERROR_DATA_DIR_CANNOT_CREATE:
Alert(self.common, strings._('error_cannot_create_data_dir').format(event["data"]["receive_mode_dir"]))
diff --git a/share/locale/en.json b/share/locale/en.json
index c26577b2..5fbf88f9 100644
--- a/share/locale/en.json
+++ b/share/locale/en.json
@@ -178,7 +178,6 @@
"gui_receive_mode_no_files": "No Files Received Yet",
"gui_receive_mode_autostop_timer_waiting": "Waiting to finish receiving",
"gui_visit_started": "Someone has visited your website {}",
- "gui_individual_file_download": "Viewed {}",
"receive_mode_upload_starting": "Upload of total size {} is starting",
"days_first_letter": "d",
"hours_first_letter": "h",
diff --git a/share/templates/405.html b/share/templates/405.html
new file mode 100644
index 00000000..55493ae7
--- /dev/null
+++ b/share/templates/405.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>OnionShare: 405 Method Not Allowed</title>
+ <link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
+ <link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
+</head>
+
+<body>
+ <div class="info-wrapper">
+ <div class="info">
+ <p><img class="logo" src="{{ static_url_path }}/img/logo_large.png" title="OnionShare"></p>
+ <p class="info-header">405 Method Not Allowed</p>
+ </div>
+ </div>
+</body>
+
+</html>