[CalendarServer-changes] [5459] CalendarServer/trunk/twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Fri Apr 9 12:20:33 PDT 2010
Revision: 5459
http://trac.macosforge.org/projects/calendarserver/changeset/5459
Author: cdaboo at apple.com
Date: 2010-04-09 12:20:32 -0700 (Fri, 09 Apr 2010)
Log Message:
-----------
WebDAV sync REPORT support for address book collections. Required some fixes to MKCOL stuff.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
CalendarServer/trunk/twistedcaldav/index.py
CalendarServer/trunk/twistedcaldav/method/delete_common.py
CalendarServer/trunk/twistedcaldav/method/mkcol.py
CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/static.py
CalendarServer/trunk/twistedcaldav/vcardindex.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
Modified: CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py 2010-04-09 19:08:40 UTC (rev 5458)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -426,7 +426,6 @@
destinationparent = newAddressBook,
vcard = vcard,
indexdestination = False,
- updatedestinationctag = False,
).run()
except:
self.log_info("Could not add record %s" % (record,))
Modified: CalendarServer/trunk/twistedcaldav/index.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/index.py 2010-04-09 19:08:40 UTC (rev 5458)
+++ CalendarServer/trunk/twistedcaldav/index.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -1018,8 +1018,8 @@
if do_commit:
self._db_commit()
- # This is a deferred but we can't defer at this point...
- self.resource.bumpSyncToken(True)
+ # Need new sync-token
+ self.resource.initSyncToken()
class IndexSchedule (CalendarIndex):
"""
@@ -1133,5 +1133,5 @@
if do_commit:
self._db_commit()
- # This is a deferred but we can't defer at this point...
- self.resource.bumpSyncToken(True)
+ # Need new sync-token
+ self.resource.initSyncToken()
Modified: CalendarServer/trunk/twistedcaldav/method/delete_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/delete_common.py 2010-04-09 19:08:40 UTC (rev 5458)
+++ CalendarServer/trunk/twistedcaldav/method/delete_common.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -316,13 +316,10 @@
yield delresource.quotaSizeAdjust(self.request, -old_size)
if response == responsecode.NO_CONTENT:
+ newrevision = (yield parent.bumpSyncToken())
index = parent.index()
- index.deleteResource(delresource.fp.basename())
+ index.deleteResource(delresource.fp.basename(), newrevision)
- # Change CTag on the parent addressbook collection
- yield parent.updateCTag()
-
-
except MemcacheLockTimeoutError:
raise HTTPError(StatusResponse(responsecode.CONFLICT, "Resource: %s currently in use on the server." % (deluri,)))
@@ -366,7 +363,7 @@
if wasShared:
yield delresource.downgradeFromShare(self.request)
- yield delresource.updateCTag()
+ yield delresource.bumpSyncToken()
more_responses = (yield self.deleteResource(delresource, deluri, parent))
if isinstance(more_responses, MultiStatusResponse):
@@ -427,6 +424,7 @@
elif isCalendarCollectionResource(self.resource):
response = (yield self.deleteCalendar(self.resource, self.resource_uri, self.parent))
+
elif isAddressBookCollectionResource(self.parent):
response = (yield self.deleteAddressBookResource(self.resource, self.resource_uri, self.parent))
Modified: CalendarServer/trunk/twistedcaldav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcol.py 2010-04-09 19:08:40 UTC (rev 5458)
+++ CalendarServer/trunk/twistedcaldav/method/mkcol.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -131,7 +131,7 @@
for property in properties:
if isinstance(property, davxml.ResourceType):
if rtype:
- error = "Multiple {DAV:}resource-type properties in MKCOL request body: %s" % (mkcol,)
+ error = "Multiple {DAV:}resourcetype properties in MKCOL request body: %s" % (mkcol,)
log.err(error)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
else:
@@ -141,18 +141,18 @@
elif property.childrenOfType(carddavxml.AddressBook):
rtype = "addressbook"
if not rtype:
- error = "No {DAV:}resource-type property in MKCOL request body: %s" % (mkcol,)
+ error = "No {DAV:}resourcetype property in MKCOL request body: %s" % (mkcol,)
log.err(error)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
elif rtype not in ("calendar", "addressbook"):
- error = "{DAV:}resource-type property in MKCOL request body not supported: %s" % (mkcol,)
+ error = "{DAV:}resourcetype property in MKCOL request body not supported: %s" % (mkcol,)
log.err(error)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
# Make sure feature is enabled
if (rtype == "calendar" and not config.EnableCalDAV or
rtype == "addressbook" and not config.EnableCardDAV):
- error = "{DAV:}resource-type property in MKCOL request body not supported: %s" % (mkcol,)
+ error = "{DAV:}resourcetype property in MKCOL request body not supported: %s" % (mkcol,)
log.err(error)
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, error))
Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py 2010-04-09 19:08:40 UTC (rev 5458)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -179,7 +179,6 @@
destination=None, destination_uri=None, destinationparent=None, destinationadbk=True,
vcard=None,
indexdestination = True,
- updatedestinationctag = True,
):
"""
Function that does common PUT/COPY/MOVE behavior.
@@ -232,13 +231,10 @@
self.vcarddata = None
self.deletesource = deletesource
self.indexdestination = indexdestination
- self.updatedestinationctag = updatedestinationctag
- #self.isiTIP = isiTIP
- #self.allowImplicitSchedule = allowImplicitSchedule
- #self.internal_request = internal_request
self.rollback = None
self.access = None
+ self.newrevision = None
def fullValidation(self):
"""
@@ -502,7 +498,8 @@
def doSourceDelete(self):
# Delete index for original item
if self.sourceadbk:
- self.source_index.deleteResource(self.source.fp.basename())
+ self.newrevision = (yield self.sourceparent.bumpSyncToken())
+ self.source_index.deleteResource(self.source.fp.basename(), self.newrevision)
self.rollback.source_index_deleted = True
log.debug("Source index removed %s" % (self.source.fp.path,))
@@ -511,14 +508,14 @@
self.rollback.source_deleted = True
log.debug("Source removed %s" % (self.source.fp.path,))
+ 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)
-
- # Change CTag on the parent vcard collection
- if self.sourceadbk:
- yield self.sourceparent.updateCTag()
returnValue(None)
@@ -546,7 +543,7 @@
# Add or update the index for this resource.
try:
- self.source_index.addResource(self.source.fp.basename(), self.vcard)
+ self.source_index.addResource(self.source.fp.basename(), self.vcard, self.newrevision)
except TooManyInstancesError, ex:
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
@@ -569,7 +566,7 @@
# Add or update the index for this resource.
try:
- self.destination_index.addResource(self.destination.fp.basename(), vcardtoindex)
+ self.destination_index.addResource(self.destination.fp.basename(), vcardtoindex, self.newrevision)
log.debug("Destination indexed %s" % (self.destination.fp.path,))
except (ValueError, TypeError), ex:
log.err("Cannot index vcard resource: %s" % (ex,))
@@ -585,7 +582,7 @@
# Delete index for original item
if self.destinationadbk:
- self.destination_index.deleteResource(self.destination.fp.basename())
+ self.destination_index.deleteResource(self.destination.fp.basename(), None)
self.rollback.destination_index_deleted = True
log.debug("Destination index removed %s" % (self.destination.fp.path,))
@@ -649,19 +646,20 @@
# Index the new resource if storing to a vcard.
if self.destinationadbk:
+ self.newrevision = (yield self.destinationparent.bumpSyncToken())
result = self.doDestinationIndex(self.vcard)
if result is not None:
self.rollback.Rollback()
returnValue(result)
+ # 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()
- if self.destinationadbk and self.updatedestinationctag:
- # Change CTag on the parent vcard collection
- yield self.destinationparent.updateCTag()
-
# Can now commit changes and forget the rollback details
self.rollback.Commit()
Modified: CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py 2010-04-09 19:08:40 UTC (rev 5458)
+++ CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -46,8 +46,8 @@
"""
Generate a sync-collection REPORT.
"""
- if not self.isPseudoCalendarCollection() or not config.EnableSyncReport:
- log.err("sync-collection report is only allowed on calendar/inbox collection resources %s" % (self,))
+ if not self.isPseudoCalendarCollection() and not self.isAddressBookCollection() or not config.EnableSyncReport:
+ log.err("sync-collection report is only allowed on calendar/inbox/addressbook collection resources %s" % (self,))
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, davxml.SupportedReport()))
responses = []
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2010-04-09 19:08:40 UTC (rev 5458)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -436,6 +436,11 @@
# Remove share
yield self.downgradeFromShare(request)
returnValue(None)
+ else:
+ # resourcetype cannot be changed but we will allow it to be set to the same value
+ currentType = (yield self.resourceType(request))
+ if currentType == property:
+ returnValue(None)
result = (yield super(CalDAVResource, self).writeProperty(property, request))
returnValue(result)
@@ -912,8 +917,8 @@
if config.EnableCardDAV:
result.append(davxml.Report(carddavxml.AddressBookQuery(),))
result.append(davxml.Report(carddavxml.AddressBookMultiGet(),))
- if self.isPseudoCalendarCollection() and config.EnableSyncReport:
- # Only allowed on calendar/inbox collections
+ if (self.isPseudoCalendarCollection() or self.isAddressBookCollection()) and config.EnableSyncReport:
+ # Only allowed on calendar/inbox/addressbook collections
result.append(davxml.Report(SyncCollection(),))
return result
Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py 2010-04-09 19:08:40 UTC (rev 5458)
+++ CalendarServer/trunk/twistedcaldav/static.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -398,7 +398,7 @@
raise HTTPError(status)
# Initialize CTag on the address book collection
- d1 = self.updateCTag()
+ d1 = self.bumpSyncToken()
# Create the index so its ready when the first PUTs come in
d1.addCallback(lambda _: self.index().create())
@@ -507,7 +507,7 @@
return changed, removed, current_token
@inlineCallbacks
- def bumpSyncToken(self, reset=False):
+ def bumpSyncToken(self):
"""
Increment the sync-token which is also the ctag.
@@ -524,8 +524,6 @@
raise HTTPError(StatusResponse(responsecode.CONFLICT, "Resource: %s currently in use on the server." % (self.uri,)))
try:
- if reset:
- raise ValueError
token = str(self.readDeadProperty(customxml.GETCTag))
caluuid, revision = token.split("#", 1)
revision = int(revision) + 1
@@ -542,6 +540,21 @@
finally:
yield lock.clean()
+ def initSyncToken(self):
+ """
+ Create a new sync-token which is also the ctag.
+ """
+ assert self.isCollection()
+
+ # Initialise it
+ caluuid = uuid4()
+ revision = 1
+ token = "%s#%d" % (caluuid, revision,)
+ try:
+ self.writeDeadProperty(customxml.GETCTag(token))
+ except:
+ return fail(Failure())
+
def updateCTag(self, token=None):
assert self.isCollection()
Added: CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -0,0 +1,184 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet import reactor
+from twisted.internet.task import deferLater
+
+from twistedcaldav.test.util import InMemoryMemcacheProtocol
+from twistedcaldav.vcard import Component
+from twistedcaldav.vcardindex import AddressBookIndex, MemcachedUIDReserver, ReservationError
+import twistedcaldav.test.util
+
+import os
+
+class SQLIndexTests (twistedcaldav.test.util.TestCase):
+ """
+ Test abstract SQL DB class
+ """
+
+ def setUp(self):
+ super(SQLIndexTests, self).setUp()
+ self.site.resource.isAddressBookCollection = lambda: True
+ self.db = AddressBookIndex(self.site.resource)
+
+
+ def test_reserve_uid_ok(self):
+ uid = "test-test-test"
+ d = self.db.isReservedUID(uid)
+ d.addCallback(self.assertFalse)
+ d.addCallback(lambda _: self.db.reserveUID(uid))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertTrue)
+ d.addCallback(lambda _: self.db.unreserveUID(uid))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertFalse)
+
+ return d
+
+
+ def test_reserve_uid_twice(self):
+ uid = "test-test-test"
+ d = self.db.reserveUID(uid)
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertTrue)
+ d.addCallback(lambda _:
+ self.assertFailure(self.db.reserveUID(uid),
+ ReservationError))
+ return d
+
+
+ def test_unreserve_unreserved(self):
+ uid = "test-test-test"
+ return self.assertFailure(self.db.unreserveUID(uid),
+ ReservationError)
+
+
+ def test_reserve_uid_timeout(self):
+ # WARNING: This test is fundamentally flawed and will fail
+ # intermittently because it uses the real clock.
+ uid = "test-test-test"
+ from twistedcaldav.config import config
+ old_timeout = config.UIDReservationTimeOut
+ config.UIDReservationTimeOut = 1
+
+ def _finally():
+ config.UIDReservationTimeOut = old_timeout
+
+ d = self.db.isReservedUID(uid)
+ d.addCallback(self.assertFalse)
+ d.addCallback(lambda _: self.db.reserveUID(uid))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertTrue)
+ d.addCallback(lambda _: deferLater(reactor, 2, lambda: None))
+ d.addCallback(lambda _: self.db.isReservedUID(uid))
+ d.addCallback(self.assertFalse)
+ self.addCleanup(_finally)
+
+ return d
+
+
+ def test_index(self):
+ data = (
+ (
+ "#1.1 Simple component",
+ "1.1",
+ """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-1.1
+FN:Cyrus Daboo
+N:Daboo;Cyrus
+EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
+END:VCARD
+""",
+ ),
+ )
+
+ revision = 0
+ for description, name, vcard_txt in data:
+ revision += 1
+ calendar = Component.fromString(vcard_txt)
+ f = open(os.path.join(self.site.resource.fp.path, name), "w")
+ f.write(vcard_txt)
+ del f
+
+ self.db.addResource(name, calendar, revision)
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+
+ self.db._db_recreate()
+ for description, name, vcard_txt in data:
+ self.assertTrue(self.db.resourceExists(name), msg=description)
+
+ def test_index_revisions(self):
+ data1 = """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-1-1.1
+FN:Cyrus Daboo
+N:Daboo;Cyrus
+EMAIL;TYPE=INTERNET,PREF:cyrus at example.com
+END:VCARD
+"""
+ data2 = """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-2-1.1
+FN:Wilfredo Sanchez
+N:Sanchez;Wilfredo
+EMAIL;TYPE=INTERNET,PREF:wsanchez at example.com
+END:VCARD
+"""
+ data3 = """BEGIN:VCARD
+VERSION:3.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+UID:12345-67890-3-1.1
+FN:Bruce Gaya
+N:Gaya;Bruce
+EMAIL;TYPE=INTERNET,PREF:bruce at example.com
+END:VCARD
+"""
+
+ vcard = Component.fromString(data1)
+ self.db.addResource("data1.vcf", vcard, 1)
+ vcard = Component.fromString(data2)
+ self.db.addResource("data2.vcf", vcard, 2)
+ vcard = Component.fromString(data3)
+ self.db.addResource("data3.vcf", vcard, 3)
+ self.db.deleteResource("data3.vcf", 4)
+
+ tests = (
+ (0, (["data1.vcf", "data2.vcf",], [],)),
+ (1, (["data2.vcf",], [],)),
+ (2, ([], [],)),
+ (3, ([], ["data3.vcf",],)),
+ (4, ([], [],)),
+ (5, ([], [],)),
+ )
+
+ for revision, results in tests:
+ self.assertEquals(self.db.whatchanged(revision), results, "Mismatched results for whatchanged with revision %d" % (revision,))
+
+class MemcacheTests(SQLIndexTests):
+ def setUp(self):
+ super(MemcacheTests, self).setUp()
+ self.memcache = InMemoryMemcacheProtocol()
+ self.db.reserver = MemcachedUIDReserver(self.db, self.memcache)
+
+
+ def tearDown(self):
+ for _ignore_k, v in self.memcache._timeouts.iteritems():
+ if v.active():
+ v.cancel()
Modified: CalendarServer/trunk/twistedcaldav/vcardindex.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/vcardindex.py 2010-04-09 19:08:40 UTC (rev 5458)
+++ CalendarServer/trunk/twistedcaldav/vcardindex.py 2010-04-09 19:20:32 UTC (rev 5459)
@@ -38,6 +38,7 @@
from twisted.internet.defer import maybeDeferred
from twistedcaldav import carddavxml
+from twistedcaldav.index import SyncTokenValidException
from twistedcaldav.query import addressbookquery
from twistedcaldav.sql import AbstractSQLDatabase
from twistedcaldav.sql import db_prefix
@@ -50,7 +51,7 @@
log = Logger()
db_basename = db_prefix + "sqlite"
-schema_version = "1"
+schema_version = "2"
class ReservationError(LookupError):
"""
@@ -296,7 +297,7 @@
if name is not None and self.resource.getChild(name_utf8) is None:
# Clean up
log.err("Stale resource record found for child %s with UID %s in %s" % (name, uid, self.resource))
- self._delete_from_db(name, uid)
+ self._delete_from_db(name, uid, None)
self._db_commit()
else:
resources.append(name_utf8)
@@ -328,7 +329,7 @@
return uid
- def addResource(self, name, vcard, fast=False):
+ def addResource(self, name, vcard, revision, fast=False):
"""
Adding or updating an existing resource.
To check for an update we attempt to get an existing UID
@@ -341,12 +342,12 @@
"""
oldUID = self.resourceUIDForName(name)
if oldUID is not None:
- self._delete_from_db(name, oldUID)
- self._add_to_db(name, vcard)
+ self._delete_from_db(name, oldUID, None)
+ self._add_to_db(name, vcard, revision)
if not fast:
self._db_commit()
- def deleteResource(self, name):
+ def deleteResource(self, name, revision):
"""
Remove this resource from the index.
@param name: the name of the resource to add.
@@ -354,7 +355,7 @@
"""
uid = self.resourceUIDForName(name)
if uid is not None:
- self._delete_from_db(name, uid)
+ self._delete_from_db(name, uid, revision)
self._db_commit()
def resourceExists(self, name):
@@ -381,6 +382,26 @@
results = self._db_values_for_sql(statement, *names)
return results
+ def whatchanged(self, revision):
+
+ results = [(name.encode("utf-8"), created, wasdeleted) for name, created, wasdeleted in self._db_execute("select NAME, CREATEDREVISION, WASDELETED from REVISIONS where REVISION > :1", revision)]
+ results.sort(key=lambda x:x[1])
+
+ changed = []
+ deleted = []
+ for name, created, wasdeleted in results:
+ if name:
+ if wasdeleted == 'Y':
+ # Don't report items that were created/deleted since the requested revision
+ if created <= revision:
+ deleted.append(name)
+ else:
+ changed.append(name)
+ else:
+ raise SyncTokenValidException
+
+ return changed, deleted,
+
def searchValid(self, filter):
if isinstance(filter, carddavxml.Filter):
qualifiers = addressbookquery.sqladdressbookquery(filter)
@@ -418,7 +439,7 @@
else:
log.err("vCard resource %s is missing from %s. Removing from index."
% (name, self.resource))
- self.deleteResource(name)
+ self.deleteResource(name, None)
def _db_version(self):
"""
@@ -461,6 +482,28 @@
)
#
+ # 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 REVISIONS (
+ NAME text unique,
+ REVISION integer default 0,
+ CREATEDREVISION integer default 0,
+ WASDELETED text(1) default "N"
+ )
+ """
+ )
+ q.execute(
+ """
+ create index REVISION on REVISIONS (REVISION)
+ """
+ )
+
+ #
# RESERVED table tracks reserved UIDs
# UID: The UID being reserved
# TIME: When the reservation was made
@@ -504,7 +547,7 @@
log.err("Non-addressbook resource: %s" % (name,))
else:
#log.msg("Indexing resource: %s" % (name,))
- self.addResource(name, vcard, True)
+ self.addResource(name, vcard, 0, True)
finally:
stream.close()
@@ -512,8 +555,50 @@
if do_commit:
self._db_commit()
- def _add_to_db(self, name, vcard, cursor = None):
+ # Need sync-token
+ self.resource.initSyncToken()
+
+ def _db_can_upgrade(self, old_version):
"""
+ Can we do an in-place upgrade
+ """
+
+ # v2 is a minor change
+ return True
+
+ def _db_upgrade_data_tables(self, q, old_version):
+ """
+ Upgrade the data from an older version of the DB.
+ """
+
+ # When going to version 2+ all we need to do is add revision table and index
+ if old_version < 2:
+ q.execute(
+ """
+ create table REVISIONS (
+ NAME text unique,
+ REVISION integer default 0,
+ CREATEDREVISION integer default 0,
+ WASDELETED text(1) default "N"
+ )
+ """
+ )
+ q.execute(
+ """
+ create index REVISION on REVISIONS (REVISION)
+ """
+ )
+
+ self._db_execute(
+ """
+ insert into REVISIONS (NAME)
+ select NAME from RESOURCE
+ """
+ )
+
+
+ def _add_to_db(self, name, vcard, revision, cursor = None):
+ """
Records the given address book resource in the index with the given name.
Resource names and UIDs must both be unique; only one resource name may
be associated with any given UID and vice versa.
@@ -531,11 +616,29 @@
values (:1, :2)
""", name, uid,
)
+
+ if revision is not None:
+ created = self._db_value_for_sql("select CREATEDREVISION from REVISIONS where NAME = :1", name)
+ if created is None:
+ created = revision
+ self._db_execute(
+ """
+ insert or replace into REVISIONS (NAME, REVISION, CREATEDREVISION, WASDELETED)
+ values (:1, :2, :3, :4)
+ """, name, revision, revision, 'N',
+ )
- def _delete_from_db(self, name, uid):
+ def _delete_from_db(self, name, uid, revision):
"""
Deletes the specified entry from all dbs.
@param name: the name of the resource to delete.
@param uid: the uid of the resource to delete.
"""
self._db_execute("delete from RESOURCE where NAME = :1", name)
+ if revision is not None:
+ self._db_execute(
+ """
+ update REVISIONS SET REVISION = :1, WASDELETED = :2
+ where NAME = :3
+ """, revision, 'Y', name
+ )
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100409/88a96666/attachment-0001.html>
More information about the calendarserver-changes
mailing list