For receive mode, use a custom flask Request, and a custom TemporaryFile, in order to keep track of file upload progress

This commit is contained in:
Micah Lee 2018-03-21 17:34:11 -07:00
parent aafa9b1543
commit facd441caf
No known key found for this signature in database
GPG Key ID: 403C2657CD994F73

View File

@ -28,12 +28,13 @@ import sys
import tempfile import tempfile
import zipfile import zipfile
import re import re
import io
from distutils.version import LooseVersion as Version from distutils.version import LooseVersion as Version
from urllib.request import urlopen from urllib.request import urlopen
from flask import ( from flask import (
Flask, Response, request, render_template, abort, make_response, flash, Flask, Response, Request, request, render_template, abort, make_response,
redirect, __version__ as flask_version flash, redirect, __version__ as flask_version
) )
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
@ -66,7 +67,9 @@ class Web(object):
self.receive_mode = receive_mode self.receive_mode = receive_mode
if self.receive_mode: if self.receive_mode:
# In receive mode, use WSGI middleware to track the progess of upload POSTs # In receive mode, use WSGI middleware to track the progess of upload POSTs
self.app.wsgi_app = UploadProgessMiddleware(self.app.wsgi_app, self) self.app.wsgi_app = UploadProgessMiddleware(self.common, self.app.wsgi_app, self)
# Use a custom Request class
self.app.request_class = ReceiveModeRequest
# Starting in Flask 0.11, render_template_string autoescapes template variables # Starting in Flask 0.11, render_template_string autoescapes template variables
# by default. To prevent content injection through template variables in # by default. To prevent content injection through template variables in
@ -535,7 +538,8 @@ class ZipWriter(object):
class UploadProgessMiddleware(object): class UploadProgessMiddleware(object):
def __init__(self, app, web): def __init__(self, common, app, web):
self.common = common
self.app = app self.app = app
self.web = web self.web = web
@ -553,7 +557,68 @@ class UploadProgessMiddleware(object):
# If this is a valid upload request, stream the upload # If this is a valid upload request, stream the upload
if valid_upload_request: if valid_upload_request:
#print(environ.get('wsgi.input')) length = environ.get('CONTENT_LENGTH')
pass self.common.log('UploadProgessMiddleware', 'upload started, {} bytes'.format(length))
return self.app(environ, start_response) return self.app(environ, start_response)
class ReceiveModeTemporaryFile(object):
"""
A custom TemporaryFile that tells ReceiveModeRequest every time data gets
written to it, in order to track the progress of uploads.
"""
def __init__(self, filename, update_func):
self.onionshare_filename = filename
self.onionshare_update_func = update_func
# Create a temporary file
self.f = tempfile.TemporaryFile('wb+')
# Make all the file-like methods and attributes actually access the
# TemporaryFile, except for write
attrs = ['close', 'closed', 'detach', 'fileno', 'flush', 'isatty', 'mode',
'name', 'peek', 'raw', 'read', 'read1', 'readable', 'readinto',
'readinto1', 'readline', 'readlines', 'seek', 'seekable', 'tell',
'truncate', 'writable', 'writelines']
for attr in attrs:
setattr(self, attr, getattr(self.f, attr))
def write(self, b):
"""
Custom write method that calls out to onionshare_update_func
"""
bytes_written = self.f.write(b)
self.onionshare_update_func(self.onionshare_filename, bytes_written)
class ReceiveModeRequest(Request):
"""
A custom flask Request object that keeps track of how much data has been
uploaded for each file, for receive mode.
"""
def __init__(self, environ, populate_request=True, shallow=False):
super(ReceiveModeRequest, self).__init__(environ, populate_request, shallow)
# The total size of this request, which may include multiple files
self.total_content_length = 0
# A dictionary that maps filenames to the bytes uploaded so far
self.onionshare_progress = {}
def _get_file_stream(self, total_content_length, content_type, filename=None, content_length=None):
"""
This gets called for each file that gets uploaded, and returns an file-like
writable stream.
"""
print('')
self.total_content_length = total_content_length
self.onionshare_progress[filename] = 0
return ReceiveModeTemporaryFile(filename, self.onionshare_update_func)
def onionshare_update_func(self, filename, length):
"""
Keep track of the bytes uploaded so far for all files.
"""
self.onionshare_progress[filename] += length
print('\r{}: {} '.format(filename, self.onionshare_progress[filename]), end='')