[CalendarServer-changes] [12143] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:21:18 PDT 2014


Revision: 12143
          http://trac.calendarserver.org//changeset/12143
Author:   gaya at apple.com
Date:     2013-12-18 22:18:02 -0800 (Wed, 18 Dec 2013)
Log Message:
-----------
merge in sharedgroupfixes, sharedgroupfixestester branches

Modified Paths:
--------------
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-one/4.xml
    CalDAVTester/trunk/scripts/tests/CalDAV/sharing-notification-sync.xml
    CalDAVTester/trunk/scripts/tests/CalDAV/sharing-sync.xml
    CalDAVTester/trunk/scripts/tests/CardDAV/sharing-sync.xml
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py
    CalendarServer/trunk/txdav/carddav/datastore/sql.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_sql_sharing.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql

Added Paths:
-----------
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-one/5.xml
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/1.vcf
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/11.xml
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/12.xml
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/14.xml
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/15.xml
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/2.vcf
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/3.vcf
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/4.vcf
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/5.vcf
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/6.vcf
    CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/7.vcf
    CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v29.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v29.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_29_to_30.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_29_to_30.sql

Removed Paths:
-------------
    CalendarServer/trunk/txdav/carddav/datastore/test/addressbook_store/ho/me/home_empty/

Property Changed:
----------------
    CalendarServer/trunk/

Modified: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-one/4.xml
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-one/4.xml	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-one/4.xml	2013-12-19 06:18:02 UTC (rev 12143)
@@ -1,20 +1,8 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<notification xmlns='http://calendarserver.org/ns/'>
-  <dtstamp></dtstamp>
-  <invite-notification shared-type='group'>
-    <uid></uid>
-    <href xmlns='DAV:'>$cuaddrurn2:</href>
-    <invite-noresponse/>
-    <access>
-      <read-write/>
-    </access>
-    <hosturl>
-      <href xmlns='DAV:'>$addressbookpath1:/3.vcf</href>
-    </hosturl>
-    <organizer>
-      <href xmlns='DAV:'>$cuaddrurn1:</href>
-      <common-name>User 01</common-name>
-    </organizer>
-    <summary>My Shared Calendar</summary>
-  </invite-notification>
-</notification>
+<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+    <CS:set>
+        <D:href>$cuaddrurn2:</D:href>
+        <CS:summary>Shared Group</CS:summary>
+        <CS:read-write/>
+    </CS:set>
+</CS:share>

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-one/5.xml
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-one/5.xml	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-one/5.xml	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,13 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<invite-reply xmlns='http://calendarserver.org/ns/'>
+  <href xmlns='DAV:'>$cuaddrurn2:</href>
+  <invite-accepted/>
+  <hosturl>
+    <href xmlns='DAV:'>$addressbookpath1:/3.vcf</href>
+  </hosturl>
+  <in-reply-to>$inviteuid:</in-reply-to>
+  <summary>Shared Group</summary>
+  <common-name>$username2:</common-name>
+  <first-name>$firstname2:</first-name>
+  <last-name>$lastname2:</last-name>
+</invite-reply>

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/1.vcf
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/1.vcf	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/1.vcf	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,11 @@
+BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default;;;
+FN:Default Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson at example.com
+TEL;type=WORK;type=pref:1-555-555-5555
+TEL;type=CELL:1-444-444-4444
+item1.ADR;type=WORK;type=pref:;;1245 Test;Sesame Street;California;11111;USA
+item1.X-ABADR:us
+UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1
+END:VCARD

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/11.xml
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/11.xml	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/11.xml	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+    <CS:set>
+        <D:href>$cuaddrurn2:</D:href>
+        <CS:summary>Shared Group12</CS:summary>
+        <CS:read-write/>
+    </CS:set>
+</CS:share>

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/12.xml
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/12.xml	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/12.xml	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,13 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<invite-reply xmlns='http://calendarserver.org/ns/'>
+  <href xmlns='DAV:'>$cuaddrurn1:</href>
+  <invite-accepted/>
+  <hosturl>
+    <href xmlns='DAV:'>$addressbookpath1:/6.vcf</href>
+  </hosturl>
+  <in-reply-to>$inviteuid:</in-reply-to>
+  <summary>Shared Group12</summary>
+  <common-name>$username2:</common-name>
+  <first-name>$firstname2:</first-name>
+  <last-name>$lastname2:</last-name>
+</invite-reply>

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/14.xml
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/14.xml	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/14.xml	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+    <CS:set>
+        <D:href>$cuaddrurn2:</D:href>
+        <CS:summary>Shared Group45</CS:summary>
+        <CS:read-write/>
+    </CS:set>
+</CS:share>

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/15.xml
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/15.xml	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/15.xml	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,13 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<invite-reply xmlns='http://calendarserver.org/ns/'>
+  <href xmlns='DAV:'>$cuaddrurn1:</href>
+  <invite-accepted/>
+  <hosturl>
+    <href xmlns='DAV:'>$addressbookpath1:/7.vcf</href>
+  </hosturl>
+  <in-reply-to>$inviteuid:</in-reply-to>
+  <summary>Shared Group45</summary>
+  <common-name>$username2:</common-name>
+  <first-name>$firstname2:</first-name>
+  <last-name>$lastname2:</last-name>
+</invite-reply>

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/2.vcf
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/2.vcf	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/2.vcf	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,17 @@
+BEGIN:VCARD
+VERSION:3.0
+N:Contact;Mulberry;;;
+FN:Mulberry Contact
+NICKNAME:mulberry
+ORG:Apple Inc.;
+EMAIL;type=INTERNET;type=WORK;type=pref:mulberry at example.com
+TEL;type=HOME;type=pref:777-777-7777
+TEL;type=WORK:8888888888
+TEL;type=WORK;type=FAX:5555555555
+item1.ADR;type=WORK;type=pref:;;1234 Infinite Circle;Exampletino\, CA 99999;USA;;
+item1.X-ABADR:us
+NOTE:This is a contact created in Mulberry.
+item2.URL;type=pref:http://www.example.com/~magic
+item2.X-ABLabel:_$!<HomePage>!$_
+UID:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E2
+END:VCARD

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/3.vcf
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/3.vcf	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/3.vcf	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,12 @@
+BEGIN:VCARD
+VERSION:3.0
+N:Kawado;Saeko;;;
+FN:Snow Leopard
+ORG:Snow Leopard;
+EMAIL;type=INTERNET;type=WORK;type=pref:snowleopard at example.com
+TEL;type=WORK;type=pref:777-777-7777
+item1.ADR;type=WORK;type=pref:;;1 Fidel Ave. Suite 100;Mountain Top;CA;99999;USA
+item1.X-ABADR:us
+X-ABShowAs:COMPANY
+UID:FCBA0FA3-00B2-4C95-B4EC-4CCC4843F8B1
+END:VCARD

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/4.vcf
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/4.vcf	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/4.vcf	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,12 @@
+BEGIN:VCARD
+VERSION:3.0
+N:Mariotte;WithNote;;;
+FN:WithNote Mariotte
+EMAIL;type=INTERNET;type=WORK;type=pref:withnmariotte at example.com
+TEL;type=WORK;type=pref:1-777-777-7777
+TEL;type=CELL:1-8888888888
+item1.ADR;type=WORK;type=pref:;;1 North Blvd;Cupertino;CA;99999;United States
+item1.X-ABADR:us
+NOTE: Address book server test contact that hsa note field filled in.
+UID:44745975-AE6D-4FB0-80A6-A298427E047A
+END:VCARD

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/5.vcf
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/5.vcf	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/5.vcf	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,18 @@
+BEGIN:VCARD
+VERSION:3.0
+N:InfoIn;All;;;
+FN:All InfoIn
+ORG:allinfo Company;
+EMAIL;type=INTERNET;type=WORK;type=pref:allinfomationin at example.com
+TEL;type=WORK;type=pref:777-777-7777
+TEL;type=CELL:8888888888
+item1.ADR;type=WORK;type=pref:;;1 Gally Street Apt #2;Mountain Top;CA;99999;USA
+item1.X-ABADR:us
+X-YAHOO;type=WORK;type=pref:saeko.where at example.com
+X-YAHOO-ID;type=WORK;type=pref:saeko.where at example.com
+item2.X-ABRELATEDNAMES;type=pref:Mayumi Yan
+item2.X-ABLabel:_$!<Friend>!$_
+item3.X-ABRELATEDNAMES:Shane
+item3.X-ABLabel:_$!<Assistant>!$_
+UID:3765A955-1B96-41EA-994D-335192BEDCCD
+END:VCARD

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/6.vcf
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/6.vcf	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/6.vcf	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,11 @@
+BEGIN:VCARD
+VERSION:3.0
+PRODID:-//Apple Inc.//AddressBook 6.1//EN
+UID:760d9b28-5a13-4880-b7eb-5769e6688fa3
+FN:Group 123
+N:Group123;;;;
+REV:20120503T194243Z
+X-ADDRESSBOOKSERVER-KIND:group
+X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E1
+X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:ED7A5AEC-AB19-4CE0-AD6A-2923A3E5C4E2
+END:VCARD

Added: CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/7.vcf
===================================================================
--- CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/7.vcf	                        (rev 0)
+++ CalDAVTester/trunk/Resource/CardDAV/sharing/sync/group-two/7.vcf	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,11 @@
+BEGIN:VCARD
+VERSION:3.0
+PRODID:-//Apple Inc.//AddressBook 6.1//EN
+UID:760d9b28-5a13-4880-b7eb-5769e6688fa4
+FN:Group 345
+N:Group345;;;;
+REV:20120503T194243Z
+X-ADDRESSBOOKSERVER-KIND:group
+X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:3765A955-1B96-41EA-994D-335192BEDCCD
+X-ADDRESSBOOKSERVER-MEMBER:urn:uuid:44745975-AE6D-4FB0-80A6-A298427E047A
+END:VCARD

Modified: CalDAVTester/trunk/scripts/tests/CalDAV/sharing-notification-sync.xml
===================================================================
--- CalDAVTester/trunk/scripts/tests/CalDAV/sharing-notification-sync.xml	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalDAVTester/trunk/scripts/tests/CalDAV/sharing-notification-sync.xml	2013-12-19 06:18:02 UTC (rev 12143)
@@ -419,8 +419,34 @@
 					<name>{DAV:}href</name>
 					<variable>$sharedcalendar:</variable>
 				</grabelement>
+				<grabelement>
+					<name>{DAV:}href</name>
+					<variable>basename($sharedcalendarbasename:)</variable>
+				</grabelement>
 			</request>
 		</test>
+		<test name='6-' ignore='no'>
+			<description>bad sync token</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$calendarhome2:/</ruri>
+				<header>
+					<name>Depth</name>
+					<value>infinity</value>
+				</header>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/CalDAV/sharing/notification-sync/4.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
 		<test name='6' ignore='no'>
 			<description>new calendar</description>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
@@ -432,18 +458,46 @@
 				</header>
 				<data substitutions='yes'>
 					<content-type>text/xml; charset=utf-8</content-type>
-					<filepath>Resource/CalDAV/sharing/notification-sync/4.xml</filepath>
+					<filepath>Resource/CalDAV/sharing/notification-sync/2.xml</filepath>
 				</data>
 				<verify>
+					<exclude-feature>
+						<feature>split-calendars</feature>
+					</exclude-feature>
 					<callback>multistatusItems</callback>
 					<arg>
-						<name>prefix</name>
-						<value/>
+						<name>okhrefs</name>
+						<value>$sharedcalendarbasename:/</value>
+						<value>$calendar:/</value>
+						<value>$inbox:/</value>
+						<value>$outbox:/</value>
+						<value>$freebusy:</value>
+						<value>$notification:/</value>
 					</arg>
 					<arg>
+						<name>badhrefs</name>
+						<value>$dropbox:/</value>
+					</arg>
+				</verify>
+				<verify>
+					<require-feature>
+						<feature>split-calendars</feature>
+					</require-feature>
+					<callback>multistatusItems</callback>
+					<arg>
 						<name>okhrefs</name>
-						<value>$sharedcalendar:/</value>
+						<value>$sharedcalendarbasename:/</value>
+						<value>$calendar:/</value>
+						<value>$tasks:/</value>
+						<value>$inbox:/</value>
+						<value>$outbox:/</value>
+						<value>$freebusy:</value>
+						<value>$notification:/</value>
 					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>$dropbox:/</value>
+					</arg>
 				</verify>
 				<grabelement>
 					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
@@ -615,8 +669,34 @@
 					<name>{DAV:}href</name>
 					<variable>$sharedcalendar:</variable>
 				</grabelement>
+				<grabelement>
+					<name>{DAV:}href</name>
+					<variable>basename($sharedcalendarbasename:)</variable>
+				</grabelement>
 			</request>
 		</test>
+		<test name='6-' ignore='no'>
+			<description>bad sync token</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$calendarhome2:/</ruri>
+				<header>
+					<name>Depth</name>
+					<value>infinity</value>
+				</header>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/CalDAV/sharing/notification-sync/4.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
 		<test name='6' ignore='no'>
 			<description>new calendar</description>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
@@ -628,18 +708,46 @@
 				</header>
 				<data substitutions='yes'>
 					<content-type>text/xml; charset=utf-8</content-type>
-					<filepath>Resource/CalDAV/sharing/notification-sync/4.xml</filepath>
+					<filepath>Resource/CalDAV/sharing/notification-sync/2.xml</filepath>
 				</data>
 				<verify>
+					<exclude-feature>
+						<feature>split-calendars</feature>
+					</exclude-feature>
 					<callback>multistatusItems</callback>
 					<arg>
-						<name>prefix</name>
-						<value/>
+						<name>okhrefs</name>
+						<value>$sharedcalendarbasename:/</value>
+						<value>$calendar:/</value>
+						<value>$inbox:/</value>
+						<value>$outbox:/</value>
+						<value>$freebusy:</value>
+						<value>$notification:/</value>
 					</arg>
 					<arg>
+						<name>badhrefs</name>
+						<value>$dropbox:/</value>
+					</arg>
+				</verify>
+				<verify>
+					<require-feature>
+						<feature>split-calendars</feature>
+					</require-feature>
+					<callback>multistatusItems</callback>
+					<arg>
 						<name>okhrefs</name>
-						<value>$sharedcalendar:/</value>
+						<value>$sharedcalendarbasename:/</value>
+						<value>$calendar:/</value>
+						<value>$tasks:/</value>
+						<value>$inbox:/</value>
+						<value>$outbox:/</value>
+						<value>$freebusy:</value>
+						<value>$notification:/</value>
 					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>$dropbox:/</value>
+					</arg>
 				</verify>
 				<grabelement>
 					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
@@ -849,8 +957,34 @@
 					<name>{DAV:}href</name>
 					<variable>$sharedcalendar:</variable>
 				</grabelement>
+				<grabelement>
+					<name>{DAV:}href</name>
+					<variable>basename($sharedcalendarbasename:)</variable>
+				</grabelement>
 			</request>
 		</test>
+		<test name='6-' ignore='no'>
+			<description>bad sync token</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$calendarhome2:/</ruri>
+				<header>
+					<name>Depth</name>
+					<value>infinity</value>
+				</header>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/CalDAV/sharing/notification-sync/4.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
 		<test name='6' ignore='no'>
 			<description>new calendar</description>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
@@ -862,19 +996,48 @@
 				</header>
 				<data substitutions='yes'>
 					<content-type>text/xml; charset=utf-8</content-type>
-					<filepath>Resource/CalDAV/sharing/notification-sync/4.xml</filepath>
+					<filepath>Resource/CalDAV/sharing/notification-sync/2.xml</filepath>
 				</data>
 				<verify>
+					<exclude-feature>
+						<feature>split-calendars</feature>
+					</exclude-feature>
 					<callback>multistatusItems</callback>
 					<arg>
-						<name>prefix</name>
-						<value/>
+						<name>okhrefs</name>
+						<value>$sharedcalendarbasename:/</value>
+						<value>$sharedcalendarbasename:/1.ics</value>
+						<value>$calendar:/</value>
+						<value>$inbox:/</value>
+						<value>$outbox:/</value>
+						<value>$freebusy:</value>
+						<value>$notification:/</value>
 					</arg>
 					<arg>
+						<name>badhrefs</name>
+						<value>$dropbox:/</value>
+					</arg>
+				</verify>
+				<verify>
+					<require-feature>
+						<feature>split-calendars</feature>
+					</require-feature>
+					<callback>multistatusItems</callback>
+					<arg>
 						<name>okhrefs</name>
-						<value>$sharedcalendar:/</value>
-						<value>$sharedcalendar:/1.ics</value>
+						<value>$sharedcalendarbasename:/</value>
+						<value>$sharedcalendarbasename:/1.ics</value>
+						<value>$calendar:/</value>
+						<value>$tasks:/</value>
+						<value>$inbox:/</value>
+						<value>$outbox:/</value>
+						<value>$freebusy:</value>
+						<value>$notification:/</value>
 					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>$dropbox:/</value>
+					</arg>
 				</verify>
 				<grabelement>
 					<name>/{DAV:}multistatus/{DAV:}sync-token</name>

Modified: CalDAVTester/trunk/scripts/tests/CalDAV/sharing-sync.xml
===================================================================
--- CalDAVTester/trunk/scripts/tests/CalDAV/sharing-sync.xml	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalDAVTester/trunk/scripts/tests/CalDAV/sharing-sync.xml	2013-12-19 06:18:02 UTC (rev 12143)
@@ -292,6 +292,21 @@
 					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
 				</data>
 				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$calendarhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
 					<callback>multistatusItems</callback>
 					<arg>
 						<name>ignoremissing</name>

Modified: CalDAVTester/trunk/scripts/tests/CardDAV/sharing-sync.xml
===================================================================
--- CalDAVTester/trunk/scripts/tests/CardDAV/sharing-sync.xml	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalDAVTester/trunk/scripts/tests/CardDAV/sharing-sync.xml	2013-12-19 06:18:02 UTC (rev 12143)
@@ -37,8 +37,9 @@
 		</request>
 	</start>
 	
-	<test-suite name='Read-write addressbook' ignore='yes'>
-		<test name='1' ignore='no'>
+	<!-- test suite below is similar to test suite in ../CalDAV/sharing-sync.xml -->
+	<test-suite name='shared addressbook' ignore='no'>
+		<test name='1a' ignore='no'>
 			<description>Initial sync tokens</description>
 			<request print-response='no'>
 				<method>REPORT</method>
@@ -55,6 +56,8 @@
 					<variable>$synctoken1:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='1b' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome1:/</ruri>
@@ -70,6 +73,8 @@
 					<variable>$synctoken2:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='1c' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookpath1:/</ruri>
@@ -85,6 +90,8 @@
 					<variable>$synctoken3:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='1d' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -100,6 +107,8 @@
 					<variable>$synctoken4:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='1e' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -184,7 +193,7 @@
 				<ruri>$notificationpath2:/</ruri>
 			</request>
 		</test>
-		<test name='5' ignore='no'>
+		<test name='5a' ignore='no'>
 			<description>Updated tokens</description>
 			<request print-response='no'>
 				<method>REPORT</method>
@@ -205,6 +214,8 @@
 					<variable>$synctoken1:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='5b' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome1:/</ruri>
@@ -224,6 +235,8 @@
 					<variable>$synctoken2:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='5c' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookpath1:/</ruri>
@@ -242,6 +255,8 @@
 					<variable>$synctoken3:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='5d' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -261,17 +276,37 @@
 					<variable>$synctoken4:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='5e-' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
-				<ruri>$addressbookhome2:/</ruri>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
 				<data substitutions='yes'>
 					<content-type>text/xml; charset=utf-8</content-type>
 					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
 				</data>
 				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+		<test name='5e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
 					<callback>multistatusItems</callback>
 					<arg>
 						<name>okhrefs</name>
+						<value>$addressbook:/</value>
 						<value>$sharedaddressbook:/</value>
 					</arg>
 				</verify>
@@ -280,8 +315,27 @@
 					<variable>$synctoken5:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='5f-' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+		<test name='5f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
 				<ruri>$addressbookhome2:/$sharedaddressbook:/</ruri>
 				<data substitutions='yes'>
 					<content-type>text/xml; charset=utf-8</content-type>
@@ -310,7 +364,7 @@
 				</verify>
 			</request>
 		</test>
-		<test name='7' ignore='no'>
+		<test name='7a' ignore='no'>
 			<description>Updated tokens</description>
 			<request print-response='no'>
 				<method>REPORT</method>
@@ -331,6 +385,8 @@
 					<variable>$synctoken1:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='7b' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome1:/</ruri>
@@ -351,6 +407,8 @@
 					<variable>$synctoken2:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='7c' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookpath1:/</ruri>
@@ -370,6 +428,8 @@
 					<variable>$synctoken3:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='7d' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -389,6 +449,8 @@
 					<variable>$synctoken4:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='7e' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -409,6 +471,8 @@
 					<variable>$synctoken5:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='7f' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/$sharedaddressbook:/</ruri>
@@ -443,7 +507,7 @@
 				</verify>
 			</request>
 		</test>
-		<test name='9' ignore='no'>
+		<test name='9a' ignore='no'>
 			<description>Updated tokens</description>
 			<request print-response='no'>
 				<method>REPORT</method>
@@ -464,6 +528,8 @@
 					<variable>$synctoken1:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='9b' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome1:/</ruri>
@@ -484,6 +550,8 @@
 					<variable>$synctoken2:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='9c' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookpath1:/</ruri>
@@ -503,6 +571,8 @@
 					<variable>$synctoken3:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='9d' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -522,6 +592,8 @@
 					<variable>$synctoken4:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='9e' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -542,6 +614,8 @@
 					<variable>$synctoken5:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='9f' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/$sharedaddressbook:/</ruri>
@@ -572,7 +646,7 @@
 				</verify>
 			</request>
 		</test>
-		<test name='11' ignore='no'>
+		<test name='11a' ignore='no'>
 			<description>Updated tokens</description>
 			<request print-response='no'>
 				<method>REPORT</method>
@@ -593,6 +667,8 @@
 					<variable>$synctoken1:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='11b' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome1:/</ruri>
@@ -616,6 +692,8 @@
 					<variable>$synctoken2:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='11c' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookpath1:/</ruri>
@@ -635,6 +713,8 @@
 					<variable>$synctoken3:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='11d' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -654,6 +734,8 @@
 					<variable>$synctoken4:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='11e' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -677,6 +759,8 @@
 					<variable>$synctoken5:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='11f' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/$sharedaddressbook:/</ruri>
@@ -711,7 +795,7 @@
 				</verify>
 			</request>
 		</test>
-		<test name='13' ignore='no'>
+		<test name='13a' ignore='no'>
 			<description>Updated tokens</description>
 			<request print-response='no'>
 				<method>REPORT</method>
@@ -732,6 +816,8 @@
 					<variable>$synctoken1:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='13b' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome1:/</ruri>
@@ -751,6 +837,8 @@
 					<variable>$synctoken2:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='13c' ignore='no'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookpath1:/</ruri>
@@ -766,6 +854,8 @@
 					<variable>$synctoken3:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='13d' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -785,6 +875,8 @@
 					<variable>$synctoken4:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='13e' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/</ruri>
@@ -804,6 +896,8 @@
 					<variable>$synctoken5:</variable>
 				</grabelement>
 			</request>
+		</test>
+		<test name='13f' ignore='no'>
 			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome2:/$sharedaddressbook:/</ruri>
@@ -822,6 +916,2676 @@
 		</test>
 	</test-suite>
 
+	<!-- test suite below is similar to test suite in ../CalDAV/sharing-sync.xml -->
+	<test-suite name='one shared group' ignore='no'>
+		<test name='0' ignore='no'>
+			<description>clean up old data</description>
+			<request user="$useradmin:" pswd="$pswdadmin:">
+				<method>DELETEALL</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<ruri>$addressbookhome2:/</ruri>
+				<ruri>$notificationpath1:/</ruri>
+				<ruri>$notificationpath2:/</ruri>
+			</request>
+			<description>create empty group</description>
+			<request>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/3.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-one/3.vcf</filepath>
+				</data>
+			</request>
+		</test>
+		<test name='1a' ignore='no'>
+			<description>Initial sync tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='1b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='1c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='1d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='1e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='2' ignore='no'>
+			<description>user1 POSTs invitation</description>
+			<request print-response="no">
+				<method>POST</method>
+				<ruri>$addressbookpath1:/3.vcf</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-one/4.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='3'>
+			<description>Check user2 notification collection and get invite uid</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>WAITCOUNT 1</method>
+				<ruri>$notificationpath2:/</ruri>
+			</request>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>GETNEW</method>
+				<ruri>$notificationpath2:/</ruri>
+				<grabelement>
+					<name>{http://calendarserver.org/ns/}invite-notification/{http://calendarserver.org/ns/}uid</name>
+					<variable>$inviteuid:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='4'>
+			<description>user2 replies ACCEPTED and deletes notification</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>POST</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions="yes">
+					<content-type>application/xml; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-one/5.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>DELETE</method>
+				<ruri>$notificationpath2:/$inviteuid:</ruri>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='5a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5e-' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+		<test name='5e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5f-' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+		<test name='5f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='6'>
+			<description>Sharee creates vcard</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>PUT</method>
+				<ruri>$addressbookhome2:/$userguid1:/1.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-one/1.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='7a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='7b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$addressbook:/1.vcf</value>
+						<value>$addressbook:/3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='7c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>1.vcf</value>
+						<value>3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='7d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='7e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/1.vcf</value>
+						<value>$userguid1:/3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='7f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>1.vcf</value>
+						<value>3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='8'>
+			<description>Sharer changes vcard</description>
+			<request print-response='no'>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/1.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-one/1.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='9a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$addressbook:/1.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>1.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/1.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>1.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='10'>
+			<description>Sharer adds unshared vCard</description>
+			<request print-response='no'>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/2.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-one/2.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='11a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$addressbook:/2.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>2.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='12'>
+			<description>Sharer changes unshared vCard</description>
+			<request print-response='no'>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/2.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-one/2.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='13a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$addressbook:/2.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>2.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='14'>
+			<description>Sharer changes unshared vCard</description>
+			<request print-response='no'>
+				<method>DELETE</method>
+				<ruri>$addressbookpath1:/2.vcf</ruri>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='15a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='15b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>$addressbook:/2.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='15c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>badhrefs</name>
+						<value>2.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='15d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='15e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='15f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='16'>
+			<description>Sharee deletes vcard</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>DELETE</method>
+				<ruri>$addressbookhome2:/$userguid1:/1.vcf</ruri>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='17a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='17b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$addressbook:/3.vcf</value>
+					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>$addressbook:/1.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='17c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>3.vcf</value>
+					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>1.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='17d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='17e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/3.vcf</value>
+					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>$userguid1:/1.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='17f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>3.vcf</value>
+					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>1.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='18'>
+			<description>Sharer unshares</description>
+			<request print-response='no'>
+				<method>POST</method>
+				<ruri>$addressbookpath1:/3.vcf</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/POST/sharingremove2.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='19a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='19b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='19c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='19d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>badhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='19e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>badhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='19f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>404</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+	</test-suite>
+	
+	<!-- test suite below is similar to test suite in ../CalDAV/sharing-sync.xml -->
+	<test-suite name='two shared groups' ignore='no'>
+		<test name='0' ignore='no'>
+			<description>clean up old data</description>
+			<request user="$useradmin:" pswd="$pswdadmin:">
+				<method>DELETEALL</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<ruri>$addressbookhome2:/</ruri>
+				<ruri>$notificationpath1:/</ruri>
+				<ruri>$notificationpath2:/</ruri>
+			</request>
+			<description>create users groups</description>
+			<request>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/1.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/1.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/2.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/2.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/4.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/4.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/5.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/5.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/6.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/6.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/7.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/7.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='1a' ignore='no'>
+			<description>Initial sync tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='1b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='1c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='1d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='1e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='2' ignore='no'>
+			<description>user1 POSTs invitation</description>
+			<request print-response="no">
+				<method>POST</method>
+				<ruri>$addressbookpath1:/6.vcf</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/11.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='3'>
+			<description>Check user2 notification collection and get invite uid</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>WAITCOUNT 1</method>
+				<ruri>$notificationpath2:/</ruri>
+			</request>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>GETNEW</method>
+				<ruri>$notificationpath2:/</ruri>
+				<grabelement>
+					<name>{http://calendarserver.org/ns/}invite-notification/{http://calendarserver.org/ns/}uid</name>
+					<variable>$inviteuid:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='4'>
+			<description>user2 replies ACCEPTED and deletes notification</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>POST</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions="yes">
+					<content-type>application/xml; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/12.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>DELETE</method>
+				<ruri>$notificationpath2:/$inviteuid:</ruri>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='5a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5e-' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+		<test name='5e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/1.vcf</value>
+						<value>$userguid1:/2.vcf</value>
+						<value>$userguid1:/6.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='5f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>1.vcf</value>
+						<value>2.vcf</value>
+						<value>6.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='6' ignore='no'>
+			<description>user1 POSTs invitation</description>
+			<request print-response="no">
+				<method>POST</method>
+				<ruri>$addressbookpath1:/7.vcf</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/14.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='7'>
+			<description>Check user2 notification collection and get invite uid</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>WAITCOUNT 1</method>
+				<ruri>$notificationpath2:/</ruri>
+			</request>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>GETNEW</method>
+				<ruri>$notificationpath2:/</ruri>
+				<grabelement>
+					<name>{http://calendarserver.org/ns/}invite-notification/{http://calendarserver.org/ns/}uid</name>
+					<variable>$inviteuid:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='8'>
+			<description>user2 replies ACCEPTED and deletes notification</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>POST</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions="yes">
+					<content-type>application/xml; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/15.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>DELETE</method>
+				<ruri>$notificationpath2:/$inviteuid:</ruri>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='9a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9e-' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+		<test name='9e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/1.vcf</value>
+						<value>$userguid1:/2.vcf</value>
+						<value>$userguid1:/4.vcf</value>
+						<value>$userguid1:/5.vcf</value>
+						<value>$userguid1:/6.vcf</value>
+						<value>$userguid1:/7.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='9f-' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+		<test name='9f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>1.vcf</value>
+						<value>2.vcf</value>
+						<value>4.vcf</value>
+						<value>5.vcf</value>
+						<value>6.vcf</value>
+						<value>7.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+
+		<test name='10'>
+			<description>Sharee creates vcard</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>PUT</method>
+				<ruri>$addressbookhome2:/$userguid1:/3.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/3.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='11a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$addressbook:/3.vcf</value>
+						<value>$addressbook:/6.vcf</value>
+						<value>$addressbook:/7.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>3.vcf</value>
+						<value>6.vcf</value>
+						<value>7.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/3.vcf</value>
+						<value>$userguid1:/6.vcf</value>
+						<value>$userguid1:/7.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='11f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>3.vcf</value>
+						<value>6.vcf</value>
+						<value>7.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='12'>
+			<description>Sharer changes vcard</description>
+			<request print-response='no'>
+				<method>PUT</method>
+				<ruri>$addressbookpath1:/3.vcf</ruri>
+				<data>
+					<content-type>text/vcard; charset=utf-8</content-type>
+					<filepath>Resource/CardDAV/sharing/sync/group-two/3.vcf</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='13a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$addressbook:/3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='13'>
+			<description>Sharee deletes vcard</description>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>DELETE</method>
+				<ruri>$addressbookhome2:/$userguid1:/3.vcf</ruri>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='14a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='14b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$addressbook:/6.vcf</value>
+						<value>$addressbook:/7.vcf</value>
+					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>$addressbook:/3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='14c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>6.vcf</value>
+						<value>7.vcf</value>
+					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='14d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='14e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/6.vcf</value>
+						<value>$userguid1:/7.vcf</value>
+					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>$userguid1:/3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='14f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>6.vcf</value>
+						<value>7.vcf</value>
+					</arg>
+					<arg>
+						<name>badhrefs</name>
+						<value>3.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='15'>
+			<description>Sharer unshares first group</description>
+			<request print-response='no'>
+				<method>POST</method>
+				<ruri>$addressbookpath1:/6.vcf</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/POST/sharingremove2.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='16a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='16b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='16c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='16d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='16e-' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+		<test name='16e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+						<value>$userguid1:/</value>
+						<value>$userguid1:/4.vcf</value>
+						<value>$userguid1:/5.vcf</value>
+						<value>$userguid1:/7.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='16f-' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>403</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+		<test name='16f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-init-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>4.vcf</value>
+						<value>5.vcf</value>
+						<value>7.vcf</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken6:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='17'>
+			<description>Sharer unshares 2nd group</description>
+			<request print-response='no'>
+				<method>POST</method>
+				<ruri>$addressbookpath1:/7.vcf</ruri>
+				<data>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/POST/sharingremove2.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+		</test>
+		<test name='18a' ignore='no'>
+			<description>Updated tokens</description>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token1-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken1:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='18b' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token2-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>okhrefs</name>
+						<value>$addressbook:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken2:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='18c' ignore='no'>
+			<request print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookpath1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token3-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken3:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='18d' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token4-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>badhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken4:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='18e' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token5-level-infinite.xml</filepath>
+				</data>
+				<verify>
+					<callback>multistatusItems</callback>
+					<arg>
+						<name>badhrefs</name>
+						<value>$userguid1:/</value>
+					</arg>
+				</verify>
+				<grabelement>
+					<name>/{DAV:}multistatus/{DAV:}sync-token</name>
+					<variable>$synctoken5:</variable>
+				</grabelement>
+			</request>
+		</test>
+		<test name='18f' ignore='no'>
+			<request user="$userid2:" pswd="$pswd2:" print-response='no'>
+				<method>REPORT</method>
+				<ruri>$addressbookhome2:/$userguid1:/</ruri>
+				<data substitutions='yes'>
+					<content-type>text/xml; charset=utf-8</content-type>
+					<filepath>Resource/Common/REPORT/sync-token6-level-1.xml</filepath>
+				</data>
+				<verify>
+					<callback>statusCode</callback>
+					<arg>
+						<name>status</name>
+						<value>404</value>
+					</arg>
+				</verify>
+			</request>
+		</test>
+	</test-suite>
+
+	<!-- test suites below are similar to those in ../CalDAV/sync-report.xml but 
+			use shared address books instead of calendars -->
+	
 	<test-suite name='support-report-set/sync-token property' ignore='no'>
 		<test name='0' ignore='no'>
 			<!-- clean up old data -->
@@ -2916,7 +5680,7 @@
 		</test>
 	</test-suite>
 
-	<test-suite name='simple reports - diff token - delete/create addressbook - home depth:infinity' ignore='no'>
+	<test-suite name='simple reports - diff token - delete/create addressbook - home depth:infinity' ignore='yes'>
 		<require-feature>
 			<feature>sync-report-home</feature>
 		</require-feature>
@@ -2956,6 +5720,20 @@
 			<description>remove resource then addressbook</description>
 			<request user="$userid3:" pswd="$pswd3:" print-response='no'>
 				<method>DELETE</method>
+				<ruri>$addressbookpath3:/2.vcf</ruri>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request user="$userid3:" pswd="$pswd3:" print-response='no'>
+				<method>DELETE</method>
+				<ruri>$addressbookpath3:/1.vcf</ruri>
+				<verify>
+					<callback>statusCode</callback>
+				</verify>
+			</request>
+			<request user="$userid3:" pswd="$pswd3:" print-response='no'>
+				<method>DELETE</method>
 				<ruri>$addressbookpath3:/</ruri>
 				<verify>
 					<callback>statusCode</callback>
@@ -3037,7 +5815,7 @@
 				<ruri>$addressbookhome1:/</ruri>
 				<header>
 					<name>Depth</name>
-					<value>infinity</value>
+					<value>1</value>
 				</header>
 				<data>
 					<content-type>text/xml; charset=utf-8</content-type>
@@ -3059,7 +5837,7 @@
 				<ruri>$addressbookhome1:/</ruri>
 				<header>
 					<name>Depth</name>
-					<value>infinity</value>
+					<value>1</value>
 				</header>
 				<data>
 					<content-type>text/xml; charset=utf-8</content-type>
@@ -3693,7 +6471,7 @@
 				</grabelement>
 			</request>
 		</test>
-		<test name='3' ignore='no'>
+		<test name='-3' ignore='no'>
 			<description>add addressbook - test last sync</description>
 			<!-- share user3 address book with user1 -->
 			<!-- user3 POSTs invitation -->
@@ -3733,6 +6511,8 @@
 				<method>DELETE</method>
 				<ruri>$notificationpath1:/$inviteuid:</ruri>
 			</request>
+		</test>
+		<test name='3' ignore='yes'>
 			<request print-response='no'>
 				<method>REPORT</method>
 				<ruri>$addressbookhome1:/</ruri>
@@ -3753,7 +6533,7 @@
 				</verify>
 			</request>
 		</test>
-		<test name='4' ignore='no'>
+		<test name='4' ignore='yes'>
 			<description>add addressbook - test previous sync</description>
 			<request print-response='no'>
 				<method>REPORT</method>
@@ -3777,30 +6557,6 @@
 		</test>
 	</test-suite>
 
-	<test-suite name='simple reports - empty inbox' ignore='yes'>
-		<test name='1' ignore='no'>
-			<description>initial query</description>
-			<request print-response='no'>
-				<method>REPORT</method>
-				<ruri>$inboxpath1:/</ruri>
-				<header>
-					<name>Depth</name>
-					<value>1</value>
-				</header>
-				<data>
-					<content-type>text/xml; charset=utf-8</content-type>
-					<filepath>Resource/CardDAV/sharing/sync/addressbook/22.xml</filepath>
-				</data>
-				<verify>
-					<callback>multistatusItems</callback>
-					<arg>
-						<name>okhrefs</name>
-					</arg>
-				</verify>
-			</request>
-		</test>
-	</test-suite>
-
 	<test-suite name='simple reports - valid token' ignore='no'>
 		<test name='1' ignore='no'>
 			<description>initial query</description>


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalDAVTester/trunk:11193-11198
/CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/json:11622-11912
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/reverse-proxy-pods:11875-11900
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/sharing-in-the-store:11935-12016
/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
/CalendarServer/branches/users/glyph/hang-fix:11465-11491
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593
   + /CalDAVTester/trunk:11193-11198
/CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/json:11622-11912
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/reverse-proxy-pods:11875-11900
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/sharing-in-the-store:11935-12016
/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/gaya/sharedgroupfixes:12120-12142
/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
/CalendarServer/branches/users/glyph/hang-fix:11465-11491
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2013-12-19 06:18:02 UTC (rev 12143)
@@ -2785,7 +2785,7 @@
         content_type = request.headers.getHeader("content-type")
         format = self.determineType(content_type)
         if format is None:
-            log.error("MIME type %s not allowed in calendar collection" % (content_type,))
+            log.error("MIME type {content_type} not allowed in calendar collection", content_type=content_type)
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
                 (caldav_namespace, "supported-calendar-data"),
@@ -3395,7 +3395,7 @@
         content_type = request.headers.getHeader("content-type")
         format = self.determineType(content_type)
         if format is None:
-            log.error("MIME type %s not allowed in vcard collection" % (content_type,))
+            log.error("MIME type {content_type} not allowed in vcard collection", content_type=content_type)
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
                 (carddav_namespace, "supported-address-data"),

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py	2013-12-19 06:18:02 UTC (rev 12143)
@@ -554,7 +554,7 @@
         otherCal = yield self.calendarUnderTest(home="user02", name=sharedName)
         self.assertNotEqual(otherCal._bindRevision, 0)
 
-        changed, deleted = yield otherCal.resourceNamesSinceRevision(otherCal._bindRevision - 1)
+        changed, deleted = yield otherCal.resourceNamesSinceRevision(0)
         self.assertNotEqual(len(changed), 0)
         self.assertEqual(len(deleted), 0)
 
@@ -563,7 +563,7 @@
         self.assertEqual(len(deleted), 0)
 
         for depth in ("1", "infinity",):
-            changed, deleted = yield otherHome.resourceNamesSinceRevision(otherCal._bindRevision - 1, depth)
+            changed, deleted = yield otherHome.resourceNamesSinceRevision(0, depth)
             self.assertNotEqual(len(changed), 0)
             self.assertEqual(len(deleted), 0)
 

Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py	2013-12-19 06:18:02 UTC (rev 12143)
@@ -56,12 +56,12 @@
 from txdav.common.datastore.sql_tables import _ABO_KIND_PERSON, \
     _ABO_KIND_GROUP, _ABO_KIND_RESOURCE, _ABO_KIND_LOCATION, schema, \
     _BIND_MODE_OWN, _BIND_MODE_WRITE, _BIND_STATUS_ACCEPTED, \
-    _BIND_STATUS_INVITED, _BIND_MODE_READ, _BIND_MODE_INDIRECT, \
-    _BIND_STATUS_DECLINED
+    _BIND_STATUS_INVITED, _BIND_MODE_INDIRECT, _BIND_STATUS_DECLINED
 from txdav.common.icommondatastore import InternalDataStoreError, \
     InvalidUIDError, UIDExistsError, ObjectResourceTooBigError, \
     InvalidObjectResourceError, InvalidComponentForStoreError, \
-    AllRetriesFailed, ObjectResourceNameAlreadyExistsError
+    AllRetriesFailed, ObjectResourceNameAlreadyExistsError, \
+    SyncTokenValidException
 
 from zope.interface.declarations import implements
 
@@ -266,6 +266,8 @@
         """
         rev = cls._revisionsSchema
         bind = cls._bindSchema
+        abo = cls._objectSchema
+        groupBind = AddressBookObject._bindSchema
         return Select(
             [Max(rev.REVISION)],
             # active shared address books
@@ -278,6 +280,22 @@
                             [bind.RESOURCE_ID],
                             From=bind,
                             Where=bind.HOME_RESOURCE_ID == Parameter("resourceID"),
+                            SetExpression=Union(
+                                Select(
+                                    [abo.ADDRESSBOOK_HOME_RESOURCE_ID],
+                                    From=abo,
+                                    Where=(
+                                        abo.RESOURCE_ID.In(
+                                            Select(
+                                                [groupBind.GROUP_RESOURCE_ID],
+                                                From=groupBind,
+                                                Where=groupBind.ADDRESSBOOK_HOME_RESOURCE_ID == Parameter("resourceID"),
+                                            )
+                                        )
+                                    )
+                                ),
+                                optype=Union.OPTYPE_ALL,
+                            )
                         )
                     )
                 ),
@@ -288,7 +306,7 @@
                         From=rev,
                         Where=(rev.HOME_RESOURCE_ID == Parameter("resourceID")).And(rev.RESOURCE_ID == None),
                         SetExpression=Union(
-                            # owned address book: owned address book cannot be deleted: See AddressBook.remove()
+                            # owned address book
                             Select(
                                 [rev.REVISION],
                                 From=rev,
@@ -307,7 +325,7 @@
     def _changesQuery(cls): #@NoSelf
         rev = cls._revisionsSchema
         return Select(
-            [rev.COLLECTION_NAME,
+            [rev.ADDRESSBOOK_NAME,
              rev.RESOURCE_NAME,
              rev.DELETED],
             From=rev,
@@ -319,9 +337,11 @@
     @inlineCallbacks
     def doChangesQuery(self, revision):
 
-        rows = yield self._changesQuery.on(self._txn,
-                                         resourceID=self._resourceID,
-                                         revision=revision)
+        rows = yield self._changesQuery.on(
+            self._txn,
+            resourceID=self._resourceID,
+            revision=revision
+        )
 
         # If the collection name is None that means we have a change to the owner's default address book,
         # so substitute in the name of that. If collection name is not None, then we have a revision
@@ -441,7 +461,10 @@
 
     def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None):
         ownerName = ownerHome.addressbook().name() if ownerHome else None
-        super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName)
+        super(AddressBook, self).__init__(
+            home, name, resourceID, mode, status, revision=revision,
+            message=message, ownerHome=ownerHome, ownerName=ownerName
+        )
         self._index = PostgresLegacyABIndexEmulator(self)
 
 
@@ -470,7 +493,221 @@
     _modified = property(getModified, setModified,)
 
 
+    @classproperty
+    def _deleteBumpTokenQuery(cls): #@NoSelf
+        rev = cls._revisionsSchema
+        return Update({rev.REVISION: schema.REVISION_SEQ,
+                       rev.OBJECT_RESOURCE_ID: Parameter("id"),
+                       rev.DELETED: True},
+                      Where=(rev.RESOURCE_ID == Parameter("resourceID")).And(
+                           rev.RESOURCE_NAME == Parameter("name")),
+                      Return=rev.REVISION)
+
+
     @inlineCallbacks
+    def _changeRevision(self, action, name, id=0):
+
+        # Need to handle the case where for some reason the revision entry is
+        # actually missing. For a "delete" we don't care, for an "update" we
+        # will turn it into an "insert".
+        if action == "delete":
+            rows = (
+                yield self._deleteBumpTokenQuery.on(
+                    self._txn, resourceID=self._resourceID, name=name, id=id))
+            if rows:
+                self._syncTokenRevision = rows[0][0]
+        elif action == "update":
+            rows = (
+                yield self._updateBumpTokenQuery.on(
+                    self._txn, resourceID=self._resourceID, name=name))
+            if rows:
+                self._syncTokenRevision = rows[0][0]
+            else:
+                action = "insert"
+
+        if action == "insert":
+            # Note that an "insert" may happen for a resource that previously
+            # existed and then was deleted. In that case an entry in the
+            # REVISIONS table still exists so we have to detect that and do db
+            # INSERT or UPDATE as appropriate
+
+            found = bool((
+                yield self._insertFindPreviouslyNamedQuery.on(
+                    self._txn, resourceID=self._resourceID, name=name)))
+            if found:
+                self._syncTokenRevision = (
+                    yield self._updatePreviouslyNamedQuery.on(
+                        self._txn, resourceID=self._resourceID, name=name)
+                )[0][0]
+            else:
+                self._syncTokenRevision = (
+                    yield self._completelyNewRevisionQuery.on(
+                        self._txn, homeID=self.ownerHome()._resourceID,
+                        resourceID=self._resourceID, name=name)
+                )[0][0]
+        self._maybeNotify()
+        returnValue(self._syncTokenRevision)
+
+
+    def _deleteRevision(self, name, id=0):
+        return self._changeRevision("delete", name, id)
+
+
+    @inlineCallbacks
+    def resourceNamesSinceRevision(self, revision):
+        """
+        Return the changed and deleted resources since a particular revision. This implementation takes
+        into account sharing by making use of the bindRevision attribute to determine if the requested
+        revision is earlier than the share acceptance. If so, then we need to return all resources in
+        the results since the collection is in effect "new".
+
+        @param revision: the revision to determine changes since
+        @type revision: C{int}
+        """
+        if self.owned():
+            returnValue((yield super(AddressBook, self).resourceNamesSinceRevision(revision)))
+
+        # call sharedChildResourceNamesSinceRevision() and filter results
+        sharedChildChanged, sharedChildDeleted = yield self.sharedChildResourceNamesSinceRevision(revision, "infinity")
+
+        selfPath = self.name() + '/'
+        lenpath = len(selfPath)
+        changed = [item[lenpath:] for item in sharedChildChanged if item.startswith(selfPath) and item != selfPath]
+        deleted = [item[lenpath:] for item in sharedChildDeleted if item.startswith(selfPath) and item != selfPath]
+        returnValue((changed, deleted,))
+
+
+    @inlineCallbacks
+    def sharedChildResourceNamesSinceRevision(self, revision, depth):
+        """
+        Determine the list of child resources that have changed since the specified sync revision.
+        We do the same SQL query for both depth "1" and "infinity", but filter the results for
+        "1" to only account for a collection change.
+
+        We need to handle shared collection a little differently from owned ones. When a shared collection
+        is bound into a home we record a revision for it using the sharee home id and sharee collection name.
+        That revision is the "starting point" for changes: so if sync occurs with a revision earlier than
+        that, we return the list of all resources in the shared collection since they are all "new" as far
+        as the client is concerned since the shared collection has just appeared. For a later revision, we
+        just report the changes since that one. When a shared collection is removed from a home, we again
+        record a revision for the sharee home and sharee collection name with the "deleted" flag set. That way
+        the shared collection can be reported as removed.
+
+        For shared groups.  Find the items that have be added and removed since revision in the aboMembers
+        tables.  Then add in changes from the revision table.
+
+        TODO: Cover the case where the sharing changes. Then we can handle revision < bindRevision
+
+        @param revision: the sync revision to compare to
+        @type revision: C{str}
+        @param depth: depth for determine what changed
+        @type depth: C{str}
+        """
+        assert not self.owned()
+
+        bindRevisions = [self._bindRevision] if self.fullyShared() else []
+
+        groupBindRows = yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
+                self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
+        )
+        if groupBindRows:
+            bindRevisions += [groupBindRow[5] for groupBindRow in groupBindRows]
+
+        if revision != 0 and revision < max(bindRevisions):
+            if depth != '1':
+                raise SyncTokenValidException
+            else:
+                revision = 0
+
+        path = self.name()
+
+        if self.fullyShared():
+            # add change for addressbook group
+            changed, deleted = yield super(AddressBook, self).sharedChildResourceNamesSinceRevision(revision, depth)
+
+            #===================================================================
+            # # Add the following to add the addressbook group in sync report:
+            #
+            # if changed or deleted and depth != "1":
+            #     changed.add("%s/%s" % (path, self._groupForSharedAddressBookName(),))
+            #===================================================================
+
+            returnValue((changed, deleted))
+
+        changed = set()
+        deleted = set()
+        acceptedGroupIDs = set([groupBindRow[2] for groupBindRow in groupBindRows])
+
+        allowedObjectIDs = set((yield self.expandGroupIDs(self._txn, acceptedGroupIDs)))
+        oldAllowedObjectIDs = set((yield self.expandGroupIDs(self._txn, acceptedGroupIDs, revision)))
+        addedObjectIDs = allowedObjectIDs - oldAllowedObjectIDs
+        removedObjectIDs = oldAllowedObjectIDs - allowedObjectIDs
+
+        # get revision table changes
+        rev = self._revisionsSchema
+        results = [(
+                name,
+                id,
+                wasdeleted,
+            ) for name, id, wasdeleted in (
+                yield Select([rev.RESOURCE_NAME, rev.OBJECT_RESOURCE_ID, rev.DELETED],
+                             From=rev,
+                            Where=(rev.REVISION > revision).And(
+                            rev.RESOURCE_ID == self._resourceID)).on(self._txn)
+            ) if name
+        ]
+
+        # get deleted object names if any
+        idToNameMap = dict([(id, name) for name, id, wasdeleted in results if wasdeleted])
+
+        # now get other names of existing objects
+        missingNameIDs = (allowedObjectIDs | oldAllowedObjectIDs) - set(idToNameMap.keys())
+        if missingNameIDs:
+            abo = schema.ADDRESSBOOK_OBJECT
+            memberIDNameRows = (
+                yield AddressBookObject._columnsWithResourceIDsQuery(
+                    [abo.RESOURCE_ID, abo.RESOURCE_NAME],
+                    missingNameIDs
+                ).on(self._txn, resourceIDs=missingNameIDs)
+            )
+            idToNameMap = dict(dict(idToNameMap), **dict(memberIDNameRows))
+
+        # now do revisions
+        if revision:
+
+            # handled added or removed objects
+            if removedObjectIDs or addedObjectIDs:
+                changed.add("%s/" % (path,))
+
+            if depth != "1":
+                for removedObjectID in removedObjectIDs:
+                    deleted.add("%s/%s" % (path, idToNameMap[removedObjectID],))
+
+                for addedObjectID in addedObjectIDs:
+                    changed.add("%s/%s" % (path, idToNameMap[addedObjectID],))
+
+            # use revisions to handle changed objects
+            for name, id, wasdeleted in results:
+                if not wasdeleted and name in idToNameMap.values():
+                    # Always report collection as changed
+                    changed.add("%s/" % (path,))
+
+                    # Resource changed - for depth "infinity" report resource as changed
+                    if depth != "1":
+                        item = "%s/%s" % (path, name,)
+                        if item not in deleted:
+                            changed.add("%s/%s" % (path, name,))
+
+        else:
+            changed.add("%s/" % (path,))
+            if depth != "1":
+                for addedObjectID in allowedObjectIDs:
+                    changed.add("%s/%s" % (path, idToNameMap[addedObjectID],))
+
+        returnValue((changed, deleted))
+
+
+    @inlineCallbacks
     def _loadPropertyStore(self, props=None):
         if props is None:
             props = yield PropertyStore.load(
@@ -513,6 +750,19 @@
 
 
     @inlineCallbacks
+    def removedObjectResource(self, child):
+        """
+            just like CommonHomeChild.removedObjectResource() but does not call self._deleteRevision()
+        """
+        self._objects.pop(child.name(), None)
+        self._objects.pop(child.uid(), None)
+        if self._objectNames and child.name() in self._objectNames:
+            self._objectNames.remove(child.name())
+        #yield self._deleteRevision(child.name())
+        yield self.notifyChanged()
+
+
+    @inlineCallbacks
     def remove(self):
 
         if self._resourceID == self._home._resourceID:
@@ -523,9 +773,6 @@
 
             yield self.unshare()  # storebridge should already have done this
 
-            # Note that revision table is NOT queried for removes
-            yield self._updateRevision(self.name())
-
             yield self.properties()._removeResource()
             yield self._loadPropertyStore()
 
@@ -571,6 +818,7 @@
             if self.fullyShared():
                 if not self._groupForSharedAddressBookName() in self._objectNames:
                     self._objectNames.append(self._groupForSharedAddressBookName())
+                    self._objectNames.sort()
 
         returnValue(self._objectNames)
 
@@ -675,9 +923,6 @@
 
     @inlineCallbacks
     def bumpModified(self):
-        # TODO: The next line seems the next line work too.  Why?
-        # returnValue((yield self.ownerHome().bumpModified()))
-        #
         if self._resourceID == self._home._resourceID:
             returnValue((yield self._home.bumpModified()))
         else:
@@ -747,9 +992,7 @@
             ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
             if ownerHome not in ownerHomeToDataRowMap:
                 groupBindRow[0] = _BIND_MODE_INDIRECT
-                groupBindRow[3] = None  # bindName
-                groupBindRow[4] = None  # bindStatus
-                groupBindRow[6] = None  # bindMessage
+                groupBindRow[3:7] = 4 * [None]  # bindName, bindStatus, bindRevision, bindMessage
                 ownerHomeToDataRowMap[ownerHome] = groupBindRow
 
         if ownerHomeToDataRowMap:
@@ -921,19 +1164,65 @@
     @classmethod
     def _memberIDsWithGroupIDsQuery(cls, groupIDs):
         """
-        DAL query to load all object resource names for a home child.
+        DAL query to find members and revisions
         """
         aboMembers = schema.ABO_MEMBERS
-        return Select(
-            [aboMembers.MEMBER_ID],
-            From=aboMembers,
-            Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs))),
-        )
+        return Select([aboMembers.MEMBER_ID, aboMembers.REMOVED, aboMembers.REVISION],
+                      From=aboMembers,
+                      Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs))),
+                     )
 
 
     @classmethod
+    def _memberIDsWithGroupIDsAndRevisionQuery(cls, groupIDs):
+        """
+        DAL query to find members and revisions
+        """
+        aboMembers = schema.ABO_MEMBERS
+        return Select([aboMembers.MEMBER_ID, aboMembers.REMOVED, aboMembers.REVISION],
+                      From=aboMembers,
+                      Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs)))
+                            .And(aboMembers.REVISION <= Parameter("revision")),
+                     )
+
+
+    @classmethod
+    def _currentMemberIDsFromMemberIDRemovedRevisionRows(cls, memberRows):
+        memberIDs = set()
+        objectIDToVersionToRemovedMap = {}
+        for id, removed, version in memberRows:
+            versionRemovedRow = objectIDToVersionToRemovedMap.get(id, [])
+            versionRemovedRow.append((version, removed,))
+            objectIDToVersionToRemovedMap[id] = versionRemovedRow
+
+        for id, versionRemovedRows in objectIDToVersionToRemovedMap.iteritems():
+            versionToRemovedMap = dict(versionRemovedRows)
+            if not versionToRemovedMap[max(versionToRemovedMap.keys())]:
+                memberIDs.add(id)
+
+        return memberIDs
+
+
+    @classmethod
     @inlineCallbacks
-    def expandGroupIDs(cls, txn, groupIDs, includeGroupIDs=True):
+    def memberIDsWithGroupIDs(cls, txn, groupIDs, atRevision=0):
+
+        if atRevision == 0:
+            memberRows = yield cls._memberIDsWithGroupIDsQuery(groupIDs).on(
+                txn, groupIDs=groupIDs
+            )
+        else:
+            memberRows = yield cls._memberIDsWithGroupIDsAndRevisionQuery(groupIDs).on(
+                txn, groupIDs=groupIDs, revision=atRevision
+            )
+
+        memberIDs = cls._currentMemberIDsFromMemberIDRemovedRevisionRows(memberRows)
+        returnValue(memberIDs)
+
+
+    @classmethod
+    @inlineCallbacks
+    def expandGroupIDs(cls, txn, groupIDs, atRevision=0, includeGroupIDs=True):
         """
         Get all AddressBookObject resource IDs contained in the given shared groups with the given groupIDs
         """
@@ -941,14 +1230,14 @@
         examinedIDs = set()
         remainingIDs = set(groupIDs)
         while remainingIDs:
-            memberRows = yield cls._memberIDsWithGroupIDsQuery(remainingIDs).on(
-                txn, groupIDs=remainingIDs
-            )
-            objectIDs |= set(memberRow[0] for memberRow in memberRows)
+
+            memberIDs = yield cls.memberIDsWithGroupIDs(txn, remainingIDs, atRevision)
+
+            objectIDs |= memberIDs
             examinedIDs |= remainingIDs
             remainingIDs = objectIDs - examinedIDs
 
-        returnValue(tuple(objectIDs))
+        returnValue(objectIDs)
 
 
     @inlineCallbacks
@@ -980,26 +1269,26 @@
 
 
     @inlineCallbacks
-    def accessControlGroupIDs(self):
+    def _groupIDAccessSets(self):
         """
         For each accepted shared group, determine what its access mode is and return the sets of read-only
         and read-write groups. Handle the case where a read-only group is actually nested in a read-write
         group by putting the read-only one into the read-write list.
         """
         if self.owned():
-            returnValue(([], []))
+            returnValue((set(), set()))
         else:
             groupBindRows = yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
                 self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
             )
-            readWriteGroupIDs = []
-            readOnlyGroupIDs = []
+            readWriteGroupIDs = set()
+            readOnlyGroupIDs = set()
             for groupBindRow in groupBindRows:
                 bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
                 if bindMode == _BIND_MODE_WRITE:
-                    readWriteGroupIDs.append(resourceID)
+                    readWriteGroupIDs.add(resourceID)
                 else:
-                    readOnlyGroupIDs.append(resourceID)
+                    readOnlyGroupIDs.add(resourceID)
 
             if readOnlyGroupIDs and readWriteGroupIDs:
                 # expand read-write groups and remove any subgroups from read-only group list
@@ -1009,20 +1298,20 @@
             else:
                 adjustedReadOnlyGroupIDs = readOnlyGroupIDs
                 adjustedReadWriteGroupIDs = readWriteGroupIDs
-            returnValue((tuple(adjustedReadOnlyGroupIDs), tuple(adjustedReadWriteGroupIDs)))
+            returnValue((adjustedReadOnlyGroupIDs, adjustedReadWriteGroupIDs))
 
 
     # FIXME: Unused
     @inlineCallbacks
     def readOnlyGroupIDs(self):
-        returnValue((yield self.accessControlGroupIDs())[0])
+        returnValue((yield self._groupIDAccessSets())[0])
 
 
     @inlineCallbacks
     def readWriteGroupIDs(self):
-        returnValue((yield self.accessControlGroupIDs())[1])
+        returnValue((yield self._groupIDAccessSets())[1])
 
-
+    '''
     # FIXME: Unused:  Use for caching access
     @inlineCallbacks
     def accessControlObjectIDs(self):
@@ -1072,13 +1361,13 @@
 
     # FIXME: Unused:  Use for caching access
     @inlineCallbacks
-    def readOnlyObjectIDs(self):
+    def readOnlyGroupIDs(self):
         returnValue((yield self.accessControlObjectIDs())[1])
 
 
     # FIXME: Unused:  Use for caching access
     @inlineCallbacks
-    def readWriteObjectIDs(self):
+    def readWriteGroupIDs(self):
         returnValue((yield self.accessControlObjectIDs())[1])
 
 
@@ -1087,6 +1376,7 @@
     def allObjectIDs(self):
         readOnlyIDs, readWriteIDs = yield self.accessControlObjectIDs()
         returnValue((readOnlyIDs + readWriteIDs))
+    '''
 
     # Convenient names for some methods
     ownerAddressBookHome = CommonHomeChild.ownerHome
@@ -1202,7 +1492,8 @@
         yield self.setShared(True)
 
         # Must send notification to ensure cache invalidation occurs
-        yield self.notifyChanged()
+        yield self.addressbook().notifyPropertyChanged()
+        yield shareeHome.notifyChanged()
 
         returnValue(bindName)
 
@@ -1230,14 +1521,23 @@
             yield super(AddressBookObjectSharingMixIn, self).unshare()
 
 
+    @property
+    def _syncTokenRevision(self):
+        return self.addressbook()._syncTokenRevision
+
+
+    def syncToken(self):
+        return self.addressbook().syncToken() # init self.addressbook()._syncTokenRevision
+
+
     @inlineCallbacks
     def updateShare(self, shareeView, mode=None, status=None, summary=None):
         """
-        Update share mode, status, and message for a home child shared with
-        this (owned) L{CommonHomeChild}.
+        Update share mode, status, and message for a address book group with
+        this (owned) L{AddressBookObject}.
 
-        @param shareeView: The sharee home child that shares this.
-        @type shareeView: L{CommonHomeChild}
+        @param shareeView: The sharee addressbook group that shares this.
+        @type shareeView: L{AddressBookObject}
 
         @param mode: The sharing mode; L{_BIND_MODE_READ} or
             L{_BIND_MODE_WRITE} or None to not update
@@ -1248,64 +1548,80 @@
             L{_BIND_STATUS_INVALID}  or None to not update
         @type status: L{str}
 
-        @param summary: The proposed message to go along with the share, which
+        @param message: The proposed message to go along with the share, which
             will be used as the default display name, or None to not update
-        @type summary: L{str}
-
-        @return: the name of the shared item in the sharee's home.
-        @rtype: a L{Deferred} which fires with a L{str}
+        @type message: L{str}
         """
         # TODO: raise a nice exception if shareeView is not, in fact, a shared
         # version of this same L{CommonHomeChild}
 
         # remove None parameters, and substitute None for empty string
         bind = self._bindSchema
-        columnMap = dict([(k, v if v != "" else None) for k, v in {
-            bind.BIND_MODE:mode,
-            bind.BIND_STATUS:status,
-            bind.MESSAGE:summary
-        }.iteritems() if v is not None])
+        columnMap = {}
+        if mode != None and mode != shareeView._bindMode:
+            columnMap[bind.BIND_MODE] = mode
+        if status != None and status != shareeView._bindStatus:
+            columnMap[bind.BIND_STATUS] = status
+        if summary != None and summary != shareeView._bindMessage:
+            columnMap[bind.MESSAGE] = summary
 
-        if len(columnMap):
+        if columnMap:
 
             # count accepted
-            if status is not None:
-                previouslyAcceptedBindCount = 1 if self.addressbook().fullyShared() else 0
-                previouslyAcceptedBindCount += len((
-                    yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
-                        self._txn, homeID=shareeView.viewerHome()._resourceID, addressbookID=self.addressbook()._resourceID
-                    )
-                ))
+            if bind.BIND_STATUS in columnMap:
+                previouslyAcceptedBindCount = 1 if shareeView.addressbook().fullyShared() else 0
+                groupBindRows = yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
+                    self._txn, homeID=shareeView.viewerHome()._resourceID, addressbookID=self.addressbook()._resourceID
+                )
+                previouslyAcceptedBindCount += len(groupBindRows)
 
             yield self._updateBindColumnsQuery(columnMap).on(
                 self._txn,
                 resourceID=self._resourceID,
-                homeID=shareeView.addressbook().viewerHome()._resourceID
+                homeID=shareeView.viewerHome()._resourceID
             )
 
             # update affected attributes
-            if mode is not None:
+            if bind.BIND_MODE in columnMap:
                 shareeView._bindMode = columnMap[bind.BIND_MODE]
 
-            if status is not None:
+            if bind.BIND_STATUS in columnMap:
                 shareeView._bindStatus = columnMap[bind.BIND_STATUS]
                 if shareeView._bindStatus == _BIND_STATUS_ACCEPTED:
                     if 0 == previouslyAcceptedBindCount:
                         yield shareeView.addressbook()._initSyncToken()
-                        yield shareeView._initBindRevision()
-                        shareeView.viewerHome()._children[shareeView.addressbook().name()] = shareeView.addressbook()
-                        shareeView.viewerHome()._children[shareeView.addressbook().id()] = shareeView.addressbook()
-                elif shareeView._bindStatus != _BIND_STATUS_INVITED:
+                        shareeView.viewerHome()._children[self.addressbook().ownerHome().uid()] = shareeView.addressbook()
+                        shareeView.viewerHome()._children[shareeView._resourceID] = shareeView.addressbook()
+                    yield shareeView._initBindRevision()
+                elif shareeView._bindStatus == _BIND_STATUS_DECLINED:
                     if 1 == previouslyAcceptedBindCount:
                         yield shareeView.addressbook()._deletedSyncToken(sharedRemoval=True)
-                        shareeView.viewerHome()._children.pop(shareeView.addressbook().name(), None)
-                        shareeView.viewerHome()._children.pop(shareeView.addressbook().id(), None)
+                        shareeView.viewerHome()._children.pop(self.addressbook().ownerHome().uid(), None)
+                        shareeView.viewerHome()._children.pop(shareeView._resourceID, None)
+                    else:
+                        # update revision in all remaining bind table rows for this address book
+                        yield shareeView.addressbook().notifyPropertyChanged()
+                        for groupBindRow in groupBindRows:
+                            if groupBindRow[2] != shareeView._resourceID:
+                                groupObject = yield shareeView.addressbook().objectResourceWithID(groupBindRow[2])
+                                yield groupObject._initBindRevision()
+                        if shareeView.addressbook().fullyShared():
+                            yield shareeView.addressbook()._initBindRevision()
+                        shareeView.addressbook()._objects = {}
+                        shareeView.addressbook()._objectNames = None
 
-            if summary is not None:
+            if bind.MESSAGE in columnMap:
                 shareeView._bindMessage = columnMap[bind.MESSAGE]
 
+            # safer to just invalidate in all cases rather than calculate when to invalidate
+            queryCacher = self._txn._queryCacher
+            if queryCacher:
+                cacheKey = queryCacher.keyForObjectWithName(shareeView.viewerHome()._resourceID, self.addressbook().ownerHome().uid())
+                queryCacher.invalidateAfterCommit(self._txn, cacheKey)
+
             # Must send notification to ensure cache invalidation occurs
-            yield self.notifyChanged()
+            yield self.addressbook().notifyPropertyChanged()
+            yield shareeView.viewerHome().notifyChanged()
 
 
     @inlineCallbacks
@@ -1323,41 +1639,37 @@
         """
 
         shareeHome = shareeView.addressbook().viewerHome()
-        sharedAddressBook = yield shareeHome.addressbookWithName(self.addressbook().name())
+        addressbookAsShared = yield shareeHome.addressbookWithName(self.addressbook().ownerHome().uid())
+        if addressbookAsShared:
 
-        if sharedAddressBook:
-
-            acceptedBindCount = 1 if sharedAddressBook.fullyShared() else 0
-            acceptedBindCount += len((
-                yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
-                    self._txn, homeID=shareeHome._resourceID, addressbookID=sharedAddressBook._resourceID
-                )
-            ))
-
+            acceptedBindCount = 1 if addressbookAsShared.fullyShared() else 0
+            groupBindRows = yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
+                    self._txn, homeID=shareeHome._resourceID, addressbookID=addressbookAsShared._resourceID
+            )
+            acceptedBindCount += len(groupBindRows)
             if acceptedBindCount == 1:
-                yield sharedAddressBook._deletedSyncToken(sharedRemoval=True)
-                shareeHome._children.pop(shareeView.addressbook().name(), None)
-                shareeHome._children.pop(shareeView.addressbook().id(), None)
+                yield addressbookAsShared._deletedSyncToken(sharedRemoval=True)
+                shareeHome._children.pop(self.ownerHome().uid(), None)
+                shareeHome._children.pop(addressbookAsShared._resourceID, None)
+            else:
+                yield addressbookAsShared.notifyPropertyChanged()
+                #update revision in all remaining bind table rows for this address book
+                for groupBindRow in groupBindRows:
+                    groupObject = yield addressbookAsShared.objectResourceWithID(groupBindRow[2])
+                    yield groupObject._initBindRevision()
+                addressbookAsShared._objects = {}
+                addressbookAsShared._objectNames = None
 
             # Must send notification to ensure cache invalidation occurs
-            yield self.notifyChanged()
+            yield self.notifyPropertyChanged()
+            yield shareeHome.notifyChanged()
 
-        # Must send notification to ensure cache invalidation occurs
-        yield self.notifyChanged()
-
-        # delete binds including invites
-        yield self._deleteBindForResourceIDAndHomeID.on(
-            self._txn,
-            resourceID=self._resourceID,
-            homeID=shareeHome._resourceID,
+        # delete bind table rows for this share
+        yield self._deleteBindForResourceIDAndHomeID.on(self._txn,
+            resourceID=self._resourceID, homeID=shareeHome._resourceID
         )
 
-        queryCacher = self._txn._queryCacher
-        if queryCacher:
-            cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, self.addressbook().name())
-            queryCacher.invalidateAfterCommit(self._txn, cacheKey)
 
-
     @inlineCallbacks
     def sharingInvites(self):
         """
@@ -1411,7 +1723,7 @@
         """
         @see: L{ICalendar.shareUID}
         """
-        return self._bindName if self._bindName else self._name
+        return self._bindName
 
 
 
@@ -1440,6 +1752,7 @@
         self._bindStatus = None
         self._bindMessage = None
         self._bindName = None
+        self._bindRevision = None
         super(AddressBookObject, self).__init__(addressbook, name, uid, resourceID, options)
         self._options = {} if options is None else options
 
@@ -1465,15 +1778,6 @@
         return self._resourceID == self.addressbook()._resourceID
 
 
-    @classmethod
-    def _deleteMembersWithMemberIDAndGroupIDsQuery(cls, memberID, groupIDs):
-        aboMembers = schema.ABO_MEMBERS
-        return Delete(
-            aboMembers,
-            Where=(aboMembers.MEMBER_ID == memberID).And(
-                    aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs)))))
-
-
     @inlineCallbacks
     def remove(self):
 
@@ -1481,44 +1785,94 @@
             yield self.unshare() # storebridge should already have done this
         else:
             # handled in storebridge as unshare, should not be here.  assert instead?
-            if self.isGroupForSharedAddressBook() or self._bindMode is not None:
+            if self.isGroupForSharedAddressBook() or self.shareUID():
                 raise HTTPError(FORBIDDEN)
 
-        if not self.owned() and not self.addressbook().fullyShared():
-            readWriteObjectIDs = []
+        partiallyShared = not self.owned() and not self.addressbook().fullyShared()
+        if partiallyShared:
             readWriteGroupIDs = yield self.addressbook().readWriteGroupIDs()
-            if readWriteGroupIDs:
-                readWriteObjectIDs = yield self.addressbook().expandGroupIDs(self._txn, readWriteGroupIDs)
-
-            # can't delete item in shared group, even if user has addressbook unbind
+            readWriteObjectIDs = (
+                set((yield self.addressbook().expandGroupIDs(self._txn, readWriteGroupIDs)))
+                    if readWriteGroupIDs else set()
+            )
+            # can't delete item in read-only shared group, even if user has addressbook unbind
             if self._resourceID not in readWriteObjectIDs:
                 raise HTTPError(FORBIDDEN)
 
-            # convert delete in sharee shared group address book to remove of memberships
-            # that make this object visible to the sharee
-            if readWriteObjectIDs:
-                yield self._deleteMembersWithMemberIDAndGroupIDsQuery(self._resourceID, readWriteObjectIDs).on(
-                    self._txn, groupIDs=readWriteObjectIDs
-                )
+        # get sync token for delete now
+        yield self.addressbook()._deleteRevision(self.name(), self._resourceID)
 
+        # get groups where this object was once a member and version info
         aboMembers = schema.ABO_MEMBERS
-        aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
-
-        groupIDRows = yield Delete(
-            aboMembers,
+        groupRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID, aboMembers.REMOVED, aboMembers.REVISION],
+            From=aboMembers,
             Where=aboMembers.MEMBER_ID == self._resourceID,
-            Return=aboMembers.GROUP_ID
         ).on(self._txn)
 
-        # add to foreign member table row by UID (aboForeignMembers on address books)
+        # combine by groupID
+        groupIDToMemberRowMap = {}
+        for groupID, id, removed, revision in groupRows:
+            memberRow = groupIDToMemberRowMap.get(groupID, [])
+            memberRow.append((id, removed, revision))
+            groupIDToMemberRowMap[groupID] = memberRow
+
+        # see if this object is in current version
+        groupIDs = set([
+            groupID for groupID, memberRows in groupIDToMemberRowMap.iteritems()
+                if self._resourceID in AddressBook._currentMemberIDsFromMemberIDRemovedRevisionRows(memberRows)
+        ])
+
+        if partiallyShared:
+            groupIDsToRemoveFrom = groupIDs & readWriteObjectIDs
+            groupIDs -= readWriteObjectIDs
+
+            # add to member table rows marked removed
+            for groupIDToRemoveFrom in groupIDsToRemoveFrom:
+                yield self._insertMemberIDQuery.on(self._txn,
+                    groupID=groupIDToRemoveFrom,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=self._resourceID,
+                    revision=self._syncTokenRevision,
+                    removed=True,
+                )
+                groupObject = yield self.addressbook().objectResourceWithID(groupIDToRemoveFrom)
+                yield self.addressbook()._updateRevision(groupObject.name())
+
+        else:
+            yield Delete(
+                aboMembers,
+                Where=aboMembers.MEMBER_ID == self._resourceID,
+            ).on(self._txn)
+
+        # add to foreign member table row by member address (aboForeignMembers on address books)
         memberAddress = "urn:uuid:" + self._uid
-        for groupID in set([groupIDRow[0] for groupIDRow in groupIDRows]) - set([self._ownerAddressBookResourceID]):
+        aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
+        for groupID in groupIDs:
             yield Insert(
                 {aboForeignMembers.GROUP_ID: groupID,
                  aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
                  aboForeignMembers.MEMBER_ADDRESS: memberAddress, }
             ).on(self._txn)
 
+        if self.kind() == _ABO_KIND_GROUP:
+            if partiallyShared:
+                # mark members as deleted
+                memberIDsToRemove = yield AddressBook.memberIDsWithGroupIDs(self._txn, [self._resourceID])
+                for memberIDToRemove in memberIDsToRemove:
+                    yield self._insertMemberIDQuery.on(
+                        self._txn,
+                        groupID=self._resourceID,
+                        addressbookID=self._ownerAddressBookResourceID,
+                        memberID=memberIDToRemove,
+                        revision=self._syncTokenRevision,
+                        removed=True,
+                    )
+            else:
+                yield Delete(
+                    aboMembers,
+                    Where=aboMembers.GROUP_ID == self._resourceID,
+                ).on(self._txn)
+
         yield super(AddressBookObject, self).remove()
         self._kind = None
         self._ownerAddressBookResourceID = None
@@ -1539,8 +1893,8 @@
 
         # Otherwise, must be in a read-write group
         readWriteGroupIDs = yield self.addressbook().readWriteGroupIDs()
-        readWriteIDs = yield self.addressbook().expandGroupIDs(self._txn, readWriteGroupIDs)
-        returnValue(self._resourceID in readWriteIDs)
+        readWriteObjectIDs = yield self.addressbook().expandGroupIDs(self._txn, readWriteGroupIDs)
+        returnValue(self._resourceID in readWriteObjectIDs)
 
 
     @classmethod
@@ -1656,10 +2010,8 @@
                         resourceIDs=allowedObjectIDs,
                     ))
             elif self._resourceID:
-                if self._resourceID not in allowedObjectIDs:
-                    # allow invited groups
-                    allowedObjectIDs = yield self.addressbook().unacceptedGroupIDs()
-                if self._resourceID in allowedObjectIDs:
+                if (self._resourceID in allowedObjectIDs or
+                        self._resourceID in (yield self.addressbook().unacceptedGroupIDs())): # allow invited groups
                     rows = (yield self._allColumnsWithResourceID.on(
                         self._txn, resourceID=self._resourceID,
                     ))
@@ -1682,11 +2034,14 @@
                     self._bindStatus = bindStatus
                     self._bindMessage = bindMessage
                     self._bindName = bindName
+                    self._bindRevision = bindRevision
                 else:
                     invites = yield self.sharingInvites()
                     if len(invites):
                         self._bindMessage = "shared"
 
+            yield self._loadPropertyStore()
+
             returnValue(self)
         else:
             returnValue(None)
@@ -1779,16 +2134,6 @@
         returnValue(rows)
 
 
-    @inlineCallbacks
-    def _changeAddressBookRevision(self, addressbook, inserting=False):
-        if inserting:
-            yield addressbook._insertRevision(self._name)
-        else:
-            yield addressbook._updateRevision(self._name)
-
-        yield addressbook.notifyChanged()
-
-
     # Stuff from put_addressbook_common
     def fullValidation(self, component, inserting):
         """
@@ -1799,7 +2144,14 @@
 
         # Valid data sizes
         if config.MaxResourceSize:
-            vcardsize = len(str(component))
+            if self._componentResourceKindToKind(component) == _ABO_KIND_GROUP:
+                thinGroup = deepcopy(component)
+                thinGroup.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
+                thinGroup.removeProperties("X-ADDRESSBOOKSERVER-KIND")
+                thinGroup.removeProperties("UID")
+                vcardsize = len(str(thinGroup))
+            else:
+                vcardsize = len(str(component))
             if vcardsize > config.MaxResourceSize:
                 raise ObjectResourceTooBigError()
 
@@ -1878,30 +2230,22 @@
 
         self._componentChanged = False
 
-        # Handle all validation operations here.
-        self.fullValidation(component, inserting)
+        if "coaddedUIDs" not in self._options:
+            # Handle all validation operations here.
+            self.fullValidation(component, inserting)
 
-        # UID lock - this will remain active until the end of the current txn
-        if not inserting or self._options.get("coaddedUIDs") is None:
+            # UID lock - this will remain active until the end of the current txn
             yield self._lockUID(component, inserting)
 
+            if inserting:
+                yield self.addressbook()._insertRevision(self._name)
+            else:
+                yield self.addressbook()._updateRevision(self._name)
+
+            yield self.addressbook().notifyChanged()
+
         yield self.updateDatabase(component, inserting=inserting)
-        yield self._changeAddressBookRevision(self._addressbook, inserting)
 
-        if self.owned():
-            # update revision table of the sharee group address book
-            if self._kind == _ABO_KIND_GROUP:  # optimization
-                invites = yield self.sharingInvites()
-                for invite in invites:
-                    shareeHome = (yield self._txn.homeWithResourceID(self.addressbook()._home._homeType, invite.shareeHomeID))
-                    yield self._changeAddressBookRevision(shareeHome.addressbook(), inserting)
-                    # one is enough because all have the same resourceID
-                    break
-        else:
-            if self.addressbook()._resourceID != self._ownerAddressBookResourceID:
-                # update revisions table of shared group's containing address book
-                yield self._changeAddressBookRevision(self.ownerHome().addressbook(), inserting)
-
         returnValue(self._componentChanged)
 
 
@@ -1935,7 +2279,7 @@
 
 
     @classproperty
-    def _insertABObject(cls): #@NoSelf
+    def _insertABObjectQuery(cls): #@NoSelf
         """
         DAL statement to create an addressbook object with all default values.
         """
@@ -1954,6 +2298,43 @@
                     abo.MODIFIED))
 
 
+    @classproperty
+    def _insertMemberIDQuery(cls): #@NoSelf
+        """
+        DAL statement to add a member table row
+        """
+        aboMembers = schema.ABO_MEMBERS
+        return Insert(
+            {aboMembers.GROUP_ID: Parameter("groupID"),
+             aboMembers.ADDRESSBOOK_ID: Parameter("addressbookID"),
+             aboMembers.MEMBER_ID: Parameter("memberID"),
+             aboMembers.REVISION: Parameter("revision"),
+             aboMembers.REMOVED: Parameter("removed"),
+             }
+        )
+
+
+    @classmethod
+    def _deleteMembersIDsThruRevisionQuery(cls, groupIDs, memberIDs):
+        """
+        DAL statement deletes rows with groupsIDs and memberIDs < revision
+
+        Note: Used after adding a member row in an owned address book, where only the last revision is needed.
+            Could be used after adding a member row to a partially shared address book if the
+            minimum valid revision is known.
+            "minimum valid revision" is the max of the bind revisions on a home over all
+            shared address books that have group binds.
+        """
+        aboMembers = schema.ABO_MEMBERS
+        return Delete(
+            aboMembers,
+            Where=(aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs)))).And(
+                aboMembers.GROUP_ID.In(Parameter("memberIDs", len(memberIDs)))).And(
+                    aboMembers.REVISION < Parameter("revision")
+                )
+        )
+
+
     @inlineCallbacks
     def updateDatabase(self, component, expand_until=None, reCreate=False, #@UnusedVariable
                        inserting=False):
@@ -2000,7 +2381,7 @@
             if not self.owned() and not self.addressbook().fullyShared():
                 # in partially shared addressbook, all members UIDs must be inside the shared groups
                 # except during bulk operations, when other UIDs added are OK
-                coaddedUIDs = set() if self._options.get("coaddedUIDs") is None else self._options["coaddedUIDs"]
+                coaddedUIDs = self._options.get("coaddedUIDs", set())
                 if missingUIDs - coaddedUIDs:
                     raise GroupWithUnsharedAddressNotAllowedError(missingUIDs)
 
@@ -2015,16 +2396,16 @@
 
             # sort unique members
             component.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
-            for memberAddress in sorted(list(memberAddresses)): # sort unique
+            for memberAddress in sorted(memberAddresses):
                 component.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
             componentText = str(component)
 
             # remove unneeded fields to get stored _objectText
-            thinComponent = deepcopy(component)
-            thinComponent.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
-            thinComponent.removeProperties("X-ADDRESSBOOKSERVER-KIND")
-            thinComponent.removeProperties("UID")
-            self._objectText = str(thinComponent)
+            thinGroup = deepcopy(component)
+            thinGroup.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
+            thinGroup.removeProperties("X-ADDRESSBOOKSERVER-KIND")
+            thinGroup.removeProperties("UID")
+            self._objectText = str(thinGroup)
         else:
             componentText = str(component)
             self._objectText = componentText
@@ -2040,11 +2421,11 @@
 
         abo = schema.ADDRESSBOOK_OBJECT
         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
-        aboMembers = schema.ABO_MEMBERS
+        partiallyShared = not self.owned() and not self.addressbook().fullyShared()
 
         if inserting:
             self._resourceID, self._created, self._modified = (
-                yield self._insertABObject.on(
+                yield self._insertABObjectQuery.on(
                     self._txn,
                     addressbookResourceID=self._ownerAddressBookResourceID,
                     name=self._name,
@@ -2055,28 +2436,39 @@
                 )
             )[0]
 
-            # delete foreign members table row for this object
+            # delete foreign members table rows for this object
             groupIDRows = yield Delete(
                 aboForeignMembers,
-                # should this be scoped to the owner address book?
                 Where=aboForeignMembers.MEMBER_ADDRESS == "urn:uuid:" + self._uid,
                 Return=aboForeignMembers.GROUP_ID
             ).on(self._txn)
             groupIDs = set([groupIDRow[0] for groupIDRow in groupIDRows])
 
-            if not self.owned() and not self.addressbook().fullyShared():
+            # add this object to shared groups
+            if partiallyShared:
                 readWriteGroupIDs = yield self.addressbook().readWriteGroupIDs()
                 assert readWriteGroupIDs, "no access"
-                groupIDs |= set(readWriteGroupIDs)
+                groupIDs |= readWriteGroupIDs
 
+                for readWriteGroupID in readWriteGroupIDs:
+                    groupObject = yield self.addressbook().objectResourceWithID(readWriteGroupID)
+                    yield self.addressbook()._updateRevision(groupObject.name())
+
             # add to member table rows
             for groupID in groupIDs:
-                yield Insert(
-                    {aboMembers.GROUP_ID: groupID,
-                     aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
-                     aboMembers.MEMBER_ID: self._resourceID, }
-                ).on(self._txn)
+                yield self._insertMemberIDQuery.on(self._txn,
+                    groupID=groupID,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=self._resourceID,
+                    revision=self._syncTokenRevision,
+                    removed=False,
+                )
 
+            # clean old revisions
+            if groupIDs and not partiallyShared:
+                yield self._deleteMembersIDsThruRevisionQuery(groupIDs, [self._resourceID]).on(
+                    self._txn, groupIDs=groupIDs, memberIDs=[self._resourceID], revision=self._syncTokenRevision)
+
         else:
             self._modified = (yield Update(
                 {abo.VCARD_TEXT: self._objectText,
@@ -2092,50 +2484,63 @@
                 memberIDs.append(self._resourceID)
 
             # get current members
-            currentMemberRows = yield Select([aboMembers.MEMBER_ID],
-                 From=aboMembers,
-                 Where=aboMembers.GROUP_ID == self._resourceID,).on(self._txn)
-            currentMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows]
-
-            memberIDsToDelete = set(currentMemberIDs) - set(memberIDs)
+            currentMemberIDs = yield AddressBook.memberIDsWithGroupIDs(self._txn, [self._resourceID])
+            memberIDsToRemove = set(currentMemberIDs) - set(memberIDs)
             memberIDsToAdd = set(memberIDs) - set(currentMemberIDs)
 
-            if memberIDsToDelete:
-                yield self._deleteMembersWithGroupIDAndMemberIDsQuery(self._resourceID, memberIDsToDelete).on(
-                    self._txn, memberIDs=memberIDsToDelete
+            for memberID in memberIDsToAdd:
+                yield self._insertMemberIDQuery.on(
+                    self._txn,
+                    groupID=self._resourceID,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=memberID,
+                    revision=self._syncTokenRevision,
+                    removed=False,
                 )
 
-            for memberIDToAdd in memberIDsToAdd:
-                yield Insert(
-                    {aboMembers.GROUP_ID: self._resourceID,
-                     aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
-                     aboMembers.MEMBER_ID: memberIDToAdd, }
-                ).on(self._txn)
+            if partiallyShared:
+                for memberID in memberIDsToRemove:
+                    yield self._insertMemberIDQuery.on(
+                        self._txn,
+                        groupID=self._resourceID,
+                        addressbookID=self._ownerAddressBookResourceID,
+                        memberID=memberID,
+                        revision=self._syncTokenRevision,
+                        removed=True,
+                    )
+            else:
+                # clean old revisions
+                if memberIDsToAdd:
+                    yield self._deleteMembersIDsThruRevisionQuery([self._resourceID], memberIDsToAdd).on(
+                        self._txn, groupIDs=[self._resourceID], memberIDs=memberIDsToAdd, revision=self._syncTokenRevision)
 
-            # don't bother with aboForeignMembers on address books
-            if self._resourceID != self._ownerAddressBookResourceID:
+                if memberIDsToRemove:
+                    yield self._deleteMembersWithGroupIDAndMemberIDsQuery(self._resourceID, memberIDsToRemove).on(
+                        self._txn, memberIDs=memberIDsToRemove
+                    )
 
-                # get current foreign members
-                currentForeignMemberRows = yield Select(
-                    [aboForeignMembers.MEMBER_ADDRESS],
-                     From=aboForeignMembers,
-                     Where=aboForeignMembers.GROUP_ID == self._resourceID,).on(self._txn)
-                currentForeignMemberAddrs = [currentForeignMemberRow[0] for currentForeignMemberRow in currentForeignMemberRows]
+            # get current foreign members
+            currentForeignMemberRows = yield Select(
+                [aboForeignMembers.MEMBER_ADDRESS],
+                 From=aboForeignMembers,
+                 Where=aboForeignMembers.GROUP_ID == self._resourceID,
+            ).on(self._txn)
+            currentForeignMemberAddrs = [currentForeignMemberRow[0] for currentForeignMemberRow in currentForeignMemberRows]
 
-                foreignMemberAddrsToDelete = set(currentForeignMemberAddrs) - set(foreignMemberAddrs)
-                foreignMemberAddrsToAdd = set(foreignMemberAddrs) - set(currentForeignMemberAddrs)
+            foreignMemberAddrsToDelete = set(currentForeignMemberAddrs) - set(foreignMemberAddrs)
+            foreignMemberAddrsToAdd = set(foreignMemberAddrs) - set(currentForeignMemberAddrs)
 
-                if foreignMemberAddrsToDelete:
-                    yield self._deleteForeignMembersWithGroupIDAndMembeAddrsQuery(self._resourceID, foreignMemberAddrsToDelete).on(
-                        self._txn, memberAddrs=foreignMemberAddrsToDelete
-                    )
+            if foreignMemberAddrsToDelete:
+                yield self._deleteForeignMembersWithGroupIDAndMembeAddrsQuery(self._resourceID, foreignMemberAddrsToDelete).on(
+                    self._txn, memberAddrs=foreignMemberAddrsToDelete
+                )
 
-                for foreignMemberAddrToAdd in foreignMemberAddrsToAdd:
-                    yield Insert(
-                        {aboForeignMembers.GROUP_ID: self._resourceID,
-                         aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
-                         aboForeignMembers.MEMBER_ADDRESS: foreignMemberAddrToAdd, }
-                    ).on(self._txn)
+            for foreignMemberAddrToAdd in foreignMemberAddrsToAdd:
+                yield Insert(
+                    {aboForeignMembers.GROUP_ID: self._resourceID,
+                     aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
+                     aboForeignMembers.MEMBER_ADDRESS: foreignMemberAddrToAdd, }
+                ).on(self._txn)
 
 
     @inlineCallbacks
@@ -2177,13 +2582,7 @@
 
                     # generate "X-ADDRESSBOOKSERVER-MEMBER" properties
                     # first get member resource ids
-                    aboMembers = schema.ABO_MEMBERS
-                    memberRows = yield Select(
-                        [aboMembers.MEMBER_ID],
-                         From=aboMembers,
-                         Where=aboMembers.GROUP_ID == self._resourceID,
-                    ).on(self._txn)
-                    memberIDs = [memberRow[0] for memberRow in memberRows]
+                    memberIDs = yield AddressBook.memberIDsWithGroupIDs(self._txn, [self._resourceID])
 
                     # then get member UIDs
                     abo = schema.ADDRESSBOOK_OBJECT
@@ -2266,10 +2665,6 @@
                            ).And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
 
 
-    def notifyChanged(self):
-        return self.addressbook().notifyChanged()
-
-
     def notifyPropertyChanged(self):
         """
         Send notifications when properties on this object change.

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py	2013-12-19 06:18:02 UTC (rev 12143)
@@ -42,6 +42,7 @@
 from txdav.common.datastore.sql import EADDRESSBOOKTYPE, CommonObjectResource
 from txdav.common.datastore.sql_tables import  _ABO_KIND_PERSON, _ABO_KIND_GROUP, schema
 from txdav.common.datastore.test.util import buildStore
+from txdav.carddav.datastore.sql import AddressBook
 
 from txdav.xml.rfc2518 import GETContentLanguage, ResourceType
 
@@ -572,11 +573,11 @@
             )
         groupObject = yield adbk.createAddressBookObjectWithName("g.vcf", group)
 
-        aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
         aboMembers = schema.ABO_MEMBERS
-        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
+        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, Where=aboMembers.REMOVED == False).on(txn)
         self.assertEqual(memberRows, [])
 
+        aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
         foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers).on(txn)
         self.assertEqual(foreignMemberRows, [[groupObject._resourceID, "urn:uuid:uid3"]])
 
@@ -595,7 +596,7 @@
             )
         subgroupObject = yield adbk.createAddressBookObjectWithName("sg.vcf", subgroup)
 
-        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
+        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, Where=aboMembers.REMOVED == False).on(txn)
         self.assertEqual(sorted(memberRows), sorted([
                                                      [groupObject._resourceID, subgroupObject._resourceID],
                                                      [subgroupObject._resourceID, personObject._resourceID],
@@ -605,12 +606,26 @@
         self.assertEqual(foreignMemberRows, [])
 
         yield subgroupObject.remove()
-        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
-        self.assertEqual(memberRows, [])
+        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID, aboMembers.REMOVED, aboMembers.REVISION], From=aboMembers).on(txn)
 
-        foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers,
-                                                 #Where=(aboForeignMembers.GROUP_ID == groupObject._resourceID),
-                                                 ).on(txn)
+        # combine by groupID
+        groupIDToMemberRowMap = {}
+        for groupID, id, removed, version in memberRows:
+            memberRow = groupIDToMemberRowMap.get(groupID, [])
+            memberRow.append((id, removed, version))
+            groupIDToMemberRowMap[groupID] = memberRow
+
+        # see if this object is in current version
+        groupIDs = set([
+            groupID for groupID, memberIDRemovedRevisionRows in groupIDToMemberRowMap.iteritems()
+                if AddressBook._currentMemberIDsFromMemberIDRemovedRevisionRows(memberIDRemovedRevisionRows)
+        ])
+
+        self.assertEqual(len(groupIDs), 0)
+
+        foreignMemberRows = yield Select(
+            [aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers,
+        ).on(txn)
         self.assertEqual(foreignMemberRows, [[groupObject._resourceID, "urn:uuid:uid3"]])
 
         yield home.removeAddressBookWithName("addressbook")

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql_sharing.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql_sharing.py	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql_sharing.py	2013-12-19 06:18:02 UTC (rev 12143)
@@ -1134,7 +1134,7 @@
         otherAB = yield self.addressbookUnderTest(home="user02", name="user01")
         self.assertNotEqual(otherAB._bindRevision, 0)
 
-        changed, deleted = yield otherAB.resourceNamesSinceRevision(otherAB._bindRevision - 1)
+        changed, deleted = yield otherAB.resourceNamesSinceRevision(0)
         self.assertNotEqual(len(changed), 0)
         self.assertEqual(len(deleted), 0)
 
@@ -1144,7 +1144,7 @@
 
         otherHome = yield self.addressbookHomeUnderTest(name="user02")
         for depth in ("1", "infinity",):
-            changed, deleted = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision - 1, depth)
+            changed, deleted = yield otherHome.resourceNamesSinceRevision(0, depth)
             self.assertNotEqual(len(changed), 0)
             self.assertEqual(len(deleted), 0)
 
@@ -1166,21 +1166,29 @@
         otherAB = yield self.addressbookUnderTest(home="user02", name="user01")
         self.assertNotEqual(otherAB._bindRevision, 0)
 
-        changed, deleted = yield otherAB.resourceNamesSinceRevision(otherAB._bindRevision - 1)
-        self.assertNotEqual(len(changed), 0)
+        changed, deleted = yield otherAB.resourceNamesSinceRevision(0)
+        self.assertEqual(set(changed), set(['card1.vcf', 'card2.vcf', 'group1.vcf']))
         self.assertEqual(len(deleted), 0)
 
         changed, deleted = yield otherAB.resourceNamesSinceRevision(otherAB._bindRevision)
         self.assertEqual(len(changed), 0)
         self.assertEqual(len(deleted), 0)
 
-        otherHome = yield self.addressbookHomeUnderTest(name="user02")
-        for depth in ("1", "infinity",):
-            changed, deleted = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision - 1, depth)
-            self.assertNotEqual(len(changed), 0)
+        for depth, result in (
+            ("1", ['addressbook/',
+                   'user01/', ]
+            ),
+            ("infinity", ['addressbook/',
+                             'user01/',
+                             'user01/card1.vcf',
+                             'user01/card2.vcf',
+                             'user01/group1.vcf']
+             )):
+            changed, deleted = yield otherAB.viewerHome().resourceNamesSinceRevision(0, depth)
+            self.assertEqual(set(changed), set(result))
             self.assertEqual(len(deleted), 0)
 
-            changed, deleted = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision, depth)
+            changed, deleted = yield otherAB.viewerHome().resourceNamesSinceRevision(otherAB._bindRevision, depth)
             self.assertEqual(len(changed), 0)
             self.assertEqual(len(deleted), 0)
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2013-12-19 06:18:02 UTC (rev 12143)
@@ -72,7 +72,8 @@
     HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
     ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
     NoSuchObjectResourceError, AllRetriesFailed, InvalidSubscriptionValues, \
-    InvalidIMIPTokenValues, TooManyObjectResourcesError
+    InvalidIMIPTokenValues, TooManyObjectResourcesError, \
+    SyncTokenValidException
 from txdav.common.idirectoryservice import IStoreDirectoryService
 from txdav.common.inotifications import INotificationCollection, \
     INotificationObject
@@ -2043,7 +2044,6 @@
         changed = set()
         deleted = set()
         deleted_collections = set()
-        changed_collections = set()
         for path, name, wasdeleted in results:
             if wasdeleted:
                 if revision:
@@ -2059,7 +2059,6 @@
                         deleted.add("%s/" % (path,))
                         deleted_collections.add(path)
 
-        for path, name, wasdeleted in results:
             if path not in deleted_collections:
                 # Always report collection as changed
                 changed.add("%s/" % (path,))
@@ -2067,47 +2066,15 @@
                     # Resource changed - for depth "infinity" report resource as changed
                     if depth != "1":
                         changed.add("%s/%s" % (path, name,))
-                else:
-                    # Collection was changed
-                    changed_collections.add(path)
 
         # Now deal with shared collections
         # TODO: think about whether this can be done in one query rather than looping over each share
-        rev = self._revisionsSchema
-        shares = yield self.children()
-        for share in shares:
+        for share in (yield self.children()):
             if not share.owned():
-                sharerevision = 0 if revision < share._bindRevision else revision
-                results = [
-                    (
-                        share.name(),
-                        name if name else "",
-                        wasdeleted
-                    )
-                    for name, wasdeleted in
-                    (yield Select([rev.RESOURCE_NAME, rev.DELETED],
-                                     From=rev,
-                                    Where=(rev.REVISION > sharerevision).And(
-                                    rev.RESOURCE_ID == share._resourceID)).on(self._txn))
-                    if name
-                ]
+                sharedChanged, sharedDeleted = yield share.sharedChildResourceNamesSinceRevision(revision, depth)
+                changed |= sharedChanged
+                deleted |= sharedDeleted
 
-                for path, name, wasdeleted in results:
-                    if wasdeleted:
-                        if sharerevision:
-                            if depth == "1":
-                                changed.add("%s/" % (path,))
-                            else:
-                                deleted.add("%s/%s" % (path, name,))
-
-                for path, name, wasdeleted in results:
-                    # Always report collection as changed
-                    changed.add("%s/" % (path,))
-                    if name:
-                        # Resource changed - for depth "infinity" report resource as changed
-                        if depth != "1":
-                            changed.add("%s/%s" % (path, name,))
-
         changed = sorted(changed)
         deleted = sorted(deleted)
         returnValue((changed, deleted))
@@ -2760,6 +2727,7 @@
                         resourceID=self._resourceID, name=name)
                 )[0][0]
         self._maybeNotify()
+        returnValue(self._syncTokenRevision)
 
 
     def _maybeNotify(self):
@@ -3230,9 +3198,6 @@
         @param summary: The proposed message to go along with the share, which
             will be used as the default display name, or None to not update
         @type summary: L{str}
-
-        @return: the name of the shared item in the sharee's home.
-        @rtype: a L{Deferred} which fires with a L{str}
         """
         # TODO: raise a nice exception if shareeView is not, in fact, a shared
         # version of this same L{CommonHomeChild}
@@ -3245,7 +3210,7 @@
             bind.MESSAGE:summary
         }.iteritems() if v is not None])
 
-        if len(columnMap):
+        if columnMap:
 
             # Count accepted
             if status is not None:
@@ -3257,10 +3222,10 @@
             )
 
             # Update affected attributes
-            if mode is not None:
+            if bind.BIND_MODE in columnMap:
                 shareeView._bindMode = columnMap[bind.BIND_MODE]
 
-            if status is not None:
+            if bind.BIND_STATUS in columnMap:
                 shareeView._bindStatus = columnMap[bind.BIND_STATUS]
                 yield shareeView._changedStatus(previouslyAcceptedCount)
 
@@ -3385,6 +3350,7 @@
 
     @inlineCallbacks
     def _initBindRevision(self):
+        yield self.syncToken() # init self._syncTokenRevision if None
         self._bindRevision = self._syncTokenRevision
 
         bind = self._bindSchema
@@ -4441,12 +4407,77 @@
         @type revision: C{int}
         """
 
-        if revision < self._bindRevision:
-            revision = 0
+        if revision != 0 and revision < self._bindRevision:
+            raise SyncTokenValidException
+
         return super(CommonHomeChild, self).resourceNamesSinceRevision(revision)
 
 
     @inlineCallbacks
+    def sharedChildResourceNamesSinceRevision(self, revision, depth):
+        """
+        Determine the list of child resources that have changed since the specified sync revision.
+        We do the same SQL query for both depth "1" and "infinity", but filter the results for
+        "1" to only account for a collection change.
+
+        We need to handle shared collection a little differently from owned ones. When a shared collection
+        is bound into a home we record a revision for it using the sharee home id and sharee collection name.
+        That revision is the "starting point" for changes: so if sync occurs with a revision earlier than
+        that, we return the list of all resources in the shared collection since they are all "new" as far
+        as the client is concerned since the shared collection has just appeared. For a later revision, we
+        just report the changes since that one. When a shared collection is removed from a home, we again
+        record a revision for the sharee home and sharee collection name with the "deleted" flag set. That way
+        the shared collection can be reported as removed.
+
+        @param revision: the sync revision to compare to
+        @type revision: C{str}
+        @param depth: depth for determine what changed
+        @type depth: C{str}
+        """
+        assert not self.owned()
+
+        if revision != 0 and revision < self._bindRevision:
+            if depth != '1':
+                raise SyncTokenValidException
+            else:
+                revision = 0
+
+        changed = set()
+        deleted = set()
+        rev = self._revisionsSchema
+        results = [
+            (
+                self.name(),
+                name if name else "",
+                wasdeleted
+            )
+            for name, wasdeleted in
+            (yield Select([rev.RESOURCE_NAME, rev.DELETED],
+                             From=rev,
+                            Where=(rev.REVISION > revision).And(
+                            rev.RESOURCE_ID == self._resourceID)).on(self._txn))
+            if name
+        ]
+
+        for path, name, wasdeleted in results:
+            if wasdeleted:
+                if revision:
+                    if depth == "1":
+                        changed.add("%s/" % (path,))
+                    else:
+                        deleted.add("%s/%s" % (path, name,))
+
+            # Always report collection as changed
+            changed.add("%s/" % (path,))
+
+            # Resource changed - for depth "infinity" report resource as changed
+            if depth != "1":
+                changed.add("%s/%s" % (path, name,))
+
+        returnValue((changed, deleted))
+
+
+    @inlineCallbacks
     def _loadPropertyStore(self, props=None):
         if props is None:
             props = yield PropertyStore.load(

Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql	2013-12-19 06:18:02 UTC (rev 12143)
@@ -256,10 +256,12 @@
 insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('resource', 2);
 insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('location', 3);
 create table ABO_MEMBERS (
-    "GROUP_ID" integer not null references ADDRESSBOOK_OBJECT on delete cascade,
+    "GROUP_ID" integer not null,
     "ADDRESSBOOK_ID" integer not null references ADDRESSBOOK_HOME on delete cascade,
-    "MEMBER_ID" integer not null references ADDRESSBOOK_OBJECT, 
-    primary key("GROUP_ID", "MEMBER_ID")
+    "MEMBER_ID" integer not null,
+    "REVISION" integer not null,
+    "REMOVED" integer default 0 not null, 
+    primary key("GROUP_ID", "MEMBER_ID", "REVISION")
 );
 
 create table ABO_FOREIGN_MEMBERS (
@@ -294,6 +296,7 @@
     "ADDRESSBOOK_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME,
     "OWNER_HOME_RESOURCE_ID" integer references ADDRESSBOOK_HOME,
     "ADDRESSBOOK_NAME" nvarchar2(255) default null,
+    "OBJECT_RESOURCE_ID" integer default 0,
     "RESOURCE_NAME" nvarchar2(255),
     "REVISION" integer not null,
     "DELETED" integer not null
@@ -370,7 +373,7 @@
     "VALUE" nvarchar2(255)
 );
 
-insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '29');
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '30');
 insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '5');
 insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
 insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');

Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql	2013-12-19 04:51:29 UTC (rev 12142)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql	2013-12-19 06:18:02 UTC (rev 12143)
@@ -454,16 +454,24 @@
 insert into ADDRESSBOOK_OBJECT_KIND values (3, 'location');
 
 
+----------------------------------
+-- Revisions, forward reference --
+----------------------------------
+
+create sequence REVISION_SEQ;
+
 ---------------------------------
 -- Address Book Object Members --
 ---------------------------------
 
 create table ABO_MEMBERS (
-    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') RESOURCE_ID
+    GROUP_ID              integer      not null, -- references ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') RESOURCE_ID
  	ADDRESSBOOK_ID		  integer      not null references ADDRESSBOOK_HOME on delete cascade,
-    MEMBER_ID             integer      not null references ADDRESSBOOK_OBJECT,						-- member AddressBook Object's RESOURCE_ID
+    MEMBER_ID             integer      not null, -- references ADDRESSBOOK_OBJECT,						-- member AddressBook Object's RESOURCE_ID
+  	REVISION              integer      default nextval('REVISION_SEQ') not null,
+  	REMOVED               boolean      default false not null,
 
-    primary key (GROUP_ID, MEMBER_ID) -- implicit index
+    primary key (GROUP_ID, MEMBER_ID, REVISION) -- implicit index
 );
 
 create index ABO_MEMBERS_ADDRESSBOOK_ID on
@@ -502,7 +510,7 @@
   MESSAGE                      		text,                  -- FIXME: xml?
 
   primary key (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_RESOURCE_ID), -- implicit index
-  unique (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_ADDRESSBOOK_NAME)     -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_ADDRESSBOOK_NAME)  -- implicit index
 );
 
 create index SHARED_GROUP_BIND_RESOURCE_ID on
@@ -513,7 +521,7 @@
 -- Revisions --
 ---------------
 
-create sequence REVISION_SEQ;
+-- create sequence REVISION_SEQ;
 
 
 -------------------------------
@@ -547,6 +555,7 @@
   ADDRESSBOOK_HOME_RESOURCE_ID 			integer			not null references ADDRESSBOOK_HOME,
   OWNER_HOME_RESOURCE_ID    			integer     	references ADDRESSBOOK_HOME,
   ADDRESSBOOK_NAME             			varchar(255) 	default null,
+  OBJECT_RESOURCE_ID					integer			default 0,
   RESOURCE_NAME                			varchar(255),
   REVISION                     			integer     	default nextval('REVISION_SEQ') not null,
   DELETED                      			boolean      	not null
@@ -702,7 +711,7 @@
   VALUE                         varchar(255)
 );
 
-insert into CALENDARSERVER values ('VERSION', '29');
+insert into CALENDARSERVER values ('VERSION', '30');
 insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
 insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
 insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v29.sql (from rev 12142, CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/old/oracle-dialect/v29.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v29.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v29.sql	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,505 @@
+create sequence RESOURCE_ID_SEQ;
+create sequence INSTANCE_ID_SEQ;
+create sequence ATTACHMENT_ID_SEQ;
+create sequence REVISION_SEQ;
+create sequence WORKITEM_SEQ;
+create table NODE_INFO (
+    "HOSTNAME" nvarchar2(255),
+    "PID" integer not null,
+    "PORT" integer not null,
+    "TIME" timestamp default CURRENT_TIMESTAMP at time zone 'UTC' not null, 
+    primary key("HOSTNAME", "PORT")
+);
+
+create table NAMED_LOCK (
+    "LOCK_NAME" nvarchar2(255) primary key
+);
+
+create table CALENDAR_HOME (
+    "RESOURCE_ID" integer primary key,
+    "OWNER_UID" nvarchar2(255) unique,
+    "DATAVERSION" integer default 0 not null
+);
+
+create table CALENDAR (
+    "RESOURCE_ID" integer primary key
+);
+
+create table CALENDAR_HOME_METADATA (
+    "RESOURCE_ID" integer primary key references CALENDAR_HOME on delete cascade,
+    "QUOTA_USED_BYTES" integer default 0 not null,
+    "DEFAULT_EVENTS" integer default null references CALENDAR on delete set null,
+    "DEFAULT_TASKS" integer default null references CALENDAR on delete set null,
+    "DEFAULT_POLLS" integer default null references CALENDAR on delete set null,
+    "ALARM_VEVENT_TIMED" nclob default null,
+    "ALARM_VEVENT_ALLDAY" nclob default null,
+    "ALARM_VTODO_TIMED" nclob default null,
+    "ALARM_VTODO_ALLDAY" nclob default null,
+    "AVAILABILITY" nclob default null,
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table CALENDAR_METADATA (
+    "RESOURCE_ID" integer primary key references CALENDAR on delete cascade,
+    "SUPPORTED_COMPONENTS" nvarchar2(255) default null,
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table NOTIFICATION_HOME (
+    "RESOURCE_ID" integer primary key,
+    "OWNER_UID" nvarchar2(255) unique,
+    "DATAVERSION" integer default 0 not null
+);
+
+create table NOTIFICATION (
+    "RESOURCE_ID" integer primary key,
+    "NOTIFICATION_HOME_RESOURCE_ID" integer not null references NOTIFICATION_HOME,
+    "NOTIFICATION_UID" nvarchar2(255),
+    "NOTIFICATION_TYPE" nvarchar2(255),
+    "NOTIFICATION_DATA" nclob,
+    "MD5" nchar(32),
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique("NOTIFICATION_UID", "NOTIFICATION_HOME_RESOURCE_ID")
+);
+
+create table CALENDAR_BIND (
+    "CALENDAR_HOME_RESOURCE_ID" integer not null references CALENDAR_HOME,
+    "CALENDAR_RESOURCE_ID" integer not null references CALENDAR on delete cascade,
+    "CALENDAR_RESOURCE_NAME" nvarchar2(255),
+    "BIND_MODE" integer not null,
+    "BIND_STATUS" integer not null,
+    "BIND_REVISION" integer default 0 not null,
+    "MESSAGE" nclob,
+    "TRANSP" integer default 0 not null,
+    "ALARM_VEVENT_TIMED" nclob default null,
+    "ALARM_VEVENT_ALLDAY" nclob default null,
+    "ALARM_VTODO_TIMED" nclob default null,
+    "ALARM_VTODO_ALLDAY" nclob default null,
+    "TIMEZONE" nclob default null, 
+    primary key("CALENDAR_HOME_RESOURCE_ID", "CALENDAR_RESOURCE_ID"), 
+    unique("CALENDAR_HOME_RESOURCE_ID", "CALENDAR_RESOURCE_NAME")
+);
+
+create table CALENDAR_BIND_MODE (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('own', 0);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('read', 1);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('write', 2);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('direct', 3);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('indirect', 4);
+create table CALENDAR_BIND_STATUS (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('invited', 0);
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('accepted', 1);
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('declined', 2);
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('invalid', 3);
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('deleted', 4);
+create table CALENDAR_TRANSP (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into CALENDAR_TRANSP (DESCRIPTION, ID) values ('opaque', 0);
+insert into CALENDAR_TRANSP (DESCRIPTION, ID) values ('transparent', 1);
+create table CALENDAR_OBJECT (
+    "RESOURCE_ID" integer primary key,
+    "CALENDAR_RESOURCE_ID" integer not null references CALENDAR on delete cascade,
+    "RESOURCE_NAME" nvarchar2(255),
+    "ICALENDAR_TEXT" nclob,
+    "ICALENDAR_UID" nvarchar2(255),
+    "ICALENDAR_TYPE" nvarchar2(255),
+    "ATTACHMENTS_MODE" integer default 0 not null,
+    "DROPBOX_ID" nvarchar2(255),
+    "ORGANIZER" nvarchar2(255),
+    "RECURRANCE_MIN" date,
+    "RECURRANCE_MAX" date,
+    "ACCESS" integer default 0 not null,
+    "SCHEDULE_OBJECT" integer default 0,
+    "SCHEDULE_TAG" nvarchar2(36) default null,
+    "SCHEDULE_ETAGS" nclob default null,
+    "PRIVATE_COMMENTS" integer default 0 not null,
+    "MD5" nchar(32),
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique("CALENDAR_RESOURCE_ID", "RESOURCE_NAME")
+);
+
+create table CALENDAR_OBJECT_ATTACHMENTS_MO (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into CALENDAR_OBJECT_ATTACHMENTS_MO (DESCRIPTION, ID) values ('none', 0);
+insert into CALENDAR_OBJECT_ATTACHMENTS_MO (DESCRIPTION, ID) values ('read', 1);
+insert into CALENDAR_OBJECT_ATTACHMENTS_MO (DESCRIPTION, ID) values ('write', 2);
+create table CALENDAR_ACCESS_TYPE (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(32) unique
+);
+
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('', 0);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('public', 1);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('private', 2);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('confidential', 3);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('restricted', 4);
+create table TIME_RANGE (
+    "INSTANCE_ID" integer primary key,
+    "CALENDAR_RESOURCE_ID" integer not null references CALENDAR on delete cascade,
+    "CALENDAR_OBJECT_RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "FLOATING" integer not null,
+    "START_DATE" timestamp not null,
+    "END_DATE" timestamp not null,
+    "FBTYPE" integer not null,
+    "TRANSPARENT" integer not null
+);
+
+create table FREE_BUSY_TYPE (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('unknown', 0);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('free', 1);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('busy', 2);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('busy-unavailable', 3);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('busy-tentative', 4);
+create table TRANSPARENCY (
+    "TIME_RANGE_INSTANCE_ID" integer not null references TIME_RANGE on delete cascade,
+    "USER_ID" nvarchar2(255),
+    "TRANSPARENT" integer not null
+);
+
+create table ATTACHMENT (
+    "ATTACHMENT_ID" integer primary key,
+    "CALENDAR_HOME_RESOURCE_ID" integer not null references CALENDAR_HOME,
+    "DROPBOX_ID" nvarchar2(255),
+    "CONTENT_TYPE" nvarchar2(255),
+    "SIZE" integer not null,
+    "MD5" nchar(32),
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "PATH" nvarchar2(1024)
+);
+
+create table ATTACHMENT_CALENDAR_OBJECT (
+    "ATTACHMENT_ID" integer not null references ATTACHMENT on delete cascade,
+    "MANAGED_ID" nvarchar2(255),
+    "CALENDAR_OBJECT_RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade, 
+    primary key("ATTACHMENT_ID", "CALENDAR_OBJECT_RESOURCE_ID"), 
+    unique("MANAGED_ID", "CALENDAR_OBJECT_RESOURCE_ID")
+);
+
+create table RESOURCE_PROPERTY (
+    "RESOURCE_ID" integer not null,
+    "NAME" nvarchar2(255),
+    "VALUE" nclob,
+    "VIEWER_UID" nvarchar2(255), 
+    primary key("RESOURCE_ID", "NAME", "VIEWER_UID")
+);
+
+create table ADDRESSBOOK_HOME (
+    "RESOURCE_ID" integer primary key,
+    "ADDRESSBOOK_PROPERTY_STORE_ID" integer not null,
+    "OWNER_UID" nvarchar2(255) unique,
+    "DATAVERSION" integer default 0 not null
+);
+
+create table ADDRESSBOOK_HOME_METADATA (
+    "RESOURCE_ID" integer primary key references ADDRESSBOOK_HOME on delete cascade,
+    "QUOTA_USED_BYTES" integer default 0 not null,
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table SHARED_ADDRESSBOOK_BIND (
+    "ADDRESSBOOK_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME,
+    "OWNER_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME on delete cascade,
+    "ADDRESSBOOK_RESOURCE_NAME" nvarchar2(255),
+    "BIND_MODE" integer not null,
+    "BIND_STATUS" integer not null,
+    "BIND_REVISION" integer default 0 not null,
+    "MESSAGE" nclob, 
+    primary key("ADDRESSBOOK_HOME_RESOURCE_ID", "OWNER_HOME_RESOURCE_ID"), 
+    unique("ADDRESSBOOK_HOME_RESOURCE_ID", "ADDRESSBOOK_RESOURCE_NAME")
+);
+
+create table ADDRESSBOOK_OBJECT (
+    "RESOURCE_ID" integer primary key,
+    "ADDRESSBOOK_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME on delete cascade,
+    "RESOURCE_NAME" nvarchar2(255),
+    "VCARD_TEXT" nclob,
+    "VCARD_UID" nvarchar2(255),
+    "KIND" integer not null,
+    "MD5" nchar(32),
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique("ADDRESSBOOK_HOME_RESOURCE_ID", "RESOURCE_NAME"), 
+    unique("ADDRESSBOOK_HOME_RESOURCE_ID", "VCARD_UID")
+);
+
+create table ADDRESSBOOK_OBJECT_KIND (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('person', 0);
+insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('group', 1);
+insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('resource', 2);
+insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('location', 3);
+create table ABO_MEMBERS (
+    "GROUP_ID" integer not null references ADDRESSBOOK_OBJECT on delete cascade,
+    "ADDRESSBOOK_ID" integer not null references ADDRESSBOOK_HOME on delete cascade,
+    "MEMBER_ID" integer not null references ADDRESSBOOK_OBJECT, 
+    primary key("GROUP_ID", "MEMBER_ID")
+);
+
+create table ABO_FOREIGN_MEMBERS (
+    "GROUP_ID" integer not null references ADDRESSBOOK_OBJECT on delete cascade,
+    "ADDRESSBOOK_ID" integer not null references ADDRESSBOOK_HOME on delete cascade,
+    "MEMBER_ADDRESS" nvarchar2(255), 
+    primary key("GROUP_ID", "MEMBER_ADDRESS")
+);
+
+create table SHARED_GROUP_BIND (
+    "ADDRESSBOOK_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME,
+    "GROUP_RESOURCE_ID" integer not null references ADDRESSBOOK_OBJECT on delete cascade,
+    "GROUP_ADDRESSBOOK_NAME" nvarchar2(255),
+    "BIND_MODE" integer not null,
+    "BIND_STATUS" integer not null,
+    "BIND_REVISION" integer default 0 not null,
+    "MESSAGE" nclob, 
+    primary key("ADDRESSBOOK_HOME_RESOURCE_ID", "GROUP_RESOURCE_ID"), 
+    unique("ADDRESSBOOK_HOME_RESOURCE_ID", "GROUP_ADDRESSBOOK_NAME")
+);
+
+create table CALENDAR_OBJECT_REVISIONS (
+    "CALENDAR_HOME_RESOURCE_ID" integer not null references CALENDAR_HOME,
+    "CALENDAR_RESOURCE_ID" integer references CALENDAR,
+    "CALENDAR_NAME" nvarchar2(255) default null,
+    "RESOURCE_NAME" nvarchar2(255),
+    "REVISION" integer not null,
+    "DELETED" integer not null
+);
+
+create table ADDRESSBOOK_OBJECT_REVISIONS (
+    "ADDRESSBOOK_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME,
+    "OWNER_HOME_RESOURCE_ID" integer references ADDRESSBOOK_HOME,
+    "ADDRESSBOOK_NAME" nvarchar2(255) default null,
+    "RESOURCE_NAME" nvarchar2(255),
+    "REVISION" integer not null,
+    "DELETED" integer not null
+);
+
+create table NOTIFICATION_OBJECT_REVISIONS (
+    "NOTIFICATION_HOME_RESOURCE_ID" integer not null references NOTIFICATION_HOME on delete cascade,
+    "RESOURCE_NAME" nvarchar2(255),
+    "REVISION" integer not null,
+    "DELETED" integer not null, 
+    unique("NOTIFICATION_HOME_RESOURCE_ID", "RESOURCE_NAME")
+);
+
+create table APN_SUBSCRIPTIONS (
+    "TOKEN" nvarchar2(255),
+    "RESOURCE_KEY" nvarchar2(255),
+    "MODIFIED" integer not null,
+    "SUBSCRIBER_GUID" nvarchar2(255),
+    "USER_AGENT" nvarchar2(255) default null,
+    "IP_ADDR" nvarchar2(255) default null, 
+    primary key("TOKEN", "RESOURCE_KEY")
+);
+
+create table IMIP_TOKENS (
+    "TOKEN" nvarchar2(255),
+    "ORGANIZER" nvarchar2(255),
+    "ATTENDEE" nvarchar2(255),
+    "ICALUID" nvarchar2(255),
+    "ACCESSED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    primary key("ORGANIZER", "ATTENDEE", "ICALUID")
+);
+
+create table IMIP_INVITATION_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "FROM_ADDR" nvarchar2(255),
+    "TO_ADDR" nvarchar2(255),
+    "ICALENDAR_TEXT" nclob
+);
+
+create table IMIP_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table IMIP_REPLY_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ORGANIZER" nvarchar2(255),
+    "ATTENDEE" nvarchar2(255),
+    "ICALENDAR_TEXT" nclob
+);
+
+create table PUSH_NOTIFICATION_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "PUSH_ID" nvarchar2(255),
+    "PRIORITY" integer not null
+);
+
+create table GROUP_CACHER_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade
+);
+
+create table CALENDARSERVER (
+    "NAME" nvarchar2(255) primary key,
+    "VALUE" nvarchar2(255)
+);
+
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '29');
+insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '5');
+insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
+create index CALENDAR_HOME_METADAT_3cb9049e on CALENDAR_HOME_METADATA (
+    DEFAULT_EVENTS
+);
+
+create index CALENDAR_HOME_METADAT_d55e5548 on CALENDAR_HOME_METADATA (
+    DEFAULT_TASKS
+);
+
+create index CALENDAR_HOME_METADAT_910264ce on CALENDAR_HOME_METADATA (
+    DEFAULT_POLLS
+);
+
+create index NOTIFICATION_NOTIFICA_f891f5f9 on NOTIFICATION (
+    NOTIFICATION_HOME_RESOURCE_ID
+);
+
+create index CALENDAR_BIND_RESOURC_e57964d4 on CALENDAR_BIND (
+    CALENDAR_RESOURCE_ID
+);
+
+create index CALENDAR_OBJECT_CALEN_a9a453a9 on CALENDAR_OBJECT (
+    CALENDAR_RESOURCE_ID,
+    ICALENDAR_UID
+);
+
+create index CALENDAR_OBJECT_CALEN_96e83b73 on CALENDAR_OBJECT (
+    CALENDAR_RESOURCE_ID,
+    RECURRANCE_MAX
+);
+
+create index CALENDAR_OBJECT_ICALE_82e731d5 on CALENDAR_OBJECT (
+    ICALENDAR_UID
+);
+
+create index CALENDAR_OBJECT_DROPB_de041d80 on CALENDAR_OBJECT (
+    DROPBOX_ID
+);
+
+create index TIME_RANGE_CALENDAR_R_beb6e7eb on TIME_RANGE (
+    CALENDAR_RESOURCE_ID
+);
+
+create index TIME_RANGE_CALENDAR_O_acf37bd1 on TIME_RANGE (
+    CALENDAR_OBJECT_RESOURCE_ID
+);
+
+create index TRANSPARENCY_TIME_RAN_5f34467f on TRANSPARENCY (
+    TIME_RANGE_INSTANCE_ID
+);
+
+create index ATTACHMENT_CALENDAR_H_0078845c on ATTACHMENT (
+    CALENDAR_HOME_RESOURCE_ID
+);
+
+create index ATTACHMENT_CALENDAR_O_81508484 on ATTACHMENT_CALENDAR_OBJECT (
+    CALENDAR_OBJECT_RESOURCE_ID
+);
+
+create index SHARED_ADDRESSBOOK_BI_e9a2e6d4 on SHARED_ADDRESSBOOK_BIND (
+    OWNER_HOME_RESOURCE_ID
+);
+
+create index ABO_MEMBERS_ADDRESSBO_4effa879 on ABO_MEMBERS (
+    ADDRESSBOOK_ID
+);
+
+create index ABO_MEMBERS_MEMBER_ID_8d66adcf on ABO_MEMBERS (
+    MEMBER_ID
+);
+
+create index ABO_FOREIGN_MEMBERS_A_1fd2c5e9 on ABO_FOREIGN_MEMBERS (
+    ADDRESSBOOK_ID
+);
+
+create index SHARED_GROUP_BIND_RES_cf52f95d on SHARED_GROUP_BIND (
+    GROUP_RESOURCE_ID
+);
+
+create index CALENDAR_OBJECT_REVIS_3a3956c4 on CALENDAR_OBJECT_REVISIONS (
+    CALENDAR_HOME_RESOURCE_ID,
+    CALENDAR_RESOURCE_ID
+);
+
+create index CALENDAR_OBJECT_REVIS_6d9d929c on CALENDAR_OBJECT_REVISIONS (
+    CALENDAR_RESOURCE_ID,
+    RESOURCE_NAME,
+    DELETED,
+    REVISION
+);
+
+create index CALENDAR_OBJECT_REVIS_265c8acf on CALENDAR_OBJECT_REVISIONS (
+    CALENDAR_RESOURCE_ID,
+    REVISION
+);
+
+create index ADDRESSBOOK_OBJECT_RE_2bfcf757 on ADDRESSBOOK_OBJECT_REVISIONS (
+    ADDRESSBOOK_HOME_RESOURCE_ID,
+    OWNER_HOME_RESOURCE_ID
+);
+
+create index ADDRESSBOOK_OBJECT_RE_00fe8288 on ADDRESSBOOK_OBJECT_REVISIONS (
+    OWNER_HOME_RESOURCE_ID,
+    RESOURCE_NAME,
+    DELETED,
+    REVISION
+);
+
+create index ADDRESSBOOK_OBJECT_RE_45004780 on ADDRESSBOOK_OBJECT_REVISIONS (
+    OWNER_HOME_RESOURCE_ID,
+    REVISION
+);
+
+create index NOTIFICATION_OBJECT_R_036a9cee on NOTIFICATION_OBJECT_REVISIONS (
+    NOTIFICATION_HOME_RESOURCE_ID,
+    REVISION
+);
+
+create index APN_SUBSCRIPTIONS_RES_9610d78e on APN_SUBSCRIPTIONS (
+    RESOURCE_KEY
+);
+
+create index IMIP_TOKENS_TOKEN_e94b918f on IMIP_TOKENS (
+    TOKEN
+);
+
+create index CALENDAR_OBJECT_SPLIT_af71dcda on CALENDAR_OBJECT_SPLITTER_WORK (
+    RESOURCE_ID
+);
+

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v29.sql (from rev 12142, CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/old/postgres-dialect/v29.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v29.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v29.sql	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,708 @@
+-- -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
+
+----
+-- Copyright (c) 2010-2013 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.
+----
+
+
+-----------------
+-- Resource ID --
+-----------------
+
+create sequence RESOURCE_ID_SEQ;
+
+
+-------------------------
+-- Cluster Bookkeeping --
+-------------------------
+
+-- Information about a process connected to this database.
+
+-- Note that this must match the node info schema in twext.enterprise.queue.
+create table NODE_INFO (
+  HOSTNAME  varchar(255) not null,
+  PID       integer      not null,
+  PORT      integer      not null,
+  TIME      timestamp    not null default timezone('UTC', CURRENT_TIMESTAMP),
+
+  primary key (HOSTNAME, PORT)
+);
+
+-- Unique named locks.  This table should always be empty, but rows are
+-- temporarily created in order to prevent undesirable concurrency.
+create table NAMED_LOCK (
+    LOCK_NAME varchar(255) primary key
+);
+
+
+-------------------
+-- Calendar Home --
+-------------------
+
+create table CALENDAR_HOME (
+  RESOURCE_ID      integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  OWNER_UID        varchar(255) not null unique,                                 -- implicit index
+  DATAVERSION      integer      default 0 not null
+);
+
+--------------
+-- Calendar --
+--------------
+
+create table CALENDAR (
+  RESOURCE_ID integer   primary key default nextval('RESOURCE_ID_SEQ') -- implicit index
+);
+
+----------------------------
+-- Calendar Home Metadata --
+----------------------------
+
+create table CALENDAR_HOME_METADATA (
+  RESOURCE_ID              integer     primary key references CALENDAR_HOME on delete cascade, -- implicit index
+  QUOTA_USED_BYTES         integer     default 0 not null,
+  DEFAULT_EVENTS           integer     default null references CALENDAR on delete set null,
+  DEFAULT_TASKS            integer     default null references CALENDAR on delete set null,
+  DEFAULT_POLLS            integer     default null references CALENDAR on delete set null,
+  ALARM_VEVENT_TIMED       text        default null,
+  ALARM_VEVENT_ALLDAY      text        default null,
+  ALARM_VTODO_TIMED        text        default null,
+  ALARM_VTODO_ALLDAY       text        default null,
+  AVAILABILITY             text        default null,
+  CREATED                  timestamp   default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                 timestamp   default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+create index CALENDAR_HOME_METADATA_DEFAULT_EVENTS on
+	CALENDAR_HOME_METADATA(DEFAULT_EVENTS);
+create index CALENDAR_HOME_METADATA_DEFAULT_TASKS on
+	CALENDAR_HOME_METADATA(DEFAULT_TASKS);
+create index CALENDAR_HOME_METADATA_DEFAULT_POLLS on
+	CALENDAR_HOME_METADATA(DEFAULT_POLLS);
+
+-----------------------
+-- Calendar Metadata --
+-----------------------
+
+create table CALENDAR_METADATA (
+  RESOURCE_ID           integer      primary key references CALENDAR on delete cascade, -- implicit index
+  SUPPORTED_COMPONENTS  varchar(255) default null,
+  CREATED               timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+---------------------------
+-- Sharing Notifications --
+---------------------------
+
+create table NOTIFICATION_HOME (
+  RESOURCE_ID integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  OWNER_UID   varchar(255) not null unique,                                -- implicit index
+  DATAVERSION integer      default 0 not null
+);
+
+create table NOTIFICATION (
+  RESOURCE_ID                   integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  NOTIFICATION_HOME_RESOURCE_ID integer      not null references NOTIFICATION_HOME,
+  NOTIFICATION_UID              varchar(255) not null,
+  NOTIFICATION_TYPE             varchar(255) not null,
+  NOTIFICATION_DATA             text         not null,
+  MD5                           char(32)     not null,
+  CREATED                       timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique(NOTIFICATION_UID, NOTIFICATION_HOME_RESOURCE_ID) -- implicit index
+);
+
+create index NOTIFICATION_NOTIFICATION_HOME_RESOURCE_ID on
+	NOTIFICATION(NOTIFICATION_HOME_RESOURCE_ID);
+
+
+-------------------
+-- Calendar Bind --
+-------------------
+
+-- Joins CALENDAR_HOME and CALENDAR
+
+create table CALENDAR_BIND (
+  CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
+  CALENDAR_RESOURCE_ID      integer      not null references CALENDAR on delete cascade,
+  CALENDAR_RESOURCE_NAME    varchar(255) not null,
+  BIND_MODE                 integer      not null, -- enum CALENDAR_BIND_MODE
+  BIND_STATUS               integer      not null, -- enum CALENDAR_BIND_STATUS
+  BIND_REVISION				integer      default 0 not null,
+  MESSAGE                   text,
+  TRANSP                    integer      default 0 not null, -- enum CALENDAR_TRANSP
+  ALARM_VEVENT_TIMED        text         default null,
+  ALARM_VEVENT_ALLDAY       text         default null,
+  ALARM_VTODO_TIMED         text         default null,
+  ALARM_VTODO_ALLDAY        text         default null,
+  TIMEZONE                  text         default null,
+
+  primary key(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID), -- implicit index
+  unique(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_NAME)     -- implicit index
+);
+
+create index CALENDAR_BIND_RESOURCE_ID on
+	CALENDAR_BIND(CALENDAR_RESOURCE_ID);
+
+-- Enumeration of calendar bind modes
+
+create table CALENDAR_BIND_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_BIND_MODE values (0, 'own'  );
+insert into CALENDAR_BIND_MODE values (1, 'read' );
+insert into CALENDAR_BIND_MODE values (2, 'write');
+insert into CALENDAR_BIND_MODE values (3, 'direct');
+insert into CALENDAR_BIND_MODE values (4, 'indirect');
+
+-- Enumeration of statuses
+
+create table CALENDAR_BIND_STATUS (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_BIND_STATUS values (0, 'invited' );
+insert into CALENDAR_BIND_STATUS values (1, 'accepted');
+insert into CALENDAR_BIND_STATUS values (2, 'declined');
+insert into CALENDAR_BIND_STATUS values (3, 'invalid');
+insert into CALENDAR_BIND_STATUS values (4, 'deleted');
+
+
+-- Enumeration of transparency
+
+create table CALENDAR_TRANSP (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_TRANSP values (0, 'opaque' );
+insert into CALENDAR_TRANSP values (1, 'transparent');
+
+
+---------------------
+-- Calendar Object --
+---------------------
+
+create table CALENDAR_OBJECT (
+  RESOURCE_ID          integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  CALENDAR_RESOURCE_ID integer      not null references CALENDAR on delete cascade,
+  RESOURCE_NAME        varchar(255) not null,
+  ICALENDAR_TEXT       text         not null,
+  ICALENDAR_UID        varchar(255) not null,
+  ICALENDAR_TYPE       varchar(255) not null,
+  ATTACHMENTS_MODE     integer      default 0 not null, -- enum CALENDAR_OBJECT_ATTACHMENTS_MODE
+  DROPBOX_ID           varchar(255),
+  ORGANIZER            varchar(255),
+  RECURRANCE_MIN       date,        -- minimum date that recurrences have been expanded to.
+  RECURRANCE_MAX       date,        -- maximum date that recurrences have been expanded to.
+  ACCESS               integer      default 0 not null,
+  SCHEDULE_OBJECT      boolean      default false,
+  SCHEDULE_TAG         varchar(36)  default null,
+  SCHEDULE_ETAGS       text         default null,
+  PRIVATE_COMMENTS     boolean      default false not null,
+  MD5                  char(32)     not null,
+  CREATED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED             timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique (CALENDAR_RESOURCE_ID, RESOURCE_NAME) -- implicit index
+
+  -- since the 'inbox' is a 'calendar resource' for the purpose of storing
+  -- calendar objects, this constraint has to be selectively enforced by the
+  -- application layer.
+
+  -- unique(CALENDAR_RESOURCE_ID, ICALENDAR_UID)
+);
+
+create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_AND_ICALENDAR_UID on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, ICALENDAR_UID);
+
+create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_RECURRANCE_MAX on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, RECURRANCE_MAX);
+
+create index CALENDAR_OBJECT_ICALENDAR_UID on
+  CALENDAR_OBJECT(ICALENDAR_UID);
+
+create index CALENDAR_OBJECT_DROPBOX_ID on
+  CALENDAR_OBJECT(DROPBOX_ID);
+
+-- Enumeration of attachment modes
+
+create table CALENDAR_OBJECT_ATTACHMENTS_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (0, 'none' );
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (1, 'read' );
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (2, 'write');
+
+
+-- Enumeration of calendar access types
+
+create table CALENDAR_ACCESS_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(32) not null unique
+);
+
+insert into CALENDAR_ACCESS_TYPE values (0, ''             );
+insert into CALENDAR_ACCESS_TYPE values (1, 'public'       );
+insert into CALENDAR_ACCESS_TYPE values (2, 'private'      );
+insert into CALENDAR_ACCESS_TYPE values (3, 'confidential' );
+insert into CALENDAR_ACCESS_TYPE values (4, 'restricted'   );
+
+
+-----------------
+-- Instance ID --
+-----------------
+
+create sequence INSTANCE_ID_SEQ;
+
+
+----------------
+-- Time Range --
+----------------
+
+create table TIME_RANGE (
+  INSTANCE_ID                 integer        primary key default nextval('INSTANCE_ID_SEQ'), -- implicit index
+  CALENDAR_RESOURCE_ID        integer        not null references CALENDAR on delete cascade,
+  CALENDAR_OBJECT_RESOURCE_ID integer        not null references CALENDAR_OBJECT on delete cascade,
+  FLOATING                    boolean        not null,
+  START_DATE                  timestamp      not null,
+  END_DATE                    timestamp      not null,
+  FBTYPE                      integer        not null,
+  TRANSPARENT                 boolean        not null
+);
+
+create index TIME_RANGE_CALENDAR_RESOURCE_ID on
+  TIME_RANGE(CALENDAR_RESOURCE_ID);
+create index TIME_RANGE_CALENDAR_OBJECT_RESOURCE_ID on
+  TIME_RANGE(CALENDAR_OBJECT_RESOURCE_ID);
+
+
+-- Enumeration of free/busy types
+
+create table FREE_BUSY_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into FREE_BUSY_TYPE values (0, 'unknown'         );
+insert into FREE_BUSY_TYPE values (1, 'free'            );
+insert into FREE_BUSY_TYPE values (2, 'busy'            );
+insert into FREE_BUSY_TYPE values (3, 'busy-unavailable');
+insert into FREE_BUSY_TYPE values (4, 'busy-tentative'  );
+
+
+------------------
+-- Transparency --
+------------------
+
+create table TRANSPARENCY (
+  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
+  USER_ID                     varchar(255) not null,
+  TRANSPARENT                 boolean      not null
+);
+
+create index TRANSPARENCY_TIME_RANGE_INSTANCE_ID on
+  TRANSPARENCY(TIME_RANGE_INSTANCE_ID);
+
+
+----------------
+-- Attachment --
+----------------
+
+create sequence ATTACHMENT_ID_SEQ;
+
+create table ATTACHMENT (
+  ATTACHMENT_ID               integer           primary key default nextval('ATTACHMENT_ID_SEQ'), -- implicit index
+  CALENDAR_HOME_RESOURCE_ID   integer           not null references CALENDAR_HOME,
+  DROPBOX_ID                  varchar(255),
+  CONTENT_TYPE                varchar(255)      not null,
+  SIZE                        integer           not null,
+  MD5                         char(32)          not null,
+  CREATED                     timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                    timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  PATH                        varchar(1024)     not null
+);
+
+create index ATTACHMENT_CALENDAR_HOME_RESOURCE_ID on
+  ATTACHMENT(CALENDAR_HOME_RESOURCE_ID);
+
+-- Many-to-many relationship between attachments and calendar objects
+create table ATTACHMENT_CALENDAR_OBJECT (
+  ATTACHMENT_ID                  integer      not null references ATTACHMENT on delete cascade,
+  MANAGED_ID                     varchar(255) not null,
+  CALENDAR_OBJECT_RESOURCE_ID    integer      not null references CALENDAR_OBJECT on delete cascade,
+
+  primary key (ATTACHMENT_ID, CALENDAR_OBJECT_RESOURCE_ID), -- implicit index
+  unique (MANAGED_ID, CALENDAR_OBJECT_RESOURCE_ID) --implicit index
+);
+
+create index ATTACHMENT_CALENDAR_OBJECT_CALENDAR_OBJECT_RESOURCE_ID on
+	ATTACHMENT_CALENDAR_OBJECT(CALENDAR_OBJECT_RESOURCE_ID);
+
+-----------------------
+-- Resource Property --
+-----------------------
+
+create table RESOURCE_PROPERTY (
+  RESOURCE_ID integer      not null, -- foreign key: *.RESOURCE_ID
+  NAME        varchar(255) not null,
+  VALUE       text         not null, -- FIXME: xml?
+  VIEWER_UID  varchar(255),
+
+  primary key (RESOURCE_ID, NAME, VIEWER_UID) -- implicit index
+);
+
+
+----------------------
+-- AddressBook Home --
+----------------------
+
+create table ADDRESSBOOK_HOME (
+  RESOURCE_ID      				integer			primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  ADDRESSBOOK_PROPERTY_STORE_ID	integer      	default nextval('RESOURCE_ID_SEQ') not null, 	-- implicit index
+  OWNER_UID        				varchar(255) 	not null unique,                                -- implicit index
+  DATAVERSION      				integer      	default 0 not null
+);
+
+
+-------------------------------
+-- AddressBook Home Metadata --
+-------------------------------
+
+create table ADDRESSBOOK_HOME_METADATA (
+  RESOURCE_ID      integer      primary key references ADDRESSBOOK_HOME on delete cascade, -- implicit index
+  QUOTA_USED_BYTES integer      default 0 not null,
+  CREATED          timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED         timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+-----------------------------
+-- Shared AddressBook Bind --
+-----------------------------
+
+-- Joins sharee ADDRESSBOOK_HOME and owner ADDRESSBOOK_HOME
+
+create table SHARED_ADDRESSBOOK_BIND (
+  ADDRESSBOOK_HOME_RESOURCE_ID			integer			not null references ADDRESSBOOK_HOME,
+  OWNER_HOME_RESOURCE_ID    			integer      	not null references ADDRESSBOOK_HOME on delete cascade,
+  ADDRESSBOOK_RESOURCE_NAME    			varchar(255) 	not null,
+  BIND_MODE                    			integer      	not null,	-- enum CALENDAR_BIND_MODE
+  BIND_STATUS                  			integer      	not null,	-- enum CALENDAR_BIND_STATUS
+  BIND_REVISION				   			integer      	default 0 not null,
+  MESSAGE                      			text,                  		-- FIXME: xml?
+
+  primary key (ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID), -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME)     -- implicit index
+);
+
+create index SHARED_ADDRESSBOOK_BIND_RESOURCE_ID on
+  SHARED_ADDRESSBOOK_BIND(OWNER_HOME_RESOURCE_ID);
+
+
+------------------------
+-- AddressBook Object --
+------------------------
+
+create table ADDRESSBOOK_OBJECT (
+  RESOURCE_ID             		integer   		primary key default nextval('RESOURCE_ID_SEQ'),    -- implicit index
+  ADDRESSBOOK_HOME_RESOURCE_ID 	integer      	not null references ADDRESSBOOK_HOME on delete cascade,
+  RESOURCE_NAME           		varchar(255) 	not null,
+  VCARD_TEXT              		text         	not null,
+  VCARD_UID               		varchar(255) 	not null,
+  KIND 			  		  		integer      	not null,  -- enum ADDRESSBOOK_OBJECT_KIND
+  MD5                     		char(32)     	not null,
+  CREATED                 		timestamp    	default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                		timestamp    	default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, RESOURCE_NAME), -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, VCARD_UID)      -- implicit index
+);
+
+
+-----------------------------
+-- AddressBook Object kind --
+-----------------------------
+
+create table ADDRESSBOOK_OBJECT_KIND (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into ADDRESSBOOK_OBJECT_KIND values (0, 'person');
+insert into ADDRESSBOOK_OBJECT_KIND values (1, 'group' );
+insert into ADDRESSBOOK_OBJECT_KIND values (2, 'resource');
+insert into ADDRESSBOOK_OBJECT_KIND values (3, 'location');
+
+
+---------------------------------
+-- Address Book Object Members --
+---------------------------------
+
+create table ABO_MEMBERS (
+    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') RESOURCE_ID
+ 	ADDRESSBOOK_ID		  integer      not null references ADDRESSBOOK_HOME on delete cascade,
+    MEMBER_ID             integer      not null references ADDRESSBOOK_OBJECT,						-- member AddressBook Object's RESOURCE_ID
+
+    primary key (GROUP_ID, MEMBER_ID) -- implicit index
+);
+
+create index ABO_MEMBERS_ADDRESSBOOK_ID on
+	ABO_MEMBERS(ADDRESSBOOK_ID);
+create index ABO_MEMBERS_MEMBER_ID on
+	ABO_MEMBERS(MEMBER_ID);
+
+------------------------------------------
+-- Address Book Object Foreign Members  --
+------------------------------------------
+
+create table ABO_FOREIGN_MEMBERS (
+    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') RESOURCE_ID
+ 	ADDRESSBOOK_ID		  integer      not null references ADDRESSBOOK_HOME on delete cascade,
+    MEMBER_ADDRESS  	  varchar(255) not null, 													-- member AddressBook Object's 'calendar' address
+
+    primary key (GROUP_ID, MEMBER_ADDRESS) -- implicit index
+);
+
+create index ABO_FOREIGN_MEMBERS_ADDRESSBOOK_ID on
+	ABO_FOREIGN_MEMBERS(ADDRESSBOOK_ID);
+
+-----------------------
+-- Shared Group Bind --
+-----------------------
+
+-- Joins ADDRESSBOOK_HOME and ADDRESSBOOK_OBJECT (kind == group)
+
+create table SHARED_GROUP_BIND (	
+  ADDRESSBOOK_HOME_RESOURCE_ID 		integer      not null references ADDRESSBOOK_HOME,
+  GROUP_RESOURCE_ID      			integer      not null references ADDRESSBOOK_OBJECT on delete cascade,
+  GROUP_ADDRESSBOOK_NAME			varchar(255) not null,
+  BIND_MODE                    		integer      not null, -- enum CALENDAR_BIND_MODE
+  BIND_STATUS                  		integer      not null, -- enum CALENDAR_BIND_STATUS
+  BIND_REVISION				   		integer      default 0 not null,
+  MESSAGE                      		text,                  -- FIXME: xml?
+
+  primary key (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_RESOURCE_ID), -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_ADDRESSBOOK_NAME)     -- implicit index
+);
+
+create index SHARED_GROUP_BIND_RESOURCE_ID on
+  SHARED_GROUP_BIND(GROUP_RESOURCE_ID);
+
+
+---------------
+-- Revisions --
+---------------
+
+create sequence REVISION_SEQ;
+
+
+-------------------------------
+-- Calendar Object Revisions --
+-------------------------------
+
+create table CALENDAR_OBJECT_REVISIONS (
+  CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
+  CALENDAR_RESOURCE_ID      integer      references CALENDAR,
+  CALENDAR_NAME             varchar(255) default null,
+  RESOURCE_NAME             varchar(255),
+  REVISION                  integer      default nextval('REVISION_SEQ') not null,
+  DELETED                   boolean      not null
+);
+
+create index CALENDAR_OBJECT_REVISIONS_HOME_RESOURCE_ID_CALENDAR_RESOURCE_ID
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID);
+
+create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_RESOURCE_NAME_DELETED_REVISION
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, RESOURCE_NAME, DELETED, REVISION);
+
+create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_REVISION
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, REVISION);
+
+
+----------------------------------
+-- AddressBook Object Revisions --
+----------------------------------
+
+create table ADDRESSBOOK_OBJECT_REVISIONS (
+  ADDRESSBOOK_HOME_RESOURCE_ID 			integer			not null references ADDRESSBOOK_HOME,
+  OWNER_HOME_RESOURCE_ID    			integer     	references ADDRESSBOOK_HOME,
+  ADDRESSBOOK_NAME             			varchar(255) 	default null,
+  RESOURCE_NAME                			varchar(255),
+  REVISION                     			integer     	default nextval('REVISION_SEQ') not null,
+  DELETED                      			boolean      	not null
+);
+
+create index ADDRESSBOOK_OBJECT_REVISIONS_HOME_RESOURCE_ID_OWNER_HOME_RESOURCE_ID
+  on ADDRESSBOOK_OBJECT_REVISIONS(ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID);
+
+create index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_RESOURCE_NAME_DELETED_REVISION
+  on ADDRESSBOOK_OBJECT_REVISIONS(OWNER_HOME_RESOURCE_ID, RESOURCE_NAME, DELETED, REVISION);
+
+create index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_REVISION
+  on ADDRESSBOOK_OBJECT_REVISIONS(OWNER_HOME_RESOURCE_ID, REVISION);
+
+
+-----------------------------------
+-- Notification Object Revisions --
+-----------------------------------
+
+create table NOTIFICATION_OBJECT_REVISIONS (
+  NOTIFICATION_HOME_RESOURCE_ID integer      not null references NOTIFICATION_HOME on delete cascade,
+  RESOURCE_NAME                 varchar(255),
+  REVISION                      integer      default nextval('REVISION_SEQ') not null,
+  DELETED                       boolean      not null,
+
+  unique(NOTIFICATION_HOME_RESOURCE_ID, RESOURCE_NAME) -- implicit index
+);
+
+create index NOTIFICATION_OBJECT_REVISIONS_RESOURCE_ID_REVISION
+  on NOTIFICATION_OBJECT_REVISIONS(NOTIFICATION_HOME_RESOURCE_ID, REVISION);
+
+
+-------------------------------------------
+-- Apple Push Notification Subscriptions --
+-------------------------------------------
+
+create table APN_SUBSCRIPTIONS (
+  TOKEN                         varchar(255) not null,
+  RESOURCE_KEY                  varchar(255) not null,
+  MODIFIED                      integer      not null,
+  SUBSCRIBER_GUID               varchar(255) not null,
+  USER_AGENT                    varchar(255) default null,
+  IP_ADDR                       varchar(255) default null,
+
+  primary key (TOKEN, RESOURCE_KEY) -- implicit index
+);
+
+create index APN_SUBSCRIPTIONS_RESOURCE_KEY
+   on APN_SUBSCRIPTIONS(RESOURCE_KEY);
+
+   
+-----------------
+-- IMIP Tokens --
+-----------------
+
+create table IMIP_TOKENS (
+  TOKEN                         varchar(255) not null,
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALUID                       varchar(255) not null,
+  ACCESSED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  primary key (ORGANIZER, ATTENDEE, ICALUID) -- implicit index
+);
+
+create index IMIP_TOKENS_TOKEN
+   on IMIP_TOKENS(TOKEN);
+
+   
+----------------
+-- Work Items --
+----------------
+
+create sequence WORKITEM_SEQ;
+
+
+---------------------------
+-- IMIP Inivitation Work --
+---------------------------
+
+create table IMIP_INVITATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  FROM_ADDR                     varchar(255) not null,
+  TO_ADDR                       varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+
+-----------------------
+-- IMIP Polling Work --
+-----------------------
+
+create table IMIP_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+---------------------
+-- IMIP Reply Work --
+---------------------
+
+create table IMIP_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+
+------------------------
+-- Push Notifications --
+------------------------
+
+create table PUSH_NOTIFICATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  PUSH_ID                       varchar(255) not null,
+  PRIORITY                      integer      not null -- 1:low 5:medium 10:high
+);
+
+-----------------
+-- GroupCacher --
+-----------------
+
+create table GROUP_CACHER_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+--------------------------
+-- Object Splitter Work --
+--------------------------
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade
+);
+
+create index CALENDAR_OBJECT_SPLITTER_WORK_RESOURCE_ID on
+	CALENDAR_OBJECT_SPLITTER_WORK(RESOURCE_ID);
+
+--------------------
+-- Schema Version --
+--------------------
+
+create table CALENDARSERVER (
+  NAME                          varchar(255) primary key, -- implicit index
+  VALUE                         varchar(255)
+);
+
+insert into CALENDARSERVER values ('VERSION', '29');
+insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
+insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_29_to_30.sql (from rev 12142, CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_29_to_30.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_29_to_30.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_29_to_30.sql	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,49 @@
+----
+-- Copyright (c) 2012-2013 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.
+----
+
+---------------------------------------------------
+-- Upgrade database schema from VERSION 29 to 30 --
+---------------------------------------------------
+
+----------------------------------------
+-- Change Address Book Object Members --
+----------------------------------------
+
+alter table ABO_MEMBERS
+	drop ("abo_members_member_id_fkey");
+alter table ABO_MEMBERS
+	drop ("abo_members_group_id_fkey");
+alter table ABO_MEMBERS
+	add ("REVISION" integer default nextval('REVISION_SEQ') not null);
+alter table ABO_MEMBERS
+	add ("REMOVED" boolean default false not null);
+alter table ABO_MEMBERS
+	 drop ("abo_members_pkey");
+alter table ABO_MEMBERS
+	 add ("abo_members_pkey" primary key ("GROUP_ID", "MEMBER_ID", "REVISION"));
+
+------------------------------------------
+-- Change Address Book Object Revisions --
+------------------------------------------
+	
+alter table ADDRESSBOOK_OBJECT_REVISIONS
+	add ("OBJECT_RESOURCE_ID" integer default 0);
+
+--------------------
+-- Update version --
+--------------------
+
+update CALENDARSERVER set VALUE = '30' where NAME = 'VERSION';

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_29_to_30.sql (from rev 12142, CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_29_to_30.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_29_to_30.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_29_to_30.sql	2013-12-19 06:18:02 UTC (rev 12143)
@@ -0,0 +1,44 @@
+----
+-- Copyright (c) 2012-2013 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.
+----
+
+---------------------------------------------------
+-- Upgrade database schema from VERSION 29 to 30 --
+---------------------------------------------------
+
+----------------------------------------
+-- Change Address Book Object Members --
+----------------------------------------
+
+alter table ABO_MEMBERS
+	drop constraint	abo_members_member_id_fkey,
+	drop constraint	abo_members_group_id_fkey,
+	add column	REVISION		integer      default nextval('REVISION_SEQ') not null,
+	add column	REMOVED         boolean      default false not null,
+	drop constraint abo_members_pkey,
+	add constraint abo_members_pkey primary key(GROUP_ID, MEMBER_ID, REVISION);
+
+------------------------------------------
+-- Change Address Book Object Revisions --
+------------------------------------------
+	
+alter table ADDRESSBOOK_OBJECT_REVISIONS
+	add column OBJECT_RESOURCE_ID integer default 0;
+	
+--------------------
+-- Update version --
+--------------------
+
+update CALENDARSERVER set VALUE = '30' where NAME = 'VERSION';
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/bf3aae32/attachment.html>


More information about the calendarserver-changes mailing list