witten
/
luminotes
Archived
1
0
Fork 0

Implemented quota enforcement when uploading a file. This occurs in two places:

1. In progress(), around the time when the file starts uploading. This causes
an upload that's too large to bail before the whole file uploads, but the
quota calculation is only an estimate and relies on the client actually
calling progress().

2. In upload(), when the file finishes uploading. This quota calculation is
exact, but only happens after the entire upload completes.
This commit is contained in:
Dan Helfman 2008-02-20 20:21:54 +00:00
parent 3d5baa1e24
commit fd0e91ea39
5 changed files with 103 additions and 24 deletions

View File

@ -14,7 +14,7 @@ from model.User import User
from view.Upload_page import Upload_page
from view.Blank_page import Blank_page
from view.Json import Json
from view.Progress_bar import stream_progress
from view.Progress_bar import stream_progress, stream_quota_error, stop_upload_script
class Access_error( Exception ):
@ -126,14 +126,12 @@ class FieldStorage( cherrypy._cpcgifs.FieldStorage ):
@type binary: NoneType
@param binary: ignored
@type user_id: unicode or NoneType
@param user_id: id of current logged-in user (if any)
@rtype: Upload_file
@return: wrapped temporary file used to store the upload
@raise Upload_error: the provided file_id value is invalid, or the filename or Content-Length is
missing
"""
def make_file( self, binary = None, user_id = None ):
def make_file( self, binary = None ):
global current_uploads, current_uploads_lock
cherrypy.server.max_request_body_size = 0 # remove CherryPy default file size limit of 100 MB
@ -256,7 +254,6 @@ class Files( object ):
return stream()
@expose( view = Upload_page )
@strongly_expire
@grab_user_id
@ -327,6 +324,12 @@ class Files( object ):
if not uploaded_file:
raise Upload_error()
current_uploads_lock.acquire()
try:
del( current_uploads[ file_id ] )
finally:
current_uploads_lock.release()
if not self.__users.check_access( user_id, notebook_id, read_write = True ):
uploaded_file.delete()
raise Access_error()
@ -336,7 +339,18 @@ class Files( object ):
# if we didn't receive all of the expected data, abort
if uploaded_file.total_received_bytes < uploaded_file.content_length:
uploaded_file.delete()
raise Upload_error( "The upload did not complete." )
raise Upload_error( u"The file did not complete uploading." )
user = self.__database.load( User, user_id )
if not user:
uploaded_file.delete()
raise Access_error()
# if the uploaded file's size would put the user over quota, bail and inform the user
rate_plan = self.__users.rate_plan( user.rate_plan )
if user.storage_bytes + uploaded_file.total_received_bytes > rate_plan.get( u"storage_quota_bytes", 0 ):
uploaded_file.delete()
return dict( script = stop_upload_script )
# record metadata on the upload in the database
db_file = File.create( file_id, notebook_id, note_id, uploaded_file.filename, uploaded_file.file_received_bytes, content_type )
@ -345,21 +359,17 @@ class Files( object ):
self.__database.commit()
uploaded_file.close()
current_uploads_lock.acquire()
try:
del( current_uploads[ file_id ] )
finally:
current_uploads_lock.release()
return dict()
@expose()
@strongly_expire
@grab_user_id
@validate(
file_id = Valid_id(),
filename = unicode,
user_id = Valid_id( none_okay = True ),
)
def progress( self, file_id, filename ):
def progress( self, file_id, filename, user_id = None ):
"""
Stream information on a file that is in the process of being uploaded. This method does not
perform any access checks, but the only information streamed is a progress bar and upload
@ -369,6 +379,8 @@ class Files( object ):
@param file_id: id of a currently uploading file
@type filename: unicode
@param filename: name of the file to report on
@type user_id: unicode or NoneType
@param user_id: id of current logged-in user (if any)
@rtype: unicode
@return: streaming HTML progress bar
"""
@ -395,6 +407,19 @@ class Files( object ):
fraction_reported = 1.0
break
# if the uploaded file's size would put the user over quota, bail and inform the user
if uploading_file:
SOFT_QUOTA_FACTOR = 1.05 # fudge factor since content_length isn't really the file's actual size
user = self.__database.load( User, user_id )
if not user:
raise Access_error()
rate_plan = self.__users.rate_plan( user.rate_plan )
if user.storage_bytes + uploading_file.content_length > rate_plan.get( u"storage_quota_bytes", 0 ) * SOFT_QUOTA_FACTOR:
return stream_quota_error()
return stream_progress( uploading_file, filename, fraction_reported )
@expose( view = Json )
@ -427,6 +452,8 @@ class Files( object ):
raise Access_error()
user = self.__database.load( User, user_id )
if not user:
raise Access_error()
return dict(
filename = db_file.filename,
@ -434,5 +461,8 @@ class Files( object ):
storage_bytes = user.storage_bytes,
)
def rename( file_id, filename ):
def delete( self, file_id ):
pass # TODO
def rename( self, file_id, filename ):
pass # TODO

View File

@ -1130,3 +1130,6 @@ class Users( object ):
result[ "invites" ] = []
return result
def rate_plan( self, plan_index ):
return self.__rate_plans[ plan_index ]

View File

@ -141,7 +141,7 @@ Wiki.prototype.display_storage_usage = function( storage_bytes ) {
this.display_message(
"You are currently using " +
usage_percent +
"% of your available storage space. Please delete some notes, empty the trash, or",
"% of your available storage space. Please delete some notes or files, empty the trash, or",
[ createDOM( "a", { "href": "/upgrade" }, "upgrade" ), " your account." ]
);
this.storage_usage_high = true;
@ -2352,19 +2352,28 @@ Upload_pulldown.prototype.update_position = function ( anchor, relative_to ) {
Pulldown.prototype.update_position.call( this, anchor, relative_to );
}
Upload_pulldown.prototype.shutdown = function ( force ) {
Upload_pulldown.prototype.shutdown = function ( force, display_quota_error ) {
// if there's an upload in progress and the force flag is not set, then bail without performing a
// shutdown
if ( this.uploading ) {
if ( force )
this.wiki.display_message( "The file upload has been cancelled." )
else
if ( force ) {
if ( !display_quota_error )
this.wiki.display_message( "The file upload has been cancelled." )
} else {
return;
}
}
Pulldown.prototype.shutdown.call( this );
if ( this.link )
this.link.pulldown = null;
if ( display_quota_error ) {
this.wiki.display_message(
"That file is too large for your available storage space. Before uploading, please delete some notes or files, empty the trash, or",
[ createDOM( "a", { "href": "/upgrade" }, "upgrade" ), " your account." ]
);
}
}
function File_link_pulldown( wiki, notebook_id, invoker, editor, link ) {

View File

@ -1,8 +1,21 @@
from Tags import Html
from Tags import Html, Head, Body, Script
class Blank_page( Html ):
def __init__( self ):
Html.__init__(
self,
)
def __init__( self, script = None ):
if script:
Html.__init__(
self,
Head(
Script( type = u"text/javascript", src = u"/static/js/MochiKit.js" ),
),
Body(
Script( script, type = u"text/javascript" ),
),
)
else:
Html.__init__(
self,
Head(),
Body(),
)

View File

@ -73,3 +73,27 @@ def stream_progress( uploading_file, filename, fraction_reported ):
</body>
</html>
"""
stop_upload_script = \
"""
withDocument( window.parent.document, function () { getElement( 'upload_frame' ).pulldown.shutdown( true, true ); } );
"""
def stream_quota_error():
yield \
u"""
<html>
<head>
<link href="/static/css/upload.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="/static/js/MochiKit.js"></script>
<meta content="text/html; charset=UTF-8" http_equiv="content-type" />
</head>
<body>
<script type="text/javascript">
%s
</script>
</body>
</html>
""" % stop_upload_script