Rewrote controller.Files.upload_file() not to use a CherryPy-2.1-style file upload filter.
Now releasing session lock before streaming the file to prevent session deadlock in the event of a cancelled upload.
This commit is contained in:
parent
362a125b1b
commit
04f86f05a6
|
@ -1,4 +1,5 @@
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
import types
|
||||||
|
|
||||||
# module-level variable that, when set to a view, overrides the view for all exposed methods. used
|
# module-level variable that, when set to a view, overrides the view for all exposed methods. used
|
||||||
# by unit tests
|
# by unit tests
|
||||||
|
@ -64,7 +65,7 @@ def expose( view = None, rss = None ):
|
||||||
result = dict( error = u"An error occurred when processing your request. Please try again or contact support." )
|
result = dict( error = u"An error occurred when processing your request. Please try again or contact support." )
|
||||||
|
|
||||||
# if the result is a generator, it's streaming data, so just let CherryPy handle it
|
# if the result is a generator, it's streaming data, so just let CherryPy handle it
|
||||||
if hasattr( result, "gi_running" ):
|
if isinstance( result, types.GeneratorType ):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
redirect = result.get( u"redirect", None )
|
redirect = result.get( u"redirect", None )
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import cgi
|
import cgi
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy.filters import basefilter
|
|
||||||
from Expose import expose
|
from Expose import expose
|
||||||
from Validate import validate
|
from Validate import validate
|
||||||
from Database import Valid_id
|
from Database import Valid_id
|
||||||
|
@ -37,21 +36,7 @@ class Upload_error( Exception ):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class File_upload_filter( basefilter.BaseFilter ):
|
|
||||||
def before_request_body( self ):
|
|
||||||
if cherrypy.request.path != "/files/upload_file":
|
|
||||||
return
|
|
||||||
|
|
||||||
if cherrypy.request.method != "POST":
|
|
||||||
raise Upload_error()
|
|
||||||
|
|
||||||
# tell CherryPy not to parse the POST data itself for this URL
|
|
||||||
cherrypy.request.processRequestBody = False
|
|
||||||
|
|
||||||
|
|
||||||
class Files( object ):
|
class Files( object ):
|
||||||
_cpFilterList = [ File_upload_filter() ]
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Controller for dealing with uploaded files, corresponding to the "/files" URL.
|
Controller for dealing with uploaded files, corresponding to the "/files" URL.
|
||||||
"""
|
"""
|
||||||
|
@ -94,20 +79,31 @@ class Files( object ):
|
||||||
@strongly_expire
|
@strongly_expire
|
||||||
@grab_user_id
|
@grab_user_id
|
||||||
@validate(
|
@validate(
|
||||||
|
upload = (),
|
||||||
|
notebook_id = Valid_id(),
|
||||||
|
note_id = Valid_id(),
|
||||||
user_id = Valid_id( none_okay = True ),
|
user_id = Valid_id( none_okay = True ),
|
||||||
)
|
)
|
||||||
def upload_file( self, user_id ):
|
def upload_file( self, upload, notebook_id, note_id, user_id ):
|
||||||
"""
|
"""
|
||||||
Upload a file from the client for attachment to a particular note.
|
Upload a file from the client for attachment to a particular note.
|
||||||
|
|
||||||
|
@type upload: cgi.FieldStorage
|
||||||
|
@param upload: file handle to uploaded file
|
||||||
@type notebook_id: unicode
|
@type notebook_id: unicode
|
||||||
@param notebook_id: id of the notebook that the upload is to
|
@param notebook_id: id of the notebook that the upload is to
|
||||||
@type note_id: unicode
|
@type note_id: unicode
|
||||||
@param note_id: id of the note that the upload is to
|
@param note_id: id of the note that the upload is to
|
||||||
@raise Access_error: the current user doesn't have access to the given notebook or note
|
@raise Access_error: the current user doesn't have access to the given notebook or note
|
||||||
|
@raise Upload_error: an error occurred when processing the uploaded file
|
||||||
|
@type user_id: unicode or NoneType
|
||||||
|
@param user_id: id of current logged-in user (if any)
|
||||||
@rtype: unicode
|
@rtype: unicode
|
||||||
@return: rendered HTML page
|
@return: rendered HTML page
|
||||||
"""
|
"""
|
||||||
|
if not self.__users.check_access( user_id, notebook_id ):
|
||||||
|
raise Access_error()
|
||||||
|
|
||||||
cherrypy.server.max_request_body_size = 0 # remove file size limit of 100 MB
|
cherrypy.server.max_request_body_size = 0 # remove file size limit of 100 MB
|
||||||
cherrypy.response.timeout = 3600 # increase upload timeout to one hour (default is 5 min)
|
cherrypy.response.timeout = 3600 # increase upload timeout to one hour (default is 5 min)
|
||||||
cherrypy.server.socket_timeout = 60 # increase socket timeout to one minute (default is 10 sec)
|
cherrypy.server.socket_timeout = 60 # increase socket timeout to one minute (default is 10 sec)
|
||||||
|
@ -125,15 +121,8 @@ class Files( object ):
|
||||||
if file_size <= 0:
|
if file_size <= 0:
|
||||||
raise Upload_error()
|
raise Upload_error()
|
||||||
|
|
||||||
parsed_form = cgi.FieldStorage( fp = cherrypy.request.rfile, headers = headers, environ = { "REQUEST_METHOD": "POST" }, keep_blank_values = 1)
|
|
||||||
upload = parsed_form[ u"file" ]
|
|
||||||
notebook_id = parsed_form.getvalue( u"notebook_id" )
|
|
||||||
note_id = parsed_form.getvalue( u"note_id" )
|
|
||||||
filename = upload.filename.strip()
|
filename = upload.filename.strip()
|
||||||
|
|
||||||
if not self.__users.check_access( user_id, notebook_id ):
|
|
||||||
raise Access_error()
|
|
||||||
|
|
||||||
def process_upload():
|
def process_upload():
|
||||||
"""
|
"""
|
||||||
Process the file upload while streaming a progress meter as it uploads.
|
Process the file upload while streaming a progress meter as it uploads.
|
||||||
|
@ -222,4 +211,8 @@ class Files( object ):
|
||||||
upload.file.close()
|
upload.file.close()
|
||||||
cherrypy.request.rfile.close()
|
cherrypy.request.rfile.close()
|
||||||
|
|
||||||
|
# release the session lock before beginning the upload, because if the upload is cancelled
|
||||||
|
# before it's done, the lock won't be released
|
||||||
|
cherrypy.session.release_lock()
|
||||||
|
|
||||||
return process_upload()
|
return process_upload()
|
||||||
|
|
|
@ -2258,7 +2258,7 @@ Upload_pulldown.prototype.init_frame = function () {
|
||||||
withDocument( doc, function () {
|
withDocument( doc, function () {
|
||||||
connect( "upload_button", "onclick", function ( event ) {
|
connect( "upload_button", "onclick", function ( event ) {
|
||||||
withDocument( doc, function () {
|
withDocument( doc, function () {
|
||||||
self.upload_started( getElement( "file" ).value );
|
self.upload_started( getElement( "upload" ).value );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Upload_page( Html ):
|
||||||
Body(
|
Body(
|
||||||
Form(
|
Form(
|
||||||
Span( u"attach file: ", class_ = u"field_label" ),
|
Span( u"attach file: ", class_ = u"field_label" ),
|
||||||
Input( type = u"file", id = u"file", name = u"file", class_ = "text_field", size = u"30" ),
|
Input( type = u"file", id = u"upload", name = u"upload", class_ = "text_field", size = u"30" ),
|
||||||
Input( type = u"submit", id = u"upload_button", class_ = u"button", value = u"upload" ),
|
Input( type = u"submit", id = u"upload_button", class_ = u"button", value = u"upload" ),
|
||||||
Input( type = u"hidden", id = u"notebook_id", name = u"notebook_id", value = notebook_id ),
|
Input( type = u"hidden", id = u"notebook_id", name = u"notebook_id", value = notebook_id ),
|
||||||
Input( type = u"hidden", id = u"note_id", name = u"note_id", value = note_id ),
|
Input( type = u"hidden", id = u"note_id", name = u"note_id", value = note_id ),
|
||||||
|
|
Reference in New Issue