witten
/
luminotes
Archived
1
0
Fork 0

Lots of work on user storage quotas:

* Each model.User now has a current storage bytes and a rate plan.
 * model.User.to_dict() updated accordingly.
 * Minor Scheduler.add() change to bail of the given thread is None.
 * controller.Users.current() returns current user's rate plan details.
 * controller.Users.update_storage() now takes an optional callback.
 * Various methods in controller.Notebooks responsible for calling controller.Users.update_storage().
 * Added rate plan details to config/Common.py.
 * Added quota utilization colors to style.css.
 * Implemented quota utilization calculation and display in Wiki.js.

Still to-do: Return updated storage bytes where appropriate in controller.Notebook and update the
client accordingly.
This commit is contained in:
Dan Helfman 2007-09-20 20:36:19 +00:00
parent ebf1538313
commit 4d736d4821
13 changed files with 243 additions and 17 deletions

View File

@ -1,6 +1,9 @@
import cherrypy import cherrypy
MEGABYTE = 1024 * 1024
settings = { settings = {
"global": { "global": {
"server.socket_port": 8081, "server.socket_port": 8081,
@ -20,5 +23,19 @@ settings = {
"luminotes.http_proxy_ip": "127.0.0.1", "luminotes.http_proxy_ip": "127.0.0.1",
"luminotes.https_proxy_ip": "127.0.0.2", "luminotes.https_proxy_ip": "127.0.0.2",
"luminotes.support_email": "support@luminotes.com", "luminotes.support_email": "support@luminotes.com",
"luminotes.rate_plans": [
{
"name": "basic",
"storage_quota_bytes": 30 * MEGABYTE,
},
{
"name": "standard",
"storage_quota_bytes": 100 * MEGABYTE,
},
{
"name": "professional",
"storage_quota_bytes": 300 * MEGABYTE,
},
],
}, },
} }

View File

@ -33,7 +33,7 @@ class Notebooks( object ):
""" """
Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL. Controller for dealing with notebooks and their notes, corresponding to the "/notebooks" URL.
""" """
def __init__( self, scheduler, database ): def __init__( self, scheduler, database, users ):
""" """
Create a new Notebooks object. Create a new Notebooks object.
@ -41,11 +41,14 @@ class Notebooks( object ):
@param scheduler: scheduler to use for asynchronous calls @param scheduler: scheduler to use for asynchronous calls
@type database: controller.Database @type database: controller.Database
@param database: database that notebooks are stored in @param database: database that notebooks are stored in
@type users: controller.Users
@param users: controller for all users, used here for updating storage utilization
@rtype: Notebooks @rtype: Notebooks
@return: newly constructed Notebooks @return: newly constructed Notebooks
""" """
self.__scheduler = scheduler self.__scheduler = scheduler
self.__database = database self.__database = database
self.__users = users
@expose( view = Main_page ) @expose( view = Main_page )
@validate( @validate(
@ -363,7 +366,11 @@ class Notebooks( object ):
startup_changed = notebook.remove_startup_note( note ) startup_changed = notebook.remove_startup_note( note )
if new_revision or startup_changed: if new_revision or startup_changed:
self.__database.save( notebook ) self.__database.save( notebook, self.__scheduler.thread )
yield Scheduler.SLEEP
self.__users.update_storage( user_id, self.__scheduler.thread )
user = ( yield Scheduler.SLEEP )
self.__database.save( user )
yield dict( yield dict(
new_revision = new_revision, new_revision = new_revision,
@ -411,7 +418,11 @@ class Notebooks( object ):
if note: if note:
notebook.add_startup_note( note ) notebook.add_startup_note( note )
self.__database.save( notebook ) self.__database.save( notebook, self.__scheduler.thread )
yield Scheduler.SLEEP
self.__users.update_storage( user_id, self.__scheduler.thread )
user = ( yield Scheduler.SLEEP )
self.__database.save( user )
yield dict() yield dict()
@ -456,7 +467,11 @@ class Notebooks( object ):
if note: if note:
notebook.remove_startup_note( note ) notebook.remove_startup_note( note )
self.__database.save( notebook ) self.__database.save( notebook, self.__scheduler.thread )
yield Scheduler.SLEEP
self.__users.update_storage( user_id, self.__scheduler.thread )
user = ( yield Scheduler.SLEEP )
self.__database.save( user )
yield dict() yield dict()
@ -508,7 +523,11 @@ class Notebooks( object ):
notebook.trash.add_note( note ) notebook.trash.add_note( note )
notebook.trash.add_startup_note( note ) notebook.trash.add_startup_note( note )
self.__database.save( notebook ) self.__database.save( notebook, self.__scheduler.thread )
yield Scheduler.SLEEP
self.__users.update_storage( user_id, self.__scheduler.thread )
user = ( yield Scheduler.SLEEP )
self.__database.save( user )
yield dict() yield dict()
@ -567,7 +586,11 @@ class Notebooks( object ):
notebook.add_note( note ) notebook.add_note( note )
notebook.add_startup_note( note ) notebook.add_startup_note( note )
self.__database.save( notebook ) self.__database.save( notebook, self.__scheduler.thread )
yield Scheduler.SLEEP
self.__users.update_storage( user_id, self.__scheduler.thread )
user = ( yield Scheduler.SLEEP )
self.__database.save( user )
yield dict() yield dict()
@ -613,7 +636,11 @@ class Notebooks( object ):
notebook.trash.add_note( note ) notebook.trash.add_note( note )
notebook.trash.add_startup_note( note ) notebook.trash.add_startup_note( note )
self.__database.save( notebook ) self.__database.save( notebook, self.__scheduler.thread )
yield Scheduler.SLEEP
self.__users.update_storage( user_id, self.__scheduler.thread )
user = ( yield Scheduler.SLEEP )
self.__database.save( user )
yield dict() yield dict()

View File

@ -33,13 +33,14 @@ class Root( object ):
self.__scheduler = scheduler self.__scheduler = scheduler
self.__database = database self.__database = database
self.__settings = settings self.__settings = settings
self.__notebooks = Notebooks( scheduler, database )
self.__users = Users( self.__users = Users(
scheduler, scheduler,
database, database,
settings[ u"global" ].get( u"luminotes.http_url", u"" ), settings[ u"global" ].get( u"luminotes.http_url", u"" ),
settings[ u"global" ].get( u"luminotes.https_url", u"" ), settings[ u"global" ].get( u"luminotes.https_url", u"" ),
settings[ u"global" ].get( u"luminotes.rate_plans", [] ),
) )
self.__notebooks = Notebooks( scheduler, database, self.__users )
@expose( view = Main_page ) @expose( view = Main_page )
def index( self ): def index( self ):

View File

@ -125,6 +125,9 @@ class Scheduler( object ):
@type args: tuple @type args: tuple
@param args: arguments to send() to the given thread when it is executed @param args: arguments to send() to the given thread when it is executed
""" """
if thread is None:
return
self.__idle.release() self.__idle.release()
if thread in self.__sleeping: if thread in self.__sleeping:

View File

@ -106,7 +106,7 @@ class Users( object ):
""" """
Controller for dealing with users, corresponding to the "/users" URL. Controller for dealing with users, corresponding to the "/users" URL.
""" """
def __init__( self, scheduler, database, http_url, https_url ): def __init__( self, scheduler, database, http_url, https_url, rate_plans ):
""" """
Create a new Users object. Create a new Users object.
@ -118,6 +118,8 @@ class Users( object ):
@param http_url: base URL to use for non-SSL http requests, or an empty string @param http_url: base URL to use for non-SSL http requests, or an empty string
@type https_url: unicode @type https_url: unicode
@param https_url: base URL to use for SSL http requests, or an empty string @param https_url: base URL to use for SSL http requests, or an empty string
@type rate_plans: [ { "name": unicode, "storage_quota_bytes": int } ]
@param rate_plans: list of configured rate plans
@rtype: Users @rtype: Users
@return: newly constructed Users @return: newly constructed Users
""" """
@ -125,6 +127,7 @@ class Users( object ):
self.__database = database self.__database = database
self.__http_url = http_url self.__http_url = http_url
self.__https_url = https_url self.__https_url = https_url
self.__rate_plans = rate_plans
@expose( view = Json ) @expose( view = Json )
@update_auth @update_auth
@ -275,7 +278,14 @@ class Users( object ):
@type user_id: unicode @type user_id: unicode
@param user_id: id of current logged-in user (if any), determined by @grab_user_id @param user_id: id of current logged-in user (if any), determined by @grab_user_id
@rtype: json dict @rtype: json dict
@return: { 'user': userdict or None, 'notebooks': notebooksdict, 'http_url': url } @return: {
'user': userdict or None,
'notebooks': notebooksdict,
'startup_notes': noteslist,
'http_url': url,
'login_url': url,
'rate_plan': rateplandict,
}
""" """
# if there's no logged-in user, default to the anonymous user # if there's no logged-in user, default to the anonymous user
self.__database.load( user_id or u"User anonymous", self.__scheduler.thread ) self.__database.load( user_id or u"User anonymous", self.__scheduler.thread )
@ -312,6 +322,7 @@ class Users( object ):
startup_notes = include_startup_notes and len( notebooks ) > 0 and notebooks[ 0 ].startup_notes or [], startup_notes = include_startup_notes and len( notebooks ) > 0 and notebooks[ 0 ].startup_notes or [],
http_url = self.__http_url, http_url = self.__http_url,
login_url = login_url, login_url = login_url,
rate_plan = ( user.rate_plan < len( self.__rate_plans ) ) and self.__rate_plans[ user.rate_plan ] or {},
) )
def calculate_storage( self, user ): def calculate_storage( self, user ):
@ -344,13 +355,20 @@ class Users( object ):
return total_bytes return total_bytes
@async @async
def update_storage( self, user ): def update_storage( self, user_id, callback = None ):
""" """
Calculate and record total storage utilization for the given user. Calculate and record total storage utilization for the given user.
@type user: User @type user_id: unicode or NoneType
@param user: user for which to calculate storage utilization @param user_id: id of user for which to calculate storage utilization
@type callback: generator or NoneType
@param callback: generator to wakeup when the update is complete (optional)
""" """
user.storage_bytes = self.calculate_storage( user ) self.__database.load( user_id, self.__scheduler.thread )
yield None user = ( yield Scheduler.SLEEP )
if user:
user.storage_bytes = self.calculate_storage( user )
yield callback, user
scheduler = property( lambda self: self.__scheduler ) scheduler = property( lambda self: self.__scheduler )

View File

@ -19,6 +19,16 @@ class Test_controller( object ):
u"luminotes.https_url" : u"https://luminotes.com", u"luminotes.https_url" : u"https://luminotes.com",
u"luminotes.http_proxy_ip" : u"127.0.0.1", u"luminotes.http_proxy_ip" : u"127.0.0.1",
u"luminotes.https_proxy_ip" : u"127.0.0.2", u"luminotes.https_proxy_ip" : u"127.0.0.2",
"luminotes.rate_plans": [
{
"name": "super",
"storage_quota_bytes": 1337,
},
{
"name": "extra super",
"storage_quota_bytes": 31337,
},
],
}, },
} }

View File

@ -66,12 +66,14 @@ class Test_notebooks( Test_controller ):
result = self.http_get( "/notebooks/%s" % self.notebook.object_id ) result = self.http_get( "/notebooks/%s" % self.notebook.object_id )
assert result.get( u"notebook_id" ) == self.notebook.object_id assert result.get( u"notebook_id" ) == self.notebook.object_id
assert self.user.storage_bytes == 0
def test_default_with_note( self ): def test_default_with_note( self ):
result = self.http_get( "/notebooks/%s?note_id=%s" % ( self.notebook.object_id, self.note.object_id ) ) result = self.http_get( "/notebooks/%s?note_id=%s" % ( self.notebook.object_id, self.note.object_id ) )
assert result.get( u"notebook_id" ) == self.notebook.object_id assert result.get( u"notebook_id" ) == self.notebook.object_id
assert result.get( u"note_id" ) == self.note.object_id assert result.get( u"note_id" ) == self.note.object_id
assert self.user.storage_bytes == 0
def test_default_with_note_and_revision( self ): def test_default_with_note_and_revision( self ):
result = self.http_get( "/notebooks/%s?note_id=%s&revision=%s" % ( result = self.http_get( "/notebooks/%s?note_id=%s&revision=%s" % (
@ -83,6 +85,7 @@ class Test_notebooks( Test_controller ):
assert result.get( u"notebook_id" ) == self.notebook.object_id assert result.get( u"notebook_id" ) == self.notebook.object_id
assert result.get( u"note_id" ) == self.note.object_id assert result.get( u"note_id" ) == self.note.object_id
assert result.get( u"revision" ) == unicode( self.note.revision ) assert result.get( u"revision" ) == unicode( self.note.revision )
assert self.user.storage_bytes == 0
def test_default_with_parent( self ): def test_default_with_parent( self ):
parent_id = "foo" parent_id = "foo"
@ -90,6 +93,7 @@ class Test_notebooks( Test_controller ):
assert result.get( u"notebook_id" ) == self.notebook.object_id assert result.get( u"notebook_id" ) == self.notebook.object_id
assert result.get( u"parent_id" ) == parent_id assert result.get( u"parent_id" ) == parent_id
assert self.user.storage_bytes == 0
def test_contents( self ): def test_contents( self ):
self.login() self.login()
@ -105,6 +109,7 @@ class Test_notebooks( Test_controller ):
assert notebook.object_id == self.notebook.object_id assert notebook.object_id == self.notebook.object_id
assert len( startup_notes ) == 1 assert len( startup_notes ) == 1
assert startup_notes[ 0 ] == self.note assert startup_notes[ 0 ] == self.note
assert self.user.storage_bytes == 0
def test_contents_with_note( self ): def test_contents_with_note( self ):
self.login() self.login()
@ -124,6 +129,7 @@ class Test_notebooks( Test_controller ):
note = result[ "note" ] note = result[ "note" ]
assert note.object_id == self.note.object_id assert note.object_id == self.note.object_id
assert self.user.storage_bytes == 0
def test_contents_with_note_and_revision( self ): def test_contents_with_note_and_revision( self ):
self.login() self.login()
@ -147,6 +153,7 @@ class Test_notebooks( Test_controller ):
note = result[ "note" ] note = result[ "note" ]
assert note.object_id == self.note.object_id assert note.object_id == self.note.object_id
assert self.user.storage_bytes == 0
def test_contents_without_login( self ): def test_contents_without_login( self ):
result = self.http_get( result = self.http_get(
@ -155,6 +162,7 @@ class Test_notebooks( Test_controller ):
) )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_load_note( self ): def test_load_note( self ):
self.login() self.login()
@ -169,6 +177,7 @@ class Test_notebooks( Test_controller ):
assert note.object_id == self.note.object_id assert note.object_id == self.note.object_id
assert note.title == self.note.title assert note.title == self.note.title
assert note.contents == self.note.contents assert note.contents == self.note.contents
assert self.user.storage_bytes == 0
def test_load_note_with_revision( self ): def test_load_note_with_revision( self ):
self.login() self.login()
@ -198,6 +207,7 @@ class Test_notebooks( Test_controller ):
assert note.revision == previous_revision assert note.revision == previous_revision
assert note.title == previous_title assert note.title == previous_title
assert note.contents == previous_contents assert note.contents == previous_contents
assert self.user.storage_bytes == 0
def test_load_note_without_login( self ): def test_load_note_without_login( self ):
result = self.http_post( "/notebooks/load_note/", dict( result = self.http_post( "/notebooks/load_note/", dict(
@ -216,6 +226,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_load_unknown_note( self ): def test_load_unknown_note( self ):
self.login() self.login()
@ -227,6 +238,7 @@ class Test_notebooks( Test_controller ):
note = result[ "note" ] note = result[ "note" ]
assert note == None assert note == None
assert self.user.storage_bytes == 0
def test_load_note_by_title( self ): def test_load_note_by_title( self ):
self.login() self.login()
@ -241,6 +253,7 @@ class Test_notebooks( Test_controller ):
assert note.object_id == self.note.object_id assert note.object_id == self.note.object_id
assert note.title == self.note.title assert note.title == self.note.title
assert note.contents == self.note.contents assert note.contents == self.note.contents
assert self.user.storage_bytes == 0
def test_load_note_by_title_without_login( self ): def test_load_note_by_title_without_login( self ):
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
@ -249,6 +262,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_load_note_by_title_with_unknown_notebook( self ): def test_load_note_by_title_with_unknown_notebook( self ):
self.login() self.login()
@ -259,6 +273,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_load_unknown_note_by_title( self ): def test_load_unknown_note_by_title( self ):
self.login() self.login()
@ -270,6 +285,7 @@ class Test_notebooks( Test_controller ):
note = result[ "note" ] note = result[ "note" ]
assert note == None assert note == None
assert self.user.storage_bytes == 0
def test_lookup_note_id( self ): def test_lookup_note_id( self ):
self.login() self.login()
@ -280,6 +296,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "note_id" ) == self.note.object_id assert result.get( "note_id" ) == self.note.object_id
assert self.user.storage_bytes == 0
def test_lookup_note_id_without_login( self ): def test_lookup_note_id_without_login( self ):
result = self.http_post( "/notebooks/lookup_note_id/", dict( result = self.http_post( "/notebooks/lookup_note_id/", dict(
@ -288,6 +305,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_lookup_note_id_with_unknown_notebook( self ): def test_lookup_note_id_with_unknown_notebook( self ):
self.login() self.login()
@ -298,6 +316,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_lookup_unknown_note_id( self ): def test_lookup_unknown_note_id( self ):
self.login() self.login()
@ -308,6 +327,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "note_id" ) == None assert result.get( "note_id" ) == None
assert self.user.storage_bytes == 0
def test_save_note( self, startup = False ): def test_save_note( self, startup = False ):
self.login() self.login()
@ -325,6 +345,7 @@ class Test_notebooks( Test_controller ):
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
assert result[ "previous_revision" ] == previous_revision assert result[ "previous_revision" ] == previous_revision
assert self.user.storage_bytes > 0
# make sure the old title can no longer be loaded # make sure the old title can no longer be loaded
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
@ -367,6 +388,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_save_startup_note_without_login( self ): def test_save_startup_note_without_login( self ):
self.test_save_note_without_login( startup = True ) self.test_save_note_without_login( startup = True )
@ -393,6 +415,7 @@ class Test_notebooks( Test_controller ):
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
assert result[ "previous_revision" ] == previous_revision assert result[ "previous_revision" ] == previous_revision
assert self.user.storage_bytes > 0
# make sure the old title can no longer be loaded # make sure the old title can no longer be loaded
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
@ -446,6 +469,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
# now attempt to save over that note again without changing the contents # now attempt to save over that note again without changing the contents
previous_storage_bytes = self.user.storage_bytes
previous_revision = self.note.revision previous_revision = self.note.revision
result = self.http_post( "/notebooks/save_note/", dict( result = self.http_post( "/notebooks/save_note/", dict(
notebook_id = self.notebook.object_id, notebook_id = self.notebook.object_id,
@ -458,6 +482,7 @@ class Test_notebooks( Test_controller ):
# assert that the note wasn't actually updated the second time # assert that the note wasn't actually updated the second time
assert result[ "new_revision" ] == None assert result[ "new_revision" ] == None
assert result[ "previous_revision" ] == previous_revision assert result[ "previous_revision" ] == previous_revision
assert self.user.storage_bytes == previous_storage_bytes
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
notebook_id = self.notebook.object_id, notebook_id = self.notebook.object_id,
@ -491,6 +516,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
# now attempt to save over that note again without changing the contents # now attempt to save over that note again without changing the contents
previous_storage_bytes = self.user.storage_bytes
previous_revision = self.note.revision previous_revision = self.note.revision
result = self.http_post( "/notebooks/save_note/", dict( result = self.http_post( "/notebooks/save_note/", dict(
notebook_id = self.notebook.object_id, notebook_id = self.notebook.object_id,
@ -503,6 +529,7 @@ class Test_notebooks( Test_controller ):
# assert that the note wasn't actually updated the second time # assert that the note wasn't actually updated the second time
assert result[ "new_revision" ] == None assert result[ "new_revision" ] == None
assert result[ "previous_revision" ] == previous_revision assert result[ "previous_revision" ] == previous_revision
assert self.user.storage_bytes == previous_storage_bytes
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
notebook_id = self.notebook.object_id, notebook_id = self.notebook.object_id,
@ -553,6 +580,7 @@ class Test_notebooks( Test_controller ):
# assert that the note wasn't actually updated the second time # assert that the note wasn't actually updated the second time
assert result[ "new_revision" ] == None assert result[ "new_revision" ] == None
assert result[ "previous_revision" ] == previous_revision assert result[ "previous_revision" ] == previous_revision
assert self.user.storage_bytes > 0
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
notebook_id = self.notebook.object_id, notebook_id = self.notebook.object_id,
@ -602,6 +630,7 @@ class Test_notebooks( Test_controller ):
assert result[ "new_revision" ] assert result[ "new_revision" ]
assert result[ "new_revision" ] not in ( first_revision, second_revision ) assert result[ "new_revision" ] not in ( first_revision, second_revision )
assert result[ "previous_revision" ] == second_revision assert result[ "previous_revision" ] == second_revision
assert self.user.storage_bytes > 0
# make sure the first title can no longer be loaded # make sure the first title can no longer be loaded
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
@ -646,6 +675,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_save_new_note( self, startup = False ): def test_save_new_note( self, startup = False ):
self.login() self.login()
@ -663,6 +693,7 @@ class Test_notebooks( Test_controller ):
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
assert result[ "previous_revision" ] == None assert result[ "previous_revision" ] == None
assert self.user.storage_bytes > 0
# make sure the new title is now loadable # make sure the new title is now loadable
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
@ -705,6 +736,7 @@ class Test_notebooks( Test_controller ):
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
assert result[ "previous_revision" ] == None assert result[ "previous_revision" ] == None
assert self.user.storage_bytes > 0
# make sure the new title is now loadable # make sure the new title is now loadable
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
@ -738,6 +770,7 @@ class Test_notebooks( Test_controller ):
assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
assert result[ "previous_revision" ] == None assert result[ "previous_revision" ] == None
assert self.user.storage_bytes > 0
# make sure the new title is now loadable # make sure the new title is now loadable
result = self.http_post( "/notebooks/load_note_by_title/", dict( result = self.http_post( "/notebooks/load_note_by_title/", dict(
@ -770,6 +803,7 @@ class Test_notebooks( Test_controller ):
assert len( notebook.startup_notes ) == 2 assert len( notebook.startup_notes ) == 2
assert notebook.startup_notes[ 0 ] == self.note assert notebook.startup_notes[ 0 ] == self.note
assert notebook.startup_notes[ 1 ] == self.note2 assert notebook.startup_notes[ 1 ] == self.note2
assert self.user.storage_bytes > 0
def test_add_startup_note_without_login( self ): def test_add_startup_note_without_login( self ):
result = self.http_post( "/notebooks/add_startup_note/", dict( result = self.http_post( "/notebooks/add_startup_note/", dict(
@ -778,6 +812,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_add_startup_note_with_unknown_notebook( self ): def test_add_startup_note_with_unknown_notebook( self ):
self.login() self.login()
@ -797,6 +832,7 @@ class Test_notebooks( Test_controller ):
assert len( notebook.startup_notes ) == 1 assert len( notebook.startup_notes ) == 1
assert notebook.startup_notes[ 0 ] == self.note assert notebook.startup_notes[ 0 ] == self.note
assert self.user.storage_bytes == 0
def test_add_startup_unknown_note( self ): def test_add_startup_unknown_note( self ):
self.login() self.login()
@ -816,6 +852,7 @@ class Test_notebooks( Test_controller ):
assert len( notebook.startup_notes ) == 1 assert len( notebook.startup_notes ) == 1
assert notebook.startup_notes[ 0 ] == self.note assert notebook.startup_notes[ 0 ] == self.note
assert self.user.storage_bytes == 0
def test_remove_startup_note( self ): def test_remove_startup_note( self ):
self.login() self.login()
@ -834,6 +871,7 @@ class Test_notebooks( Test_controller ):
notebook = result[ "notebook" ] notebook = result[ "notebook" ]
assert len( notebook.startup_notes ) == 0 assert len( notebook.startup_notes ) == 0
assert self.user.storage_bytes > 0
def test_remove_startup_note_without_login( self ): def test_remove_startup_note_without_login( self ):
result = self.http_post( "/notebooks/remove_startup_note/", dict( result = self.http_post( "/notebooks/remove_startup_note/", dict(
@ -842,6 +880,7 @@ class Test_notebooks( Test_controller ):
), session_id = self.session_id ) ), session_id = self.session_id )
assert result.get( "error" ) assert result.get( "error" )
assert self.user.storage_bytes == 0
def test_remove_startup_note_with_unknown_notebook( self ): def test_remove_startup_note_with_unknown_notebook( self ):
self.login() self.login()
@ -861,6 +900,7 @@ class Test_notebooks( Test_controller ):
assert len( notebook.startup_notes ) == 1 assert len( notebook.startup_notes ) == 1
assert notebook.startup_notes[ 0 ] == self.note assert notebook.startup_notes[ 0 ] == self.note
assert self.user.storage_bytes == 0
def test_remove_startup_unknown_note( self ): def test_remove_startup_unknown_note( self ):
self.login() self.login()
@ -880,6 +920,7 @@ class Test_notebooks( Test_controller ):
assert len( notebook.startup_notes ) == 1 assert len( notebook.startup_notes ) == 1
assert notebook.startup_notes[ 0 ] == self.note assert notebook.startup_notes[ 0 ] == self.note
assert self.user.storage_bytes == 0
def test_is_startup_note( self ): def test_is_startup_note( self ):
self.login() self.login()

View File

@ -98,6 +98,10 @@ class Test_users( Test_controller ):
else: else:
assert startup_notes == [] assert startup_notes == []
rate_plan = result[ u"rate_plan" ]
assert rate_plan[ u"name" ] == u"super"
assert rate_plan[ u"storage_quota_bytes" ] == 1337
def test_current_with_startup_notes_after_signup( self ): def test_current_with_startup_notes_after_signup( self ):
self.test_current_after_signup( include_startup_notes = True ) self.test_current_after_signup( include_startup_notes = True )
@ -229,10 +233,37 @@ class Test_users( Test_controller ):
def test_update_storage( self ): def test_update_storage( self ):
previous_revision = self.user.revision previous_revision = self.user.revision
cherrypy.root.users.update_storage( self.user ) cherrypy.root.users.update_storage( self.user.object_id )
self.scheduler.wait_until_idle() self.scheduler.wait_until_idle()
expected_size = cherrypy.root.users.calculate_storage( self.user ) expected_size = cherrypy.root.users.calculate_storage( self.user )
assert self.user.storage_bytes == expected_size assert self.user.storage_bytes == expected_size
assert self.user.revision > previous_revision assert self.user.revision > previous_revision
def test_update_storage_with_unknown_user_id( self ):
original_revision = self.user.revision
cherrypy.root.users.update_storage( 77 )
self.scheduler.wait_until_idle()
expected_size = cherrypy.root.users.calculate_storage( self.user )
assert self.user.storage_bytes == 0
assert self.user.revision == original_revision
def test_update_storage_with_callback( self ):
def gen():
previous_revision = self.user.revision
cherrypy.root.users.update_storage( self.user.object_id, self.scheduler.thread )
user = ( yield Scheduler.SLEEP )
expected_size = cherrypy.root.users.calculate_storage( self.user )
assert user == self.user
assert self.user.storage_bytes == expected_size
assert self.user.revision > previous_revision
g = gen()
self.scheduler.add( g )
self.scheduler.wait_for( g )

View File

@ -13,7 +13,9 @@ class User( Persistent ):
def __setstate__( self, state ): def __setstate__( self, state ):
if "_User__storage_bytes" not in state: if "_User__storage_bytes" not in state:
state[ "_User__storage_bytes" ] = False state[ "_User__storage_bytes" ] = 0
if "_User__rate_plan" not in state:
state[ "_User__rate_plan" ] = 0
self.__dict__.update( state ) self.__dict__.update( state )
@ -40,6 +42,7 @@ class User( Persistent ):
self.__email_address = email_address self.__email_address = email_address
self.__notebooks = notebooks or [] self.__notebooks = notebooks or []
self.__storage_bytes = 0 # total storage bytes for this user's notebooks, notes, and revisions self.__storage_bytes = 0 # total storage bytes for this user's notebooks, notes, and revisions
self.__rate_plan = 0 # each rate plan is an integer index into the array in config/Common.py
def __create_salt( self ): def __create_salt( self ):
return "".join( [ random.choice( self.SALT_CHARS ) for i in range( self.SALT_SIZE ) ] ) return "".join( [ random.choice( self.SALT_CHARS ) for i in range( self.SALT_SIZE ) ] )
@ -82,6 +85,8 @@ class User( Persistent ):
d = Persistent.to_dict( self ) d = Persistent.to_dict( self )
d.update( dict( d.update( dict(
username = self.username, username = self.username,
storage_bytes = self.__storage_bytes,
rate_plan = self.__rate_plan,
) ) ) )
return d return d
@ -99,10 +104,15 @@ class User( Persistent ):
self.update_revision() self.update_revision()
self.__storage_bytes = storage_bytes self.__storage_bytes = storage_bytes
def __set_rate_plan( self, rate_plan ):
self.update_revision()
self.__rate_plan = rate_plan
username = property( lambda self: self.secondary_id ) username = property( lambda self: self.secondary_id )
email_address = property( lambda self: self.__email_address ) email_address = property( lambda self: self.__email_address )
password = property( None, __set_password ) password = property( None, __set_password )
storage_bytes = property( lambda self: self.__storage_bytes, __set_storage_bytes ) storage_bytes = property( lambda self: self.__storage_bytes, __set_storage_bytes )
rate_plan = property( lambda self: self.__rate_plan, __set_rate_plan )
# the notebooks (read-only and read-write) that this user has access to # the notebooks (read-only and read-write) that this user has access to
notebooks = property( lambda self: copy( self.__notebooks ), __set_notebooks ) notebooks = property( lambda self: copy( self.__notebooks ), __set_notebooks )

View File

@ -17,6 +17,7 @@ class Test_user( object ):
assert self.user.email_address == self.email_address assert self.user.email_address == self.email_address
assert self.user.notebooks == [] assert self.user.notebooks == []
assert self.user.storage_bytes == 0 assert self.user.storage_bytes == 0
assert self.user.rate_plan == 0
def test_check_correct_password( self ): def test_check_correct_password( self ):
assert self.user.check_password( self.password ) == True assert self.user.check_password( self.password ) == True
@ -60,6 +61,14 @@ class Test_user( object ):
assert self.user.storage_bytes == storage_bytes assert self.user.storage_bytes == storage_bytes
assert self.user.revision > previous_revision assert self.user.revision > previous_revision
def test_set_rate_plan( self ):
previous_revision = self.user.revision
rate_plan = 2
self.user.rate_plan = rate_plan
assert self.user.rate_plan == rate_plan
assert self.user.revision > previous_revision
class Test_user_with_notebooks( object ): class Test_user_with_notebooks( object ):
def setUp( self ): def setUp( self ):
@ -116,3 +125,10 @@ class Test_user_with_notebooks( object ):
trash = Notebook( trash_id, u"trash" ) trash = Notebook( trash_id, u"trash" )
notebook = Notebook( notebook_id, u"my new notebook", trash ) notebook = Notebook( notebook_id, u"my new notebook", trash )
assert self.user.has_access( notebook.object_id ) == False assert self.user.has_access( notebook.object_id ) == False
def test_to_dict( self ):
d = self.user.to_dict()
assert d.get( "username" ) == self.username
assert d.get( "storage_bytes" ) == self.user.storage_bytes
assert d.get( "rate_plan" ) == self.user.rate_plan

View File

@ -346,6 +346,27 @@ ol li {
padding: 0.25em 0.25em 0.25em 0.5em; padding: 0.25em 0.25em 0.25em 0.5em;
} }
.storage_usage_area {
}
.storage_usage_low {
color: green;
font-weight: bold;
margin-top: 1.5em;
}
.storage_usage_medium {
color: orange;
font-weight: bold;
margin-top: 1.5em;
}
.storage_usage_high {
color: red;
font-weight: bold;
margin-top: 1.5em;
}
.current_notebook_name { .current_notebook_name {
background-color: #b0d0ff; background-color: #b0d0ff;
-moz-border-radius: 0.5em; -moz-border-radius: 0.5em;

View File

@ -12,6 +12,7 @@ function Wiki( invoker ) {
this.search_results_editor = null; // editor for display of search results this.search_results_editor = null; // editor for display of search results
this.invoker = invoker; this.invoker = invoker;
this.search_titles_only = true; this.search_titles_only = true;
this.rate_plan = null;
connect( this.invoker, "error_message", this, "display_error" ); connect( this.invoker, "error_message", this, "display_error" );
connect( "search_form", "onsubmit", this, "search" ); connect( "search_form", "onsubmit", this, "search" );
@ -86,6 +87,9 @@ Wiki.prototype.display_user = function ( result ) {
}, notebook.name ) ) ); }, notebook.name ) ) );
} }
this.rate_plan = result.rate_plan;
this.display_storage_usage( result.user.storage_bytes );
// display the name of the logged in user and a logout link // display the name of the logged in user and a logout link
appendChildNodes( user_span, "logged in as " + result.user.username ); appendChildNodes( user_span, "logged in as " + result.user.username );
appendChildNodes( user_span, " | " ); appendChildNodes( user_span, " | " );
@ -99,6 +103,30 @@ Wiki.prototype.display_user = function ( result ) {
} ); } );
} }
Wiki.prototype.display_storage_usage = function( storage_bytes ) {
// display the user's current storage usage
var MEGABYTE = 1024 * 1024;
function bytes_to_megabytes( storage_bytes ) {
return Math.round( storage_bytes / MEGABYTE );
}
var quota_bytes = this.rate_plan.storage_quota_bytes || 0;
var usage_percent = Math.round( storage_bytes / quota_bytes * 100.0 );
if ( usage_percent > 90 )
var storage_usage_class = "storage_usage_high";
else if ( usage_percent > 75 )
var storage_usage_class = "storage_usage_medium";
else
var storage_usage_class = "storage_usage_low";
replaceChildNodes(
"storage_usage_area",
createDOM( "div", { "class": storage_usage_class },
bytes_to_megabytes( storage_bytes ) + " MB (" + usage_percent + "%) of " + bytes_to_megabytes( quota_bytes ) + " MB used" )
);
}
Wiki.prototype.populate = function ( result ) { Wiki.prototype.populate = function ( result ) {
this.notebook = result.notebook; this.notebook = result.notebook;
var self = this; var self = this;

View File

@ -11,4 +11,7 @@ class Link_area( Div ):
Div( Div(
id = u"notebooks_area", id = u"notebooks_area",
), ),
Div(
id = u"storage_usage_area",
),
) )