r867 - in zope-quotafolder: branches/upstream/0.1.1 branches/upstream/current trunk
lunar at alioth.debian.org
lunar at alioth.debian.org
Mon May 14 10:26:42 UTC 2007
Author: lunar
Date: 2007-05-14 10:26:40 +0000 (Mon, 14 May 2007)
New Revision: 867
Added:
zope-quotafolder/branches/upstream/0.1.1/DESIGN
zope-quotafolder/branches/upstream/0.1.1/KNOWNBUGS
zope-quotafolder/branches/upstream/0.1.1/LICENSE
zope-quotafolder/branches/upstream/0.1.1/QuotaFolder.py
zope-quotafolder/branches/upstream/0.1.1/README.txt
zope-quotafolder/branches/upstream/0.1.1/TODO
zope-quotafolder/branches/upstream/0.1.1/__init__.py
zope-quotafolder/branches/upstream/0.1.1/manage_addQuotaFolderForm.dtml
zope-quotafolder/branches/upstream/0.1.1/manage_editQuotaForm.dtml
zope-quotafolder/branches/upstream/0.1.1/refresh.txt
zope-quotafolder/branches/upstream/current/DESIGN
zope-quotafolder/branches/upstream/current/KNOWNBUGS
zope-quotafolder/branches/upstream/current/LICENSE
zope-quotafolder/branches/upstream/current/QuotaFolder.py
zope-quotafolder/branches/upstream/current/README.txt
zope-quotafolder/branches/upstream/current/TODO
zope-quotafolder/branches/upstream/current/__init__.py
zope-quotafolder/branches/upstream/current/manage_addQuotaFolderForm.dtml
zope-quotafolder/branches/upstream/current/manage_editQuotaForm.dtml
zope-quotafolder/branches/upstream/current/refresh.txt
zope-quotafolder/trunk/DESIGN
zope-quotafolder/trunk/KNOWNBUGS
zope-quotafolder/trunk/LICENSE
zope-quotafolder/trunk/QuotaFolder.py
zope-quotafolder/trunk/README.txt
zope-quotafolder/trunk/TODO
zope-quotafolder/trunk/__init__.py
zope-quotafolder/trunk/manage_addQuotaFolderForm.dtml
zope-quotafolder/trunk/manage_editQuotaForm.dtml
zope-quotafolder/trunk/refresh.txt
Removed:
zope-quotafolder/branches/upstream/0.1.1/QuotaFolder-0.1.1.tar.gz
zope-quotafolder/branches/upstream/current/QuotaFolder-0.1.1.tar.gz
zope-quotafolder/trunk/QuotaFolder-0.1.1.tar.gz
zope-quotafolder/trunk/QuotaFolder-0.1.1.tar.gz.cdbs-config_list
Log:
Really use upstream tarball instead of a tarball of a tarball. *sigh*
Added: zope-quotafolder/branches/upstream/0.1.1/DESIGN
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/DESIGN (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/DESIGN 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,117 @@
+Patch ObjectManager zdd:
+
+ - het get_usage() ondersteunt
+ Deze itereert over alle subobjects en roept, indien mogelijk, get_usage
+ aan.
+
+ Return: files, size
+
+ - creatie van objecten verifieert met parent object
+
+
+Patch File en evt. andere objecten zdd.
+
+ - deze get_usage() ondersteunen
+ - manage_upload / whatever checken
+
+ Quota awareness
+
+Implementeer een speciale QuotaFolder welke op basis van get_usage()
+gebruik bepaalt.
+
+Hou rekening met:
+
+- transparante migratie
+- sync optie om quota up2date te krijgen
+- Nesten van QuotaFolders
+- consistentie bij fouten/exceptions (i.e. QuotaExceeded)
+
+- hard/soft quota
+- mailnotificatie
+- security check in context van parent
+
+- wat als een folderstructuur gepaste wordt? Met een te-groot object?
+
+Is het zinnig quota etc bij te houden als er geen QuotaFolder parent is?
+Alleen als parent later QuotaFolder wordt... Kan dit? (migratie)?
+Waarom dan niet gewoon syncen?
+
+Nesten van QuotaFolders is uiteindelijk wel wenselijk, hier dient rekening
+mee gehouden te worden...
+
+- wat als folder gedelete wordt?
+- als data geupload/vergroot wordt?
+- check ftp, webdav
+- len(GET()) geeft rendered lengte?
+- len(.zexp)?
+
+Bij aanpassing van een object relatieve verschil quota_checken(), of
+recursief naar boven toe herberekenen? (geen optie bij grote site)
+
+
+-----------------------------------------------------------------------
+
+Alle objecten en in het byzonder ObjectManagers worden quota aware gemaakt.
+Dit betekent dat:
+
+Objectmanagers
+
+- add/delete van objecten bijhouden (eigen count) en aan parent doorgeven
+- size van subobjecten bijhouden (recursief)
+- notificaties krijgen van size changes van subobjecten
+
+Andere Objecten (File, Image, DTMLDocument, PythonScript, ...)
+
+- eigen size bijhouden
+- uploads bijhouden en melden aan parent (=ObjectManager)
+- changes bijhouden en melden aan parent
+
+ (beiden kunnen groei/krimp zijn)
+
+- stort het systeem in (afgezien van quotafolders) als er geen QuotaProduct
+ meer is?
+- Ignore quota voor superuser
+- undo van (delete) transaction -> gevolgen?
+
+Toekomst:
+
+- soft/hard limit, expiry
+- mogelijkheid tot aanroepen script bij quotum exceeding (soft/hard) -> mail
+- Filteren/beperken meta types
+
+
+Specifiek testen:
+
+- exception bij overschrijding abort transaction?
+- support bij ftp, webdav (+ errorhandling)
+- photofolder
+- Btree folder
+- Transparentfolder?
+- Wat gebeurt er als je een quota aware folder importeert in een ander
+ systeem? (niks! denk ik)
+- undo quota change door manager?
+- objecten deleten, tegen quotum aanzitten, deletion undo-en
+- versions ?! Lijken transparant te werken... (alhoewel je wel veel state in versions kan bewaren)
+- metapublisher/formulator
+
+scripts als:
+
+doc = container.doc
+container.manage_delObjects(['doc'])
+doc.manage_edit('hello'*100, 'hacked')
+
+Evt. met vervangende pseudo parent
+
+BUGS
+----
+
+- zcatalog aanmaken geeft 1 object, zcatalog deleten is -2 FIXED
+- tutorial geeft niet teveel objecten/size, deleten gaat fout FIXED
+- manage import/export voegt ook objecten toe zonder setOb FIXED
+- acquisition werkt mogelijk niet meteen/direct voor imported data FIXED
+
+Probleem:
+---------
+
+Naast .zexp zijn ook pasted objecten objecten met veel subobjects (evt
+hierarchisch)
Added: zope-quotafolder/branches/upstream/0.1.1/KNOWNBUGS
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/KNOWNBUGS (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/KNOWNBUGS 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,52 @@
+This is a list of known issues / incompatibilities with QuotaFolder
+
+Bugs in Zope:
+
+- ZopeTutorial does some inproper exception catching, causing QuotaExceeded
+ errors to be ignored (and actually the product to be imported twice).
+ The relevant code is:
+
+ try:
+ folder.manage_importObject(tutorialExamplesFile)
+ except:
+ folder._p_jar=self.Destination()._p_jar
+ folder.manage_importObject(tutorialExamplesFile)
+
+- A similair problem probably occurs when installing userfolders - if a user is past
+ his quota and tries to create a userfolder, the error 'this folder already contains a
+ userfolder', and the userfolder is created nonetheless
+
+ This actually brings the quotafolder out of sync!!! (because due to the exception,
+ the quota isn't updated, but the transaction isn't aborted either)
+
+ -- RESOLVED 16Mar02 This bug has been worked around, though the error 'this object
+ already has a userfolder' will appear if the quota is exceeded.
+
+Bugs in QuotaFolder:
+
+- Maximum object size is not always enforced, esp. when importing objects
+ (or hierarchies of objects)
+
+- The following script used to give problems:
+
+ doc = container.doc
+ container.manage_delObjects(['doc'])
+ doc.manage_edit('hello'*100, 'hacked')
+
+ Hooked objects now check if they are still contained in their parent.
+ What if doc is placed in a pseudo parent folder?
+
+- Pressing 'Sync' in the Quota tab brings you back to the Quota tab, however,
+ the tab itself indicates 'Contents' is selected. -- RESOLVED 18Mar02
+
+- QuotaExceeded exceptions aren't always precise - it displays how much quota
+ you would need for the operation, but that may not be correct.
+
+- QuotaFolder seems to mess up undo capabilities (under what circumstances?)
+
+- QuotaFolder is not compatible with FLE. Creation goes fine (it seems), but
+ newly created objects / modified objects are not added. -- RESOLVED 16Mar02
+
+- It is still possible to store large amounts of data in properties. A solution
+ would be to test if an object is an instance of PropertyManager, and account
+ the size of the properties appropriately.
Added: zope-quotafolder/branches/upstream/0.1.1/LICENSE
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/LICENSE (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/LICENSE 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,27 @@
+Copyright (c) 2002 Ivo van der Wijk, Amaze Internet Services (ivo at amaze.nl)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Deleted: zope-quotafolder/branches/upstream/0.1.1/QuotaFolder-0.1.1.tar.gz
===================================================================
(Binary files differ)
Added: zope-quotafolder/branches/upstream/0.1.1/QuotaFolder.py
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/QuotaFolder.py (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/QuotaFolder.py 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,253 @@
+##
+## Copyright (c) 2002 Ivo van der Wijk, Amaze Internet Services (ivo at amaze.nl)
+##
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions
+## are met:
+##
+## 1. Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in the
+## documentation and/or other materials provided with the distribution.
+## 3. The name of the author may not be used to endorse or promote products
+## derived from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+##
+
+import App, Globals, OFS
+import string
+
+from Persistence import Persistent
+
+from Globals import DTMLFile
+from OFS.Folder import Folder
+from Acquisition import aq_base, aq_parent, Acquired, Implicit
+from AccessControl import getSecurityManager, SpecialUsers
+
+from zLOG import LOG, WARNING
+
+def manage_addQuotaFolder(self, id, title, quota_bytes=0, quota_objects=0,
+ quota_maxsize=0, REQUEST=None):
+ """ Add a new QuotaFolder """
+
+ o = QuotaFolder(id, title, quota_bytes, quota_maxsize, quota_objects, REQUEST)
+ self._setObject(id, o)
+ o = self._getOb(id)
+
+ if REQUEST:
+ return self.manage_main(self, REQUEST, update_menu=1)
+
+def _replaceFolder(parent, id, quota_bytes=0, quota_objects=0, quota_maxsize=0):
+ # Replaces an OFS.Folder with a QuotaFolder. Borrowed from BTreeFolder
+ ob = QuotaFolder(id, "", quota_bytes, quota_maxsize, quota_objects)
+ f = parent._getOb(id)
+ # Copy the contents of the folder to the new QuotaFolder
+
+ ids = f.objectIds()
+ for key, value in f.__dict__.items():
+ if key not in ids:
+ # Not an ObjectManager item.
+ ob.__dict__[key] = value
+ for key in ids:
+ subob = f._getOb(key)
+ subob = getattr(subob, 'aq_base', subob)
+ ob._setOb(key, subob)
+ parent._setOb(id, ob)
+
+
+manage_addQuotaFolderForm=DTMLFile('manage_addQuotaFolderForm', globals())
+
+QuotaExceededException='Quota Exceeded'
+
+class QuotaFolder(Folder, Persistent, Implicit):
+ """ A QuotaFolder """
+
+ meta_type='QuotaFolder'
+
+ manage_options=(
+ Folder.manage_options+
+ (
+ {'label': 'Quota', 'action': 'manage_editQuotaForm'},
+ )
+ )
+
+ __ac_permissions__= \
+ Folder.__ac_permissions__ + \
+ (
+ ('View current quota', ('currentCount','currentSize')),
+ )
+
+ manage_editQuotaForm=DTMLFile('manage_editQuotaForm', globals())
+
+ def __init__(self, id, title="", quota=0, maxFileSize=0, maxNumberOfFiles=0,
+ manager_role=1, REQUEST=None):
+ self.id=id
+ self.title=title
+ self._quota_bytes=int(quota)
+ self._quota_objects=int(maxNumberOfFiles)
+ self._quota_maxsize=int(maxFileSize)
+
+ ##
+ ## current accounting
+ self._quota_filecount = 0
+ self._quota_size = 0
+
+ self._manager_role = 0
+ if manager_role:
+ self._manager_role = 1
+ self._debug = 0
+ self._allow_nested = 0
+
+ def _check_quota(self, filechange, sizechange=0, objsize=0):
+ """ Check if the change in usage violates the quota settings """
+ #
+ # print "I'm a quota folder: ", filechange, sizechange, objsize
+ # print "Current quota: ", self._quota_filecount, self._quota_size
+
+ #
+ # The emergency user can create *some* objects (i.e. userfolders).
+ # make sure this never gives a QuotaExceeded exception
+
+ emergency = 0
+ user=getSecurityManager().getUser()
+ if (SpecialUsers.emergency_user and
+ aq_base(user) is SpecialUsers.emergency_user):
+ LOG('QuotaFolder', INFO,
+ "Emergency user detected - not enforcing quota")
+ emergency = 1
+
+ if not emergency and \
+ filechange > 0 and \
+ self._quota_filecount + filechange > self._quota_objects \
+ > 0:
+ raise QuotaExceededException, \
+ "Too many files, this operation would require a quotum " + \
+ "of %d files, your current quotum is %d files" % \
+ (self._quota_filecount + filechange, self._quota_objects)
+ if not emergency and \
+ sizechange > 0 and \
+ self._quota_size + sizechange > self._quota_bytes > 0:
+ raise QuotaExceededException, \
+ "Too much space, this operation would require a " + \
+ "quotum of %d bytes, your current quotum is %d bytes" % \
+ (self._quota_size + sizechange, self._quota_bytes)
+ if not emergency and objsize > self._quota_maxsize > 0:
+ raise QuotaExceededException, \
+ "The object is too large. It requires %d bytes, " + \
+ "your maximum object size limit is %d bytes" % \
+ (objsize, self._quota_maxsize)
+
+ self._quota_filecount = self._quota_filecount + filechange
+ self._quota_size = self._quota_size + sizechange
+
+ ##
+ ## This should not happen...
+ path = string.join(self.getPhysicalPath(), "/")
+ if self._quota_filecount < 0:
+ LOG('QuotaFolder', WARNING, "Negative quota filecount in %s" % path)
+ if not self._debug:
+ self._quota_filecount = 0
+ if self._quota_size < 0:
+ LOG('QuotaFolder', WARNING, "Negative quota size in %s" % path)
+ if not self._debug:
+ self._quota_size = 0
+
+ # print "New quota: ", self._quota_filecount, self._quota_size
+
+ def current_count(self):
+ return self._quota_filecount
+
+ def current_size(self):
+ return self._quota_size
+
+ def quota_bytes(self):
+ return self._quota_bytes
+
+ def quota_objects(self):
+ return self._quota_objects
+
+ def quota_maxsize(self):
+ return self._quota_maxsize
+
+ def authorized_to_edit(self):
+ """
+ return true/false if user is authorized to change in current context
+ """
+
+ security=getSecurityManager()
+ security.addContext(self)
+ user = security.getUser()
+ if not self._manager_role:
+ ## XXX use permission?
+ return user.allowed(self, ['Manager'])
+
+ ## use aq_base?
+ return user.allowed(aq_parent(self), ['Manager'])
+
+ def manager_role(self):
+ return self._manager_role
+
+ def manage_editQuota(self, quota=-1, maxFileSize=-1, maxNumberOfFiles=-1,
+ REQUEST=None):
+ """ Update the current Quota """
+ ##
+ ## Perhaps always sync after edit?
+
+ if REQUEST.has_key('Sync'):
+ self._qa_sync()
+ ## XXX report difference to parent?
+ if REQUEST:
+ ## A redirect selects the proper tab
+ message = "Quota synchronized"
+ REQUEST.RESPONSE.redirect(
+ 'manage_editQuotaForm?manage_tabs_message=%s' % message)
+ return self.manage_editQuotaForm(self, REQUEST,
+ manage_tabs_message=message)
+ return ''
+
+ if not self.authorized_to_edit():
+ raise "Unauthorized", \
+ "You are not authorized to edit this quota folder"
+
+ if quota != -1:
+ self._quota_bytes=int(quota)
+ if maxFileSize != -1:
+ self._quota_maxsize=int(maxFileSize)
+ if maxNumberOfFiles != -1:
+ self._quota_objects=int(maxNumberOfFiles)
+ self._manager_role = REQUEST.get("manager_role", 0)
+ if REQUEST:
+ message = "Changes saved"
+ ## A redirect selects the proper tab
+ REQUEST.RESPONSE.redirect(
+ 'manage_editQuotaForm?manage_tabs_message=%s' % message)
+ return self.manage_editQuotaForm(self, REQUEST,
+ manage_tabs_message=message)
+ return ''
+
+ def manage_afterAdd(self, item, container):
+ # print "afterAdd %s %s"%(str(item), str(container))
+ if not self._allow_nested:
+ for parent in self.aq_chain[1:]:
+ if hasattr(parent, "meta_type") and \
+ parent.meta_type == "QuotaFolder":
+ raise QuotaExceededException, \
+ "Cannot add a QuotaFolder inside another QuotaFolder"
+
+ Folder.manage_afterAdd(self, item, container)
+
+Globals.default__class_init__(QuotaFolder)
Added: zope-quotafolder/branches/upstream/0.1.1/README.txt
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/README.txt (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/README.txt 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,187 @@
+QuotaFolder - Quota support for Zope
+
+What is it?
+-----------
+
+QuotaFolder is a folder-ish object that restricts the total number of objects,
+their total size and their individual maximum size. QuotaFolder takes
+subfolders (recursively) into account, so it should not be possible to escape
+the quota restrictions.
+
+The basic goal of QuotaFolder is not to put an absolute limit on ZODB usage
+- it is impossible to determine this. For example, each object may have
+several revisions in the ZODB, and some of the internal state of the object
+is stored in the ZODB that's not returned by get_size(). The goal is to
+limit the use of objects in general, to prevent people from offering large
+files or using up resources with enormous amounts of objects.
+
+How does it work?
+-----------------
+
+QuotaFolder 'MonkeyPatches (tm)' some of the internal Zope components such
+as ObjectManager, File, Image, DTMLMethod, DTMLDocument and more. The patching
+constist of making the components 'quota aware'. This basically means the
+objects will track changes and report them to parent folders. If one if the
+parent folders is a QuotaFolder, and the QuotaFolder detects that the change
+will exceed the quota, a QuotaExceededException is raised, and the transaction
+is rolled back.
+
+At this moment, if an object supports the get_size() method, it's used. The
+value reported by this method will not take all usage (i.e. properties) into
+account, so this may be changed in the future.
+
+Supported products
+------------------
+
+The QuotaFolder product knows how to account File, Image, DTMLMethod,
+DTMLDocument and PythonScript objects. It also understands ObjectManager
+based products such as Folder and BTreeFolder (TransparentFolder is not
+thoroughly tested, but it seems to work well)
+
+Most products (i.e. FLE, Squishdot, PhotoFolder, ZWiki) exist of ObjectManagers
+with subobjects, and QuotaFolder knows how to handle these quite well.
+Unknown objects who's size (and changes in size) cannot be determined are
+accounted as 1 object with size 0. If this is not satisfactory (for example,
+it's not at this moment for TinyTablePlus), extra support can be built in for
+these objects (as has been done with, for example, ZPT).
+
+QuotaFolder also supports .zexp imports and copy/paste operations.
+
+QuotaFolder has been tested with Zope 2.4.3, 2.4.4b1 and 2.5.0. It has not
+been tested in ZEO setups.
+
+Installing
+----------
+
+**WARNING**
+
+This product patches classes in your Zope server. Please test the code on
+a test-server or shadow server first! This code has been known to work
+succesfully with Zope 2.4.x and Zope 2.5. Use this product at your own
+risk, and backup your Data.fs first!
+
+Install the QuotaFolder product by simply unpacking into your Products
+directory (either in your SOFTWARE_HOME or your INSTANCE_HOME) and restart
+your Zope server. The QuotaFolder product should appear in
+/Control_Panel/Products, and it should also be available in the products
+dropdown.
+
+When creating (or editting) a QuotaFolder, you will be presented with the
+following fields:
+
+id
+title
+Quota size in bytes - this is the maximum total size of all objects
+ that's allowed to be created
+Maximum object size in bytes - The maximum allowed total size for single
+ objects
+Maximum number of objects - this is the maximum number of objects that's
+ allowed to be created
+Require manager role in parent context?
+ - If this setting is enabled, a user must be
+ manager in the folder context *above* the
+ quotafolder itself to be able to edit the
+ quota. If you want to limit your users in their
+ usage, you don't want them to be able to edit
+ their quota themselves, do you?
+
+After creation, you will get the same contents view as with a standard folder,
+but with an extra tab to the right where you can view and (optionally edit)
+the quota.
+
+When visiting the quota tab, you will see an extra button 'Sync'. Pressing
+this button will cause the QuotaFolder to recalculate all usage. Usually,
+the folder shouldn't be out of sync. If you manage to get a QuotaFolder
+out of sync, please contact me.
+
+Migrating
+---------
+
+There are two ways to migrate a standard folder to a QuotaFolder:
+
+- Create a new QuotaFolder, copy all objects from the old folder, paste
+ them into the QuotaFolder and rename the folders. This will probably not
+ work if you have versions (cut/paste may work with versions)
+
+- Use the builtin _replaceFolder method. I.e. create the following external
+ method:
+
+ from Products.QuotaFolder.QuotaFolder import _replaceFolder
+
+ def replace(self, name, quota_bytes, quota_objects, quota_maxsize, REQUEST):
+ _replaceFolder(self, name, quota_bytes, quota_objects, quota_maxsize)
+ return "%s converted" % name
+
+ Create an appropriate external method object in your zope server and invoke
+ it with an appropriately formatted url or create a dtml form.
+
+Hacking contest!
+----------------
+
+QuotaFolder has been thoroughly tested, and seems very stable and compatible.
+However, every now and then, new situations seem to appear where it's possible
+to use more objects or space than the QuotaFolder should enforce. This may
+be through unsupported objects (though these should generaly not make it
+possible to create more free space for other objects), or using trickery
+with PythonScripts, etc.
+
+If anyone finds issues like this, please contact me (info below), so we can
+make this product even more stable and robust :)
+
+Please check the file KNOWNBUGS before reporting issues.
+
+Release info
+------------
+
+0.1 Initial version, basic support for quota
+
+Future plans
+------------
+
+- Support soft and hardlimit quota, with configurable timeleft
+- Restrict installable metatypes
+- Limit number of certain metatypes
+
+Contact/License
+---------------
+
+QuotaFolder is partially based on ideas by Andrew Kenneth, though most of his
+old QuotaFolder has disappeared.
+
+QuotaFolder is written by Ivo van der Wijk as part of Amaze Internet Service's
+FreeZope.org Free Zope hosting environment.
+
+I can be contacted through ivo at amaze.nl or on IRC as VladDrac / VladDrak @ OPN
+
+Recent versions of QuotaFolder and other Zope products can be found at:
+
+http://www.zope.org/Members/ivo
+http://vanderijk.info/
+
+QuotaFolder is (c) 2002 Ivo van der Wijk / Amaze Internet Services
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Added: zope-quotafolder/branches/upstream/0.1.1/TODO
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/TODO (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/TODO 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,23 @@
+First release:
+
+- check required attributes on objects (i.e. data on File/Image), report
+ warning if missing
+- Provide a conversion script (external method) to convert standard folders
+ to QuotaFolders DONE
+- Prevent 0-changes from propagating
+- properly determine size for TinyTablePlus objects
+- better QuotaExceeded exception strings DONE
+- ignore superuser, etc. DONE
+- add proper permissions DONE?
+- Do not raise exceptions on negative changes DONE
+- LICENSE
+
+Later release
+- support TinyTablePlus
+- soft/hard quota
+- Limit/restrict installable meta types
+- configurable size for objects without explicit size? (seems pointless)
+- configure max object size per meta type (+ limit # of instances)
+- optionally prevent quota updates from propagating beyond a folder (checkbox)
+ -> this doesn't have to break nested quotafolders! (though it may give
+ problems)
Added: zope-quotafolder/branches/upstream/0.1.1/__init__.py
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/__init__.py (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/__init__.py 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,395 @@
+##
+## Copyright (c) 2002 Ivo van der Wijk, Amaze Internet Services (ivo at amaze.nl)
+##
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions
+## are met:
+##
+## 1. Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in the
+## documentation and/or other materials provided with the distribution.
+## 3. The name of the author may not be used to endorse or promote products
+## derived from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+##
+
+from zLOG import LOG, WARNING, BLATHER, INFO
+
+from OFS.ObjectManager import ObjectManager
+from Acquisition import aq_parent, aq_chain, aq_inner
+import QuotaFolder
+
+##
+## NOTE TO DEVELOPERS:
+##
+## Refreshing this product may not always work. If you do not get the expected
+## behaviour after refreshing, try restarting your zopeserver!
+##
+
+##
+## Idea: use *args / **args for wrapper methods and apply parameters to methods
+##
+## QA stands for Quota Aware, QU for Quota Unaware
+
+##
+## Invoke chain of _qf_check's, so all parents can update? (for delete of
+## folders etc)
+
+def QA_setOb(self, id, object):
+ ##
+ ## Is it smarter to patch setObject instead? Is setOb ever invoked directly?
+
+ ##
+ ## We rely on the transaction being rolled back if an exception occurs.
+ ## This means that QuotaExceeded exceptions shouldn't occur. Unfortunately,
+ ## sometimes they are caught.
+ if isinstance(object, ObjectManager):
+ make_quota_aware(object)
+
+ if hasattr(object, "_qa_sync"):
+ object._qa_sync()
+ _files, _size = QA_determine_usage(object)
+ try:
+ self._qf_check(_files, _size)
+ except QuotaFolder.QuotaExceededException:
+ ##
+ ## This horrible fix is because manage_addUserFolder ignores all
+ ## exceptions, and assumes there already is a UserFolder, while
+ ## _setObject happily changes _objects as well while ignoring
+ ## anything that may go wrong
+ # print "O", self._objects
+ o = []
+ ##
+ ## Could this go wrong if there already is an object with id?
+ ## (replacable?) (check using getattr?)
+ if not hasattr(self, id):
+ for i in self._objects:
+ if i['id'] != id:
+ o.append(i)
+ self._objects = tuple(o)
+ raise
+ result = self._qu_setOb(id, object)
+ return result
+
+def QA_delOb(self, id):
+ # we can't use _getOb because it doesn't allow _attrs
+ ##
+ ## this works with BtreeFolders as well (well, with non-basicBtreeFolders)
+ obj = getattr(self, id)
+ try:
+ _files,_size = QA_determine_usage(obj)
+ except:
+ LOG('QuotaProduct', BLATHER, "Can't get size for %s" % id)
+ _files, _size = 1,0
+
+ self._qf_check(-_files, -_size)
+
+ return self._qu_delOb(id)
+
+def getid(id):
+ """ sometimes id is a string, sometimes a method """
+ if callable(id): return id()
+ return id
+
+def findparent(object):
+ """ find the direct parent """
+
+ for parent in aq_chain(object):
+ if parent is object:
+ continue
+ if parent == object:
+ continue
+ if isinstance(parent, ObjectManager):
+ return parent
+ return None
+
+def QA_check_quota(self, filechange, sizechange=0, objsize=0):
+ """
+ Notify a change in usage:
+
+ filechange number of added/removed files
+ sizechange change in size of usage
+ objsize total size of object
+
+ changes can be negative as well
+ """
+
+ if hasattr(self, "_check_quota"):
+ ## check hook for QuotaFolder
+ self._check_quota(filechange, sizechange, objsize)
+ else:
+ ## do own administration
+ make_quota_aware(self)
+ self._quota_filecount = self._quota_filecount + filechange
+ self._quota_size = self._quota_size + sizechange
+
+ ##
+ ## Acquisition is scary. The chain looks different when traversing
+ ## through ftp/webdav. basically, 'self' may appear in the chain multiple
+ ## times (with A NullResource in between), and may not always be equal
+ ## through 'is' (so try == as well)
+ ##
+ ## Tip: use aq_base(parent) XXX
+
+ for parent in aq_chain(self):
+ if parent is self:
+ continue
+ if parent == self:
+ continue
+ if hasattr(parent, "_qf_check"):
+ if hasattr(parent, getid(self.id)):
+ parent._qf_check(filechange,sizechange,objsize)
+ else:
+ for name,ob in parent.objectItems():
+ if ob.id == getid(self.id):
+ parent._qf_check(filechange,sizechange,objsize)
+ return # !!
+ break
+
+def QA_manage_importObject(self, file, REQUEST=None, set_owner=1):
+ """ currently a useless hook. To be removed if not used """
+ result = apply(self._qu_manage_importObject, (file, REQUEST, set_owner))
+ return result
+
+def QA_sync(self):
+ """ synchronize, i.e. recursively recalculate sizes """
+ self._quota_filecount = 0 # not 1, get_usage adds 1
+ self._quota_size = 0
+
+ for child in self.objectValues():
+ if hasattr(child, "_qa_sync"):
+ child._qa_sync()
+
+ _files, _size = QA_determine_usage(child)
+ self._quota_filecount = self._quota_filecount + _files
+ self._quota_size = self._quota_size + _size
+
+def make_quota_aware(object):
+ if not hasattr(object, "_quota_filecount"):
+ object._quota_filecount = 0
+ if not hasattr(object, "_quota_size"):
+ object._quota_size = 0
+
+def QA_get_usage(self):
+ """
+ Return the size of an ObjectManager object. This is 1 + the number
+ of subobjects. The size of an empty ObjectManager is considered to
+ be 0
+ """
+ return self._quota_filecount+1, self._quota_size
+
+def QA_determine_usage(object):
+ """
+ attempt to determine number of size and number of subobjects of object
+ """
+ ##
+ ## ZPT doesn't return the size of the unrendered content it seems,
+ ## which leads to
+ ## very inconsistent sizes. Let's hope this fixes it...
+ if zpt_installed and isinstance(object, PageTemplate):
+ # workaround for ZPT
+ if hasattr(object, "_text"):
+ return 1, len(object._text)
+ return 1, 0
+ if hasattr(object, "_get_usage"):
+ return object._get_usage()
+ elif hasattr(object, "get_size"):
+ ##
+ ## Some products (such as PhotoFolders Photo) raise an exception
+ ## when requested their size at init time
+ try:
+ _size = object.get_size()
+ except: # can be any kind of exception
+ LOG('QuotaProduct', BLATHER, 'Failed to get size for %s/%s' % \
+ (getid(object.id), object.meta_type))
+ _size = 0
+ return 1, _size
+ elif hasattr(object, "getSize"):
+ ## getSize is deprecated, but if it's all there is...
+ return 1, object.getSize()
+ else:
+ LOG('QuotaProduct', BLATHER, "Can't get size for %s/%s" % \
+ (getid(object.id), object.meta_type))
+ return 1, 0
+
+##
+## Patch the PythonScript object
+
+from Products.PythonScripts.PythonScript import PythonScript
+
+def QA_ps_write(self, text):
+ """ invoked at creation, edit and upload - what else do you want? """
+ oldsize = self.get_size()
+ result = self._qu_write(text)
+ newsize = self.get_size()
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Patch DT_String, which effectively patches DTMLDocument and DTMLMethod
+
+from DocumentTemplate.DT_String import String
+
+def QA_dt_munge(self, source_string=None, mapping=None, **vars):
+ oldsize = self.get_size()
+ result = apply(self._qu_dt_munge, (source_string, mapping), vars)
+ newsize = self.get_size()
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Patch File and Image
+
+from OFS.Image import File, Image
+
+def QA_file_update_data(self, data, content_type=None, size=None):
+ ##
+ ## This method may be invoked when self.data hasn't been initialized
+ if not hasattr(self, "data"):
+ oldsize = 0
+ else:
+ oldsize = self.get_size()
+ result = apply(self._qu_update_data, (data, content_type, size))
+ newsize = self.get_size()
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Patch BTreeFolder, if available
+
+btree_installed = 0
+
+try:
+ from Products.BTreeFolder.BTreeFolder import BTreeFolder
+ LOG('QuotaProduct', INFO, "Patching BTreeFolder")
+ btree_installed = 1
+
+except ImportError:
+ pass
+
+##
+## Patch ZopePageTemplate, if available
+
+zpt_installed = 0
+
+try:
+ from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate, PageTemplate
+ LOG('QuotaProduct', INFO, "Patching ZPT")
+ zpt_installed = 1
+except ImportError:
+ zpt_installed = 0
+
+##
+## ZPT write hook
+def QA_zpt_write(self, text):
+ oldsize = len(self._text)
+ result = self._qu_write(text)
+ newsize = len(self._text)
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Install all hooks
+##
+## Attempt to detect when we're refreshing and when we're restarting
+if not hasattr(ObjectManager, "_quota_aware"):
+ ##
+ ## save 'old' methods
+ LOG('QuotaProduct', INFO, 'Detected server restart')
+ ObjectManager._qu_setOb = ObjectManager._setOb
+ ObjectManager._qu_delOb = ObjectManager._delOb
+ ObjectManager._quota_aware = 1
+ ObjectManager._qu_manage_importObject = ObjectManager.manage_importObject
+ PythonScript._qu_write = PythonScript.write
+ String._qu_dt_munge = String.munge
+ File._qu_update_data = File.update_data
+ Image._qu_update_data = Image.update_data
+
+ if btree_installed:
+ BTreeFolder._qu_setOb = BTreeFolder._setOb
+ BTreeFolder._qu_delOb = BTreeFolder._delOb
+ if zpt_installed:
+ ZopePageTemplate._qu_write = ZopePageTemplate.write
+else:
+ LOG('QuotaProduct', INFO, 'Detected product refresh')
+
+##
+## Install the new methods. It won't hurt if this happens during a refresh
+ObjectManager._setOb = QA_setOb
+ObjectManager._delOb = QA_delOb
+ObjectManager.manage_importObject = QA_manage_importObject
+ObjectManager._qf_check = QA_check_quota
+ObjectManager._get_usage = QA_get_usage
+ObjectManager._qa_sync = QA_sync
+PythonScript.write = QA_ps_write
+String.munge = QA_dt_munge
+File.update_data = QA_file_update_data
+Image.update_data = QA_file_update_data
+
+if btree_installed:
+ BTreeFolder._setOb = QA_setOb
+ BTreeFolder._delOb = QA_delOb
+if zpt_installed:
+ ZopePageTemplate.write = QA_zpt_write
+
+##
+## finally, install the QuotaFolder Product
+
+import QuotaFolder
+
+def initialize(context):
+ try:
+ context.registerClass(
+ QuotaFolder.QuotaFolder,
+ meta_type='QuotaFolder',
+ permission='Add QuotaFolder',
+ constructors=(QuotaFolder.manage_addQuotaFolderForm,
+ QuotaFolder.manage_addQuotaFolder),
+ )
+
+ except:
+ import traceback
+ traceback.print_exc()
Added: zope-quotafolder/branches/upstream/0.1.1/manage_addQuotaFolderForm.dtml
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/manage_addQuotaFolderForm.dtml (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/manage_addQuotaFolderForm.dtml 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,86 @@
+<dtml-var manage_page_header>
+<dtml-var "manage_form_title(this(), _,
+ form_title='Add Quota Folder'
+ )">
+
+<FORM ACTION="manage_addQuotaFolder" METHOD="POST">
+
+<table cellspacing="0" cellpadding="2" border="0">
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Id
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="id" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-optional">
+ Title
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="title" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Quota Size in Bytes (0 for unlimited)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="quota" size="40" value="0"/>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum Object Size in Bytes (0 for no limit)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="filesize" size="40" value="0" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum number of Objects (0 for unlimited)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="numberFiles" size="40" value="0" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Require Manager role in parent context to edit
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="checkbox" name="manager_role" value="yes" CHECKED>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ </td>
+ <td align="left" valign="top">
+ <div class="form-element">
+ <input class="form-element" type="submit" name="submit"
+ value="Add" />
+ </div>
+ </td>
+ </tr>
+</table>
+</form>
+
+
+<dtml-var manage_page_footer>
Added: zope-quotafolder/branches/upstream/0.1.1/manage_editQuotaForm.dtml
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/manage_editQuotaForm.dtml (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/manage_editQuotaForm.dtml 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,114 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<dtml-var "manage_form_title(this(), _,
+ form_title='Edit Quota Folder'
+ )">
+
+<FORM ACTION="manage_editQuota" METHOD="POST">
+<table cellspacing="0" cellpadding="2" border="0">
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Current quota usage
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-var current_size> bytes
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Number of objects
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-var current_count> Objects
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Quota Size in Bytes (0 for no limit)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="text" name="quota" size="40" value="<dtml-var quota_bytes>"/>
+ <dtml-else>
+ <dtml-var quota_bytes>
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum number of Objects (0 for unlimited)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="text" name="maxNumberOfFiles" size="40" value="<dtml-var quota_objects>"/>
+ <dtml-else>
+ <dtml-var quota_objects>
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum Object Size in Bytes (0 for no limit)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="text" name="maxFileSize" size="40" value="<dtml-var quota_maxsize>" />
+ <dtml-else>
+ <dtml-var quota_maxsize>
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Require Manager role in parent context to edit
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="checkbox" name="manager_role" value="yes"
+ <dtml-if manager_role>
+ CHECKED
+ </dtml-if>
+ >
+ (Warning: checking this box may revoke your rights to edit quota!)
+ <dtml-else>
+ <dtml-if manager_role>
+ YES
+ <dtml-else>
+ NO
+ </dtml-if>
+ (You are not authorized to edit this quota folder)
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <input class="form-element" type="submit" name="Sync" value="Sync" />
+ </td>
+ <dtml-if authorized_to_edit>
+ <td align="left" valign="top">
+ <div class="form-element">
+ <input class="form-element" type="submit" name="submit" value="Edit" />
+ </div>
+ </td>
+ <dtml-else>
+ <td></td>
+ </dtml-if>
+ </tr>
+</table>
+</form>
+
+
+<dtml-var manage_page_footer>
Added: zope-quotafolder/branches/upstream/0.1.1/refresh.txt
===================================================================
--- zope-quotafolder/branches/upstream/0.1.1/refresh.txt (rev 0)
+++ zope-quotafolder/branches/upstream/0.1.1/refresh.txt 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,6 @@
+
+NOTE TO DEVELOPERS:
+
+Refreshing this product may not always work. If you do not get the expected
+behaviour after refreshing, try restarting your zopeserver!
+
Added: zope-quotafolder/branches/upstream/current/DESIGN
===================================================================
--- zope-quotafolder/branches/upstream/current/DESIGN (rev 0)
+++ zope-quotafolder/branches/upstream/current/DESIGN 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,117 @@
+Patch ObjectManager zdd:
+
+ - het get_usage() ondersteunt
+ Deze itereert over alle subobjects en roept, indien mogelijk, get_usage
+ aan.
+
+ Return: files, size
+
+ - creatie van objecten verifieert met parent object
+
+
+Patch File en evt. andere objecten zdd.
+
+ - deze get_usage() ondersteunen
+ - manage_upload / whatever checken
+
+ Quota awareness
+
+Implementeer een speciale QuotaFolder welke op basis van get_usage()
+gebruik bepaalt.
+
+Hou rekening met:
+
+- transparante migratie
+- sync optie om quota up2date te krijgen
+- Nesten van QuotaFolders
+- consistentie bij fouten/exceptions (i.e. QuotaExceeded)
+
+- hard/soft quota
+- mailnotificatie
+- security check in context van parent
+
+- wat als een folderstructuur gepaste wordt? Met een te-groot object?
+
+Is het zinnig quota etc bij te houden als er geen QuotaFolder parent is?
+Alleen als parent later QuotaFolder wordt... Kan dit? (migratie)?
+Waarom dan niet gewoon syncen?
+
+Nesten van QuotaFolders is uiteindelijk wel wenselijk, hier dient rekening
+mee gehouden te worden...
+
+- wat als folder gedelete wordt?
+- als data geupload/vergroot wordt?
+- check ftp, webdav
+- len(GET()) geeft rendered lengte?
+- len(.zexp)?
+
+Bij aanpassing van een object relatieve verschil quota_checken(), of
+recursief naar boven toe herberekenen? (geen optie bij grote site)
+
+
+-----------------------------------------------------------------------
+
+Alle objecten en in het byzonder ObjectManagers worden quota aware gemaakt.
+Dit betekent dat:
+
+Objectmanagers
+
+- add/delete van objecten bijhouden (eigen count) en aan parent doorgeven
+- size van subobjecten bijhouden (recursief)
+- notificaties krijgen van size changes van subobjecten
+
+Andere Objecten (File, Image, DTMLDocument, PythonScript, ...)
+
+- eigen size bijhouden
+- uploads bijhouden en melden aan parent (=ObjectManager)
+- changes bijhouden en melden aan parent
+
+ (beiden kunnen groei/krimp zijn)
+
+- stort het systeem in (afgezien van quotafolders) als er geen QuotaProduct
+ meer is?
+- Ignore quota voor superuser
+- undo van (delete) transaction -> gevolgen?
+
+Toekomst:
+
+- soft/hard limit, expiry
+- mogelijkheid tot aanroepen script bij quotum exceeding (soft/hard) -> mail
+- Filteren/beperken meta types
+
+
+Specifiek testen:
+
+- exception bij overschrijding abort transaction?
+- support bij ftp, webdav (+ errorhandling)
+- photofolder
+- Btree folder
+- Transparentfolder?
+- Wat gebeurt er als je een quota aware folder importeert in een ander
+ systeem? (niks! denk ik)
+- undo quota change door manager?
+- objecten deleten, tegen quotum aanzitten, deletion undo-en
+- versions ?! Lijken transparant te werken... (alhoewel je wel veel state in versions kan bewaren)
+- metapublisher/formulator
+
+scripts als:
+
+doc = container.doc
+container.manage_delObjects(['doc'])
+doc.manage_edit('hello'*100, 'hacked')
+
+Evt. met vervangende pseudo parent
+
+BUGS
+----
+
+- zcatalog aanmaken geeft 1 object, zcatalog deleten is -2 FIXED
+- tutorial geeft niet teveel objecten/size, deleten gaat fout FIXED
+- manage import/export voegt ook objecten toe zonder setOb FIXED
+- acquisition werkt mogelijk niet meteen/direct voor imported data FIXED
+
+Probleem:
+---------
+
+Naast .zexp zijn ook pasted objecten objecten met veel subobjects (evt
+hierarchisch)
Added: zope-quotafolder/branches/upstream/current/KNOWNBUGS
===================================================================
--- zope-quotafolder/branches/upstream/current/KNOWNBUGS (rev 0)
+++ zope-quotafolder/branches/upstream/current/KNOWNBUGS 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,52 @@
+This is a list of known issues / incompatibilities with QuotaFolder
+
+Bugs in Zope:
+
+- ZopeTutorial does some inproper exception catching, causing QuotaExceeded
+ errors to be ignored (and actually the product to be imported twice).
+ The relevant code is:
+
+ try:
+ folder.manage_importObject(tutorialExamplesFile)
+ except:
+ folder._p_jar=self.Destination()._p_jar
+ folder.manage_importObject(tutorialExamplesFile)
+
+- A similair problem probably occurs when installing userfolders - if a user is past
+ his quota and tries to create a userfolder, the error 'this folder already contains a
+ userfolder', and the userfolder is created nonetheless
+
+ This actually brings the quotafolder out of sync!!! (because due to the exception,
+ the quota isn't updated, but the transaction isn't aborted either)
+
+ -- RESOLVED 16Mar02 This bug has been worked around, though the error 'this object
+ already has a userfolder' will appear if the quota is exceeded.
+
+Bugs in QuotaFolder:
+
+- Maximum object size is not always enforced, esp. when importing objects
+ (or hierarchies of objects)
+
+- The following script used to give problems:
+
+ doc = container.doc
+ container.manage_delObjects(['doc'])
+ doc.manage_edit('hello'*100, 'hacked')
+
+ Hooked objects now check if they are still contained in their parent.
+ What if doc is placed in a pseudo parent folder?
+
+- Pressing 'Sync' in the Quota tab brings you back to the Quota tab, however,
+ the tab itself indicates 'Contents' is selected. -- RESOLVED 18Mar02
+
+- QuotaExceeded exceptions aren't always precise - it displays how much quota
+ you would need for the operation, but that may not be correct.
+
+- QuotaFolder seems to mess up undo capabilities (under what circumstances?)
+
+- QuotaFolder is not compatible with FLE. Creation goes fine (it seems), but
+ newly created objects / modified objects are not added. -- RESOLVED 16Mar02
+
+- It is still possible to store large amounts of data in properties. A solution
+ would be to test if an object is an instance of PropertyManager, and account
+ the size of the properties appropriately.
Added: zope-quotafolder/branches/upstream/current/LICENSE
===================================================================
--- zope-quotafolder/branches/upstream/current/LICENSE (rev 0)
+++ zope-quotafolder/branches/upstream/current/LICENSE 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,27 @@
+Copyright (c) 2002 Ivo van der Wijk, Amaze Internet Services (ivo at amaze.nl)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Deleted: zope-quotafolder/branches/upstream/current/QuotaFolder-0.1.1.tar.gz
===================================================================
(Binary files differ)
Added: zope-quotafolder/branches/upstream/current/QuotaFolder.py
===================================================================
--- zope-quotafolder/branches/upstream/current/QuotaFolder.py (rev 0)
+++ zope-quotafolder/branches/upstream/current/QuotaFolder.py 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,253 @@
+##
+## Copyright (c) 2002 Ivo van der Wijk, Amaze Internet Services (ivo at amaze.nl)
+##
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions
+## are met:
+##
+## 1. Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in the
+## documentation and/or other materials provided with the distribution.
+## 3. The name of the author may not be used to endorse or promote products
+## derived from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+##
+
+import App, Globals, OFS
+import string
+
+from Persistence import Persistent
+
+from Globals import DTMLFile
+from OFS.Folder import Folder
+from Acquisition import aq_base, aq_parent, Acquired, Implicit
+from AccessControl import getSecurityManager, SpecialUsers
+
+from zLOG import LOG, WARNING
+
+def manage_addQuotaFolder(self, id, title, quota_bytes=0, quota_objects=0,
+ quota_maxsize=0, REQUEST=None):
+ """ Add a new QuotaFolder """
+
+ o = QuotaFolder(id, title, quota_bytes, quota_maxsize, quota_objects, REQUEST)
+ self._setObject(id, o)
+ o = self._getOb(id)
+
+ if REQUEST:
+ return self.manage_main(self, REQUEST, update_menu=1)
+
+def _replaceFolder(parent, id, quota_bytes=0, quota_objects=0, quota_maxsize=0):
+ # Replaces an OFS.Folder with a QuotaFolder. Borrowed from BTreeFolder
+ ob = QuotaFolder(id, "", quota_bytes, quota_maxsize, quota_objects)
+ f = parent._getOb(id)
+ # Copy the contents of the folder to the new QuotaFolder
+
+ ids = f.objectIds()
+ for key, value in f.__dict__.items():
+ if key not in ids:
+ # Not an ObjectManager item.
+ ob.__dict__[key] = value
+ for key in ids:
+ subob = f._getOb(key)
+ subob = getattr(subob, 'aq_base', subob)
+ ob._setOb(key, subob)
+ parent._setOb(id, ob)
+
+
+manage_addQuotaFolderForm=DTMLFile('manage_addQuotaFolderForm', globals())
+
+QuotaExceededException='Quota Exceeded'
+
+class QuotaFolder(Folder, Persistent, Implicit):
+ """ A QuotaFolder """
+
+ meta_type='QuotaFolder'
+
+ manage_options=(
+ Folder.manage_options+
+ (
+ {'label': 'Quota', 'action': 'manage_editQuotaForm'},
+ )
+ )
+
+ __ac_permissions__= \
+ Folder.__ac_permissions__ + \
+ (
+ ('View current quota', ('currentCount','currentSize')),
+ )
+
+ manage_editQuotaForm=DTMLFile('manage_editQuotaForm', globals())
+
+ def __init__(self, id, title="", quota=0, maxFileSize=0, maxNumberOfFiles=0,
+ manager_role=1, REQUEST=None):
+ self.id=id
+ self.title=title
+ self._quota_bytes=int(quota)
+ self._quota_objects=int(maxNumberOfFiles)
+ self._quota_maxsize=int(maxFileSize)
+
+ ##
+ ## current accounting
+ self._quota_filecount = 0
+ self._quota_size = 0
+
+ self._manager_role = 0
+ if manager_role:
+ self._manager_role = 1
+ self._debug = 0
+ self._allow_nested = 0
+
+ def _check_quota(self, filechange, sizechange=0, objsize=0):
+ """ Check if the change in usage violates the quota settings """
+ #
+ # print "I'm a quota folder: ", filechange, sizechange, objsize
+ # print "Current quota: ", self._quota_filecount, self._quota_size
+
+ #
+ # The emergency user can create *some* objects (i.e. userfolders).
+ # make sure this never gives a QuotaExceeded exception
+
+ emergency = 0
+ user=getSecurityManager().getUser()
+ if (SpecialUsers.emergency_user and
+ aq_base(user) is SpecialUsers.emergency_user):
+ LOG('QuotaFolder', INFO,
+ "Emergency user detected - not enforcing quota")
+ emergency = 1
+
+ if not emergency and \
+ filechange > 0 and \
+ self._quota_filecount + filechange > self._quota_objects \
+ > 0:
+ raise QuotaExceededException, \
+ "Too many files, this operation would require a quotum " + \
+ "of %d files, your current quotum is %d files" % \
+ (self._quota_filecount + filechange, self._quota_objects)
+ if not emergency and \
+ sizechange > 0 and \
+ self._quota_size + sizechange > self._quota_bytes > 0:
+ raise QuotaExceededException, \
+ "Too much space, this operation would require a " + \
+ "quotum of %d bytes, your current quotum is %d bytes" % \
+ (self._quota_size + sizechange, self._quota_bytes)
+ if not emergency and objsize > self._quota_maxsize > 0:
+ raise QuotaExceededException, \
+ "The object is too large. It requires %d bytes, " + \
+ "your maximum object size limit is %d bytes" % \
+ (objsize, self._quota_maxsize)
+
+ self._quota_filecount = self._quota_filecount + filechange
+ self._quota_size = self._quota_size + sizechange
+
+ ##
+ ## This should not happen...
+ path = string.join(self.getPhysicalPath(), "/")
+ if self._quota_filecount < 0:
+ LOG('QuotaFolder', WARNING, "Negative quota filecount in %s" % path)
+ if not self._debug:
+ self._quota_filecount = 0
+ if self._quota_size < 0:
+ LOG('QuotaFolder', WARNING, "Negative quota size in %s" % path)
+ if not self._debug:
+ self._quota_size = 0
+
+ # print "New quota: ", self._quota_filecount, self._quota_size
+
+ def current_count(self):
+ return self._quota_filecount
+
+ def current_size(self):
+ return self._quota_size
+
+ def quota_bytes(self):
+ return self._quota_bytes
+
+ def quota_objects(self):
+ return self._quota_objects
+
+ def quota_maxsize(self):
+ return self._quota_maxsize
+
+ def authorized_to_edit(self):
+ """
+ return true/false if user is authorized to change in current context
+ """
+
+ security=getSecurityManager()
+ security.addContext(self)
+ user = security.getUser()
+ if not self._manager_role:
+ ## XXX use permission?
+ return user.allowed(self, ['Manager'])
+
+ ## use aq_base?
+ return user.allowed(aq_parent(self), ['Manager'])
+
+ def manager_role(self):
+ return self._manager_role
+
+ def manage_editQuota(self, quota=-1, maxFileSize=-1, maxNumberOfFiles=-1,
+ REQUEST=None):
+ """ Update the current Quota """
+ ##
+ ## Perhaps always sync after edit?
+
+ if REQUEST.has_key('Sync'):
+ self._qa_sync()
+ ## XXX report difference to parent?
+ if REQUEST:
+ ## A redirect selects the proper tab
+ message = "Quota synchronized"
+ REQUEST.RESPONSE.redirect(
+ 'manage_editQuotaForm?manage_tabs_message=%s' % message)
+ return self.manage_editQuotaForm(self, REQUEST,
+ manage_tabs_message=message)
+ return ''
+
+ if not self.authorized_to_edit():
+ raise "Unauthorized", \
+ "You are not authorized to edit this quota folder"
+
+ if quota != -1:
+ self._quota_bytes=int(quota)
+ if maxFileSize != -1:
+ self._quota_maxsize=int(maxFileSize)
+ if maxNumberOfFiles != -1:
+ self._quota_objects=int(maxNumberOfFiles)
+ self._manager_role = REQUEST.get("manager_role", 0)
+ if REQUEST:
+ message = "Changes saved"
+ ## A redirect selects the proper tab
+ REQUEST.RESPONSE.redirect(
+ 'manage_editQuotaForm?manage_tabs_message=%s' % message)
+ return self.manage_editQuotaForm(self, REQUEST,
+ manage_tabs_message=message)
+ return ''
+
+ def manage_afterAdd(self, item, container):
+ # print "afterAdd %s %s"%(str(item), str(container))
+ if not self._allow_nested:
+ for parent in self.aq_chain[1:]:
+ if hasattr(parent, "meta_type") and \
+ parent.meta_type == "QuotaFolder":
+ raise QuotaExceededException, \
+ "Cannot add a QuotaFolder inside another QuotaFolder"
+
+ Folder.manage_afterAdd(self, item, container)
+
+Globals.default__class_init__(QuotaFolder)
Added: zope-quotafolder/branches/upstream/current/README.txt
===================================================================
--- zope-quotafolder/branches/upstream/current/README.txt (rev 0)
+++ zope-quotafolder/branches/upstream/current/README.txt 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,187 @@
+QuotaFolder - Quota support for Zope
+
+What is it?
+-----------
+
+QuotaFolder is a folder-ish object that restricts the total number of objects,
+their total size and their individual maximum size. QuotaFolder takes
+subfolders (recursively) into account, so it should not be possible to escape
+the quota restrictions.
+
+The basic goal of QuotaFolder is not to put an absolute limit on ZODB usage
+- it is impossible to determine this. For example, each object may have
+several revisions in the ZODB, and some of the internal state of the object
+is stored in the ZODB that's not returned by get_size(). The goal is to
+limit the use of objects in general, to prevent people from offering large
+files or using up resources with enormous amounts of objects.
+
+How does it work?
+-----------------
+
+QuotaFolder 'MonkeyPatches (tm)' some of the internal Zope components such
+as ObjectManager, File, Image, DTMLMethod, DTMLDocument and more. The patching
+constist of making the components 'quota aware'. This basically means the
+objects will track changes and report them to parent folders. If one if the
+parent folders is a QuotaFolder, and the QuotaFolder detects that the change
+will exceed the quota, a QuotaExceededException is raised, and the transaction
+is rolled back.
+
+At this moment, if an object supports the get_size() method, it's used. The
+value reported by this method will not take all usage (i.e. properties) into
+account, so this may be changed in the future.
+
+Supported products
+------------------
+
+The QuotaFolder product knows how to account File, Image, DTMLMethod,
+DTMLDocument and PythonScript objects. It also understands ObjectManager
+based products such as Folder and BTreeFolder (TransparentFolder is not
+thoroughly tested, but it seems to work well)
+
+Most products (i.e. FLE, Squishdot, PhotoFolder, ZWiki) exist of ObjectManagers
+with subobjects, and QuotaFolder knows how to handle these quite well.
+Unknown objects who's size (and changes in size) cannot be determined are
+accounted as 1 object with size 0. If this is not satisfactory (for example,
+it's not at this moment for TinyTablePlus), extra support can be built in for
+these objects (as has been done with, for example, ZPT).
+
+QuotaFolder also supports .zexp imports and copy/paste operations.
+
+QuotaFolder has been tested with Zope 2.4.3, 2.4.4b1 and 2.5.0. It has not
+been tested in ZEO setups.
+
+Installing
+----------
+
+**WARNING**
+
+This product patches classes in your Zope server. Please test the code on
+a test-server or shadow server first! This code has been known to work
+succesfully with Zope 2.4.x and Zope 2.5. Use this product at your own
+risk, and backup your Data.fs first!
+
+Install the QuotaFolder product by simply unpacking into your Products
+directory (either in your SOFTWARE_HOME or your INSTANCE_HOME) and restart
+your Zope server. The QuotaFolder product should appear in
+/Control_Panel/Products, and it should also be available in the products
+dropdown.
+
+When creating (or editting) a QuotaFolder, you will be presented with the
+following fields:
+
+id
+title
+Quota size in bytes - this is the maximum total size of all objects
+ that's allowed to be created
+Maximum object size in bytes - The maximum allowed total size for single
+ objects
+Maximum number of objects - this is the maximum number of objects that's
+ allowed to be created
+Require manager role in parent context?
+ - If this setting is enabled, a user must be
+ manager in the folder context *above* the
+ quotafolder itself to be able to edit the
+ quota. If you want to limit your users in their
+ usage, you don't want them to be able to edit
+ their quota themselves, do you?
+
+After creation, you will get the same contents view as with a standard folder,
+but with an extra tab to the right where you can view and (optionally edit)
+the quota.
+
+When visiting the quota tab, you will see an extra button 'Sync'. Pressing
+this button will cause the QuotaFolder to recalculate all usage. Usually,
+the folder shouldn't be out of sync. If you manage to get a QuotaFolder
+out of sync, please contact me.
+
+Migrating
+---------
+
+There are two ways to migrate a standard folder to a QuotaFolder:
+
+- Create a new QuotaFolder, copy all objects from the old folder, paste
+ them into the QuotaFolder and rename the folders. This will probably not
+ work if you have versions (cut/paste may work with versions)
+
+- Use the builtin _replaceFolder method. I.e. create the following external
+ method:
+
+ from Products.QuotaFolder.QuotaFolder import _replaceFolder
+
+ def replace(self, name, quota_bytes, quota_objects, quota_maxsize, REQUEST):
+ _replaceFolder(self, name, quota_bytes, quota_objects, quota_maxsize)
+ return "%s converted" % name
+
+ Create an appropriate external method object in your zope server and invoke
+ it with an appropriately formatted url or create a dtml form.
+
+Hacking contest!
+----------------
+
+QuotaFolder has been thoroughly tested, and seems very stable and compatible.
+However, every now and then, new situations seem to appear where it's possible
+to use more objects or space than the QuotaFolder should enforce. This may
+be through unsupported objects (though these should generaly not make it
+possible to create more free space for other objects), or using trickery
+with PythonScripts, etc.
+
+If anyone finds issues like this, please contact me (info below), so we can
+make this product even more stable and robust :)
+
+Please check the file KNOWNBUGS before reporting issues.
+
+Release info
+------------
+
+0.1 Initial version, basic support for quota
+
+Future plans
+------------
+
+- Support soft and hardlimit quota, with configurable timeleft
+- Restrict installable metatypes
+- Limit number of certain metatypes
+
+Contact/License
+---------------
+
+QuotaFolder is partially based on ideas by Andrew Kenneth, though most of his
+old QuotaFolder has disappeared.
+
+QuotaFolder is written by Ivo van der Wijk as part of Amaze Internet Service's
+FreeZope.org Free Zope hosting environment.
+
+I can be contacted through ivo at amaze.nl or on IRC as VladDrac / VladDrak @ OPN
+
+Recent versions of QuotaFolder and other Zope products can be found at:
+
+http://www.zope.org/Members/ivo
+http://vanderijk.info/
+
+QuotaFolder is (c) 2002 Ivo van der Wijk / Amaze Internet Services
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Added: zope-quotafolder/branches/upstream/current/TODO
===================================================================
--- zope-quotafolder/branches/upstream/current/TODO (rev 0)
+++ zope-quotafolder/branches/upstream/current/TODO 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,23 @@
+First release:
+
+- check required attributes on objects (i.e. data on File/Image), report
+ warning if missing
+- Provide a conversion script (external method) to convert standard folders
+ to QuotaFolders DONE
+- Prevent 0-changes from propagating
+- properly determine size for TinyTablePlus objects
+- better QuotaExceeded exception strings DONE
+- ignore superuser, etc. DONE
+- add proper permissions DONE?
+- Do not raise exceptions on negative changes DONE
+- LICENSE
+
+Later release
+- support TinyTablePlus
+- soft/hard quota
+- Limit/restrict installable meta types
+- configurable size for objects without explicit size? (seems pointless)
+- configure max object size per meta type (+ limit # of instances)
+- optionally prevent quota updates from propagating beyond a folder (checkbox)
+ -> this doesn't have to break nested quotafolders! (though it may give
+ problems)
Added: zope-quotafolder/branches/upstream/current/__init__.py
===================================================================
--- zope-quotafolder/branches/upstream/current/__init__.py (rev 0)
+++ zope-quotafolder/branches/upstream/current/__init__.py 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,395 @@
+##
+## Copyright (c) 2002 Ivo van der Wijk, Amaze Internet Services (ivo at amaze.nl)
+##
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions
+## are met:
+##
+## 1. Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in the
+## documentation and/or other materials provided with the distribution.
+## 3. The name of the author may not be used to endorse or promote products
+## derived from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+##
+
+from zLOG import LOG, WARNING, BLATHER, INFO
+
+from OFS.ObjectManager import ObjectManager
+from Acquisition import aq_parent, aq_chain, aq_inner
+import QuotaFolder
+
+##
+## NOTE TO DEVELOPERS:
+##
+## Refreshing this product may not always work. If you do not get the expected
+## behaviour after refreshing, try restarting your zopeserver!
+##
+
+##
+## Idea: use *args / **args for wrapper methods and apply parameters to methods
+##
+## QA stands for Quota Aware, QU for Quota Unaware
+
+##
+## Invoke chain of _qf_check's, so all parents can update? (for delete of
+## folders etc)
+
+def QA_setOb(self, id, object):
+ ##
+ ## Is it smarter to patch setObject instead? Is setOb ever invoked directly?
+
+ ##
+ ## We rely on the transaction being rolled back if an exception occurs.
+ ## This means that QuotaExceeded exceptions shouldn't occur. Unfortunately,
+ ## sometimes they are caught.
+ if isinstance(object, ObjectManager):
+ make_quota_aware(object)
+
+ if hasattr(object, "_qa_sync"):
+ object._qa_sync()
+ _files, _size = QA_determine_usage(object)
+ try:
+ self._qf_check(_files, _size)
+ except QuotaFolder.QuotaExceededException:
+ ##
+ ## This horrible fix is because manage_addUserFolder ignores all
+ ## exceptions, and assumes there already is a UserFolder, while
+ ## _setObject happily changes _objects as well while ignoring
+ ## anything that may go wrong
+ # print "O", self._objects
+ o = []
+ ##
+ ## Could this go wrong if there already is an object with id?
+ ## (replacable?) (check using getattr?)
+ if not hasattr(self, id):
+ for i in self._objects:
+ if i['id'] != id:
+ o.append(i)
+ self._objects = tuple(o)
+ raise
+ result = self._qu_setOb(id, object)
+ return result
+
+def QA_delOb(self, id):
+ # we can't use _getOb because it doesn't allow _attrs
+ ##
+ ## this works with BtreeFolders as well (well, with non-basicBtreeFolders)
+ obj = getattr(self, id)
+ try:
+ _files,_size = QA_determine_usage(obj)
+ except:
+ LOG('QuotaProduct', BLATHER, "Can't get size for %s" % id)
+ _files, _size = 1,0
+
+ self._qf_check(-_files, -_size)
+
+ return self._qu_delOb(id)
+
+def getid(id):
+ """ sometimes id is a string, sometimes a method """
+ if callable(id): return id()
+ return id
+
+def findparent(object):
+ """ find the direct parent """
+
+ for parent in aq_chain(object):
+ if parent is object:
+ continue
+ if parent == object:
+ continue
+ if isinstance(parent, ObjectManager):
+ return parent
+ return None
+
+def QA_check_quota(self, filechange, sizechange=0, objsize=0):
+ """
+ Notify a change in usage:
+
+ filechange number of added/removed files
+ sizechange change in size of usage
+ objsize total size of object
+
+ changes can be negative as well
+ """
+
+ if hasattr(self, "_check_quota"):
+ ## check hook for QuotaFolder
+ self._check_quota(filechange, sizechange, objsize)
+ else:
+ ## do own administration
+ make_quota_aware(self)
+ self._quota_filecount = self._quota_filecount + filechange
+ self._quota_size = self._quota_size + sizechange
+
+ ##
+ ## Acquisition is scary. The chain looks different when traversing
+ ## through ftp/webdav. basically, 'self' may appear in the chain multiple
+ ## times (with A NullResource in between), and may not always be equal
+ ## through 'is' (so try == as well)
+ ##
+ ## Tip: use aq_base(parent) XXX
+
+ for parent in aq_chain(self):
+ if parent is self:
+ continue
+ if parent == self:
+ continue
+ if hasattr(parent, "_qf_check"):
+ if hasattr(parent, getid(self.id)):
+ parent._qf_check(filechange,sizechange,objsize)
+ else:
+ for name,ob in parent.objectItems():
+ if ob.id == getid(self.id):
+ parent._qf_check(filechange,sizechange,objsize)
+ return # !!
+ break
+
+def QA_manage_importObject(self, file, REQUEST=None, set_owner=1):
+ """ currently a useless hook. To be removed if not used """
+ result = apply(self._qu_manage_importObject, (file, REQUEST, set_owner))
+ return result
+
+def QA_sync(self):
+ """ synchronize, i.e. recursively recalculate sizes """
+ self._quota_filecount = 0 # not 1, get_usage adds 1
+ self._quota_size = 0
+
+ for child in self.objectValues():
+ if hasattr(child, "_qa_sync"):
+ child._qa_sync()
+
+ _files, _size = QA_determine_usage(child)
+ self._quota_filecount = self._quota_filecount + _files
+ self._quota_size = self._quota_size + _size
+
+def make_quota_aware(object):
+ if not hasattr(object, "_quota_filecount"):
+ object._quota_filecount = 0
+ if not hasattr(object, "_quota_size"):
+ object._quota_size = 0
+
+def QA_get_usage(self):
+ """
+ Return the size of an ObjectManager object. This is 1 + the number
+ of subobjects. The size of an empty ObjectManager is considered to
+ be 0
+ """
+ return self._quota_filecount+1, self._quota_size
+
+def QA_determine_usage(object):
+ """
+ attempt to determine number of size and number of subobjects of object
+ """
+ ##
+ ## ZPT doesn't return the size of the unrendered content it seems,
+ ## which leads to
+ ## very inconsistent sizes. Let's hope this fixes it...
+ if zpt_installed and isinstance(object, PageTemplate):
+ # workaround for ZPT
+ if hasattr(object, "_text"):
+ return 1, len(object._text)
+ return 1, 0
+ if hasattr(object, "_get_usage"):
+ return object._get_usage()
+ elif hasattr(object, "get_size"):
+ ##
+ ## Some products (such as PhotoFolders Photo) raise an exception
+ ## when requested their size at init time
+ try:
+ _size = object.get_size()
+ except: # can be any kind of exception
+ LOG('QuotaProduct', BLATHER, 'Failed to get size for %s/%s' % \
+ (getid(object.id), object.meta_type))
+ _size = 0
+ return 1, _size
+ elif hasattr(object, "getSize"):
+ ## getSize is deprecated, but if it's all there is...
+ return 1, object.getSize()
+ else:
+ LOG('QuotaProduct', BLATHER, "Can't get size for %s/%s" % \
+ (getid(object.id), object.meta_type))
+ return 1, 0
+
+##
+## Patch the PythonScript object
+
+from Products.PythonScripts.PythonScript import PythonScript
+
+def QA_ps_write(self, text):
+ """ invoked at creation, edit and upload - what else do you want? """
+ oldsize = self.get_size()
+ result = self._qu_write(text)
+ newsize = self.get_size()
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Patch DT_String, which effectively patches DTMLDocument and DTMLMethod
+
+from DocumentTemplate.DT_String import String
+
+def QA_dt_munge(self, source_string=None, mapping=None, **vars):
+ oldsize = self.get_size()
+ result = apply(self._qu_dt_munge, (source_string, mapping), vars)
+ newsize = self.get_size()
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Patch File and Image
+
+from OFS.Image import File, Image
+
+def QA_file_update_data(self, data, content_type=None, size=None):
+ ##
+ ## This method may be invoked when self.data hasn't been initialized
+ if not hasattr(self, "data"):
+ oldsize = 0
+ else:
+ oldsize = self.get_size()
+ result = apply(self._qu_update_data, (data, content_type, size))
+ newsize = self.get_size()
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Patch BTreeFolder, if available
+
+btree_installed = 0
+
+try:
+ from Products.BTreeFolder.BTreeFolder import BTreeFolder
+ LOG('QuotaProduct', INFO, "Patching BTreeFolder")
+ btree_installed = 1
+
+except ImportError:
+ pass
+
+##
+## Patch ZopePageTemplate, if available
+
+zpt_installed = 0
+
+try:
+ from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate, PageTemplate
+ LOG('QuotaProduct', INFO, "Patching ZPT")
+ zpt_installed = 1
+except ImportError:
+ zpt_installed = 0
+
+##
+## ZPT write hook
+def QA_zpt_write(self, text):
+ oldsize = len(self._text)
+ result = self._qu_write(text)
+ newsize = len(self._text)
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Install all hooks
+##
+## Attempt to detect when we're refreshing and when we're restarting
+if not hasattr(ObjectManager, "_quota_aware"):
+ ##
+ ## save 'old' methods
+ LOG('QuotaProduct', INFO, 'Detected server restart')
+ ObjectManager._qu_setOb = ObjectManager._setOb
+ ObjectManager._qu_delOb = ObjectManager._delOb
+ ObjectManager._quota_aware = 1
+ ObjectManager._qu_manage_importObject = ObjectManager.manage_importObject
+ PythonScript._qu_write = PythonScript.write
+ String._qu_dt_munge = String.munge
+ File._qu_update_data = File.update_data
+ Image._qu_update_data = Image.update_data
+
+ if btree_installed:
+ BTreeFolder._qu_setOb = BTreeFolder._setOb
+ BTreeFolder._qu_delOb = BTreeFolder._delOb
+ if zpt_installed:
+ ZopePageTemplate._qu_write = ZopePageTemplate.write
+else:
+ LOG('QuotaProduct', INFO, 'Detected product refresh')
+
+##
+## Install the new methods. It won't hurt if this happens during a refresh
+ObjectManager._setOb = QA_setOb
+ObjectManager._delOb = QA_delOb
+ObjectManager.manage_importObject = QA_manage_importObject
+ObjectManager._qf_check = QA_check_quota
+ObjectManager._get_usage = QA_get_usage
+ObjectManager._qa_sync = QA_sync
+PythonScript.write = QA_ps_write
+String.munge = QA_dt_munge
+File.update_data = QA_file_update_data
+Image.update_data = QA_file_update_data
+
+if btree_installed:
+ BTreeFolder._setOb = QA_setOb
+ BTreeFolder._delOb = QA_delOb
+if zpt_installed:
+ ZopePageTemplate.write = QA_zpt_write
+
+##
+## finally, install the QuotaFolder Product
+
+import QuotaFolder
+
+def initialize(context):
+ try:
+ context.registerClass(
+ QuotaFolder.QuotaFolder,
+ meta_type='QuotaFolder',
+ permission='Add QuotaFolder',
+ constructors=(QuotaFolder.manage_addQuotaFolderForm,
+ QuotaFolder.manage_addQuotaFolder),
+ )
+
+ except:
+ import traceback
+ traceback.print_exc()
Added: zope-quotafolder/branches/upstream/current/manage_addQuotaFolderForm.dtml
===================================================================
--- zope-quotafolder/branches/upstream/current/manage_addQuotaFolderForm.dtml (rev 0)
+++ zope-quotafolder/branches/upstream/current/manage_addQuotaFolderForm.dtml 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,86 @@
+<dtml-var manage_page_header>
+<dtml-var "manage_form_title(this(), _,
+ form_title='Add Quota Folder'
+ )">
+
+<FORM ACTION="manage_addQuotaFolder" METHOD="POST">
+
+<table cellspacing="0" cellpadding="2" border="0">
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Id
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="id" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-optional">
+ Title
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="title" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Quota Size in Bytes (0 for unlimited)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="quota" size="40" value="0"/>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum Object Size in Bytes (0 for no limit)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="filesize" size="40" value="0" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum number of Objects (0 for unlimited)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="numberFiles" size="40" value="0" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Require Manager role in parent context to edit
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="checkbox" name="manager_role" value="yes" CHECKED>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ </td>
+ <td align="left" valign="top">
+ <div class="form-element">
+ <input class="form-element" type="submit" name="submit"
+ value="Add" />
+ </div>
+ </td>
+ </tr>
+</table>
+</form>
+
+
+<dtml-var manage_page_footer>
Added: zope-quotafolder/branches/upstream/current/manage_editQuotaForm.dtml
===================================================================
--- zope-quotafolder/branches/upstream/current/manage_editQuotaForm.dtml (rev 0)
+++ zope-quotafolder/branches/upstream/current/manage_editQuotaForm.dtml 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,114 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<dtml-var "manage_form_title(this(), _,
+ form_title='Edit Quota Folder'
+ )">
+
+<FORM ACTION="manage_editQuota" METHOD="POST">
+<table cellspacing="0" cellpadding="2" border="0">
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Current quota usage
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-var current_size> bytes
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Number of objects
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-var current_count> Objects
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Quota Size in Bytes (0 for no limit)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="text" name="quota" size="40" value="<dtml-var quota_bytes>"/>
+ <dtml-else>
+ <dtml-var quota_bytes>
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum number of Objects (0 for unlimited)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="text" name="maxNumberOfFiles" size="40" value="<dtml-var quota_objects>"/>
+ <dtml-else>
+ <dtml-var quota_objects>
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum Object Size in Bytes (0 for no limit)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="text" name="maxFileSize" size="40" value="<dtml-var quota_maxsize>" />
+ <dtml-else>
+ <dtml-var quota_maxsize>
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Require Manager role in parent context to edit
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="checkbox" name="manager_role" value="yes"
+ <dtml-if manager_role>
+ CHECKED
+ </dtml-if>
+ >
+ (Warning: checking this box may revoke your rights to edit quota!)
+ <dtml-else>
+ <dtml-if manager_role>
+ YES
+ <dtml-else>
+ NO
+ </dtml-if>
+ (You are not authorized to edit this quota folder)
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <input class="form-element" type="submit" name="Sync" value="Sync" />
+ </td>
+ <dtml-if authorized_to_edit>
+ <td align="left" valign="top">
+ <div class="form-element">
+ <input class="form-element" type="submit" name="submit" value="Edit" />
+ </div>
+ </td>
+ <dtml-else>
+ <td></td>
+ </dtml-if>
+ </tr>
+</table>
+</form>
+
+
+<dtml-var manage_page_footer>
Added: zope-quotafolder/branches/upstream/current/refresh.txt
===================================================================
--- zope-quotafolder/branches/upstream/current/refresh.txt (rev 0)
+++ zope-quotafolder/branches/upstream/current/refresh.txt 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,6 @@
+
+NOTE TO DEVELOPERS:
+
+Refreshing this product may not always work. If you do not get the expected
+behaviour after refreshing, try restarting your zopeserver!
+
Added: zope-quotafolder/trunk/DESIGN
===================================================================
--- zope-quotafolder/trunk/DESIGN (rev 0)
+++ zope-quotafolder/trunk/DESIGN 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,117 @@
+Patch ObjectManager zdd:
+
+ - het get_usage() ondersteunt
+ Deze itereert over alle subobjects en roept, indien mogelijk, get_usage
+ aan.
+
+ Return: files, size
+
+ - creatie van objecten verifieert met parent object
+
+
+Patch File en evt. andere objecten zdd.
+
+ - deze get_usage() ondersteunen
+ - manage_upload / whatever checken
+
+ Quota awareness
+
+Implementeer een speciale QuotaFolder welke op basis van get_usage()
+gebruik bepaalt.
+
+Hou rekening met:
+
+- transparante migratie
+- sync optie om quota up2date te krijgen
+- Nesten van QuotaFolders
+- consistentie bij fouten/exceptions (i.e. QuotaExceeded)
+
+- hard/soft quota
+- mailnotificatie
+- security check in context van parent
+
+- wat als een folderstructuur gepaste wordt? Met een te-groot object?
+
+Is het zinnig quota etc bij te houden als er geen QuotaFolder parent is?
+Alleen als parent later QuotaFolder wordt... Kan dit? (migratie)?
+Waarom dan niet gewoon syncen?
+
+Nesten van QuotaFolders is uiteindelijk wel wenselijk, hier dient rekening
+mee gehouden te worden...
+
+- wat als folder gedelete wordt?
+- als data geupload/vergroot wordt?
+- check ftp, webdav
+- len(GET()) geeft rendered lengte?
+- len(.zexp)?
+
+Bij aanpassing van een object relatieve verschil quota_checken(), of
+recursief naar boven toe herberekenen? (geen optie bij grote site)
+
+
+-----------------------------------------------------------------------
+
+Alle objecten en in het byzonder ObjectManagers worden quota aware gemaakt.
+Dit betekent dat:
+
+Objectmanagers
+
+- add/delete van objecten bijhouden (eigen count) en aan parent doorgeven
+- size van subobjecten bijhouden (recursief)
+- notificaties krijgen van size changes van subobjecten
+
+Andere Objecten (File, Image, DTMLDocument, PythonScript, ...)
+
+- eigen size bijhouden
+- uploads bijhouden en melden aan parent (=ObjectManager)
+- changes bijhouden en melden aan parent
+
+ (beiden kunnen groei/krimp zijn)
+
+- stort het systeem in (afgezien van quotafolders) als er geen QuotaProduct
+ meer is?
+- Ignore quota voor superuser
+- undo van (delete) transaction -> gevolgen?
+
+Toekomst:
+
+- soft/hard limit, expiry
+- mogelijkheid tot aanroepen script bij quotum exceeding (soft/hard) -> mail
+- Filteren/beperken meta types
+
+
+Specifiek testen:
+
+- exception bij overschrijding abort transaction?
+- support bij ftp, webdav (+ errorhandling)
+- photofolder
+- Btree folder
+- Transparentfolder?
+- Wat gebeurt er als je een quota aware folder importeert in een ander
+ systeem? (niks! denk ik)
+- undo quota change door manager?
+- objecten deleten, tegen quotum aanzitten, deletion undo-en
+- versions ?! Lijken transparant te werken... (alhoewel je wel veel state in versions kan bewaren)
+- metapublisher/formulator
+
+scripts als:
+
+doc = container.doc
+container.manage_delObjects(['doc'])
+doc.manage_edit('hello'*100, 'hacked')
+
+Evt. met vervangende pseudo parent
+
+BUGS
+----
+
+- zcatalog aanmaken geeft 1 object, zcatalog deleten is -2 FIXED
+- tutorial geeft niet teveel objecten/size, deleten gaat fout FIXED
+- manage import/export voegt ook objecten toe zonder setOb FIXED
+- acquisition werkt mogelijk niet meteen/direct voor imported data FIXED
+
+Probleem:
+---------
+
+Naast .zexp zijn ook pasted objecten objecten met veel subobjects (evt
+hierarchisch)
Added: zope-quotafolder/trunk/KNOWNBUGS
===================================================================
--- zope-quotafolder/trunk/KNOWNBUGS (rev 0)
+++ zope-quotafolder/trunk/KNOWNBUGS 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,52 @@
+This is a list of known issues / incompatibilities with QuotaFolder
+
+Bugs in Zope:
+
+- ZopeTutorial does some inproper exception catching, causing QuotaExceeded
+ errors to be ignored (and actually the product to be imported twice).
+ The relevant code is:
+
+ try:
+ folder.manage_importObject(tutorialExamplesFile)
+ except:
+ folder._p_jar=self.Destination()._p_jar
+ folder.manage_importObject(tutorialExamplesFile)
+
+- A similair problem probably occurs when installing userfolders - if a user is past
+ his quota and tries to create a userfolder, the error 'this folder already contains a
+ userfolder', and the userfolder is created nonetheless
+
+ This actually brings the quotafolder out of sync!!! (because due to the exception,
+ the quota isn't updated, but the transaction isn't aborted either)
+
+ -- RESOLVED 16Mar02 This bug has been worked around, though the error 'this object
+ already has a userfolder' will appear if the quota is exceeded.
+
+Bugs in QuotaFolder:
+
+- Maximum object size is not always enforced, esp. when importing objects
+ (or hierarchies of objects)
+
+- The following script used to give problems:
+
+ doc = container.doc
+ container.manage_delObjects(['doc'])
+ doc.manage_edit('hello'*100, 'hacked')
+
+ Hooked objects now check if they are still contained in their parent.
+ What if doc is placed in a pseudo parent folder?
+
+- Pressing 'Sync' in the Quota tab brings you back to the Quota tab, however,
+ the tab itself indicates 'Contents' is selected. -- RESOLVED 18Mar02
+
+- QuotaExceeded exceptions aren't always precise - it displays how much quota
+ you would need for the operation, but that may not be correct.
+
+- QuotaFolder seems to mess up undo capabilities (under what circumstances?)
+
+- QuotaFolder is not compatible with FLE. Creation goes fine (it seems), but
+ newly created objects / modified objects are not added. -- RESOLVED 16Mar02
+
+- It is still possible to store large amounts of data in properties. A solution
+ would be to test if an object is an instance of PropertyManager, and account
+ the size of the properties appropriately.
Added: zope-quotafolder/trunk/LICENSE
===================================================================
--- zope-quotafolder/trunk/LICENSE (rev 0)
+++ zope-quotafolder/trunk/LICENSE 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,27 @@
+Copyright (c) 2002 Ivo van der Wijk, Amaze Internet Services (ivo at amaze.nl)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Deleted: zope-quotafolder/trunk/QuotaFolder-0.1.1.tar.gz
===================================================================
(Binary files differ)
Deleted: zope-quotafolder/trunk/QuotaFolder-0.1.1.tar.gz.cdbs-config_list
===================================================================
Added: zope-quotafolder/trunk/QuotaFolder.py
===================================================================
--- zope-quotafolder/trunk/QuotaFolder.py (rev 0)
+++ zope-quotafolder/trunk/QuotaFolder.py 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,253 @@
+##
+## Copyright (c) 2002 Ivo van der Wijk, Amaze Internet Services (ivo at amaze.nl)
+##
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions
+## are met:
+##
+## 1. Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in the
+## documentation and/or other materials provided with the distribution.
+## 3. The name of the author may not be used to endorse or promote products
+## derived from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+##
+
+import App, Globals, OFS
+import string
+
+from Persistence import Persistent
+
+from Globals import DTMLFile
+from OFS.Folder import Folder
+from Acquisition import aq_base, aq_parent, Acquired, Implicit
+from AccessControl import getSecurityManager, SpecialUsers
+
+from zLOG import LOG, WARNING
+
+def manage_addQuotaFolder(self, id, title, quota_bytes=0, quota_objects=0,
+ quota_maxsize=0, REQUEST=None):
+ """ Add a new QuotaFolder """
+
+ o = QuotaFolder(id, title, quota_bytes, quota_maxsize, quota_objects, REQUEST)
+ self._setObject(id, o)
+ o = self._getOb(id)
+
+ if REQUEST:
+ return self.manage_main(self, REQUEST, update_menu=1)
+
+def _replaceFolder(parent, id, quota_bytes=0, quota_objects=0, quota_maxsize=0):
+ # Replaces an OFS.Folder with a QuotaFolder. Borrowed from BTreeFolder
+ ob = QuotaFolder(id, "", quota_bytes, quota_maxsize, quota_objects)
+ f = parent._getOb(id)
+ # Copy the contents of the folder to the new QuotaFolder
+
+ ids = f.objectIds()
+ for key, value in f.__dict__.items():
+ if key not in ids:
+ # Not an ObjectManager item.
+ ob.__dict__[key] = value
+ for key in ids:
+ subob = f._getOb(key)
+ subob = getattr(subob, 'aq_base', subob)
+ ob._setOb(key, subob)
+ parent._setOb(id, ob)
+
+
+manage_addQuotaFolderForm=DTMLFile('manage_addQuotaFolderForm', globals())
+
+QuotaExceededException='Quota Exceeded'
+
+class QuotaFolder(Folder, Persistent, Implicit):
+ """ A QuotaFolder """
+
+ meta_type='QuotaFolder'
+
+ manage_options=(
+ Folder.manage_options+
+ (
+ {'label': 'Quota', 'action': 'manage_editQuotaForm'},
+ )
+ )
+
+ __ac_permissions__= \
+ Folder.__ac_permissions__ + \
+ (
+ ('View current quota', ('currentCount','currentSize')),
+ )
+
+ manage_editQuotaForm=DTMLFile('manage_editQuotaForm', globals())
+
+ def __init__(self, id, title="", quota=0, maxFileSize=0, maxNumberOfFiles=0,
+ manager_role=1, REQUEST=None):
+ self.id=id
+ self.title=title
+ self._quota_bytes=int(quota)
+ self._quota_objects=int(maxNumberOfFiles)
+ self._quota_maxsize=int(maxFileSize)
+
+ ##
+ ## current accounting
+ self._quota_filecount = 0
+ self._quota_size = 0
+
+ self._manager_role = 0
+ if manager_role:
+ self._manager_role = 1
+ self._debug = 0
+ self._allow_nested = 0
+
+ def _check_quota(self, filechange, sizechange=0, objsize=0):
+ """ Check if the change in usage violates the quota settings """
+ #
+ # print "I'm a quota folder: ", filechange, sizechange, objsize
+ # print "Current quota: ", self._quota_filecount, self._quota_size
+
+ #
+ # The emergency user can create *some* objects (i.e. userfolders).
+ # make sure this never gives a QuotaExceeded exception
+
+ emergency = 0
+ user=getSecurityManager().getUser()
+ if (SpecialUsers.emergency_user and
+ aq_base(user) is SpecialUsers.emergency_user):
+ LOG('QuotaFolder', INFO,
+ "Emergency user detected - not enforcing quota")
+ emergency = 1
+
+ if not emergency and \
+ filechange > 0 and \
+ self._quota_filecount + filechange > self._quota_objects \
+ > 0:
+ raise QuotaExceededException, \
+ "Too many files, this operation would require a quotum " + \
+ "of %d files, your current quotum is %d files" % \
+ (self._quota_filecount + filechange, self._quota_objects)
+ if not emergency and \
+ sizechange > 0 and \
+ self._quota_size + sizechange > self._quota_bytes > 0:
+ raise QuotaExceededException, \
+ "Too much space, this operation would require a " + \
+ "quotum of %d bytes, your current quotum is %d bytes" % \
+ (self._quota_size + sizechange, self._quota_bytes)
+ if not emergency and objsize > self._quota_maxsize > 0:
+ raise QuotaExceededException, \
+ "The object is too large. It requires %d bytes, " + \
+ "your maximum object size limit is %d bytes" % \
+ (objsize, self._quota_maxsize)
+
+ self._quota_filecount = self._quota_filecount + filechange
+ self._quota_size = self._quota_size + sizechange
+
+ ##
+ ## This should not happen...
+ path = string.join(self.getPhysicalPath(), "/")
+ if self._quota_filecount < 0:
+ LOG('QuotaFolder', WARNING, "Negative quota filecount in %s" % path)
+ if not self._debug:
+ self._quota_filecount = 0
+ if self._quota_size < 0:
+ LOG('QuotaFolder', WARNING, "Negative quota size in %s" % path)
+ if not self._debug:
+ self._quota_size = 0
+
+ # print "New quota: ", self._quota_filecount, self._quota_size
+
+ def current_count(self):
+ return self._quota_filecount
+
+ def current_size(self):
+ return self._quota_size
+
+ def quota_bytes(self):
+ return self._quota_bytes
+
+ def quota_objects(self):
+ return self._quota_objects
+
+ def quota_maxsize(self):
+ return self._quota_maxsize
+
+ def authorized_to_edit(self):
+ """
+ return true/false if user is authorized to change in current context
+ """
+
+ security=getSecurityManager()
+ security.addContext(self)
+ user = security.getUser()
+ if not self._manager_role:
+ ## XXX use permission?
+ return user.allowed(self, ['Manager'])
+
+ ## use aq_base?
+ return user.allowed(aq_parent(self), ['Manager'])
+
+ def manager_role(self):
+ return self._manager_role
+
+ def manage_editQuota(self, quota=-1, maxFileSize=-1, maxNumberOfFiles=-1,
+ REQUEST=None):
+ """ Update the current Quota """
+ ##
+ ## Perhaps always sync after edit?
+
+ if REQUEST.has_key('Sync'):
+ self._qa_sync()
+ ## XXX report difference to parent?
+ if REQUEST:
+ ## A redirect selects the proper tab
+ message = "Quota synchronized"
+ REQUEST.RESPONSE.redirect(
+ 'manage_editQuotaForm?manage_tabs_message=%s' % message)
+ return self.manage_editQuotaForm(self, REQUEST,
+ manage_tabs_message=message)
+ return ''
+
+ if not self.authorized_to_edit():
+ raise "Unauthorized", \
+ "You are not authorized to edit this quota folder"
+
+ if quota != -1:
+ self._quota_bytes=int(quota)
+ if maxFileSize != -1:
+ self._quota_maxsize=int(maxFileSize)
+ if maxNumberOfFiles != -1:
+ self._quota_objects=int(maxNumberOfFiles)
+ self._manager_role = REQUEST.get("manager_role", 0)
+ if REQUEST:
+ message = "Changes saved"
+ ## A redirect selects the proper tab
+ REQUEST.RESPONSE.redirect(
+ 'manage_editQuotaForm?manage_tabs_message=%s' % message)
+ return self.manage_editQuotaForm(self, REQUEST,
+ manage_tabs_message=message)
+ return ''
+
+ def manage_afterAdd(self, item, container):
+ # print "afterAdd %s %s"%(str(item), str(container))
+ if not self._allow_nested:
+ for parent in self.aq_chain[1:]:
+ if hasattr(parent, "meta_type") and \
+ parent.meta_type == "QuotaFolder":
+ raise QuotaExceededException, \
+ "Cannot add a QuotaFolder inside another QuotaFolder"
+
+ Folder.manage_afterAdd(self, item, container)
+
+Globals.default__class_init__(QuotaFolder)
Added: zope-quotafolder/trunk/README.txt
===================================================================
--- zope-quotafolder/trunk/README.txt (rev 0)
+++ zope-quotafolder/trunk/README.txt 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,187 @@
+QuotaFolder - Quota support for Zope
+
+What is it?
+-----------
+
+QuotaFolder is a folder-ish object that restricts the total number of objects,
+their total size and their individual maximum size. QuotaFolder takes
+subfolders (recursively) into account, so it should not be possible to escape
+the quota restrictions.
+
+The basic goal of QuotaFolder is not to put an absolute limit on ZODB usage
+- it is impossible to determine this. For example, each object may have
+several revisions in the ZODB, and some of the internal state of the object
+is stored in the ZODB that's not returned by get_size(). The goal is to
+limit the use of objects in general, to prevent people from offering large
+files or using up resources with enormous amounts of objects.
+
+How does it work?
+-----------------
+
+QuotaFolder 'MonkeyPatches (tm)' some of the internal Zope components such
+as ObjectManager, File, Image, DTMLMethod, DTMLDocument and more. The patching
+constist of making the components 'quota aware'. This basically means the
+objects will track changes and report them to parent folders. If one if the
+parent folders is a QuotaFolder, and the QuotaFolder detects that the change
+will exceed the quota, a QuotaExceededException is raised, and the transaction
+is rolled back.
+
+At this moment, if an object supports the get_size() method, it's used. The
+value reported by this method will not take all usage (i.e. properties) into
+account, so this may be changed in the future.
+
+Supported products
+------------------
+
+The QuotaFolder product knows how to account File, Image, DTMLMethod,
+DTMLDocument and PythonScript objects. It also understands ObjectManager
+based products such as Folder and BTreeFolder (TransparentFolder is not
+thoroughly tested, but it seems to work well)
+
+Most products (i.e. FLE, Squishdot, PhotoFolder, ZWiki) exist of ObjectManagers
+with subobjects, and QuotaFolder knows how to handle these quite well.
+Unknown objects who's size (and changes in size) cannot be determined are
+accounted as 1 object with size 0. If this is not satisfactory (for example,
+it's not at this moment for TinyTablePlus), extra support can be built in for
+these objects (as has been done with, for example, ZPT).
+
+QuotaFolder also supports .zexp imports and copy/paste operations.
+
+QuotaFolder has been tested with Zope 2.4.3, 2.4.4b1 and 2.5.0. It has not
+been tested in ZEO setups.
+
+Installing
+----------
+
+**WARNING**
+
+This product patches classes in your Zope server. Please test the code on
+a test-server or shadow server first! This code has been known to work
+succesfully with Zope 2.4.x and Zope 2.5. Use this product at your own
+risk, and backup your Data.fs first!
+
+Install the QuotaFolder product by simply unpacking into your Products
+directory (either in your SOFTWARE_HOME or your INSTANCE_HOME) and restart
+your Zope server. The QuotaFolder product should appear in
+/Control_Panel/Products, and it should also be available in the products
+dropdown.
+
+When creating (or editting) a QuotaFolder, you will be presented with the
+following fields:
+
+id
+title
+Quota size in bytes - this is the maximum total size of all objects
+ that's allowed to be created
+Maximum object size in bytes - The maximum allowed total size for single
+ objects
+Maximum number of objects - this is the maximum number of objects that's
+ allowed to be created
+Require manager role in parent context?
+ - If this setting is enabled, a user must be
+ manager in the folder context *above* the
+ quotafolder itself to be able to edit the
+ quota. If you want to limit your users in their
+ usage, you don't want them to be able to edit
+ their quota themselves, do you?
+
+After creation, you will get the same contents view as with a standard folder,
+but with an extra tab to the right where you can view and (optionally edit)
+the quota.
+
+When visiting the quota tab, you will see an extra button 'Sync'. Pressing
+this button will cause the QuotaFolder to recalculate all usage. Usually,
+the folder shouldn't be out of sync. If you manage to get a QuotaFolder
+out of sync, please contact me.
+
+Migrating
+---------
+
+There are two ways to migrate a standard folder to a QuotaFolder:
+
+- Create a new QuotaFolder, copy all objects from the old folder, paste
+ them into the QuotaFolder and rename the folders. This will probably not
+ work if you have versions (cut/paste may work with versions)
+
+- Use the builtin _replaceFolder method. I.e. create the following external
+ method:
+
+ from Products.QuotaFolder.QuotaFolder import _replaceFolder
+
+ def replace(self, name, quota_bytes, quota_objects, quota_maxsize, REQUEST):
+ _replaceFolder(self, name, quota_bytes, quota_objects, quota_maxsize)
+ return "%s converted" % name
+
+ Create an appropriate external method object in your zope server and invoke
+ it with an appropriately formatted url or create a dtml form.
+
+Hacking contest!
+----------------
+
+QuotaFolder has been thoroughly tested, and seems very stable and compatible.
+However, every now and then, new situations seem to appear where it's possible
+to use more objects or space than the QuotaFolder should enforce. This may
+be through unsupported objects (though these should generaly not make it
+possible to create more free space for other objects), or using trickery
+with PythonScripts, etc.
+
+If anyone finds issues like this, please contact me (info below), so we can
+make this product even more stable and robust :)
+
+Please check the file KNOWNBUGS before reporting issues.
+
+Release info
+------------
+
+0.1 Initial version, basic support for quota
+
+Future plans
+------------
+
+- Support soft and hardlimit quota, with configurable timeleft
+- Restrict installable metatypes
+- Limit number of certain metatypes
+
+Contact/License
+---------------
+
+QuotaFolder is partially based on ideas by Andrew Kenneth, though most of his
+old QuotaFolder has disappeared.
+
+QuotaFolder is written by Ivo van der Wijk as part of Amaze Internet Service's
+FreeZope.org Free Zope hosting environment.
+
+I can be contacted through ivo at amaze.nl or on IRC as VladDrac / VladDrak @ OPN
+
+Recent versions of QuotaFolder and other Zope products can be found at:
+
+http://www.zope.org/Members/ivo
+http://vanderijk.info/
+
+QuotaFolder is (c) 2002 Ivo van der Wijk / Amaze Internet Services
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Added: zope-quotafolder/trunk/TODO
===================================================================
--- zope-quotafolder/trunk/TODO (rev 0)
+++ zope-quotafolder/trunk/TODO 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,23 @@
+First release:
+
+- check required attributes on objects (i.e. data on File/Image), report
+ warning if missing
+- Provide a conversion script (external method) to convert standard folders
+ to QuotaFolders DONE
+- Prevent 0-changes from propagating
+- properly determine size for TinyTablePlus objects
+- better QuotaExceeded exception strings DONE
+- ignore superuser, etc. DONE
+- add proper permissions DONE?
+- Do not raise exceptions on negative changes DONE
+- LICENSE
+
+Later release
+- support TinyTablePlus
+- soft/hard quota
+- Limit/restrict installable meta types
+- configurable size for objects without explicit size? (seems pointless)
+- configure max object size per meta type (+ limit # of instances)
+- optionally prevent quota updates from propagating beyond a folder (checkbox)
+ -> this doesn't have to break nested quotafolders! (though it may give
+ problems)
Added: zope-quotafolder/trunk/__init__.py
===================================================================
--- zope-quotafolder/trunk/__init__.py (rev 0)
+++ zope-quotafolder/trunk/__init__.py 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,395 @@
+##
+## Copyright (c) 2002 Ivo van der Wijk, Amaze Internet Services (ivo at amaze.nl)
+##
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted provided that the following conditions
+## are met:
+##
+## 1. Redistributions of source code must retain the above copyright
+## notice, this list of conditions and the following disclaimer.
+## 2. Redistributions in binary form must reproduce the above copyright
+## notice, this list of conditions and the following disclaimer in the
+## documentation and/or other materials provided with the distribution.
+## 3. The name of the author may not be used to endorse or promote products
+## derived from this software without specific prior written permission.
+##
+##
+## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+##
+
+from zLOG import LOG, WARNING, BLATHER, INFO
+
+from OFS.ObjectManager import ObjectManager
+from Acquisition import aq_parent, aq_chain, aq_inner
+import QuotaFolder
+
+##
+## NOTE TO DEVELOPERS:
+##
+## Refreshing this product may not always work. If you do not get the expected
+## behaviour after refreshing, try restarting your zopeserver!
+##
+
+##
+## Idea: use *args / **args for wrapper methods and apply parameters to methods
+##
+## QA stands for Quota Aware, QU for Quota Unaware
+
+##
+## Invoke chain of _qf_check's, so all parents can update? (for delete of
+## folders etc)
+
+def QA_setOb(self, id, object):
+ ##
+ ## Is it smarter to patch setObject instead? Is setOb ever invoked directly?
+
+ ##
+ ## We rely on the transaction being rolled back if an exception occurs.
+ ## This means that QuotaExceeded exceptions shouldn't occur. Unfortunately,
+ ## sometimes they are caught.
+ if isinstance(object, ObjectManager):
+ make_quota_aware(object)
+
+ if hasattr(object, "_qa_sync"):
+ object._qa_sync()
+ _files, _size = QA_determine_usage(object)
+ try:
+ self._qf_check(_files, _size)
+ except QuotaFolder.QuotaExceededException:
+ ##
+ ## This horrible fix is because manage_addUserFolder ignores all
+ ## exceptions, and assumes there already is a UserFolder, while
+ ## _setObject happily changes _objects as well while ignoring
+ ## anything that may go wrong
+ # print "O", self._objects
+ o = []
+ ##
+ ## Could this go wrong if there already is an object with id?
+ ## (replacable?) (check using getattr?)
+ if not hasattr(self, id):
+ for i in self._objects:
+ if i['id'] != id:
+ o.append(i)
+ self._objects = tuple(o)
+ raise
+ result = self._qu_setOb(id, object)
+ return result
+
+def QA_delOb(self, id):
+ # we can't use _getOb because it doesn't allow _attrs
+ ##
+ ## this works with BtreeFolders as well (well, with non-basicBtreeFolders)
+ obj = getattr(self, id)
+ try:
+ _files,_size = QA_determine_usage(obj)
+ except:
+ LOG('QuotaProduct', BLATHER, "Can't get size for %s" % id)
+ _files, _size = 1,0
+
+ self._qf_check(-_files, -_size)
+
+ return self._qu_delOb(id)
+
+def getid(id):
+ """ sometimes id is a string, sometimes a method """
+ if callable(id): return id()
+ return id
+
+def findparent(object):
+ """ find the direct parent """
+
+ for parent in aq_chain(object):
+ if parent is object:
+ continue
+ if parent == object:
+ continue
+ if isinstance(parent, ObjectManager):
+ return parent
+ return None
+
+def QA_check_quota(self, filechange, sizechange=0, objsize=0):
+ """
+ Notify a change in usage:
+
+ filechange number of added/removed files
+ sizechange change in size of usage
+ objsize total size of object
+
+ changes can be negative as well
+ """
+
+ if hasattr(self, "_check_quota"):
+ ## check hook for QuotaFolder
+ self._check_quota(filechange, sizechange, objsize)
+ else:
+ ## do own administration
+ make_quota_aware(self)
+ self._quota_filecount = self._quota_filecount + filechange
+ self._quota_size = self._quota_size + sizechange
+
+ ##
+ ## Acquisition is scary. The chain looks different when traversing
+ ## through ftp/webdav. basically, 'self' may appear in the chain multiple
+ ## times (with A NullResource in between), and may not always be equal
+ ## through 'is' (so try == as well)
+ ##
+ ## Tip: use aq_base(parent) XXX
+
+ for parent in aq_chain(self):
+ if parent is self:
+ continue
+ if parent == self:
+ continue
+ if hasattr(parent, "_qf_check"):
+ if hasattr(parent, getid(self.id)):
+ parent._qf_check(filechange,sizechange,objsize)
+ else:
+ for name,ob in parent.objectItems():
+ if ob.id == getid(self.id):
+ parent._qf_check(filechange,sizechange,objsize)
+ return # !!
+ break
+
+def QA_manage_importObject(self, file, REQUEST=None, set_owner=1):
+ """ currently a useless hook. To be removed if not used """
+ result = apply(self._qu_manage_importObject, (file, REQUEST, set_owner))
+ return result
+
+def QA_sync(self):
+ """ synchronize, i.e. recursively recalculate sizes """
+ self._quota_filecount = 0 # not 1, get_usage adds 1
+ self._quota_size = 0
+
+ for child in self.objectValues():
+ if hasattr(child, "_qa_sync"):
+ child._qa_sync()
+
+ _files, _size = QA_determine_usage(child)
+ self._quota_filecount = self._quota_filecount + _files
+ self._quota_size = self._quota_size + _size
+
+def make_quota_aware(object):
+ if not hasattr(object, "_quota_filecount"):
+ object._quota_filecount = 0
+ if not hasattr(object, "_quota_size"):
+ object._quota_size = 0
+
+def QA_get_usage(self):
+ """
+ Return the size of an ObjectManager object. This is 1 + the number
+ of subobjects. The size of an empty ObjectManager is considered to
+ be 0
+ """
+ return self._quota_filecount+1, self._quota_size
+
+def QA_determine_usage(object):
+ """
+ attempt to determine number of size and number of subobjects of object
+ """
+ ##
+ ## ZPT doesn't return the size of the unrendered content it seems,
+ ## which leads to
+ ## very inconsistent sizes. Let's hope this fixes it...
+ if zpt_installed and isinstance(object, PageTemplate):
+ # workaround for ZPT
+ if hasattr(object, "_text"):
+ return 1, len(object._text)
+ return 1, 0
+ if hasattr(object, "_get_usage"):
+ return object._get_usage()
+ elif hasattr(object, "get_size"):
+ ##
+ ## Some products (such as PhotoFolders Photo) raise an exception
+ ## when requested their size at init time
+ try:
+ _size = object.get_size()
+ except: # can be any kind of exception
+ LOG('QuotaProduct', BLATHER, 'Failed to get size for %s/%s' % \
+ (getid(object.id), object.meta_type))
+ _size = 0
+ return 1, _size
+ elif hasattr(object, "getSize"):
+ ## getSize is deprecated, but if it's all there is...
+ return 1, object.getSize()
+ else:
+ LOG('QuotaProduct', BLATHER, "Can't get size for %s/%s" % \
+ (getid(object.id), object.meta_type))
+ return 1, 0
+
+##
+## Patch the PythonScript object
+
+from Products.PythonScripts.PythonScript import PythonScript
+
+def QA_ps_write(self, text):
+ """ invoked at creation, edit and upload - what else do you want? """
+ oldsize = self.get_size()
+ result = self._qu_write(text)
+ newsize = self.get_size()
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Patch DT_String, which effectively patches DTMLDocument and DTMLMethod
+
+from DocumentTemplate.DT_String import String
+
+def QA_dt_munge(self, source_string=None, mapping=None, **vars):
+ oldsize = self.get_size()
+ result = apply(self._qu_dt_munge, (source_string, mapping), vars)
+ newsize = self.get_size()
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Patch File and Image
+
+from OFS.Image import File, Image
+
+def QA_file_update_data(self, data, content_type=None, size=None):
+ ##
+ ## This method may be invoked when self.data hasn't been initialized
+ if not hasattr(self, "data"):
+ oldsize = 0
+ else:
+ oldsize = self.get_size()
+ result = apply(self._qu_update_data, (data, content_type, size))
+ newsize = self.get_size()
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Patch BTreeFolder, if available
+
+btree_installed = 0
+
+try:
+ from Products.BTreeFolder.BTreeFolder import BTreeFolder
+ LOG('QuotaProduct', INFO, "Patching BTreeFolder")
+ btree_installed = 1
+
+except ImportError:
+ pass
+
+##
+## Patch ZopePageTemplate, if available
+
+zpt_installed = 0
+
+try:
+ from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate, PageTemplate
+ LOG('QuotaProduct', INFO, "Patching ZPT")
+ zpt_installed = 1
+except ImportError:
+ zpt_installed = 0
+
+##
+## ZPT write hook
+def QA_zpt_write(self, text):
+ oldsize = len(self._text)
+ result = self._qu_write(text)
+ newsize = len(self._text)
+
+ parent = findparent(self)
+
+ if parent:
+ if hasattr(parent, getid(self.id)):
+ if hasattr(parent, "_qf_check"):
+ parent._qf_check(0, newsize-oldsize, newsize)
+
+ return result
+
+##
+## Install all hooks
+##
+## Attempt to detect when we're refreshing and when we're restarting
+if not hasattr(ObjectManager, "_quota_aware"):
+ ##
+ ## save 'old' methods
+ LOG('QuotaProduct', INFO, 'Detected server restart')
+ ObjectManager._qu_setOb = ObjectManager._setOb
+ ObjectManager._qu_delOb = ObjectManager._delOb
+ ObjectManager._quota_aware = 1
+ ObjectManager._qu_manage_importObject = ObjectManager.manage_importObject
+ PythonScript._qu_write = PythonScript.write
+ String._qu_dt_munge = String.munge
+ File._qu_update_data = File.update_data
+ Image._qu_update_data = Image.update_data
+
+ if btree_installed:
+ BTreeFolder._qu_setOb = BTreeFolder._setOb
+ BTreeFolder._qu_delOb = BTreeFolder._delOb
+ if zpt_installed:
+ ZopePageTemplate._qu_write = ZopePageTemplate.write
+else:
+ LOG('QuotaProduct', INFO, 'Detected product refresh')
+
+##
+## Install the new methods. It won't hurt if this happens during a refresh
+ObjectManager._setOb = QA_setOb
+ObjectManager._delOb = QA_delOb
+ObjectManager.manage_importObject = QA_manage_importObject
+ObjectManager._qf_check = QA_check_quota
+ObjectManager._get_usage = QA_get_usage
+ObjectManager._qa_sync = QA_sync
+PythonScript.write = QA_ps_write
+String.munge = QA_dt_munge
+File.update_data = QA_file_update_data
+Image.update_data = QA_file_update_data
+
+if btree_installed:
+ BTreeFolder._setOb = QA_setOb
+ BTreeFolder._delOb = QA_delOb
+if zpt_installed:
+ ZopePageTemplate.write = QA_zpt_write
+
+##
+## finally, install the QuotaFolder Product
+
+import QuotaFolder
+
+def initialize(context):
+ try:
+ context.registerClass(
+ QuotaFolder.QuotaFolder,
+ meta_type='QuotaFolder',
+ permission='Add QuotaFolder',
+ constructors=(QuotaFolder.manage_addQuotaFolderForm,
+ QuotaFolder.manage_addQuotaFolder),
+ )
+
+ except:
+ import traceback
+ traceback.print_exc()
Added: zope-quotafolder/trunk/manage_addQuotaFolderForm.dtml
===================================================================
--- zope-quotafolder/trunk/manage_addQuotaFolderForm.dtml (rev 0)
+++ zope-quotafolder/trunk/manage_addQuotaFolderForm.dtml 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,86 @@
+<dtml-var manage_page_header>
+<dtml-var "manage_form_title(this(), _,
+ form_title='Add Quota Folder'
+ )">
+
+<FORM ACTION="manage_addQuotaFolder" METHOD="POST">
+
+<table cellspacing="0" cellpadding="2" border="0">
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Id
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="id" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-optional">
+ Title
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="title" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Quota Size in Bytes (0 for unlimited)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="quota" size="40" value="0"/>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum Object Size in Bytes (0 for no limit)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="filesize" size="40" value="0" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum number of Objects (0 for unlimited)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="numberFiles" size="40" value="0" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Require Manager role in parent context to edit
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="checkbox" name="manager_role" value="yes" CHECKED>
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ </td>
+ <td align="left" valign="top">
+ <div class="form-element">
+ <input class="form-element" type="submit" name="submit"
+ value="Add" />
+ </div>
+ </td>
+ </tr>
+</table>
+</form>
+
+
+<dtml-var manage_page_footer>
Added: zope-quotafolder/trunk/manage_editQuotaForm.dtml
===================================================================
--- zope-quotafolder/trunk/manage_editQuotaForm.dtml (rev 0)
+++ zope-quotafolder/trunk/manage_editQuotaForm.dtml 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,114 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<dtml-var "manage_form_title(this(), _,
+ form_title='Edit Quota Folder'
+ )">
+
+<FORM ACTION="manage_editQuota" METHOD="POST">
+<table cellspacing="0" cellpadding="2" border="0">
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Current quota usage
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-var current_size> bytes
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Number of objects
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-var current_count> Objects
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Quota Size in Bytes (0 for no limit)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="text" name="quota" size="40" value="<dtml-var quota_bytes>"/>
+ <dtml-else>
+ <dtml-var quota_bytes>
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum number of Objects (0 for unlimited)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="text" name="maxNumberOfFiles" size="40" value="<dtml-var quota_objects>"/>
+ <dtml-else>
+ <dtml-var quota_objects>
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Maximum Object Size in Bytes (0 for no limit)
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="text" name="maxFileSize" size="40" value="<dtml-var quota_maxsize>" />
+ <dtml-else>
+ <dtml-var quota_maxsize>
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Require Manager role in parent context to edit
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <dtml-if authorized_to_edit>
+ <input type="checkbox" name="manager_role" value="yes"
+ <dtml-if manager_role>
+ CHECKED
+ </dtml-if>
+ >
+ (Warning: checking this box may revoke your rights to edit quota!)
+ <dtml-else>
+ <dtml-if manager_role>
+ YES
+ <dtml-else>
+ NO
+ </dtml-if>
+ (You are not authorized to edit this quota folder)
+ </dtml-if>
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <input class="form-element" type="submit" name="Sync" value="Sync" />
+ </td>
+ <dtml-if authorized_to_edit>
+ <td align="left" valign="top">
+ <div class="form-element">
+ <input class="form-element" type="submit" name="submit" value="Edit" />
+ </div>
+ </td>
+ <dtml-else>
+ <td></td>
+ </dtml-if>
+ </tr>
+</table>
+</form>
+
+
+<dtml-var manage_page_footer>
Added: zope-quotafolder/trunk/refresh.txt
===================================================================
--- zope-quotafolder/trunk/refresh.txt (rev 0)
+++ zope-quotafolder/trunk/refresh.txt 2007-05-14 10:26:40 UTC (rev 867)
@@ -0,0 +1,6 @@
+
+NOTE TO DEVELOPERS:
+
+Refreshing this product may not always work. If you do not get the expected
+behaviour after refreshing, try restarting your zopeserver!
+
More information about the pkg-zope-commits
mailing list