[CalendarServer-changes] [6489] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Sun Oct 31 18:22:11 PDT 2010
Revision: 6489
http://trac.macosforge.org/projects/calendarserver/changeset/6489
Author: cdaboo at apple.com
Date: 2010-10-31 18:22:09 -0700 (Sun, 31 Oct 2010)
Log Message:
-----------
Move quota used bytes into newstore. Clean-up some missing pieces in the file store.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
CalendarServer/trunk/twistedcaldav/method/report_common.py
CalendarServer/trunk/twistedcaldav/notifications.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/storebridge.py
CalendarServer/trunk/txdav/caldav/datastore/file.py
CalendarServer/trunk/txdav/caldav/datastore/index_file.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/caldav/datastore/test/common.py
CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
CalendarServer/trunk/txdav/carddav/datastore/file.py
CalendarServer/trunk/txdav/carddav/datastore/index_file.py
CalendarServer/trunk/txdav/carddav/datastore/sql.py
CalendarServer/trunk/txdav/carddav/datastore/test/common.py
CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
CalendarServer/trunk/txdav/common/datastore/file.py
CalendarServer/trunk/txdav/common/datastore/sql.py
CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql
CalendarServer/trunk/txdav/common/datastore/sql_tables.py
Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -318,35 +318,6 @@
@inlineCallbacks
- def checkQuota(self):
- """
- Get quota details for destination and source before we start messing with adding other files.
- """
-
- if self.request is None:
- self.destquota = None
- else:
- self.destquota = (yield self.destination.quota(self.request))
- if self.destquota is not None and self.destination.exists():
- self.old_dest_size = (yield self.destination.quotaSize(self.request))
- else:
- self.old_dest_size = 0
-
- if self.request is None:
- self.sourcequota = None
- elif self.source is not None:
- self.sourcequota = (yield self.source.quota(self.request))
- if self.sourcequota is not None and self.source.exists():
- self.old_source_size = (yield self.source.quotaSize(self.request))
- else:
- self.old_source_size = 0
- else:
- self.sourcequota = None
- self.old_source_size = 0
-
- returnValue(None)
-
- @inlineCallbacks
def doStore(self):
# Do put or copy based on whether source exists
source = self.source
@@ -387,28 +358,15 @@
returnValue(None)
@inlineCallbacks
- def doSourceQuotaCheck(self):
- # Update quota
- if self.sourcequota is not None:
- delete_size = 0 - self.old_source_size
- yield self.source.quotaSizeAdjust(self.request, delete_size)
-
- returnValue(None)
-
- @inlineCallbacks
def doDestinationQuotaCheck(self):
- # Get size of new/old resources
- new_dest_size = (yield self.destination.quotaSize(self.request))
-
- diff_size = new_dest_size - self.old_dest_size
-
- if diff_size >= self.destquota[0]:
- log.err("Over quota: available %d, need %d" % (self.destquota[0], diff_size))
+ """
+ Look at current quota after changes and see if we have gone over the top.
+ """
+ quota = (yield self.destination.quota(self.request))
+ if quota[0] < 0:
+ log.err("Over quota by %d" % (-quota[0],))
raise HTTPError(ErrorResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, (dav_namespace, "quota-not-exceeded")))
- yield self.destination.quotaSizeAdjust(self.request, diff_size)
- returnValue(None)
-
@inlineCallbacks
def run(self):
"""
@@ -442,9 +400,6 @@
NoUIDConflict(davxml.HRef.fromString(joinURL(parentForURL(self.destination_uri), rname.encode("utf-8"))))
))
- # Get current quota state.
- yield self.checkQuota()
-
# Do the actual put or copy
response = (yield self.doStore())
@@ -458,13 +413,8 @@
davxml.GETContentType.fromString(generateContentType(content_type))
)
- # Delete the original source if needed.
- if self.deletesource:
- yield self.doSourceQuotaCheck()
-
# Do quota check on destination
- if self.destquota is not None:
- yield self.doDestinationQuotaCheck()
+ yield self.doDestinationQuotaCheck()
if reservation:
yield reservation.unreserve()
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -499,36 +499,6 @@
return succeed(None)
- @inlineCallbacks
- def checkQuota(self):
- """
- Get quota details for destination and source before we start messing with adding other files.
- """
-
- if self.request is None:
- self.destquota = None
- else:
- self.destquota = (yield self.destination.quota(self.request))
- if self.destquota is not None and self.destination.exists():
- self.old_dest_size = (yield self.destination.quotaSize(self.request))
- else:
- self.old_dest_size = 0
-
- if self.request is None:
- self.sourcequota = None
- elif self.source is not None:
- self.sourcequota = (yield self.source.quota(self.request))
- if self.sourcequota is not None and self.source.exists():
- self.old_source_size = (yield self.source.quotaSize(self.request))
- else:
- self.old_source_size = 0
- else:
- self.sourcequota = None
- self.old_source_size = 0
-
- returnValue(None)
-
-
def truncateRecurrence(self):
if config.MaxInstancesForRRULE != 0:
@@ -780,29 +750,16 @@
returnValue(None)
@inlineCallbacks
- def doSourceQuotaCheck(self):
- # Update quota
- if self.sourcequota is not None:
- delete_size = 0 - self.old_source_size
- yield self.source.quotaSizeAdjust(self.request, delete_size)
-
- returnValue(None)
-
- @inlineCallbacks
def doDestinationQuotaCheck(self):
- # Get size of new/old resources
- new_dest_size = (yield self.destination.quotaSize(self.request))
-
- diff_size = new_dest_size - self.old_dest_size
-
- if diff_size >= self.destquota[0]:
- log.err("Over quota: available %d, need %d" % (self.destquota[0], diff_size))
+ """
+ Look at current quota after changes and see if we have gone over the top.
+ """
+ quota = (yield self.destination.quota(self.request))
+ if quota[0] < 0:
+ log.err("Over quota by %d" % (-quota[0],))
raise HTTPError(ErrorResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, (dav_namespace, "quota-not-exceeded")))
- yield self.destination.quotaSizeAdjust(self.request, diff_size)
- returnValue(None)
-
@inlineCallbacks
def run(self):
"""
@@ -838,9 +795,6 @@
rname.encode("utf-8"))))))
- # Get current quota state.
- yield self.checkQuota()
-
# Handle RRULE truncation
rruleChanged = self.truncateRecurrence()
@@ -956,13 +910,8 @@
davxml.GETContentType.fromString(generateContentType(content_type))
)
- # Delete the original source if needed.
- if self.deletesource:
- yield self.doSourceQuotaCheck()
-
# Do quota check on destination
- if self.destquota is not None:
- yield self.doDestinationQuotaCheck()
+ yield self.doDestinationQuotaCheck()
if reservation:
yield reservation.unreserve()
Modified: CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -25,7 +25,8 @@
from twext.python.log import Logger
from twext.web2.dav.http import ErrorResponse
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue,\
+ maybeDeferred
from twext.web2 import responsecode
from twext.web2.dav import davxml
from twext.web2.dav.element.base import PCDATAElement
@@ -175,9 +176,9 @@
try:
# Get list of children that match the search and have read
# access
- records = yield calresource.index().indexedSearch(filter)
+ records = yield maybeDeferred(calresource.index().indexedSearch, filter)
except IndexedSearchException:
- records = yield calresource.index().bruteForceSearch()
+ records = yield maybeDeferred(calresource.index().bruteForceSearch)
index_query_ok = False
names = [name for name, ignore_uid, ignore_type in records]
Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -39,7 +39,7 @@
from vobject.icalendar import utc
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
from twisted.python.failure import Failure
from twext.web2 import responsecode
from twext.web2.dav import davxml
@@ -460,11 +460,11 @@
else:
useruid = ""
try:
- resources = yield calresource.index().indexedSearch(
+ resources = yield maybeDeferred(calresource.index().indexedSearch,
filter, useruid=useruid, fbtype=True
)
except IndexedSearchException:
- resources = yield calresource.index().bruteForceSearch()
+ resources = yield maybeDeferred(calresource.index().bruteForceSearch)
# We care about separate instances for VEVENTs only
aggregated_resources = {}
Modified: CalendarServer/trunk/twistedcaldav/notifications.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notifications.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/twistedcaldav/notifications.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -33,6 +33,8 @@
from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn, CalDAVResource
from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
+from txdav.common.icommondatastore import SyncTokenValidException
+
import os
import types
@@ -167,15 +169,66 @@
values (:1, :2, :3)
""", record.uid, record.name, record.xmltype,
)
+
+ self._db_execute(
+ """
+ insert or replace into REVISIONS (NAME, REVISION, DELETED)
+ values (:1, :2, :3)
+ """, record.name, self.bumpRevision(fast=True), 'N',
+ )
def removeRecordForUID(self, uid):
- self._db_execute("delete from NOTIFICATIONS where UID = :1", uid)
+ record = self.recordForUID(uid)
+ self.removeRecordForName(record.name)
def removeRecordForName(self, rname):
self._db_execute("delete from NOTIFICATIONS where NAME = :1", rname)
+ self._db_execute(
+ """
+ update REVISIONS SET REVISION = :1, DELETED = :2
+ where NAME = :3
+ """, self.bumpRevision(fast=True), 'Y', rname
+ )
+ def whatchanged(self, revision):
+
+ results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)]
+ results.sort(key=lambda x:x[1])
+
+ changed = []
+ deleted = []
+ for name, wasdeleted in results:
+ if name:
+ if wasdeleted == 'Y':
+ if revision:
+ deleted.append(name)
+ else:
+ changed.append(name)
+ else:
+ raise SyncTokenValidException
+
+ return changed, deleted,
+
+ def lastRevision(self):
+ return self._db_value_for_sql(
+ "select REVISION from REVISION_SEQUENCE"
+ )
+
+ def bumpRevision(self, fast=False):
+ self._db_execute(
+ """
+ update REVISION_SEQUENCE set REVISION = REVISION + 1
+ """,
+ )
+ self._db_commit()
+ return self._db_value_for_sql(
+ """
+ select REVISION from REVISION_SEQUENCE
+ """,
+ )
+
def _db_version(self):
"""
@return: the schema version assigned to this index.
@@ -215,6 +268,39 @@
"""
)
+ #
+ # REVISIONS table tracks changes
+ # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
+ # REVISION: revision number
+ # WASDELETED: Y if revision deleted, N if added or changed
+ #
+ q.execute(
+ """
+ create table REVISION_SEQUENCE (
+ REVISION integer
+ )
+ """
+ )
+ q.execute(
+ """
+ insert into REVISION_SEQUENCE (REVISION) values (0)
+ """
+ )
+ q.execute(
+ """
+ create table REVISIONS (
+ NAME text unique,
+ REVISION integer,
+ DELETED text(1)
+ )
+ """
+ )
+ q.execute(
+ """
+ create index REVISION on REVISIONS (REVISION)
+ """
+ )
+
def _db_upgrade_data_tables(self, q, old_version):
"""
Upgrade the data from an older version of the DB.
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -2008,6 +2008,12 @@
"""
return config.UserQuota if config.UserQuota != 0 else None
+ def currentQuotaUse(self, request):
+ """
+ Get the quota use value
+ """
+ return maybeDeferred(self._newStoreHome.quotaUsedBytes)
+
def supportedReports(self):
result = super(CommonHomeResource, self).supportedReports()
if config.EnableSyncReport:
@@ -2546,7 +2552,6 @@
returnValue((changed, deleted, notallowed))
-
class GlobalAddressBookResource (ReadOnlyResourceMixIn, CalDAVResource):
"""
Global address book. All we care about is making sure permissions are setup.
Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -280,12 +280,7 @@
returnValue(sorted(children))
- def quotaSize(self, request):
- # FIXME: tests, workingness
- return succeed(0)
-
-
class StoreScheduleInboxResource(_CalendarChildHelper, ScheduleInboxResource):
def __init__(self, *a, **kw):
@@ -774,7 +769,7 @@
isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
accessPrincipal = (yield self.resourceOwnerPrincipal(request))
- for name, uid, type in (yield self.index().bruteForceSearch()): #@UnusedVariable
+ for name, uid, type in (yield maybeDeferred(self.index().bruteForceSearch)): #@UnusedVariable
try:
child = yield request.locateChildResource(self, name)
except TypeError:
@@ -1043,12 +1038,7 @@
# FIXME: should be deleted, or raise an exception
- def quotaSize(self, request):
- # FIXME: tests, workingness
- return succeed(0)
-
-
class CalendarObjectResource(_NewStoreFileMetaDataHelper, CalDAVResource, FancyEqMixin):
"""
A resource wrapping a calendar object.
@@ -1104,10 +1094,8 @@
return True
- @inlineCallbacks
def quotaSize(self, request):
- # FIXME: tests
- returnValue(len((yield self.iCalendarText())))
+ return succeed(self._newStoreObject.size())
def iCalendarText(self):
@@ -1132,6 +1120,7 @@
@inlineCallbacks
def storeStream(self, stream):
+
# FIXME: direct tests
component = vcomponent.VComponent.fromString(
(yield allDataFromStream(stream))
@@ -1199,13 +1188,6 @@
if not isinbox:
self.validIfScheduleMatch(request)
- # Do quota checks before we start deleting things
- myquota = (yield self.quota(request))
- if myquota is not None:
- old_size = (yield self.quotaSize(request))
- else:
- old_size = 0
-
scheduler = None
lock = None
if not isinbox and implicitly:
@@ -1240,10 +1222,6 @@
del self._newStoreObject
self.__class__ = ProtoCalendarObjectResource
- # Adjust quota
- if myquota is not None:
- yield self.quotaSizeAdjust(request, -old_size)
-
# Do scheduling
if not isinbox and implicitly:
yield scheduler.doImplicitScheduling()
@@ -1319,7 +1297,6 @@
return self._name
def quotaSize(self, request):
- # FIXME: tests, workingness
return succeed(0)
@@ -1427,12 +1404,6 @@
- def quotaSize(self, request):
- # FIXME: tests, workingness
- return succeed(0)
-
-
-
class AddressBookCollectionResource(_AddressBookChildHelper, CalDAVResource):
"""
Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
@@ -1677,11 +1648,6 @@
# FIXME: should be deleted, or raise an exception
- def quotaSize(self, request):
- # FIXME: tests, workingness
- return succeed(0)
-
-
class GlobalAddressBookCollectionResource(GlobalAddressBookResource, AddressBookCollectionResource):
"""
Wrapper around a L{txdav.carddav.iaddressbook.IAddressBook}.
@@ -1722,10 +1688,8 @@
return True
- @inlineCallbacks
def quotaSize(self, request):
- # FIXME: tests
- returnValue(len((yield self._newStoreObject.vCardText())))
+ return succeed(self._newStoreObject.size())
def vCardText(self, ignored=None):
@@ -1756,6 +1720,7 @@
@inlineCallbacks
def storeStream(self, stream):
+
# FIXME: direct tests
component = VCard.fromString(
(yield allDataFromStream(stream))
@@ -1769,12 +1734,6 @@
"""
Remove this addressbook object.
"""
- # Do quota checks before we start deleting things
- myquota = (yield self.quota(request))
- if myquota is not None:
- old_size = (yield self.quotaSize(request))
- else:
- old_size = 0
try:
@@ -1792,10 +1751,6 @@
del self._newStoreObject
self.__class__ = ProtoAddressBookObjectResource
- # Adjust quota
- if myquota is not None:
- yield self.quotaSizeAdjust(request, -old_size)
-
except MemcacheLockTimeoutError:
raise HTTPError(StatusResponse(CONFLICT, "Resource: %s currently in use on the server." % (where,)))
@@ -1950,12 +1905,7 @@
returnValue(children)
- def quotaSize(self, request):
- # FIXME: tests, workingness
- return succeed(0)
-
-
class StoreNotificationCollectionResource(_NotificationChildHelper,
NotificationCollectionResource):
"""
@@ -2079,12 +2029,7 @@
# FIXME: should be deleted, or raise an exception
- def quotaSize(self, request):
- # FIXME: tests, workingness
- return succeed(0)
-
-
class StoreNotificationObjectFile(NotificationResource):
"""
A resource wrapping a calendar object.
@@ -2130,10 +2075,8 @@
return self._newStoreObject.properties()
- @inlineCallbacks
def quotaSize(self, request):
- # FIXME: tests
- returnValue(len((yield self._newStoreObject.xmldata())))
+ return succeed(self._newStoreObject.size())
def text(self, ignored=None):
@@ -2163,13 +2106,6 @@
"""
Remove this notification object.
"""
- # Do quota checks before we start deleting things
- myquota = (yield self.quota(request))
- if myquota is not None:
- old_size = (yield self.quotaSize(request))
- else:
- old_size = 0
-
try:
storeNotifications = self._newStoreObject.notificationCollection()
@@ -2186,10 +2122,6 @@
del self._newStoreObject
self.__class__ = ProtoStoreNotificationObjectFile
- # Adjust quota
- if myquota is not None:
- yield self.quotaSizeAdjust(request, -old_size)
-
except MemcacheLockTimeoutError:
raise HTTPError(StatusResponse(CONFLICT, "Resource: %s currently in use on the server." % (where,)))
Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -235,6 +235,9 @@
@writeOperation
def setComponent(self, component, inserting=False):
+
+ old_size = 0 if inserting else self.size()
+
validateCalendarComponent(self, self._calendar, component, inserting)
self._calendar.retrieveOldIndex().addResource(
@@ -269,11 +272,16 @@
# Now re-write the original properties on the updated file
self.properties().flush()
+ # Adjust quota
+ quota_adjustment = self.size() - old_size
+ self._calendar._home.adjustQuotaUsedBytes(quota_adjustment)
+
def undo():
if backup:
backup.moveTo(self._path)
else:
self._path.remove()
+ self._calendar._home.adjustQuotaUsedBytes(-quota_adjustment)
return undo
self._transaction.addOperation(do, "set calendar component %r" % (self.name(),))
@@ -363,11 +371,18 @@
Implement L{ICalendarObject.removeAttachmentWithName}.
"""
# FIXME: rollback, tests for rollback
+
+ attachment = (yield self.attachmentWithName(name))
+ old_size = attachment.size()
+
(yield self._dropboxPath()).child(name).remove()
if name in self._attachments:
del self._attachments[name]
+ # Adjust quota
+ self._calendar._home.adjustQuotaUsedBytes(-old_size)
+
@inlineCallbacks
def attachmentWithName(self, name):
# Attachments can be local or remote, but right now we only care about
@@ -458,6 +473,9 @@
def loseConnection(self):
+
+ old_size = self._attachment.size()
+
# FIXME: do anything
self._file.close()
@@ -465,6 +483,9 @@
props = self._attachment.properties()
props[contentTypeKey] = GETContentType(generateContentType(self._contentType))
props[md5key] = TwistedGETContentMD5.fromString(md5)
+
+ # Adjust quota
+ self._attachment._calendarObject._calendar._home.adjustQuotaUsedBytes(self._attachment.size() - old_size)
props.flush()
Modified: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/index_file.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/caldav/datastore/index_file.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -28,7 +28,6 @@
"MemcachedUIDReserver",
"Index",
"IndexSchedule",
- "IndexedSearchException",
]
import datetime
@@ -269,7 +268,7 @@
self.log_info("Search falls outside range of index for %s %s" % (name, minDate))
self.reExpandResource(name, minDate)
- def whatchanged(self, revision, depth):
+ def whatchanged(self, revision):
results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)]
results.sort(key=lambda x:x[1])
@@ -341,7 +340,7 @@
maxDate += datetime.timedelta(days=365)
self.testAndUpdateIndex(maxDate)
else:
- # We cannot handler this filter in an indexed search
+ # We cannot handle this filter in an indexed search
raise IndexedSearchException()
else:
@@ -368,7 +367,7 @@
rowiter = self._db_execute("select DISTINCT RESOURCE.NAME, RESOURCE.UID, RESOURCE.TYPE" + qualifiers[0], *qualifiers[1])
# Check result for missing resources
-
+ results = []
for row in rowiter:
name = row[0]
if self.resource.getChild(name.encode("utf-8")):
@@ -377,12 +376,14 @@
if row[9]:
row[8] = row[9]
del row[9]
- yield row
+ results.append(row)
else:
log.err("Calendar resource %s is missing from %s. Removing from index."
% (name, self.resource))
self.deleteResource(name)
+ return results
+
def bruteForceSearch(self):
"""
List the whole index and tests for existence, updating the index
@@ -393,16 +394,19 @@
# Check result for missing resources:
+ results = []
for row in rowiter:
name = row[0]
if self.resource.getChild(name.encode("utf-8")):
- yield row
+ results.append(row)
else:
log.err("Calendar resource %s is missing from %s. Removing from index."
% (name, self.resource))
self.deleteResource(name)
+ return results
+
def _db_version(self):
"""
@return: the schema version assigned to this index.
@@ -666,7 +670,7 @@
expand = datetime.date.today() + default_future_expansion_duration
if expand > (datetime.date.today() + maximum_future_expansion_duration):
- raise IndexedSearchException
+ raise IndexedSearchException()
try:
instances = calendar.expandTimeRanges(expand, ignoreInvalidInstances=reCreate)
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -53,7 +53,7 @@
SQLLegacyCalendarShares, PostgresLegacyInboxIndexEmulator
from txdav.common.datastore.sql_tables import CALENDAR_TABLE,\
CALENDAR_BIND_TABLE, CALENDAR_OBJECT_REVISIONS_TABLE, CALENDAR_OBJECT_TABLE,\
- _ATTACHMENTS_MODE_WRITE
+ _ATTACHMENTS_MODE_WRITE, CALENDAR_HOME_TABLE
from txdav.base.propertystore.base import PropertyName
from vobject.icalendar import utc
@@ -66,14 +66,15 @@
implements(ICalendarHome)
- def __init__(self, transaction, ownerUID, resourceID, notifier):
+ def __init__(self, transaction, ownerUID, notifier):
+ self._homeTable = CALENDAR_HOME_TABLE
self._childClass = Calendar
self._childTable = CALENDAR_TABLE
self._bindTable = CALENDAR_BIND_TABLE
self._revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
- super(CalendarHome, self).__init__(transaction, ownerUID, resourceID, notifier)
+ super(CalendarHome, self).__init__(transaction, ownerUID, notifier)
self._shares = SQLLegacyCalendarShares(self)
createCalendarWithName = CommonHome.createChildWithName
@@ -244,6 +245,9 @@
@inlineCallbacks
def setComponent(self, component, inserting=False):
+
+ old_size = 0 if inserting else self.size()
+
validateCalendarComponent(self, self._calendar, component, inserting)
yield self.updateDatabase(component, inserting=inserting)
@@ -252,6 +256,9 @@
else:
yield self._calendar._updateRevision(self._name)
+ # Adjust quota
+ yield self._calendar._home.adjustQuotaUsedBytes(self.size() - old_size)
+
self._calendar.notifyChanged()
@@ -485,7 +492,8 @@
@inlineCallbacks
def removeAttachmentWithName(self, name):
- attachment = Attachment(self, name)
+ attachment = (yield self.attachmentWithName(name))
+ old_size = attachment.size()
self._txn.postCommit(attachment._path.remove)
yield self._txn.execSQL(
"""
@@ -494,7 +502,10 @@
""", [self._resourceID, name]
)
+ # Adjust quota
+ yield self._calendar._home.adjustQuotaUsedBytes(-old_size)
+
@inlineCallbacks
def attachmentWithName(self, name):
attachment = Attachment(self, name)
@@ -578,6 +589,9 @@
@inlineCallbacks
def loseConnection(self):
+
+ old_size = self.attachment.size()
+
self.attachment._path.setContent(self.buf)
self.attachment._contentType = self.contentType
self.attachment._md5 = self.hash.hexdigest()
@@ -597,6 +611,8 @@
]
))[0]
+ # Adjust quota
+ yield self.attachment._calendarObject._calendar._home.adjustQuotaUsedBytes(self.attachment.size() - old_size)
class Attachment(object):
@@ -606,6 +622,7 @@
def __init__(self, calendarObject, name):
self._calendarObject = calendarObject
self._name = name
+ self._size = 0
@property
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -1147,7 +1147,7 @@
@inlineCallbacks
def deleteIt(ignored):
obj = yield self.calendarObjectUnderTest()
- obj.removeAttachmentWithName("new.attachment")
+ yield obj.removeAttachmentWithName("new.attachment")
obj = yield refresh(obj)
self.assertIdentical(
None, (yield obj.attachmentWithName("new.attachment"))
Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -927,8 +927,7 @@
)
for revision, results in tests:
- for depth in ("1", "infinity"):
- self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
+ self.assertEquals(self.db.whatchanged(revision), results, "Mismatched results for whatchanged with revision %d" % (revision,))
class MemcacheTests(SQLIndexTests):
def setUp(self):
Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -165,6 +165,9 @@
@writeOperation
def setComponent(self, component, inserting=False):
+
+ old_size = 0 if inserting else self.size()
+
validateAddressBookComponent(self, self._addressbook, component, inserting)
self._addressbook.retrieveOldIndex().addResource(
@@ -199,11 +202,16 @@
# Now re-write the original properties on the updated file
self.properties().flush()
+ # Adjust quota
+ quota_adjustment = self.size() - old_size
+ self._addressbook._home.adjustQuotaUsedBytes(quota_adjustment)
+
def undo():
if backup:
backup.moveTo(self._path)
else:
self._path.remove()
+ self._addressbook._home.adjustQuotaUsedBytes(-quota_adjustment)
return undo
self._transaction.addOperation(do, "set addressbook component %r" % (self.name(),))
Modified: CalendarServer/trunk/txdav/carddav/datastore/index_file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/index_file.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/carddav/datastore/index_file.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -380,7 +380,7 @@
results = self._db_values_for_sql(statement, *names)
return results
- def whatchanged(self, revision, depth):
+ def whatchanged(self, revision):
results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)]
results.sort(key=lambda x:x[1])
Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -48,7 +48,7 @@
CommonObjectResource
from txdav.common.datastore.sql_tables import ADDRESSBOOK_TABLE,\
ADDRESSBOOK_BIND_TABLE, ADDRESSBOOK_OBJECT_REVISIONS_TABLE,\
- ADDRESSBOOK_OBJECT_TABLE
+ ADDRESSBOOK_OBJECT_TABLE, ADDRESSBOOK_HOME_TABLE
from txdav.base.propertystore.base import PropertyName
@@ -57,14 +57,15 @@
implements(IAddressBookHome)
- def __init__(self, transaction, ownerUID, resourceID, notifier):
+ def __init__(self, transaction, ownerUID, notifier):
+ self._homeTable = ADDRESSBOOK_HOME_TABLE
self._childClass = AddressBook
self._childTable = ADDRESSBOOK_TABLE
self._bindTable = ADDRESSBOOK_BIND_TABLE
self._revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
- super(AddressBookHome, self).__init__(transaction, ownerUID, resourceID, notifier)
+ super(AddressBookHome, self).__init__(transaction, ownerUID, notifier)
self._shares = SQLLegacyAddressBookShares(self)
@@ -178,6 +179,9 @@
@inlineCallbacks
def setComponent(self, component, inserting=False):
+
+ old_size = 0 if inserting else self.size()
+
validateAddressBookComponent(self, self._addressbook, component, inserting)
yield self.updateDatabase(component, inserting=inserting)
@@ -186,6 +190,9 @@
else:
yield self._addressbook._updateRevision(self._name)
+ # Adjust quota
+ yield self._addressbook._home.adjustQuotaUsedBytes(self.size() - old_size)
+
self._addressbook.notifyChanged()
Modified: CalendarServer/trunk/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/common.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/common.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -413,7 +413,7 @@
transaction, even if it has not yet been committed.
"""
addressbook1 = yield self.addressbookUnderTest()
- addressbook1.removeAddressBookObjectWithName("2.vcf")
+ yield addressbook1.removeAddressBookObjectWithName("2.vcf")
addressbookObjects = list((yield addressbook1.addressbookObjects()))
self.assertEquals(set(o.name() for o in addressbookObjects),
set(addressbook1_objectNames) - set(["2.vcf"]))
Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -197,8 +197,7 @@
)
for revision, results in tests:
- for depth in ("1", "infinity"):
- self.assertEquals(self.db.whatchanged(revision, depth), results, "Mismatched results for whatchanged with revision %d" % (revision,))
+ self.assertEquals(self.db.whatchanged(revision), results, "Mismatched results for whatchanged with revision %d" % (revision,))
class MemcacheTests(SQLIndexTests):
def setUp(self):
Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/common/datastore/file.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -23,8 +23,10 @@
from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType, HRef
from twext.web2.dav.element.rfc5842 import ResourceID
from twext.web2.http_headers import generateContentType, MimeType
-from twext.web2.dav.resource import TwistedGETContentMD5
+from twext.web2.dav.resource import TwistedGETContentMD5,\
+ TwistedQuotaUsedProperty
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue
from twisted.python.util import FancyEqMixin
from twisted.python import hashlib
@@ -511,6 +513,27 @@
child.notifyChanged()
+ @inlineCallbacks
+ def syncToken(self):
+
+ maxrev = 0
+ for child in self.children():
+ maxrev = max(int((yield child.syncToken()).split("#")[1]), maxrev)
+
+ try:
+ urnuuid = str(self.properties()[PropertyName.fromElement(ResourceID)].children[0])
+ except KeyError:
+ urnuuid = uuid.uuid4().urn
+ self.properties()[PropertyName(*ResourceID.qname())] = ResourceID(HRef.fromString(urnuuid))
+ returnValue("%s#%s" % (urnuuid[9:], maxrev))
+
+
+ def resourceNamesSinceToken(self, token, depth):
+ deleted = []
+ changed = []
+ return succeed((changed, deleted))
+
+
# @cached
def properties(self):
# FIXME: needs tests for actual functionality
@@ -520,6 +543,27 @@
self._transaction.addOperation(props.flush, "flush home properties")
return props
+ def quotaUsedBytes(self):
+
+ try:
+ return int(str(self.properties()[PropertyName.fromElement(TwistedQuotaUsedProperty)]))
+ except KeyError:
+ return 0
+
+ def adjustQuotaUsedBytes(self, delta):
+ """
+ Adjust quota used. We need to get a lock on the row first so that the adjustment
+ is done atomically.
+ """
+
+ old_used = self.quotaUsedBytes()
+ new_used = old_used + delta
+ if new_used < 0:
+ self.log_error("Fixing quota adjusted below zero to %s by change amount %s" % (new_used, delta,))
+ new_used = 0
+ self.properties()[PropertyName.fromElement(TwistedQuotaUsedProperty)] = TwistedQuotaUsedProperty(str(new_used))
+
+
def notifierID(self, label="default"):
if self._notifier:
return self._notifier.getID(label)
@@ -714,6 +758,10 @@
objectResourcePath = self._path.child(name)
if objectResourcePath.isfile():
+ # Handle quota adjustment
+ child = self.objectResourceWithName(name)
+ old_size = child.size()
+
self._removedObjectResources.add(name)
# FIXME: test for undo
def do():
@@ -721,6 +769,10 @@
return lambda: None
self._transaction.addOperation(do, "remove object resource object %r" %
(name,))
+
+ # Adjust quota
+ self._home.adjustQuotaUsedBytes(-old_size)
+
self.notifyChanged()
else:
raise NoSuchObjectResourceError(name)
@@ -739,13 +791,17 @@
except KeyError:
urnuuid = uuid.uuid4().urn
self.properties()[PropertyName(*ResourceID.qname())] = ResourceID(HRef.fromString(urnuuid))
- return "%s#%s" % (urnuuid[9:], self.retrieveOldIndex().lastRevision())
+ return succeed("%s#%s" % (urnuuid[9:], self.retrieveOldIndex().lastRevision()))
def objectResourcesSinceToken(self, token):
raise NotImplementedError()
+ def resourceNamesSinceToken(self, token):
+ return succeed(self.retrieveOldIndex().whatchanged(token))
+
+
# FIXME: property writes should be a write operation
@cached
def properties(self):
Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -216,7 +216,11 @@
Transaction implementation for SQL database.
"""
_homeClass = {}
+ _homeTable = {}
+ noisy = False
+ id = 0
+
def __init__(self, store, connectionFactory,
enableCalendars, enableAddressBooks,
notifierFactory, label, migrating=False):
@@ -229,6 +233,8 @@
self._notifierFactory = notifierFactory
self._label = label
self._migrating = migrating
+ CommonStoreTransaction.id += 1
+ self._txid = CommonStoreTransaction.id
extraInterfaces = []
if enableCalendars:
@@ -241,6 +247,8 @@
from txdav.carddav.datastore.sql import AddressBookHome
CommonStoreTransaction._homeClass[ECALENDARTYPE] = CalendarHome
CommonStoreTransaction._homeClass[EADDRESSBOOKTYPE] = AddressBookHome
+ CommonStoreTransaction._homeTable[ECALENDARTYPE] = CALENDAR_HOME_TABLE
+ CommonStoreTransaction._homeTable[EADDRESSBOOKTYPE] = ADDRESSBOOK_HOME_TABLE
self._holder = ThreadHolder(reactor)
self._holder.start()
def initCursor():
@@ -273,8 +281,6 @@
return None
- noisy = False
-
def execSQL(self, *args, **kw):
result = self._holder.submit(
lambda : self._reallyExecSQL(*args, **kw)
@@ -283,8 +289,8 @@
def reportResult(results):
sys.stdout.write("\n".join([
"",
- "SQL: %r %r" % (args, kw),
- "Results: %r" % (results,),
+ "SQL (%d): %r %r" % (self._txid, args, kw),
+ "Results (%d): %r" % (self._txid, results,),
"",
]))
return results
@@ -310,19 +316,20 @@
@inlineCallbacks
def homeWithUID(self, storeType, uid, create=False):
- if storeType == ECALENDARTYPE:
- homeTable = CALENDAR_HOME_TABLE
- elif storeType == EADDRESSBOOKTYPE:
- homeTable = ADDRESSBOOK_HOME_TABLE
- else:
+ if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
raise RuntimeError("Unknown home type.")
- data = yield self.execSQL(
- "select %(column_RESOURCE_ID)s from %(name)s"
- " where %(column_OWNER_UID)s = %%s" % homeTable,
- [uid]
- )
- if not data:
+ if self._notifierFactory:
+ notifier = self._notifierFactory.newNotifier(
+ id=uid, prefix=NotifierPrefixes[storeType]
+ )
+ else:
+ notifier = None
+ homeObject = self._homeClass[storeType](self, uid, notifier)
+ homeObject = (yield homeObject.initFromStore())
+ if homeObject is not None:
+ returnValue(homeObject)
+ else:
if not create:
returnValue(None)
# Need to lock to prevent race condition
@@ -331,34 +338,49 @@
# does allow concurrent reads so the only thing we block is other
# attempts to provision a home, which is not too bad
yield self.execSQL(
- "lock %(name)s in exclusive mode" % homeTable,
+ "lock %(name)s in exclusive mode" % CommonStoreTransaction._homeTable[storeType],
)
# Now test again
- data = yield self.execSQL(
+ exists = yield self.execSQL(
"select %(column_RESOURCE_ID)s from %(name)s"
- " where %(column_OWNER_UID)s = %%s" % homeTable,
+ " where %(column_OWNER_UID)s = %%s" % CommonStoreTransaction._homeTable[storeType],
[uid]
)
- if not data:
+ if not exists:
yield self.execSQL(
- "insert into %(name)s (%(column_OWNER_UID)s) values (%%s)" % homeTable,
+ "insert into %(name)s (%(column_OWNER_UID)s) values (%%s)" % CommonStoreTransaction._homeTable[storeType],
[uid]
)
- home = yield self.homeWithUID(storeType, uid)
+ home = yield self.homeWithUID(storeType, uid)
+ if not exists:
yield home.createdHome()
- returnValue(home)
- resid = data[0][0]
- if self._notifierFactory:
- notifier = self._notifierFactory.newNotifier(
- id=uid, prefix=NotifierPrefixes[storeType]
+ returnValue(home)
+
+ def createHomeWithUIDLocked(self, storeType, uid):
+ # Need to lock to prevent race condition
+ # FIXME: this is an entire table lock - ideally we want a row lock
+ # but the row does not exist yet. However, the "exclusive" mode
+ # does allow concurrent reads so the only thing we block is other
+ # attempts to provision a home, which is not too bad
+
+ if storeType not in (ECALENDARTYPE, EADDRESSBOOKTYPE):
+ raise RuntimeError("Unknown home type.")
+
+ yield self.execSQL(
+ "lock %(name)s in exclusive mode" % CommonStoreTransaction._homeTable[storeType],
+ )
+ # Now test again
+ exists = yield self.execSQL(
+ "select %(column_RESOURCE_ID)s from %(name)s"
+ " where %(column_OWNER_UID)s = %%s" % CommonStoreTransaction._homeTable[storeType],
+ [uid]
+ )
+ if not exists:
+ yield self.execSQL(
+ "insert into %(name)s (%(column_OWNER_UID)s) values (%%s)" % CommonStoreTransaction._homeTable[storeType],
+ [uid]
)
- else:
- notifier = None
- homeObject = self._homeClass[storeType](self, uid, resid, notifier)
- yield homeObject._loadPropertyStore()
- returnValue(homeObject)
-
@memoizedKey("uid", "_notificationHomes")
@inlineCallbacks
def notificationsWithUID(self, uid):
@@ -426,16 +448,17 @@
class CommonHome(LoggingMixIn):
+ _homeTable = None
_childClass = None
_childTable = None
_bindTable = None
_revisionsTable = None
_notificationRevisionsTable = NOTIFICATION_OBJECT_REVISIONS_TABLE
- def __init__(self, transaction, ownerUID, resourceID, notifier):
+ def __init__(self, transaction, ownerUID, notifier):
self._txn = transaction
self._ownerUID = ownerUID
- self._resourceID = resourceID
+ self._resourceID = None
self._shares = None
self._children = {}
self._sharedChildren = {}
@@ -448,6 +471,25 @@
for key, value in self._bindTable.iteritems():
self._revisionBindJoinTable["BIND:%s" % (key,)] = value
+ @inlineCallbacks
+ def initFromStore(self):
+ """
+ Initialise this object from the store. We read in and cache all the extra metadata
+ from the DB to avoid having to do DB queries for those individually later.
+ """
+
+ result = yield self._txn.execSQL(
+ "select %(column_RESOURCE_ID)s from %(name)s"
+ " where %(column_OWNER_UID)s = %%s" % self._homeTable,
+ [self._ownerUID]
+ )
+ if result:
+ self._resourceID = result[0][0]
+ yield self._loadPropertyStore()
+ returnValue(self)
+ else:
+ returnValue(None)
+
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
@@ -831,6 +873,51 @@
return None
+ @inlineCallbacks
+ def quotaUsedBytes(self):
+ returnValue((yield self._txn.execSQL(
+ "select %(column_QUOTA_USED_BYTES)s from %(name)s"
+ " where %(column_OWNER_UID)s = %%s" % self._homeTable,
+ [self._ownerUID]
+ ))[0][0])
+
+ @inlineCallbacks
+ def adjustQuotaUsedBytes(self, delta):
+ """
+ Adjust quota used. We need to get a lock on the row first so that the adjustment
+ is done atomically. It is import to do the 'select ... for update' because a race also
+ exists in the 'update ... x = x + 1' case as seen via unit tests.
+ """
+
+ yield self._txn.execSQL("""
+ select * from %(name)s
+ where %(column_RESOURCE_ID)s = %%s
+ for update
+ """ % self._homeTable,
+ [self._resourceID]
+ )
+
+ quotaUsedBytes = (yield self._txn.execSQL("""
+ update %(name)s
+ set %(column_QUOTA_USED_BYTES)s = %(column_QUOTA_USED_BYTES)s + %%s
+ where %(column_RESOURCE_ID)s = %%s
+ returning %(column_QUOTA_USED_BYTES)s
+ """ % self._homeTable,
+ [delta, self._resourceID]
+ ))[0][0]
+
+ # Double check integrity
+ if quotaUsedBytes < 0:
+ log.error("Fixing quota adjusted below zero to %s by change amount %s" % (quotaUsedBytes, delta,))
+ yield self._txn.execSQL("""
+ update %(name)s
+ set %(column_QUOTA_USED_BYTES)s = 0
+ where %(column_RESOURCE_ID)s = %%s
+ """ % self._homeTable,
+ [self._resourceID]
+ )
+
+
def notifierID(self, label="default"):
if self._notifier:
return self._notifier.getID(label)
@@ -1050,35 +1137,41 @@
@inlineCallbacks
def removeObjectResourceWithName(self, name):
- rows = yield self._txn.execSQL(
+
+ uid, old_size = (yield self._txn.execSQL(
"delete from %(name)s "
"where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s "
- "returning %(column_UID)s" % self._objectTable,
+ "returning %(column_UID)s, character_length(%(column_TEXT)s)" % self._objectTable,
[name, self._resourceID],
raiseOnZeroRowCount=lambda:NoSuchObjectResourceError()
- )
- uid = rows[0][0]
+ ))[0]
self._objects.pop(name, None)
self._objects.pop(uid, None)
yield self._deleteRevision(name)
+ # Adjust quota
+ yield self._home.adjustQuotaUsedBytes(-old_size)
+
self.notifyChanged()
@inlineCallbacks
def removeObjectResourceWithUID(self, uid):
- rows = yield self._txn.execSQL(
+
+ name, old_size = (yield self._txn.execSQL(
"delete from %(name)s "
"where %(column_UID)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s "
- "returning %(column_RESOURCE_NAME)s" % self._objectTable,
+ "returning %(column_RESOURCE_NAME)s, character_length(%(column_TEXT)s)" % self._objectTable,
[uid, self._resourceID],
raiseOnZeroRowCount=lambda:NoSuchObjectResourceError()
- )
- name = rows[0][0]
+ ))[0]
self._objects.pop(name, None)
self._objects.pop(uid, None)
yield self._deleteRevision(name)
+ # Adjust quota
+ yield self._home.adjustQuotaUsedBytes(-old_size)
+
self.notifyChanged()
@@ -1411,7 +1504,7 @@
@inlineCallbacks
def _loadPropertyStore(self):
props = yield PropertyStore.load(
- self.uid(),
+ self._parentCollection.ownerHome().uid(),
self._txn,
self._resourceID
)
@@ -1423,6 +1516,14 @@
return self._propertyStore
+ def initPropertyStore(self, props):
+ """
+ A hook for subclasses to override in order to set up their property
+ store after it's been created.
+
+ @param props: the L{PropertyStore} from C{properties()}.
+ """
+
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._resourceID)
@@ -1453,15 +1554,7 @@
return self._name
- def initPropertyStore(self, props):
- """
- A hook for subclasses to override in order to set up their property
- store after it's been created.
- @param props: the L{PropertyStore} from C{properties()}.
- """
-
-
# IDataStoreResource
def contentType(self):
raise NotImplementedError()
Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql 2010-11-01 01:22:09 UTC (rev 6489)
@@ -10,8 +10,9 @@
-------------------
create table CALENDAR_HOME (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- OWNER_UID varchar(255) not null unique
+ RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
+ OWNER_UID varchar(255) not null unique,
+ QUOTA_USED_BYTES integer default 0 not null
);
@@ -247,8 +248,9 @@
----------------------
create table ADDRESSBOOK_HOME (
- RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
- OWNER_UID varchar(255) not null unique
+ RESOURCE_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
+ OWNER_UID varchar(255) not null unique,
+ QUOTA_USED_BYTES integer default 0 not null
);
Modified: CalendarServer/trunk/txdav/common/datastore/sql_tables.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_tables.py 2010-10-31 01:35:59 UTC (rev 6488)
+++ CalendarServer/trunk/txdav/common/datastore/sql_tables.py 2010-11-01 01:22:09 UTC (rev 6489)
@@ -20,15 +20,17 @@
"""
CALENDAR_HOME_TABLE = {
- "name" : "CALENDAR_HOME",
- "column_RESOURCE_ID" : "RESOURCE_ID",
- "column_OWNER_UID" : "OWNER_UID",
+ "name" : "CALENDAR_HOME",
+ "column_RESOURCE_ID" : "RESOURCE_ID",
+ "column_OWNER_UID" : "OWNER_UID",
+ "column_QUOTA_USED_BYTES" : "QUOTA_USED_BYTES",
}
ADDRESSBOOK_HOME_TABLE = {
- "name" : "ADDRESSBOOK_HOME",
- "column_RESOURCE_ID" : "RESOURCE_ID",
- "column_OWNER_UID" : "OWNER_UID",
+ "name" : "ADDRESSBOOK_HOME",
+ "column_RESOURCE_ID" : "RESOURCE_ID",
+ "column_OWNER_UID" : "OWNER_UID",
+ "column_QUOTA_USED_BYTES" : "QUOTA_USED_BYTES",
}
NOTIFICATION_HOME_TABLE = {
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101031/77f96ab2/attachment-0001.html>
More information about the calendarserver-changes
mailing list