[CalendarServer-changes] [13732] CalendarServer/branches/users/gaya/groupsharee2

source_changes at macosforge.org source_changes at macosforge.org
Tue Jul 8 00:05:29 PDT 2014


Revision: 13732
          http://trac.calendarserver.org//changeset/13732
Author:   gaya at apple.com
Date:     2014-07-08 00:05:29 -0700 (Tue, 08 Jul 2014)
Log Message:
-----------
merge in trunk to r13731; fix problem where group membership cache is out of sync when first added

Revision Links:
--------------
    http://trac.calendarserver.org//changeset/13731

Modified Paths:
--------------
    CalendarServer/branches/users/gaya/groupsharee2/bin/test
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/provision/root.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/provision/test/test_root.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/push/notifier.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tap/caldav.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/anonymize.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/calverify.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/dashboard.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/dbinspect.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/gateway.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/principals.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/purge.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/cmd.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/directory.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/vfs.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/deprovision/caldavd.plist
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/gateway/caldavd.plist
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/principals/caldavd.plist
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/test_calverify.py
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/util.py
    CalendarServer/branches/users/gaya/groupsharee2/conf/auth/generate_test_accounts.py
    CalendarServer/branches/users/gaya/groupsharee2/conf/caldavd-test.plist
    CalendarServer/branches/users/gaya/groupsharee2/conf/resources/caldavd-resources.plist
    CalendarServer/branches/users/gaya/groupsharee2/contrib/performance/loadtest/ical.py
    CalendarServer/branches/users/gaya/groupsharee2/requirements-dev.txt
    CalendarServer/branches/users/gaya/groupsharee2/requirements-stable.txt
    CalendarServer/branches/users/gaya/groupsharee2/setup.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/datafilters/hiddeninstance.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/augment.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/digest.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/principal.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/test/test_principal.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/util.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/ical.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/resource.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/scheduling_store/caldav/resource.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/stdconfig.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/storebridge.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/test/test_icalendar.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/test/test_upgrade.py
    CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/upgrade.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/schedule.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/implicit.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/itip.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/processing.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/test/test_implicit.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/work.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/carddav/datastore/sql.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/carddav/datastore/test/test_sql.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current-oracle-dialect-extras.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_43_to_44.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_tables.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/dps/client.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/dps/server.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/augment.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/delegates.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/directory.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/groups.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/opendirectory.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_delegates.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_group_attendees.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_groups.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/util.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/who/wiki.py

Added Paths:
-----------
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/platform/darwin/sacl.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/test/test_work.py
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/oracle-dialect/v44.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/oracle-dialect/v45.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/postgres-dialect/v44.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/postgres-dialect/v45.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_44_to_45.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_45_to_46.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_44_to_45.sql
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_45_to_46.sql

Removed Paths:
-------------
    CalendarServer/branches/users/gaya/groupsharee2/calendarserver/platform/darwin/_sacl.c
    CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql

Modified: CalendarServer/branches/users/gaya/groupsharee2/bin/test
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/bin/test	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/bin/test	2014-07-08 07:05:29 UTC (rev 13732)
@@ -83,15 +83,7 @@
 done;
 shift $((${OPTIND} - 1));
 
-if [ $# -eq 0 ]; then
-  lint="true";
-  set - calendarserver twistedcaldav txdav txweb2 contrib twext;
-else
-  lint="false";
-fi;
 
-
-
 #
 # Dependencies
 #
@@ -112,11 +104,10 @@
 # Unit tests
 #
 
-for module in "$@"; do
-  if [ -f "${module}" ]; then
-    module="--testmodule=${module}";
-  fi;
-
+if [ $# -eq 0 ]; then
+  lint="true";
+  # Test these modules with a single invocation of trial
+  set - calendarserver twistedcaldav txdav txweb2 contrib
   cd "${wd}" &&                                       \
     "${wd}/bin/trial"                                 \
     --temp-directory="${dev_home}/trial"              \
@@ -127,10 +118,30 @@
     ${no_color}                                       \
     ${coverage}                                       \
     ${numjobs}                                        \
-    "${module}";
-done;
+    "$@";
+else
+  lint="false";
+  # Loop over the passed-in modules
+  for module in "$@"; do
+    if [ -f "${module}" ]; then
+      module="--testmodule=${module}";
+    fi;
+    cd "${wd}" &&                                       \
+      "${wd}/bin/trial"                                 \
+      --temp-directory="${dev_home}/trial"              \
+      --rterrors                                        \
+      ${reactor}                                        \
+      ${random}                                         \
+      ${until_fail}                                     \
+      ${no_color}                                       \
+      ${coverage}                                       \
+      ${numjobs}                                        \
+      "${module}";
+  done;
+fi;
 
 
+
 if ! "${lint}"; then
   exit 0;
 fi;

Deleted: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/platform/darwin/_sacl.c
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/platform/darwin/_sacl.c	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/platform/darwin/_sacl.c	2014-07-08 07:05:29 UTC (rev 13732)
@@ -1,96 +0,0 @@
-/*
- * Copyright (c) 2006-2014 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.
- */
-
-#include "Python.h"
-#include <Security/Security.h>
-#include <membership.h>
-
-int mbr_check_service_membership(const uuid_t user, const char* servicename, int* ismember);
-int mbr_user_name_to_uuid(const char* name, uuid_t uu);
-int mbr_group_name_to_uuid(const char* name, uuid_t uu);
-
-/*
-    CheckSACL(userOrGroupName, service)
-    Checks user or group membership in a service.
-*/
-static PyObject *appleauth_CheckSACL(PyObject *self, PyObject *args) {
-    char *username;
-    int usernameSize;
-    char *serviceName;
-    int serviceNameSize;
-
-    char *prefix = "com.apple.access_";
-    char groupName[256];
-    uuid_t group_uu;
-
-    // get the args
-    if (!PyArg_ParseTuple(args, "s#s#", &username,
-                          &usernameSize, &serviceName, &serviceNameSize)) {
-        return NULL;
-    }
-
-    // If the username is empty, see if there is a com.apple.access_<service>
-    // group
-    if ( usernameSize == 0 ) {
-        if ( strlen(serviceName) > 255 - strlen(prefix) ) {
-            return Py_BuildValue("i", (-3));
-        }
-        memcpy(groupName, prefix, strlen(prefix));
-        strcpy(groupName + strlen(prefix), serviceName);
-        if ( mbr_group_name_to_uuid(groupName, group_uu) == 0 ) {
-            // com.apple.access_<serviceName> group does exist, so
-            // unauthenticated users are not allowed
-            return Py_BuildValue("i", (-1));
-        } else {
-            // com.apple.access_<serviceName> group doesn't exist, so
-            // unauthenticated users are allowed
-            return Py_BuildValue("i", 0);
-        }
-    }
-
-    // get a uuid for the user
-    uuid_t user;
-    int result = mbr_user_name_to_uuid(username, user);
-    int isMember = 0;
-
-    if ( result != 0 ) {
-        // no uuid for the user, we might be a group.
-        result = mbr_group_name_to_uuid(username, user);
-    }
-
-    if ( result != 0 ) {
-        return Py_BuildValue("i", (-1));
-    }
-
-    result = mbr_check_service_membership(user, serviceName, &isMember);
-
-    if ( ( result == 0 && isMember == 1 ) || ( result == ENOENT ) ) {
-        // passed
-        return Py_BuildValue("i", 0);
-    }
-
-    return Py_BuildValue("i", (-2));
-}
-
-/* Method definitions. */
-static struct PyMethodDef _sacl_methods[] = {
-    {"CheckSACL", appleauth_CheckSACL},
-    {NULL, NULL} /* Sentinel */
-};
-
-void init_sacl(void) {
-    Py_InitModule("_sacl", _sacl_methods);
-}

Copied: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/platform/darwin/sacl.py (from rev 13731, CalendarServer/trunk/calendarserver/platform/darwin/sacl.py)
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/platform/darwin/sacl.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/platform/darwin/sacl.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,85 @@
+##
+# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from __future__ import print_function
+
+__all__ = [
+    "checkSACL"
+]
+
+from cffi import FFI, VerificationError
+
+ffi = FFI()
+
+definitions = """
+    typedef unsigned char uuid_t[16];
+    int mbr_check_service_membership(const uuid_t user, const char* servicename, int* ismember);
+    int mbr_user_name_to_uuid(const char* name, uuid_t uu);
+    int mbr_group_name_to_uuid(const char* name, uuid_t uu);
+"""
+
+ffi.cdef(definitions)
+
+try:
+    lib = ffi.verify(definitions, libraries=[])
+except VerificationError as ve:
+    raise ImportError(ve)
+
+
+def checkSACL(userOrGroupName, serviceName):
+    """
+    Check to see if a given user or group is a member of an OS X Server
+    service's access group.  If userOrGroupName is an empty string, we
+    want to know if unauthenticated access is allowed for the given service.
+
+    @param userOrGroupName: the name of the user or group
+    @type userOrGroupName: C{unicode}
+
+    @param serviceName: the name of the service (e.g. calendar, addressbook)
+    @type serviceName: C{str}
+
+    @return: True if the user or group is allowed access to service
+    @rtype: C{bool}
+    """
+
+    userOrGroupName = userOrGroupName.encode("utf-8")
+    prefix = "com.apple.access_"
+    uu = ffi.new("uuid_t")
+
+    # See if the access group exists.  If it does not, then there are no
+    # restrictions
+    groupName = prefix + serviceName
+    groupMissing = lib.mbr_group_name_to_uuid(groupName, uu)
+    if groupMissing:
+        return True
+
+    # See if userOrGroupName matches a user
+    result = lib.mbr_user_name_to_uuid(userOrGroupName, uu)
+    if result:
+        # Not a user, try looking up a group of that name
+        result = lib.mbr_group_name_to_uuid(userOrGroupName, uu)
+
+    if result:
+        # Neither a user nor a group matches the name
+        return False
+
+    # See if the uuid is a member of the service access group
+    isMember = ffi.new("int *")
+    result = lib.mbr_check_service_membership(uu, serviceName, isMember)
+    if not result and isMember[0]:
+        return True
+
+    return False

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/provision/root.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/provision/root.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/provision/root.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -19,6 +19,12 @@
     "RootResource",
 ]
 
+try:
+    from calendarserver.platform.darwin.sacl import checkSACL
+except ImportError:
+    # OS X Server SACLs not supported on this system, make SACL check a no-op
+    checkSACL = lambda *ignored: True
+
 from twext.python.log import Logger
 from twisted.cred.error import LoginFailed, UnauthorizedLogin
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
@@ -75,10 +81,7 @@
         super(RootResource, self).__init__(path, *args, **kwargs)
 
         if config.EnableSACLs:
-            if RootResource.CheckSACL:
-                self.useSacls = True
-            else:
-                log.warn("SACLs are enabled, but SACLs are not supported.")
+            self.useSacls = True
 
         self.contentFilters = []
 
@@ -122,7 +125,7 @@
 
 
     @inlineCallbacks
-    def checkSacl(self, request):
+    def checkSACL(self, request):
         """
         Check SACLs against the current request
         """
@@ -147,7 +150,7 @@
         # with an empty string.
         if authzUser is None:
             for saclService in saclServices:
-                if RootResource.CheckSACL("", saclService) == 0:
+                if checkSACL("", saclService):
                     # No group actually exists for this SACL, so allow
                     # unauthenticated access
                     returnValue(True)
@@ -169,7 +172,7 @@
 
         access = False
         for saclService in saclServices:
-            if RootResource.CheckSACL(username, saclService) == 0:
+            if checkSACL(username, saclService):
                 # Access is allowed
                 access = True
                 break
@@ -346,7 +349,7 @@
             self.useSacls and
             not hasattr(request, "checkedSACL")
         ):
-            yield self.checkSacl(request)
+            yield self.checkSACL(request)
 
         if config.RejectClients:
             #
@@ -432,11 +435,3 @@
 
     def http_DELETE(self, request):
         return responsecode.FORBIDDEN
-
-# So CheckSACL will be parameterized
-# We do this after RootResource is defined
-try:
-    from calendarserver.platform.darwin._sacl import CheckSACL
-    RootResource.CheckSACL = CheckSACL
-except ImportError:
-    RootResource.CheckSACL = None

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/provision/test/test_root.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/provision/test/test_root.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/provision/test/test_root.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -26,22 +26,27 @@
 from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
 
-from calendarserver.provision.root import RootResource
+import calendarserver.provision.root  # for patching checkSACL
 
 
-class FakeCheckSACL(object):
-    def __init__(self, sacls=None):
-        self.sacls = sacls or {}
+TEST_SACLS = {
+    "calendar": [
+        "dreid"
+    ],
+    "addressbook": [
+        "dreid"
+    ],
+}
 
 
-    def __call__(self, username, service):
-        if service not in self.sacls:
-            return 1
+def stubCheckSACL(username, service):
+    if service not in TEST_SACLS:
+        return True
 
-        if username in self.sacls[service]:
-            return 0
+    if username in TEST_SACLS[service]:
+        return True
 
-        return 1
+    return False
 
 
 
@@ -51,7 +56,7 @@
     def setUp(self):
         yield super(RootTests, self).setUp()
 
-        RootResource.CheckSACL = FakeCheckSACL(sacls={"calendar": ["dreid"]})
+        self.patch(calendarserver.provision.root, "checkSACL", stubCheckSACL)
 
 
 
@@ -337,7 +342,7 @@
             "PROPFIND",
             "/principals/users/dreid/",
             headers=http_headers.Headers({
-                    'Depth': '1',
+                'Depth': '1',
             }),
             authPrincipal=principal,
             content=body
@@ -353,7 +358,7 @@
             "PROPFIND",
             "/principals/users/dreid/",
             headers=http_headers.Headers({
-                    'Depth': '1',
+                'Depth': '1',
             }),
             authPrincipal=principal,
             content=body

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/push/notifier.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/push/notifier.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/push/notifier.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -20,7 +20,7 @@
 
 from twext.enterprise.dal.record import fromTable
 from twext.enterprise.dal.syntax import Delete, Select, Parameter
-from twext.enterprise.jobqueue import WorkItem, WORK_PRIORITY_HIGH, \
+from twext.enterprise.jobqueue import JobItem, WorkItem, WORK_PRIORITY_HIGH, \
     WORK_WEIGHT_1
 from twext.python.log import Logger
 
@@ -50,7 +50,7 @@
 
         # Find all work items with the same push ID and find the highest
         # priority.  Delete matching work items.
-        results = (yield Select([self.table.WORK_ID, self.table.PUSH_PRIORITY],
+        results = (yield Select([self.table.WORK_ID, self.table.JOB_ID, self.table.PUSH_PRIORITY],
             From=self.table, Where=self.table.PUSH_ID == self.pushID).on(
             self.transaction))
 
@@ -59,17 +59,20 @@
         # If there are other enqueued work items for this push ID, find the
         # highest priority one and use that value
         if results:
-            workIDs = []
-            for workID, priority in results:
-                if priority > maxPriority:
-                    maxPriority = priority
-                workIDs.append(workID)
+            workIDs, jobIDs, priorities = zip(*results)
+            maxPriority = max(priorities)
 
-            # Delete the work items we selected
+            # Delete the work items and jobs we selected - deleting the job will ensure that there are no
+            # orphaned" jobs left in the job queue which would otherwise get to run at some later point,
+            # though not do anything because there is no related work item.
             yield Delete(
                 From=self.table,
                 Where=self.table.WORK_ID.In(Parameter("workIDs", len(workIDs)))
             ).on(self.transaction, workIDs=workIDs)
+            yield Delete(
+                From=JobItem.table, #@UndefinedVariable
+                Where=JobItem.jobID.In(Parameter("jobIDs", len(jobIDs))) #@UndefinedVariable
+            ).on(self.transaction, jobIDs=jobIDs)
 
         pushDistributor = self.transaction._pushDistributor
         if pushDistributor is not None:

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tap/caldav.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tap/caldav.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -67,7 +67,7 @@
 from twext.enterprise.adbapi2 import ConnectionPool
 from twext.enterprise.ienterprise import ORACLE_DIALECT
 from twext.enterprise.ienterprise import POSTGRES_DIALECT
-from twext.enterprise.jobqueue import NonPerformingQueuer
+from twext.enterprise.jobqueue import NonPerformingQueuer, JobItem
 from twext.enterprise.jobqueue import PeerConnectionPool
 from twext.enterprise.jobqueue import WorkerFactory as QueueWorkerFactory
 from twext.application.service import ReExecService
@@ -881,6 +881,8 @@
         if pool is not None:
             pool.setServiceParent(result)
 
+        self._initJobQueue(None)
+
         if config.ControlSocket:
             id = config.ControlSocket
             self.log.info("Control via AF_UNIX: {id}", id=id)
@@ -1281,6 +1283,7 @@
             pool = PeerConnectionPool(
                 reactor, store.newTransaction, config.WorkQueue.ampPort
             )
+            self._initJobQueue(pool)
             store.queuer = store.queuer.transferProposalCallbacks(pool)
             pool.setServiceParent(result)
 
@@ -1869,6 +1872,7 @@
             pool = PeerConnectionPool(
                 reactor, store.newTransaction, config.WorkQueue.ampPort
             )
+            self._initJobQueue(pool)
             store.queuer = store.queuer.transferProposalCallbacks(pool)
             controlSocket.addFactory(
                 _QUEUE_ROUTE, pool.workerListenerFactory()
@@ -1991,7 +1995,34 @@
                         remove(checkSocket)
 
 
+    def _initJobQueue(self, pool):
+        """
+        Common job queue initialization
 
+        @param pool: the connection pool to init or L{None}
+        @type pool: L{PeerConnectionPool} or C{None}
+        """
+
+        # Initialize queue polling parameters from config settings
+        if pool is not None:
+            for attr in (
+                "queuePollInterval",
+                "queueOverdueTimeout",
+                "overloadLevel",
+                "highPriorityLevel",
+                "mediumPriorityLevel",
+            ):
+                setattr(pool, attr, getattr(config.WorkQueue, attr))
+
+        # Initialize job parameters from config settings
+        for attr in (
+            "failureRescheduleInterval",
+            "lockRescheduleInterval",
+        ):
+            setattr(JobItem, attr, getattr(config.WorkQueue, attr))
+
+
+
 class TwistdSlaveProcess(object):
     """
     A L{TwistdSlaveProcess} is information about how to start a slave process

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/anonymize.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/anonymize.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/anonymize.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -342,7 +342,7 @@
             except KeyError:
                 pass
 
-    return pyobj.getText(includeTimezones=True)
+    return pyobj.getText(includeTimezones=Calendar.ALL_TIMEZONES)
 
 
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/calverify.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/calverify.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -255,7 +255,6 @@
 
 Options for --ical:
 
---badcua   : only look for bad calendar user addresses.
 --uuid     : only scan specified calendar homes. Can be a partial GUID
              to scan all GUIDs with that as a prefix.
 --uid      : scan only calendar data with the specific iCalendar UID.
@@ -2719,7 +2718,7 @@
             return NukeService(store, options, output, reactor, config)
         elif options["missing"]:
             return OrphansService(store, options, output, reactor, config)
-        elif options["ical"] or options["badcua"]:
+        elif options["ical"]:
             return BadDataService(store, options, output, reactor, config)
         elif options["mismatch"]:
             return SchedulingMismatchService(store, options, output, reactor, config)

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/dashboard.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/dashboard.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/dashboard.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -545,8 +545,8 @@
             "Total:",
             total_queued,
             total_assigned,
+            total_late,
             total_failed,
-            total_late,
             total_completed,
             safeDivision(total_time, total_completed, 1000.0)
         )

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/dbinspect.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/dbinspect.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/dbinspect.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -96,26 +96,27 @@
 
 
 
+ at inlineCallbacks
 def UserNameFromUID(txn, uid):
-    record = txn.directoryService().recordWithGUID(uid)
-    return record.shortNames[0] if record else "(%s)" % (uid,)
+    record = yield txn.directoryService().recordWithGUID(uid)
+    returnValue(record.shortNames[0] if record else "(%s)" % (uid,))
 
 
-
+ at inlineCallbacks
 def UIDFromInput(txn, value):
     try:
-        return str(UUID(value)).upper()
+        returnValue(str(UUID(value)).upper())
     except (ValueError, TypeError):
         pass
 
-    record = txn.directoryService().recordWithShortName(RecordType.user, value)
+    record = yield txn.directoryService().recordWithShortName(RecordType.user, value)
     if record is None:
-        record = txn.directoryService().recordWithShortName(CalRecordType.location, value)
+        record = yield txn.directoryService().recordWithShortName(CalRecordType.location, value)
     if record is None:
-        record = txn.directoryService().recordWithShortName(CalRecordType.resource, value)
+        record = yield txn.directoryService().recordWithShortName(CalRecordType.resource, value)
     if record is None:
-        record = txn.directoryService().recordWithShortName(RecordType.group, value)
-    return record.guid if record else None
+        record = yield txn.directoryService().recordWithShortName(RecordType.group, value)
+    returnValue(record.guid if record else None)
 
 
 
@@ -184,7 +185,7 @@
         table = tables.Table()
         table.addHeader(("Owner UID", "Short Name"))
         for uid in sorted(uids):
-            shortname = UserNameFromUID(txn, uid)
+            shortname = yield UserNameFromUID(txn, uid)
             if shortname.startswith("("):
                 missing += 1
             table.addRow((
@@ -230,7 +231,7 @@
         table.addHeader(("Owner UID", "Short Name", "Calendars", "Resources"))
         totals = [0, 0, 0]
         for uid in sorted(results.keys()):
-            shortname = UserNameFromUID(txn, uid)
+            shortname = yield UserNameFromUID(txn, uid)
             table.addRow((
                 uid,
                 shortname,
@@ -287,7 +288,7 @@
         table = tables.Table()
         table.addHeader(("Owner UID", "Short Name", "Calendar", "Resources"))
         for uid, calname, count in sorted(uids, key=lambda x: (x[0], x[1])):
-            shortname = UserNameFromUID(txn, uid)
+            shortname = yield UserNameFromUID(txn, uid)
             table.addRow((
                 uid,
                 shortname,
@@ -329,7 +330,7 @@
     def doIt(self, txn):
 
         uid = raw_input("Owner UID/Name: ")
-        uid = UIDFromInput(txn, uid)
+        uid = yield UIDFromInput(txn, uid)
         uids = yield self.getCalendars(txn, uid)
 
         # Print table of results
@@ -337,7 +338,7 @@
         table.addHeader(("Owner UID", "Short Name", "Calendars", "ID", "Resources"))
         totals = [0, 0, ]
         for uid, calname, resid, count in sorted(uids, key=lambda x: x[1]):
-            shortname = UserNameFromUID(txn, uid)
+            shortname = yield UserNameFromUID(txn, uid)
             table.addRow((
                 uid if totals[0] == 0 else "",
                 shortname if totals[0] == 0 else "",
@@ -390,7 +391,7 @@
         table = tables.Table()
         table.addHeader(("Owner UID", "Short Name", "Calendar", "ID", "Type", "UID"))
         for uid, calname, id, caltype, caluid in sorted(uids, key=lambda x: (x[0], x[1])):
-            shortname = UserNameFromUID(txn, uid)
+            shortname = yield UserNameFromUID(txn, uid)
             table.addRow((
                 uid,
                 shortname,
@@ -484,9 +485,10 @@
     """
     Base class for common event details commands.
     """
+    @inlineCallbacks
     def printEventDetails(self, txn, details):
         owner, calendar, resource_id, resource, created, modified, data = details
-        shortname = UserNameFromUID(txn, owner)
+        shortname = yield UserNameFromUID(txn, owner)
         table = tables.Table()
         table.addRow(("Owner UID:", owner,))
         table.addRow(("User Name:", shortname,))
@@ -540,7 +542,7 @@
             returnValue(None)
         result = yield self.getData(txn, rid)
         if result:
-            self.printEventDetails(txn, result[0])
+            yield self.printEventDetails(txn, result[0])
         else:
             print("Could not find resource")
 
@@ -562,7 +564,7 @@
         rows = yield self.getData(txn, uid)
         if rows:
             for result in rows:
-                self.printEventDetails(txn, result)
+                yield self.printEventDetails(txn, result)
         else:
             print("Could not find icalendar data")
 
@@ -584,7 +586,7 @@
         rows = yield self.getData(txn, name)
         if rows:
             for result in rows:
-                self.printEventDetails(txn, result)
+                yield self.printEventDetails(txn, result)
         else:
             print("Could not find icalendar data")
 
@@ -603,11 +605,11 @@
     def doIt(self, txn):
 
         uid = raw_input("Owner UID/Name: ")
-        uid = UIDFromInput(txn, uid)
+        uid = yield UIDFromInput(txn, uid)
         rows = yield self.getData(txn, uid)
         if rows:
             for result in rows:
-                self.printEventDetails(txn, result)
+                yield self.printEventDetails(txn, result)
         else:
             print("Could not find icalendar data")
 
@@ -626,12 +628,12 @@
     def doIt(self, txn):
 
         uid = raw_input("Owner UID/Name: ")
-        uid = UIDFromInput(txn, uid)
+        uid = yield UIDFromInput(txn, uid)
         name = raw_input("Calendar resource name: ")
         rows = yield self.getData(txn, uid, name)
         if rows:
             for result in rows:
-                self.printEventDetails(txn, result)
+                yield self.printEventDetails(txn, result)
         else:
             print("Could not find icalendar data")
 
@@ -661,7 +663,7 @@
         rows = yield self.getData(txn, homeName, calendarName, resourceName)
         if rows:
             for result in rows:
-                self.printEventDetails(txn, result)
+                yield self.printEventDetails(txn, result)
         else:
             print("Could not find icalendar data")
 
@@ -693,7 +695,7 @@
         rows = yield self.getData(txn, uid)
         if rows:
             for result in rows:
-                self.printEventDetails(txn, result)
+                yield self.printEventDetails(txn, result)
         else:
             print("Could not find icalendar data")
 
@@ -772,7 +774,7 @@
             event = yield calendar.calendarObjectWithName(name)
             ical_data = yield event.component()
             ical_data = PerUserDataFilter(uid).filter(ical_data)
-            ical_data.stripKnownTimezones()
+            ical_data.stripStandardTimezones()
 
             table = tables.Table()
             table.addRow(("Calendar:", calendar.name(),))
@@ -782,7 +784,7 @@
             table.addRow(("Modified", event.modified()))
             print("\n")
             table.printTable()
-            print(ical_data.getTextWithTimezones(includeTimezones=False))
+            print(ical_data.getTextWithoutTimezones())
 
 
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/gateway.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/gateway.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -380,7 +380,7 @@
             self.respondWithError("Principal not found: %s" % (uid,))
             return
         recordDict = recordToDict(record)
-        # recordDict['AutoSchedule'] = principal.getAutoSchedule()
+        # recordDict['AutoSchedule'] = yield principal.getAutoSchedule()
         try:
             recordDict['AutoAcceptGroup'] = record.autoAcceptGroup
         except AttributeError:
@@ -398,7 +398,7 @@
     # Resources
 
     def command_getResourceList(self, command):
-        self.respondWithRecordsOfTypes(self.dir, command, ["resources"])
+        return self.respondWithRecordsOfTypes(self.dir, command, ["resources"])
 
 
     # deferred
@@ -416,7 +416,7 @@
     def _delete(self, typeName, command):
         uid = command['GeneratedUID']
         yield self.dir.removeRecords([uid])
-        self.respondWithRecordsOfTypes(self.dir, command, [typeName])
+        yield self.respondWithRecordsOfTypes(self.dir, command, [typeName])
 
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/principals.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/principals.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -409,12 +409,15 @@
                 )
             )
             print("   UID: {u}".format(u=record.uid,))
-            print(
-                "   Record name{plural}: {names}".format(
-                    plural=("s" if len(record.shortNames) > 1 else ""),
-                    names=(", ".join(record.shortNames))
+            try:
+                print(
+                    "   Record name{plural}: {names}".format(
+                        plural=("s" if len(record.shortNames) > 1 else ""),
+                        names=(", ".join(record.shortNames))
+                    )
                 )
-            )
+            except AttributeError:
+                pass
             try:
                 if record.emailAddresses:
                     print(
@@ -787,7 +790,9 @@
     txn = store.newTransaction()
     groupUIDs = yield txn.allGroupDelegates()
     for groupUID in groupUIDs:
-        groupID, name, _ignore_membershipHash, modified = yield txn.groupByUID(
+        (
+            groupID, name, _ignore_membershipHash, modified, extant
+        ) = yield txn.groupByUID(
             groupUID
         )
         print("Group: \"{name}\" ({uid})".format(name=name, uid=groupUID))
@@ -803,7 +808,7 @@
                 )
 
         print("Group members:")
-        memberUIDs = yield txn.membersOfGroup(groupID)
+        memberUIDs = yield txn.groupMemberUIDs(groupID)
         for memberUID in memberUIDs:
             record = yield directory.recordWithUID(memberUID)
             print(prettyRecord(record))

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/purge.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/purge.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -234,6 +234,7 @@
 
         # Check for pending scheduling operations
         sow = schema.SCHEDULE_ORGANIZER_WORK
+        sosw = schema.SCHEDULE_ORGANIZER_SEND_WORK
         srw = schema.SCHEDULE_REPLY_WORK
         srcw = schema.SCHEDULE_REPLY_CANCEL_WORK
         rows = yield Select(
@@ -242,14 +243,21 @@
             Where=(sow.HOME_RESOURCE_ID == self.homeResourceID),
             SetExpression=Union(
                 Select(
-                    [srw.HOME_RESOURCE_ID],
-                    From=srw,
-                    Where=(srw.HOME_RESOURCE_ID == self.homeResourceID),
+                    [sosw.HOME_RESOURCE_ID],
+                    From=sosw,
+                    Where=(sosw.HOME_RESOURCE_ID == self.homeResourceID),
                     SetExpression=Union(
                         Select(
-                            [srcw.HOME_RESOURCE_ID],
-                            From=srcw,
-                            Where=(srcw.HOME_RESOURCE_ID == self.homeResourceID),
+                            [srw.HOME_RESOURCE_ID],
+                            From=srw,
+                            Where=(srw.HOME_RESOURCE_ID == self.homeResourceID),
+                            SetExpression=Union(
+                                Select(
+                                    [srcw.HOME_RESOURCE_ID],
+                                    From=srcw,
+                                    Where=(srcw.HOME_RESOURCE_ID == self.homeResourceID),
+                                )
+                            ),
                         )
                     ),
                 )

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/cmd.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/cmd.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/cmd.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -163,6 +163,7 @@
                 returnValue(())
 
 
+    @inlineCallbacks
     def directoryRecordWithID(self, id):
         """
         Obtains a directory record corresponding to the given C{id}.
@@ -173,7 +174,7 @@
         """
         directory = self.protocol.service.directory
 
-        record = directory.recordWithUID(id)
+        record = yield directory.recordWithUID(id)
 
         if not record:
             # Try type:name form
@@ -182,9 +183,9 @@
             except ValueError:
                 pass
             else:
-                record = directory.recordWithShortName(recordType, shortName)
+                record = yield directory.recordWithShortName(recordType, shortName)
 
-        return record
+        returnValue(record)
 
 
     def commands(self, showHidden=False):
@@ -628,7 +629,7 @@
 
         directory = self.protocol.service.directory
 
-        record = self.directoryRecordWithID(id)
+        record = yield self.directoryRecordWithID(id)
 
         if record:
             self.terminal.write((yield recordInfo(directory, record)))
@@ -657,7 +658,7 @@
 
         records = []
         for id in tokens:
-            record = self.directoryRecordWithID(id)
+            record = yield self.directoryRecordWithID(id)
             records.append(record)
 
             if not record:
@@ -701,6 +702,7 @@
     # Sharing
     #
 
+    @inlineCallbacks
     def cmd_share(self, tokens):
         """
         Share a resource with a principal.
@@ -715,12 +717,12 @@
         mode = tokens.pop(0)
         principalID = tokens.pop(0)
 
-        record = self.directoryRecordWithID(principalID)
+        record = yield self.directoryRecordWithID(principalID)
 
         if not record:
             self.terminal.write("Principal not found: %s\n" % (principalID,))
 
-        targets = self.getTargets(tokens)
+        targets = yield self.getTargets(tokens)
 
         if mode == "r":
             mode = None

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/directory.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/directory.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/directory.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -155,7 +155,7 @@
             # I don't know how to get DirectoryRecord objects for the proxyUID here, so, let's cheat for now.
             proxyUID, proxyType = proxyUID.split("#")
             if (proxyUID, proxyType) not in proxyInfoSeen:
-                proxyRecord = directory.recordWithUID(proxyUID)
+                proxyRecord = yield directory.recordWithUID(proxyUID)
                 rows.append((proxyUID, proxyRecord.recordType, proxyRecord.shortNames[0], proxyRecord.fullName, proxyType))
                 proxyInfoSeen.add((proxyUID, proxyType))
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/vfs.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/vfs.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/shell/vfs.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -290,10 +290,11 @@
         # FIXME: Merge in directory UIDs also?
         # FIXME: Add directory info (eg. name) to list entry
 
+        @inlineCallbacks
         def addResult(ignoredTxn, home):
             uid = home.uid()
 
-            record = self.service.directory.recordWithUID(uid)
+            record = yield self.service.directory.recordWithUID(uid)
             if record:
                 info = {
                     "Record Type": record.recordType,
@@ -331,12 +332,13 @@
         )
 
 
+    @inlineCallbacks
     def list(self):
         names = set()
 
-        for record in self.service.directory.recordsWithRecordType(
+        for record in (yield self.service.directory.recordsWithRecordType(
             self.recordType
-        ):
+        )):
             for shortName in record.shortNames:
                 if shortName in names:
                     continue
@@ -395,6 +397,7 @@
         Folder.__init__(self, service, path)
 
         if record is None:
+            # FIXME: recordWithUID returns a Deferred but we cannot return or yield it in an __init__ method
             record = self.service.directory.recordWithUID(uid)
 
         if record is not None:

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/deprovision/caldavd.plist
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/deprovision/caldavd.plist	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/deprovision/caldavd.plist	2014-07-08 07:05:29 UTC (rev 13732)
@@ -115,12 +115,6 @@
     <key>MaxAttendeesPerInstance</key>
     <integer>100</integer>
 
-    <!-- Maximum number of instances allowed for a single RRULE -->
-    <!-- 0 for no limit -->
-    <key>MaxInstancesForRRULE</key>
-    <integer>400</integer>
-
-
     <!--
         Directory service
 
@@ -135,7 +129,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
-      
+
       <key>params</key>
       <dict>
         <key>xmlFile</key>
@@ -155,7 +149,7 @@
       <true/>
       <key>type</key>
       <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
-      
+
       <key>params</key>
       <dict>
         <key>xmlFile</key>
@@ -167,14 +161,14 @@
         </array>
       </dict>
     </dict>
-    
+
     <!-- Open Directory Service (Mac OS X) -->
     <!--
     <key>DirectoryService</key>
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
-      
+
       <key>params</key>
       <dict>
         <key>node</key>
@@ -198,7 +192,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>xmlFiles</key>
@@ -207,14 +201,14 @@
         </array>
       </dict>
     </dict>
-    
+
     <!-- Sqlite Augment Service -->
     <!--
     <key>AugmentService</key>
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>dbpath</key>
@@ -229,7 +223,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>host</key>
@@ -245,7 +239,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>dbpath</key>
@@ -259,7 +253,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>host</key>
@@ -687,7 +681,7 @@
     <!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 -->
     <key>ResponseCompression</key>
     <false/>
-    
+
     <!-- The retry-after value (in seconds) to return with a 503 error. -->
     <key>HTTPRetryAfter</key>
     <integer>180</integer>

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/gateway/caldavd.plist
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/gateway/caldavd.plist	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/gateway/caldavd.plist	2014-07-08 07:05:29 UTC (rev 13732)
@@ -127,12 +127,6 @@
     <key>MaxAttendeesPerInstance</key>
     <integer>100</integer>
 
-    <!-- Maximum number of instances allowed for a single RRULE -->
-    <!-- 0 for no limit -->
-    <key>MaxInstancesForRRULE</key>
-    <integer>400</integer>
-
-
     <!--
         Directory service
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/principals/caldavd.plist
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/principals/caldavd.plist	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/principals/caldavd.plist	2014-07-08 07:05:29 UTC (rev 13732)
@@ -119,12 +119,6 @@
     <key>MaxAttendeesPerInstance</key>
     <integer>100</integer>
 
-    <!-- Maximum number of instances allowed for a single RRULE -->
-    <!-- 0 for no limit -->
-    <key>MaxInstancesForRRULE</key>
-    <integer>400</integer>
-
-
     <!--
         Directory service
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/test_calverify.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/test_calverify.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/test/test_calverify.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -2541,9 +2541,9 @@
 UID:INVITE_VALID_ORGANIZER_ICS
 DTSTART:%(now_fwd11)s
 DURATION:PT1H
-ATTENDEE;CN=Example User1;EMAIL=example1 at example.com:urn:x-uid:%(uuid1)s
-ATTENDEE;CN=Example User2;EMAIL=example2 at example.com:urn:x-uid:%(uuid2)s
-ORGANIZER;CN=Example User1;EMAIL=example1 at example.com:urn:x-uid:%(uuid1)s
+ATTENDEE:urn:x-uid:%(uuid1)s
+ATTENDEE:urn:x-uid:%(uuid2)s
+ORGANIZER:urn:x-uid:%(uuid1)s
 RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
 RRULE:FREQ=DAILY
 SEQUENCE:1
@@ -2560,9 +2560,9 @@
 UID:INVITE_VALID_ORGANIZER_ICS
 DTSTART:%(now_fwd11)s
 DURATION:PT1H
-ATTENDEE;CN=Example User1;EMAIL=example1 at example.com:urn:x-uid:%(uuid1)s
-ATTENDEE;CN=Example User2;EMAIL=example2 at example.com:urn:x-uid:%(uuid2)s
-ORGANIZER;CN=Example User1;EMAIL=example1 at example.com:urn:x-uid:%(uuid1)s
+ATTENDEE:urn:x-uid:%(uuid1)s
+ATTENDEE:urn:x-uid:%(uuid2)s
+ORGANIZER:urn:x-uid:%(uuid1)s
 RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
 RRULE:FREQ=DAILY
 SEQUENCE:1
@@ -2579,9 +2579,9 @@
 UID:%(uid)s
 DTSTART:%(now)s
 DURATION:PT1H
-ATTENDEE;CN=Example User1;EMAIL=example1 at example.com:urn:x-uid:%(uuid1)s
-ATTENDEE;CN=Example User2;EMAIL=example2 at example.com:urn:x-uid:%(uuid2)s
-ORGANIZER;CN=Example User1;EMAIL=example1 at example.com:urn:x-uid:%(uuid1)s
+ATTENDEE:urn:x-uid:%(uuid1)s
+ATTENDEE:urn:x-uid:%(uuid2)s
+ORGANIZER:urn:x-uid:%(uuid1)s
 RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
 RRULE:FREQ=DAILY;UNTIL=%(now_fwd11_1)s
 SEQUENCE:1
@@ -2598,9 +2598,9 @@
 UID:%(uid)s
 DTSTART:%(now)s
 DURATION:PT1H
-ATTENDEE;CN=Example User1;EMAIL=example1 at example.com:urn:x-uid:%(uuid1)s
-ATTENDEE;CN=Example User2;EMAIL=example2 at example.com:urn:x-uid:%(uuid2)s
-ORGANIZER;CN=Example User1;EMAIL=example1 at example.com:urn:x-uid:%(uuid1)s
+ATTENDEE:urn:x-uid:%(uuid1)s
+ATTENDEE:urn:x-uid:%(uuid2)s
+ORGANIZER:urn:x-uid:%(uuid1)s
 RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
 RRULE:FREQ=DAILY;UNTIL=%(now_fwd11_1)s
 SEQUENCE:1

Modified: CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/util.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/util.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/calendarserver/tools/util.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -560,11 +560,15 @@
 
 
 def prettyRecord(record):
+    try:
+        shortNames = record.shortNames
+    except AttributeError:
+        shortNames = []
     return "\"{d}\" {uid} ({rt}) {sn}".format(
         d=record.displayName,
         rt=record.recordType.name,
         uid=record.uid,
-        sn=(", ".join(record.shortNames))
+        sn=(", ".join(shortNames))
     )
 
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/conf/auth/generate_test_accounts.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/conf/auth/generate_test_accounts.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/conf/auth/generate_test_accounts.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -24,6 +24,8 @@
 
 """
 
+EXTRA_GROUPS = False
+
 # The uids and guids for CDT test accounts are the same
 # The short name is of the form userNN
 USERGUIDS = "10000000-0000-0000-0000-000000000%03d"
@@ -58,7 +60,7 @@
 """.format(uid=uid, guid=guid, fullName=fullName))
 
 # user01-100
-for i in xrange(1, 101):
+for i in xrange(1, 501 if EXTRA_GROUPS else 101):
     out.write("""<record type="user">
     <uid>{guid}</uid>
     <guid>{guid}</guid>
@@ -92,6 +94,621 @@
     GROUPGUIDS % 7: (USERGUIDS % 22, USERGUIDS % 23, USERGUIDS % 24),
 }
 
+if EXTRA_GROUPS:
+    members.update({
+    GROUPGUIDS % 8: (
+        USERGUIDS % 1,
+        USERGUIDS % 2,
+        USERGUIDS % 3,
+        USERGUIDS % 4,
+        USERGUIDS % 5,
+    ),
+    GROUPGUIDS % 9: (
+        USERGUIDS % 1,
+        USERGUIDS % 2,
+        USERGUIDS % 3,
+        USERGUIDS % 4,
+        USERGUIDS % 5,
+        USERGUIDS % 6,
+        USERGUIDS % 7,
+        USERGUIDS % 8,
+        USERGUIDS % 9,
+        USERGUIDS % 10,
+        USERGUIDS % 11,
+        USERGUIDS % 12,
+        USERGUIDS % 13,
+        USERGUIDS % 14,
+        USERGUIDS % 15,
+        USERGUIDS % 16,
+        USERGUIDS % 17,
+        USERGUIDS % 18,
+        USERGUIDS % 19,
+        USERGUIDS % 20,
+        USERGUIDS % 21,
+        USERGUIDS % 22,
+        USERGUIDS % 23,
+        USERGUIDS % 24,
+        USERGUIDS % 25,
+        USERGUIDS % 26,
+        USERGUIDS % 27,
+        USERGUIDS % 28,
+        USERGUIDS % 29,
+        USERGUIDS % 30,
+        USERGUIDS % 31,
+        USERGUIDS % 32,
+        USERGUIDS % 33,
+        USERGUIDS % 34,
+        USERGUIDS % 35,
+        USERGUIDS % 36,
+        USERGUIDS % 37,
+        USERGUIDS % 38,
+        USERGUIDS % 39,
+        USERGUIDS % 40,
+        USERGUIDS % 41,
+        USERGUIDS % 42,
+        USERGUIDS % 43,
+        USERGUIDS % 44,
+        USERGUIDS % 45,
+        USERGUIDS % 46,
+        USERGUIDS % 47,
+        USERGUIDS % 48,
+        USERGUIDS % 49,
+        USERGUIDS % 50,
+        USERGUIDS % 51,
+        USERGUIDS % 52,
+        USERGUIDS % 53,
+        USERGUIDS % 54,
+        USERGUIDS % 55,
+        USERGUIDS % 56,
+        USERGUIDS % 57,
+        USERGUIDS % 58,
+        USERGUIDS % 59,
+        USERGUIDS % 60,
+        USERGUIDS % 61,
+        USERGUIDS % 62,
+        USERGUIDS % 63,
+        USERGUIDS % 64,
+        USERGUIDS % 65,
+        USERGUIDS % 66,
+        USERGUIDS % 67,
+        USERGUIDS % 68,
+        USERGUIDS % 69,
+        USERGUIDS % 70,
+        USERGUIDS % 71,
+        USERGUIDS % 72,
+        USERGUIDS % 73,
+        USERGUIDS % 74,
+        USERGUIDS % 75,
+        USERGUIDS % 76,
+        USERGUIDS % 77,
+        USERGUIDS % 78,
+        USERGUIDS % 79,
+        USERGUIDS % 80,
+        USERGUIDS % 81,
+        USERGUIDS % 82,
+        USERGUIDS % 83,
+        USERGUIDS % 84,
+        USERGUIDS % 85,
+        USERGUIDS % 86,
+        USERGUIDS % 87,
+        USERGUIDS % 88,
+        USERGUIDS % 89,
+        USERGUIDS % 90,
+        USERGUIDS % 91,
+        USERGUIDS % 92,
+        USERGUIDS % 93,
+        USERGUIDS % 94,
+        USERGUIDS % 95,
+        USERGUIDS % 96,
+        USERGUIDS % 97,
+        USERGUIDS % 98,
+        USERGUIDS % 99,
+        USERGUIDS % 100,
+    ),
+    GROUPGUIDS % 10: (
+        USERGUIDS % 1,
+        USERGUIDS % 2,
+        USERGUIDS % 3,
+        USERGUIDS % 4,
+        USERGUIDS % 5,
+        USERGUIDS % 6,
+        USERGUIDS % 7,
+        USERGUIDS % 8,
+        USERGUIDS % 9,
+        USERGUIDS % 10,
+        USERGUIDS % 11,
+        USERGUIDS % 12,
+        USERGUIDS % 13,
+        USERGUIDS % 14,
+        USERGUIDS % 15,
+        USERGUIDS % 16,
+        USERGUIDS % 17,
+        USERGUIDS % 18,
+        USERGUIDS % 19,
+        USERGUIDS % 20,
+        USERGUIDS % 21,
+        USERGUIDS % 22,
+        USERGUIDS % 23,
+        USERGUIDS % 24,
+        USERGUIDS % 25,
+        USERGUIDS % 26,
+        USERGUIDS % 27,
+        USERGUIDS % 28,
+        USERGUIDS % 29,
+        USERGUIDS % 30,
+        USERGUIDS % 31,
+        USERGUIDS % 32,
+        USERGUIDS % 33,
+        USERGUIDS % 34,
+        USERGUIDS % 35,
+        USERGUIDS % 36,
+        USERGUIDS % 37,
+        USERGUIDS % 38,
+        USERGUIDS % 39,
+        USERGUIDS % 40,
+        USERGUIDS % 41,
+        USERGUIDS % 42,
+        USERGUIDS % 43,
+        USERGUIDS % 44,
+        USERGUIDS % 45,
+        USERGUIDS % 46,
+        USERGUIDS % 47,
+        USERGUIDS % 48,
+        USERGUIDS % 49,
+        USERGUIDS % 50,
+        USERGUIDS % 51,
+        USERGUIDS % 52,
+        USERGUIDS % 53,
+        USERGUIDS % 54,
+        USERGUIDS % 55,
+        USERGUIDS % 56,
+        USERGUIDS % 57,
+        USERGUIDS % 58,
+        USERGUIDS % 59,
+        USERGUIDS % 60,
+        USERGUIDS % 61,
+        USERGUIDS % 62,
+        USERGUIDS % 63,
+        USERGUIDS % 64,
+        USERGUIDS % 65,
+        USERGUIDS % 66,
+        USERGUIDS % 67,
+        USERGUIDS % 68,
+        USERGUIDS % 69,
+        USERGUIDS % 70,
+        USERGUIDS % 71,
+        USERGUIDS % 72,
+        USERGUIDS % 73,
+        USERGUIDS % 74,
+        USERGUIDS % 75,
+        USERGUIDS % 76,
+        USERGUIDS % 77,
+        USERGUIDS % 78,
+        USERGUIDS % 79,
+        USERGUIDS % 80,
+        USERGUIDS % 81,
+        USERGUIDS % 82,
+        USERGUIDS % 83,
+        USERGUIDS % 84,
+        USERGUIDS % 85,
+        USERGUIDS % 86,
+        USERGUIDS % 87,
+        USERGUIDS % 88,
+        USERGUIDS % 89,
+        USERGUIDS % 90,
+        USERGUIDS % 91,
+        USERGUIDS % 92,
+        USERGUIDS % 93,
+        USERGUIDS % 94,
+        USERGUIDS % 95,
+        USERGUIDS % 96,
+        USERGUIDS % 97,
+        USERGUIDS % 98,
+        USERGUIDS % 99,
+        USERGUIDS % 100,
+        USERGUIDS % 101,
+        USERGUIDS % 102,
+        USERGUIDS % 103,
+        USERGUIDS % 104,
+        USERGUIDS % 105,
+        USERGUIDS % 106,
+        USERGUIDS % 107,
+        USERGUIDS % 108,
+        USERGUIDS % 109,
+        USERGUIDS % 110,
+        USERGUIDS % 111,
+        USERGUIDS % 112,
+        USERGUIDS % 113,
+        USERGUIDS % 114,
+        USERGUIDS % 115,
+        USERGUIDS % 116,
+        USERGUIDS % 117,
+        USERGUIDS % 118,
+        USERGUIDS % 119,
+        USERGUIDS % 120,
+        USERGUIDS % 121,
+        USERGUIDS % 122,
+        USERGUIDS % 123,
+        USERGUIDS % 124,
+        USERGUIDS % 125,
+        USERGUIDS % 126,
+        USERGUIDS % 127,
+        USERGUIDS % 128,
+        USERGUIDS % 129,
+        USERGUIDS % 130,
+        USERGUIDS % 131,
+        USERGUIDS % 132,
+        USERGUIDS % 133,
+        USERGUIDS % 134,
+        USERGUIDS % 135,
+        USERGUIDS % 136,
+        USERGUIDS % 137,
+        USERGUIDS % 138,
+        USERGUIDS % 139,
+        USERGUIDS % 140,
+        USERGUIDS % 141,
+        USERGUIDS % 142,
+        USERGUIDS % 143,
+        USERGUIDS % 144,
+        USERGUIDS % 145,
+        USERGUIDS % 146,
+        USERGUIDS % 147,
+        USERGUIDS % 148,
+        USERGUIDS % 149,
+        USERGUIDS % 150,
+        USERGUIDS % 151,
+        USERGUIDS % 152,
+        USERGUIDS % 153,
+        USERGUIDS % 154,
+        USERGUIDS % 155,
+        USERGUIDS % 156,
+        USERGUIDS % 157,
+        USERGUIDS % 158,
+        USERGUIDS % 159,
+        USERGUIDS % 160,
+        USERGUIDS % 161,
+        USERGUIDS % 162,
+        USERGUIDS % 163,
+        USERGUIDS % 164,
+        USERGUIDS % 165,
+        USERGUIDS % 166,
+        USERGUIDS % 167,
+        USERGUIDS % 168,
+        USERGUIDS % 169,
+        USERGUIDS % 170,
+        USERGUIDS % 171,
+        USERGUIDS % 172,
+        USERGUIDS % 173,
+        USERGUIDS % 174,
+        USERGUIDS % 175,
+        USERGUIDS % 176,
+        USERGUIDS % 177,
+        USERGUIDS % 178,
+        USERGUIDS % 179,
+        USERGUIDS % 180,
+        USERGUIDS % 181,
+        USERGUIDS % 182,
+        USERGUIDS % 183,
+        USERGUIDS % 184,
+        USERGUIDS % 185,
+        USERGUIDS % 186,
+        USERGUIDS % 187,
+        USERGUIDS % 188,
+        USERGUIDS % 189,
+        USERGUIDS % 190,
+        USERGUIDS % 191,
+        USERGUIDS % 192,
+        USERGUIDS % 193,
+        USERGUIDS % 194,
+        USERGUIDS % 195,
+        USERGUIDS % 196,
+        USERGUIDS % 197,
+        USERGUIDS % 198,
+        USERGUIDS % 199,
+        USERGUIDS % 200,
+        USERGUIDS % 201,
+        USERGUIDS % 202,
+        USERGUIDS % 203,
+        USERGUIDS % 204,
+        USERGUIDS % 205,
+        USERGUIDS % 206,
+        USERGUIDS % 207,
+        USERGUIDS % 208,
+        USERGUIDS % 209,
+        USERGUIDS % 210,
+        USERGUIDS % 211,
+        USERGUIDS % 212,
+        USERGUIDS % 213,
+        USERGUIDS % 214,
+        USERGUIDS % 215,
+        USERGUIDS % 216,
+        USERGUIDS % 217,
+        USERGUIDS % 218,
+        USERGUIDS % 219,
+        USERGUIDS % 220,
+        USERGUIDS % 221,
+        USERGUIDS % 222,
+        USERGUIDS % 223,
+        USERGUIDS % 224,
+        USERGUIDS % 225,
+        USERGUIDS % 226,
+        USERGUIDS % 227,
+        USERGUIDS % 228,
+        USERGUIDS % 229,
+        USERGUIDS % 230,
+        USERGUIDS % 231,
+        USERGUIDS % 232,
+        USERGUIDS % 233,
+        USERGUIDS % 234,
+        USERGUIDS % 235,
+        USERGUIDS % 236,
+        USERGUIDS % 237,
+        USERGUIDS % 238,
+        USERGUIDS % 239,
+        USERGUIDS % 240,
+        USERGUIDS % 241,
+        USERGUIDS % 242,
+        USERGUIDS % 243,
+        USERGUIDS % 244,
+        USERGUIDS % 245,
+        USERGUIDS % 246,
+        USERGUIDS % 247,
+        USERGUIDS % 248,
+        USERGUIDS % 249,
+        USERGUIDS % 250,
+        USERGUIDS % 251,
+        USERGUIDS % 252,
+        USERGUIDS % 253,
+        USERGUIDS % 254,
+        USERGUIDS % 255,
+        USERGUIDS % 256,
+        USERGUIDS % 257,
+        USERGUIDS % 258,
+        USERGUIDS % 259,
+        USERGUIDS % 260,
+        USERGUIDS % 261,
+        USERGUIDS % 262,
+        USERGUIDS % 263,
+        USERGUIDS % 264,
+        USERGUIDS % 265,
+        USERGUIDS % 266,
+        USERGUIDS % 267,
+        USERGUIDS % 268,
+        USERGUIDS % 269,
+        USERGUIDS % 270,
+        USERGUIDS % 271,
+        USERGUIDS % 272,
+        USERGUIDS % 273,
+        USERGUIDS % 274,
+        USERGUIDS % 275,
+        USERGUIDS % 276,
+        USERGUIDS % 277,
+        USERGUIDS % 278,
+        USERGUIDS % 279,
+        USERGUIDS % 280,
+        USERGUIDS % 281,
+        USERGUIDS % 282,
+        USERGUIDS % 283,
+        USERGUIDS % 284,
+        USERGUIDS % 285,
+        USERGUIDS % 286,
+        USERGUIDS % 287,
+        USERGUIDS % 288,
+        USERGUIDS % 289,
+        USERGUIDS % 290,
+        USERGUIDS % 291,
+        USERGUIDS % 292,
+        USERGUIDS % 293,
+        USERGUIDS % 294,
+        USERGUIDS % 295,
+        USERGUIDS % 296,
+        USERGUIDS % 297,
+        USERGUIDS % 298,
+        USERGUIDS % 299,
+        USERGUIDS % 300,
+        USERGUIDS % 301,
+        USERGUIDS % 302,
+        USERGUIDS % 303,
+        USERGUIDS % 304,
+        USERGUIDS % 305,
+        USERGUIDS % 306,
+        USERGUIDS % 307,
+        USERGUIDS % 308,
+        USERGUIDS % 309,
+        USERGUIDS % 310,
+        USERGUIDS % 311,
+        USERGUIDS % 312,
+        USERGUIDS % 313,
+        USERGUIDS % 314,
+        USERGUIDS % 315,
+        USERGUIDS % 316,
+        USERGUIDS % 317,
+        USERGUIDS % 318,
+        USERGUIDS % 319,
+        USERGUIDS % 320,
+        USERGUIDS % 321,
+        USERGUIDS % 322,
+        USERGUIDS % 323,
+        USERGUIDS % 324,
+        USERGUIDS % 325,
+        USERGUIDS % 326,
+        USERGUIDS % 327,
+        USERGUIDS % 328,
+        USERGUIDS % 329,
+        USERGUIDS % 330,
+        USERGUIDS % 331,
+        USERGUIDS % 332,
+        USERGUIDS % 333,
+        USERGUIDS % 334,
+        USERGUIDS % 335,
+        USERGUIDS % 336,
+        USERGUIDS % 337,
+        USERGUIDS % 338,
+        USERGUIDS % 339,
+        USERGUIDS % 340,
+        USERGUIDS % 341,
+        USERGUIDS % 342,
+        USERGUIDS % 343,
+        USERGUIDS % 344,
+        USERGUIDS % 345,
+        USERGUIDS % 346,
+        USERGUIDS % 347,
+        USERGUIDS % 348,
+        USERGUIDS % 349,
+        USERGUIDS % 350,
+        USERGUIDS % 351,
+        USERGUIDS % 352,
+        USERGUIDS % 353,
+        USERGUIDS % 354,
+        USERGUIDS % 355,
+        USERGUIDS % 356,
+        USERGUIDS % 357,
+        USERGUIDS % 358,
+        USERGUIDS % 359,
+        USERGUIDS % 360,
+        USERGUIDS % 361,
+        USERGUIDS % 362,
+        USERGUIDS % 363,
+        USERGUIDS % 364,
+        USERGUIDS % 365,
+        USERGUIDS % 366,
+        USERGUIDS % 367,
+        USERGUIDS % 368,
+        USERGUIDS % 369,
+        USERGUIDS % 370,
+        USERGUIDS % 371,
+        USERGUIDS % 372,
+        USERGUIDS % 373,
+        USERGUIDS % 374,
+        USERGUIDS % 375,
+        USERGUIDS % 376,
+        USERGUIDS % 377,
+        USERGUIDS % 378,
+        USERGUIDS % 379,
+        USERGUIDS % 380,
+        USERGUIDS % 381,
+        USERGUIDS % 382,
+        USERGUIDS % 383,
+        USERGUIDS % 384,
+        USERGUIDS % 385,
+        USERGUIDS % 386,
+        USERGUIDS % 387,
+        USERGUIDS % 388,
+        USERGUIDS % 389,
+        USERGUIDS % 390,
+        USERGUIDS % 391,
+        USERGUIDS % 392,
+        USERGUIDS % 393,
+        USERGUIDS % 394,
+        USERGUIDS % 395,
+        USERGUIDS % 396,
+        USERGUIDS % 397,
+        USERGUIDS % 398,
+        USERGUIDS % 399,
+        USERGUIDS % 400,
+        USERGUIDS % 401,
+        USERGUIDS % 402,
+        USERGUIDS % 403,
+        USERGUIDS % 404,
+        USERGUIDS % 405,
+        USERGUIDS % 406,
+        USERGUIDS % 407,
+        USERGUIDS % 408,
+        USERGUIDS % 409,
+        USERGUIDS % 410,
+        USERGUIDS % 411,
+        USERGUIDS % 412,
+        USERGUIDS % 413,
+        USERGUIDS % 414,
+        USERGUIDS % 415,
+        USERGUIDS % 416,
+        USERGUIDS % 417,
+        USERGUIDS % 418,
+        USERGUIDS % 419,
+        USERGUIDS % 420,
+        USERGUIDS % 421,
+        USERGUIDS % 422,
+        USERGUIDS % 423,
+        USERGUIDS % 424,
+        USERGUIDS % 425,
+        USERGUIDS % 426,
+        USERGUIDS % 427,
+        USERGUIDS % 428,
+        USERGUIDS % 429,
+        USERGUIDS % 430,
+        USERGUIDS % 431,
+        USERGUIDS % 432,
+        USERGUIDS % 433,
+        USERGUIDS % 434,
+        USERGUIDS % 435,
+        USERGUIDS % 436,
+        USERGUIDS % 437,
+        USERGUIDS % 438,
+        USERGUIDS % 439,
+        USERGUIDS % 440,
+        USERGUIDS % 441,
+        USERGUIDS % 442,
+        USERGUIDS % 443,
+        USERGUIDS % 444,
+        USERGUIDS % 445,
+        USERGUIDS % 446,
+        USERGUIDS % 447,
+        USERGUIDS % 448,
+        USERGUIDS % 449,
+        USERGUIDS % 450,
+        USERGUIDS % 451,
+        USERGUIDS % 452,
+        USERGUIDS % 453,
+        USERGUIDS % 454,
+        USERGUIDS % 455,
+        USERGUIDS % 456,
+        USERGUIDS % 457,
+        USERGUIDS % 458,
+        USERGUIDS % 459,
+        USERGUIDS % 460,
+        USERGUIDS % 461,
+        USERGUIDS % 462,
+        USERGUIDS % 463,
+        USERGUIDS % 464,
+        USERGUIDS % 465,
+        USERGUIDS % 466,
+        USERGUIDS % 467,
+        USERGUIDS % 468,
+        USERGUIDS % 469,
+        USERGUIDS % 470,
+        USERGUIDS % 471,
+        USERGUIDS % 472,
+        USERGUIDS % 473,
+        USERGUIDS % 474,
+        USERGUIDS % 475,
+        USERGUIDS % 476,
+        USERGUIDS % 477,
+        USERGUIDS % 478,
+        USERGUIDS % 479,
+        USERGUIDS % 480,
+        USERGUIDS % 481,
+        USERGUIDS % 482,
+        USERGUIDS % 483,
+        USERGUIDS % 484,
+        USERGUIDS % 485,
+        USERGUIDS % 486,
+        USERGUIDS % 487,
+        USERGUIDS % 488,
+        USERGUIDS % 489,
+        USERGUIDS % 490,
+        USERGUIDS % 491,
+        USERGUIDS % 492,
+        USERGUIDS % 493,
+        USERGUIDS % 494,
+        USERGUIDS % 495,
+        USERGUIDS % 496,
+        USERGUIDS % 497,
+        USERGUIDS % 498,
+        USERGUIDS % 499,
+        USERGUIDS % 500,
+    ),
+})
+
 for i in xrange(1, 101):
 
     memberElements = []

Modified: CalendarServer/branches/users/gaya/groupsharee2/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/conf/caldavd-test.plist	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/conf/caldavd-test.plist	2014-07-08 07:05:29 UTC (rev 13732)
@@ -816,6 +816,8 @@
 		<dict>
 			<key>Enabled</key>
 			<true/>
+			
+			<!-- Make these short for testing -->
             <key>RequestDelaySeconds</key>
             <real>0.1</real>
             <key>ReplyDelaySeconds</key>
@@ -830,6 +832,14 @@
       </dict>
     </dict>
 
+    <key>WorkQueue</key>
+    <dict>
+	  <!-- Make these short for testing -->
+      <key>failureRescheduleInterval</key>
+      <integer>1</integer>
+      <key>lockRescheduleInterval</key>
+      <integer>1</integer>
+    </dict>
 
     <!--
         Free-busy URL protocol

Modified: CalendarServer/branches/users/gaya/groupsharee2/conf/resources/caldavd-resources.plist
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/conf/resources/caldavd-resources.plist	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/conf/resources/caldavd-resources.plist	2014-07-08 07:05:29 UTC (rev 13732)
@@ -99,12 +99,6 @@
     <key>MaxAttendeesPerInstance</key>
     <integer>100</integer>
 
-    <!-- Maximum number of instances allowed for a single RRULE -->
-    <!-- 0 for no limit -->
-    <key>MaxInstancesForRRULE</key>
-    <integer>400</integer>
-
-
     <!--
         Directory service
 
@@ -119,7 +113,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
-      
+
       <key>params</key>
       <dict>
         <key>xmlFile</key>
@@ -131,14 +125,14 @@
         </array>
       </dict>
     </dict>
-    
+
     <key>ResourceService</key>
     <dict>
       <key>Enabled</key>
       <true/>
       <key>type</key>
       <string>twistedcaldav.directory.xmlfile.XMLDirectoryService</string>
-      
+
       <key>params</key>
       <dict>
         <key>xmlFile</key>
@@ -150,14 +144,14 @@
         </array>
       </dict>
     </dict>
-    
+
     <!-- Open Directory Service (Mac OS X) -->
     <!--
     <key>DirectoryService</key>
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.appleopendirectory.OpenDirectoryService</string>
-      
+
       <key>params</key>
       <dict>
         <key>node</key>
@@ -181,7 +175,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.augment.AugmentXMLDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>xmlFiles</key>
@@ -190,14 +184,14 @@
         </array>
       </dict>
     </dict>
-    
+
     <!-- Sqlite Augment Service -->
     <!--
     <key>AugmentService</key>
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.augment.AugmentSqliteDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>dbpath</key>
@@ -212,7 +206,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.augment.AugmentPostgreSQLDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>host</key>
@@ -228,7 +222,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.calendaruserproxy.ProxySqliteDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>dbpath</key>
@@ -242,7 +236,7 @@
     <dict>
       <key>type</key>
       <string>twistedcaldav.directory.calendaruserproxy.ProxyPostgreSQLDB</string>
-      
+
       <key>params</key>
       <dict>
         <key>host</key>
@@ -672,7 +666,7 @@
     <!-- Support for Content-Encoding compression -->
     <key>ResponseCompression</key>
     <false/>
-    
+
     <!-- The retry-after value (in seconds) to return with a 503 error. -->
     <key>HTTPRetryAfter</key>
     <integer>180</integer>

Modified: CalendarServer/branches/users/gaya/groupsharee2/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/contrib/performance/loadtest/ical.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/contrib/performance/loadtest/ical.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -1477,7 +1477,7 @@
         )
 
         # Finally, re-retrieve the event to update the etag
-        self._updateEvent(response, href)
+        yield self._updateEvent(response, href)
 
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/gaya/groupsharee2/requirements-dev.txt
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/requirements-dev.txt	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/requirements-dev.txt	2014-07-08 07:05:29 UTC (rev 13732)
@@ -7,4 +7,4 @@
 mockldap
 q
 --editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk@13420#egg=CalDAVClientLibrary
---editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@13670#egg=CalDAVTester
+--editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@13716#egg=CalDAVTester

Modified: CalendarServer/branches/users/gaya/groupsharee2/requirements-stable.txt
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/requirements-stable.txt	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/requirements-stable.txt	2014-07-08 07:05:29 UTC (rev 13732)
@@ -5,9 +5,9 @@
 # For CalendarServer development, don't try to get these projects from PyPI; use svn.
 
 -e .
--e svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@13660#egg=twextpy
+-e svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@13724#egg=twextpy
 -e svn+http://svn.calendarserver.org/repository/calendarserver/PyKerberos/trunk@13420#egg=kerberos
--e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@13621#egg=pycalendar
+-e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@13711#egg=pycalendar
 
 # Specify specific versions of our dependencies so that we're all testing the same config.
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/setup.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/setup.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/setup.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -256,17 +256,8 @@
 
 extensions = []
 
-# if sys.platform == "darwin":
-#     extensions.append(
-#         Extension(
-#             "calendarserver.platform.darwin._sacl",
-#             extra_link_args=["-framework", "Security"],
-#             sources=["calendarserver/platform/darwin/_sacl.c"]
-#         )
-#     )
 
 
-
 #
 # Run setup
 #

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/datafilters/hiddeninstance.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/datafilters/hiddeninstance.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/datafilters/hiddeninstance.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -15,7 +15,7 @@
 ##
 
 from twistedcaldav.datafilters.filter import CalendarFilter
-from twistedcaldav.ical import Component, ignoredComponents, Property
+from twistedcaldav.ical import Component, ignoredComponents
 
 __all__ = [
     "HiddenInstanceFilter",
@@ -50,10 +50,7 @@
 
                 # Add EXDATE and try to preserve same timezone as DTSTART
                 if master is not None:
-                    dtstart = master.getProperty("DTSTART")
-                    if dtstart is not None and not dtstart.value().isDateOnly() and dtstart.value().local():
-                        rid.adjustTimezone(dtstart.value().getTimezone())
-                    master.addProperty(Property("EXDATE", [rid, ]))
+                    master.addExdate(rid)
 
         return ical
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/augment.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/augment.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -504,8 +504,7 @@
         Remove all records.
         """
 
-        self.removeAugmentRecords(self.db.keys())
-        return succeed(None)
+        return self.removeAugmentRecords(self.db.keys())
 
 
     def _shouldReparse(self, xmlFiles):

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/calendaruserproxy.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/calendaruserproxy.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -858,7 +858,7 @@
             yield self.open()
 
         for group in [row[0] for row in (yield self.query("select GROUPNAME from GROUPS"))]:
-            self.removeGroup(group)
+            yield self.removeGroup(group)
 
         yield super(ProxyDB, self).clean()
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/digest.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/digest.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/digest.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -167,8 +167,7 @@
             header.
         """
 
-        challenge = yield (super(QopDigestCredentialFactory, self)
-                           .getChallenge(peer))
+        challenge = yield (super(QopDigestCredentialFactory, self).getChallenge(peer))
         c = challenge['nonce']
 
         # Make sure it is not a duplicate

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/principal.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/principal.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -290,15 +290,17 @@
         #
         # Create children
         #
-
-        self.supportedChildTypes = (
+        self.supportedChildTypes = [
             self.directory.recordType.user,
             self.directory.recordType.group,
             self.directory.recordType.location,
             self.directory.recordType.resource,
             self.directory.recordType.address,
-            self.directory.recordType.macOSXServerWiki,
-        )
+        ]
+        if config.Authentication.Wiki.Enabled:
+            self.supportedChildTypes.append(
+                self.directory.recordType.macOSXServerWiki
+            )
 
         for name, recordType in [
             (self.directory.recordTypeToOldName(r), r)

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/test/test_principal.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/test/test_principal.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -97,7 +97,9 @@
                         self.directory.recordType.location,
                         self.directory.recordType.resource,
                         self.directory.recordType.address,
-                        self.directory.recordType.macOSXServerWiki,
+                        # FIXME: add a test where wikis are enabled, then this
+                        # resource should appear:
+                        # self.directory.recordType.macOSXServerWiki,
                     )
                 ]
             )

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/util.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/util.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/directory/util.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -39,6 +39,7 @@
 
 log = Logger()
 
+
 def uuidFromName(namespace, name):
     """
     Generate a version 5 (SHA-1) UUID from a namespace UUID and a name.
@@ -69,6 +70,7 @@
 
 TRANSACTION_KEY = '_newStoreTransaction'
 
+
 def transactionFromRequest(request, newStore):
     """
     Return the associated transaction from the given HTTP request, creating a
@@ -97,6 +99,7 @@
         else:
             authz_uid = None
         transaction = newStore.newTransaction(repr(request), authz_uid=authz_uid)
+
         def abortIfUncommitted(request, response):
             try:
                 # TODO: missing 'yield' here.  For formal correctness as per
@@ -186,7 +189,11 @@
                 record = principal.parent.record
             except:
                 return None
-        return (record.recordType, record.shortNames[0])
+        try:
+            shortName = record.shortNames[0]
+        except AttributeError:
+            shortName = u""
+        return (record.recordType, shortName)
 
 
     def describe(principal):

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/ical.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/ical.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -45,7 +45,7 @@
 from twistedcaldav.dateops import timeRangesOverlap, normalizeForIndex, differenceDateTime, \
     normalizeForExpand
 from twistedcaldav.instance import InstanceList, InvalidOverriddenInstanceError
-from twistedcaldav.timezones import hasTZ, TimezoneException
+from twistedcaldav.timezones import hasTZ
 
 from txdav.caldav.datastore.scheduling.utils import normalizeCUAddr
 
@@ -602,13 +602,31 @@
 
 
     def getText(self, format=None):
-        return self.getTextWithTimezones(False, format)
+        """
+        Return text representation and include non-standard timezones.
+        """
+        return self._getTextWithTimezones(includeTimezones=Calendar.NONSTD_TIMEZONES, format=format)
 
 
     def getTextWithTimezones(self, includeTimezones, format=None):
         """
         Return text representation and include timezones if the option is on.
         """
+        includeTimezones = Calendar.ALL_TIMEZONES if includeTimezones else Calendar.NONSTD_TIMEZONES
+        return self._getTextWithTimezones(includeTimezones=includeTimezones, format=format)
+
+
+    def getTextWithoutTimezones(self, format=None):
+        """
+        Return text representation without including timezones.
+        """
+        return self._getTextWithTimezones(includeTimezones=Calendar.NO_TIMEZONES, format=format)
+
+
+    def _getTextWithTimezones(self, includeTimezones, format=None):
+        """
+        Return text representation and include timezones if the option is on.
+        """
         assert self.name() == "VCALENDAR", "Must be a VCALENDAR: {0!r}".format(self,)
 
         result = self._pycalendar.getText(includeTimezones=includeTimezones, format=format)
@@ -1708,6 +1726,21 @@
         return rid in new_rids
 
 
+    def addExdate(self, exdate):
+        """
+        Add an EXDATE to a master recurring component and ensure the value type, TZID
+        etc match the DTSTART of the master. This method assumes that L{self} is the
+        master component - no checking of that will be done.
+
+        @param exdate: the exdate to add
+        @type exdate: L{DateTime}
+        """
+        dtstart = self.getProperty("DTSTART")
+        if dtstart is not None and not dtstart.value().isDateOnly() and dtstart.value().local():
+            exdate.adjustTimezone(dtstart.value().getTimezone())
+        self.addProperty(Property("EXDATE", [exdate, ]))
+
+
     def resourceUID(self):
         """
         @return: the UID of the subcomponents in this component.
@@ -1766,28 +1799,13 @@
         return self._resource_type
 
 
-    def stripKnownTimezones(self):
+    def stripStandardTimezones(self):
         """
         Remove timezones that this server knows about
         """
+        return self._pycalendar.stripStandardTimezones()
 
-        changed = False
-        for subcomponent in tuple(self.subcomponents()):
-            if subcomponent.name() == "VTIMEZONE":
-                tzid = subcomponent.propertyValue("TZID")
-                try:
-                    hasTZ(tzid)
-                except TimezoneException:
-                    # tzid not available - do not strip
-                    pass
-                else:
-                    # tzid known - strip component out
-                    self.removeComponent(subcomponent)
-                    changed = True
 
-        return changed
-
-
     def validCalendarData(self, doFix=True, doRaise=True, validateRecurrences=False):
         """
         @return: tuple of fixed, unfixed issues

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/resource.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/resource.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -886,7 +886,7 @@
     @inlineCallbacks
     def ownerPrincipal(self, request):
         """
-        Return the DAV:owner property value (MUST be a DAV:href or None).
+        Return the principal resource for the owner of this resource.
         """
         if hasattr(self, "_newStoreObject"):
             if not hasattr(self._newStoreObject, "ownerHome"):
@@ -906,7 +906,7 @@
     @inlineCallbacks
     def resourceOwnerPrincipal(self, request):
         """
-        This is the owner of the resource based on the URI used to access it. For a shared
+        This is the principal resource of the owner of the resource based on the URI used to access it. For a shared
         collection it will be the sharee, otherwise it will be the regular the ownerPrincipal.
         """
         parent = (yield self.locateParent(
@@ -1884,7 +1884,7 @@
                 ))
 
             # elif name == "auto-schedule" and self.calendarsEnabled():
-            #     autoSchedule = self.getAutoSchedule()
+            #     autoSchedule = yield self.getAutoSchedule()
             #     returnValue(customxml.AutoSchedule("true" if autoSchedule else "false"))
 
             elif name == "auto-schedule-mode" and self.calendarsEnabled():

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/scheduling_store/caldav/resource.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/scheduling_store/caldav/resource.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/scheduling_store/caldav/resource.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -471,17 +471,13 @@
 
     @inlineCallbacks
     def loadOriginatorFromRequestDetails(self, request):
-        # Get the originator who is the authenticated user
-        originatorPrincipal = None
+        # The originator is the owner of the Outbox. We will have checked prior to this
+        # that the authenticated user has privileges to schedule as the owner.
         originator = ""
-        authz_principal = self.currentPrincipal(request).children[0]
-        if isinstance(authz_principal, davxml.HRef):
-            originatorPrincipalURL = str(authz_principal)
-            if originatorPrincipalURL:
-                originatorPrincipal = (yield request.locateResource(originatorPrincipalURL))
-                if originatorPrincipal:
-                    # Pick the canonical CUA:
-                    originator = originatorPrincipal.canonicalCalendarUserAddress()
+        originatorPrincipal = (yield self.ownerPrincipal(request))
+        if originatorPrincipal:
+            # Pick the canonical CUA:
+            originator = originatorPrincipal.canonicalCalendarUserAddress()
 
         if not originator:
             self.log.error("%s request must have Originator" % (self.method,))
@@ -545,7 +541,8 @@
                 )
             )
         else:
-            returnValue(super(ScheduleOutboxResource, self).defaultAccessControlList())
+            result = yield super(ScheduleOutboxResource, self).defaultAccessControlList()
+            returnValue(result)
 
 
     def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/stdconfig.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/stdconfig.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -323,7 +323,17 @@
     # Work queue configuration information
     #
     "WorkQueue": {
-        "ampPort": 7654,            # Port used for hosts in a cluster to take to each other
+        "ampPort": 7654,            # Port used for hosts in a cluster to talk to each other
+
+        "queuePollInterval": 0.1,   # Interval in seconds for job queue polling
+        "queueOverdueTimeout": 300, # Number of seconds before an assigned job is considered overdue
+
+        "overloadLevel": 95,        # Queue capacity (percentage) which causes job processing to halt
+        "highPriorityLevel": 80,    # Queue capacity (percentage) at which only high priority items are run
+        "mediumPriorityLevel": 50,  # Queue capacity (percentage) at which only high/medium priority items are run
+
+        "failureRescheduleInterval": 60,    # When a job fails, reschedule it this number of seconds in the future
+        "lockRescheduleInterval": 60,       # When a job can't run because of a lock, reschedule it this number of seconds in the future
     },
 
     #

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/storebridge.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/storebridge.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -2888,7 +2888,7 @@
             except ResourceDeletedError:
                 # This is OK - it just means the server deleted the resource during the PUT. We make it look
                 # like the PUT succeeded.
-                response = responsecode.CREATED if self.exists() else responsecode.NO_CONTENT
+                response = responsecode.NO_CONTENT if self.exists() else responsecode.CREATED
 
                 # Re-initialize to get stuff setup again now we have no object
                 self._initializeWithObject(None, self._newStoreParent)
@@ -3622,7 +3622,7 @@
             except ResourceDeletedError:
                 # This is OK - it just means the server deleted the resource during the PUT. We make it look
                 # like the PUT succeeded.
-                response = responsecode.CREATED if self.exists() else responsecode.NO_CONTENT
+                response = responsecode.NO_CONTENT if self.exists() else responsecode.CREATED
 
                 # Re-initialize to get stuff setup again now we have no object
                 self._initializeWithObject(None, self._newStoreParent)
@@ -3804,7 +3804,7 @@
         @return: a sequence of the names of all known children of this resource.
         """
         children = set(self.putChildren.keys())
-        children.update(self._newStoreNotifications.listNotificationObjects())
+        children.update((yield self._newStoreNotifications.listNotificationObjects()))
         returnValue(children)
 
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/test/test_icalendar.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/test/test_icalendar.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -5673,6 +5673,93 @@
                 self.fail("Valid calendar should validate")
 
 
+
+
+    def test_add_exdate(self):
+        data = ((
+            """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 3.0//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:FB81D520-ED27-4DBA-8894-45B7612A7621
+DTSTART;TZID=US/Pacific:20090705T100000
+DTEND;TZID=US/Pacific:20090730T103000
+CREATED:20090604T225706Z
+DTSTAMP:20090604T230500Z
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:TEST
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""",
+            (
+                DateTime(2009, 7, 6, 17, 0, 0, tzid=Timezone(utc=True)),
+                DateTime(2009, 7, 6, 10, 0, 0, tzid=Timezone(tzid="US/Pacific")),
+            ),
+
+            """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 3.0//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:FB81D520-ED27-4DBA-8894-45B7612A7621
+DTSTART;TZID=US/Pacific:20090705T100000
+DTEND;TZID=US/Pacific:20090730T103000
+CREATED:20090604T225706Z
+DTSTAMP:20090604T230500Z
+EXDATE;TZID=US/Pacific:20090706T100000
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:TEST
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""",
+        ))
+
+        for exdate in data[1]:
+            calendar = Component.fromString(data[0])
+            result = Component.fromString(data[2])
+            calendar.masterComponent().addExdate(exdate)
+            self.assertEqual(normalize_iCalStr(calendar), normalize_iCalStr(result), "Failed exdate add: {}".format(exdate))
+
+
     def test_allperuseruids(self):
         data = """BEGIN:VCALENDAR
 VERSION:2.0

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/test/test_upgrade.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/test/test_upgrade.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/test/test_upgrade.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -14,26 +14,26 @@
 # limitations under the License.
 ##
 
+import cPickle
 import hashlib
 import os
 import zlib
-import cPickle
 
 from twisted.internet.defer import inlineCallbacks, succeed
-
-from txdav.xml.parser import WebDAVDocument
-from txdav.caldav.datastore.index_file import db_basename
-
+from twisted.python.filepath import FilePath
 from twistedcaldav.config import config
-from txdav.caldav.datastore.scheduling.imip.mailgateway import MailGatewayTokensDatabase
+from twistedcaldav.directory.calendaruserproxy import ProxySqliteDB
+from twistedcaldav.test.util import StoreTestCase
 from twistedcaldav.upgrade import (
     xattrname, upgradeData, updateFreeBusySet,
     removeIllegalCharacters, normalizeCUAddrs,
-    loadDelegatesFromXML, migrateDelegatesToStore
+    loadDelegatesFromXML, migrateDelegatesToStore,
+    upgradeResourcesXML
 )
-from twistedcaldav.test.util import StoreTestCase
+from txdav.caldav.datastore.index_file import db_basename
+from txdav.caldav.datastore.scheduling.imip.mailgateway import MailGatewayTokensDatabase
 from txdav.who.delegates import delegatesOf
-from twistedcaldav.directory.calendaruserproxy import ProxySqliteDB
+from txdav.xml.parser import WebDAVDocument
 
 
 
@@ -1503,7 +1503,34 @@
         yield txn.commit()
 
 
+    def test_resourcesXML(self):
+        """
+        Verify conversion of old resources.xml format to twext.who.xml format
+        """
+        fileName = self.mktemp()
+        fp = FilePath(fileName)
+        fp.setContent(oldResourcesFormat)
+        upgradeResourcesXML(fp)
+        self.assertEquals(fp.getContent(), newResourcesFormat)
 
+
+oldResourcesFormat = """<accounts realm="/Search">
+  <location>
+    <uid>location1</uid>
+    <guid>C4F46062-9094-4D34-8591-61A42D993FAA</guid>
+    <name>location name</name>
+  </location>
+  <resource>
+    <uid>resource1</uid>
+    <guid>60B771CC-D727-4453-ACE0-0FE13CD7445A</guid>
+    <name>resource name</name>
+  </resource>
+</accounts>
+"""
+
+newResourcesFormat = """<directory realm="/Search"><record type="location"><short-name>location1</short-name><guid>C4F46062-9094-4D34-8591-61A42D993FAA</guid><uid>C4F46062-9094-4D34-8591-61A42D993FAA</uid><full-name>location name</full-name></record><record type="resource"><short-name>resource1</short-name><guid>60B771CC-D727-4453-ACE0-0FE13CD7445A</guid><uid>60B771CC-D727-4453-ACE0-0FE13CD7445A</uid><full-name>resource name</full-name></record></directory>"""
+
+
 normalizeEvent = """BEGIN:VCALENDAR
 VERSION:2.0
 BEGIN:VEVENT

Modified: CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/upgrade.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/twistedcaldav/upgrade.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -30,6 +30,10 @@
 from zlib import compress
 from cPickle import loads as unpickle, UnpicklingError
 
+from xml.etree.ElementTree import (
+    parse as parseXML, ParseError as XMLParseError,
+    tostring as etreeToString, Element as XMLElement
+)
 
 from twext.python.log import Logger
 from txdav.xml import element
@@ -48,6 +52,7 @@
 from twisted.internet.defer import (
     inlineCallbacks, succeed, returnValue
 )
+from twisted.python.filepath import FilePath
 from twisted.python.reflect import namedAny
 from twisted.python.reflect import namedClass
 
@@ -646,7 +651,54 @@
         raise UpgradeError("Data upgrade failed, see error.log for details")
 
 
+def upgradeResourcesXML(resourcesFilePath):
+    """
+    Convert the old XML format to the twext.who.xml format
 
+    @param resourcesFilePath: the file to convert
+    @type resourcesFilePath: L{FilePath}
+    """
+    try:
+        with resourcesFilePath.open() as fh:
+            try:
+                etree = parseXML(fh)
+            except XMLParseError:
+                log.error("Cannot parse {path}", path=resourcesFilePath.path)
+                return
+    except (OSError, IOError):
+        # Can't read the file
+        log.error("Cannot read {path}", path=resourcesFilePath.path)
+        return
+
+    accountsNode = etree.getroot()
+    if accountsNode.tag != "accounts":
+        return
+
+    tagMap = {
+        "uid": ("short-name",),
+        "guid": ("guid", "uid"),
+        "name": ("full-name",),
+    }
+    log.info("Converting resources.xml")
+    directoryNode = XMLElement("directory")
+    directoryNode.set("realm", accountsNode.get("realm"))
+    for sourceNode in accountsNode:
+        recordType = sourceNode.tag
+        destNode = XMLElement("record")
+        destNode.set("type", recordType)
+        for sourceFieldNode in sourceNode:
+            tags = tagMap.get(sourceFieldNode.tag, None)
+            if tags:
+                for tag in tags:
+                    destFieldNode = XMLElement(tag)
+                    destFieldNode.text = sourceFieldNode.text
+                    destNode.append(destFieldNode)
+
+        directoryNode.append(destNode)
+
+    resourcesFilePath.setContent(etreeToString(directoryNode, "utf-8"))
+
+
 # The on-disk version number (which defaults to zero if .calendarserver_version
 # doesn't exist), is compared with each of the numbers in the upgradeMethods
 # array.  If it is less than the number, the associated method is called.
@@ -660,6 +712,14 @@
 @inlineCallbacks
 def upgradeData(config, directory):
 
+    if config.ResourceService.Enabled:
+        resourcesFileName = config.ResourceService.params.xmlFile
+        if resourcesFileName[0] not in ("/", "."):
+            resourcesFileName = os.path.join(config.DataRoot, resourcesFileName)
+        resourcesFilePath = FilePath(resourcesFileName)
+        if resourcesFilePath.exists():
+            upgradeResourcesXML(resourcesFilePath)
+
     triggerPath = os.path.join(config.ServerRoot, TRIGGER_FILE)
     if os.path.exists(triggerPath):
         try:

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/schedule.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/schedule.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/schedule.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -43,8 +43,7 @@
     @inlineCallbacks
     def calendarHomeWithUID(self, uid, create=False):
         # FIXME: 'create' flag
-        newHome = yield super(ImplicitTransaction, self
-            ).calendarHomeWithUID(uid, create)
+        newHome = yield super(ImplicitTransaction, self).calendarHomeWithUID(uid, create)
 #        return ImplicitCalendarHome(newHome, self)
         if newHome is None:
             returnValue(None)

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/implicit.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/implicit.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -30,12 +30,13 @@
     LocalCalendarUser, OtherServerCalendarUser, \
     calendarUserFromCalendarUserAddress, \
     calendarUserFromCalendarUserUID
-from txdav.caldav.datastore.scheduling.utils import normalizeCUAddr
+from txdav.caldav.datastore.scheduling.utils import normalizeCUAddr,\
+    uidFromCalendarUserAddress
 from txdav.caldav.datastore.scheduling.icaldiff import iCalDiff
 from txdav.caldav.datastore.scheduling.itip import iTipGenerator, iTIPRequestStatus
 from txdav.caldav.datastore.scheduling.utils import getCalendarObjectForRecord
 from txdav.caldav.datastore.scheduling.work import ScheduleReplyWork, \
-    ScheduleReplyCancelWork, ScheduleOrganizerWork
+    ScheduleReplyCancelWork, ScheduleOrganizerWork, ScheduleOrganizerSendWork
 
 import collections
 
@@ -468,6 +469,38 @@
 
 
     @inlineCallbacks
+    def queuedOrganizerSending(self, txn, action, home, resource, uid, organizer, attendee, itipmsg, no_refresh):
+        """
+        Process an organizer scheduling work queue item. The basic goal here is to setup the ImplicitScheduler as if
+        this operation were the equivalent of the PUT that enqueued the work, and then do the actual work.
+        """
+
+        self.txn = txn
+        self.action = action
+        self.state = "organizer"
+        self.calendar_home = home
+        self.resource = resource
+        self.queuedResponses = []
+        self.suppress_refresh = no_refresh
+        self.uid = uid
+        self.calendar = None
+        self.oldcalendar = None
+
+        self.organizer = organizer
+        self.attendees = None
+        self.organizerAddress = None
+
+        # Originator is the organizer in this case
+        self.originator = self.organizer
+
+        self.except_attendees = ()
+        self.only_refresh_attendees = None
+        self.split_details = None
+
+        yield self.processSend(attendee, itipmsg, jobqueue=False)
+
+
+    @inlineCallbacks
     def sendAttendeeReply(self, txn, resource):
 
         self.txn = txn
@@ -591,8 +624,24 @@
         if not self.organizer:
             returnValue(False)
 
-        # Check to see whether any attendee is the owner
+        # Performance optimization: calling L{calendarUserFromCalendarUserAddress} results
+        # in a directory lookup which may be expensive and we may end up doing it for every
+        # attendee. However, all we need is the uid from the cu-address, so do one loop first
+        # just using L{uidFromCalendarUserAddress} which is super fast, and if that does not
+        # match, then do the slower loop
+
+        # Fast loop: Check to see whether any attendee is the owner
         for attendee in self.attendees:
+            uid = uidFromCalendarUserAddress(attendee)
+            if uid is not None and uid == self.calendar_home.uid():
+                attendeeAddress = yield calendarUserFromCalendarUserAddress(attendee, self.txn)
+                if attendeeAddress.hosted() and attendeeAddress.record.uid == self.calendar_home.uid():
+                    self.attendee = attendee
+                    self.attendeeAddress = attendeeAddress
+                    returnValue(True)
+
+        # Slow Loop: Check to see whether any attendee is the owner
+        for attendee in self.attendees:
             attendeeAddress = yield calendarUserFromCalendarUserAddress(attendee, self.txn)
             if attendeeAddress.hosted() and attendeeAddress.record.uid == self.calendar_home.uid():
                 self.attendee = attendee
@@ -1226,7 +1275,7 @@
 
         # Process regular requests next
         if self.action in ("create", "modify",):
-            total += (yield self.processRequests())
+            total += (yield self.processRequests(total))
 
         if self.logItems is not None:
             self.logItems["itip.requests"] = total
@@ -1280,21 +1329,15 @@
                     itipmsg.addProperty(Property("X-CALENDARSERVER-SPLIT-RID", rid))
                     itipmsg.addProperty(Property("X-CALENDARSERVER-SPLIT-OLDER-UID" if newer_piece else "X-CALENDARSERVER-SPLIT-NEWER-UID", uid))
 
-                # This is a local CALDAV scheduling operation.
-                scheduler = self.makeScheduler()
+                yield self.processSend(attendee, itipmsg, count=count)
 
-                # Do the PUT processing
-                log.info("Implicit CANCEL - organizer: '{organizer}' to attendee: '{attendee}', UID: '{uid}', RIDs: '{rids}'", organizer=self.organizer, attendee=attendee, uid=self.uid, rids=rids)
-                response = (yield scheduler.doSchedulingViaPUT(self.originator, (attendee,), itipmsg, internal_request=True, suppress_refresh=self.suppress_refresh))
-                self.handleSchedulingResponse(response, True)
-
                 count += 1
 
         returnValue(count)
 
 
     @inlineCallbacks
-    def processRequests(self):
+    def processRequests(self, cancel_count=0):
 
         # TODO: a better policy here is to aggregate by attendees with the same set of instances
         # being requested, but for now we will do one scheduling message per attendee.
@@ -1341,19 +1384,57 @@
                     itipmsg.addProperty(Property("X-CALENDARSERVER-SPLIT-RID", rid))
                     itipmsg.addProperty(Property("X-CALENDARSERVER-SPLIT-OLDER-UID" if newer_piece else "X-CALENDARSERVER-SPLIT-NEWER-UID", uid))
 
-                # This is a local CALDAV scheduling operation.
-                scheduler = self.makeScheduler()
+                yield self.processSend(attendee, itipmsg, count=count + cancel_count)
 
-                # Do the PUT processing
-                log.info("Implicit REQUEST - organizer: '{organizer}' to attendee: '{attendee}', UID: '{uid}'", organizer=self.organizer, attendee=attendee, uid=self.uid)
-                response = (yield scheduler.doSchedulingViaPUT(self.originator, (attendee,), itipmsg, internal_request=True, suppress_refresh=self.suppress_refresh))
-                self.handleSchedulingResponse(response, True)
-
                 count += 1
 
         returnValue(count)
 
 
+    @inlineCallbacks
+    def processSend(self, attendee, itipmsg, jobqueue=True, count=0):
+        """
+        Send an iTIP message to an attendee. This might send it directly, or it might create job to
+        send it later.
+
+        @param attendee: the calendar user address of the attendee to send the message to
+        @type attendee: L{str}
+        @param itipmsg: the iTIP message to send
+        @type itipmsg: L{Component}
+        @param jobqueue: if allowed, queue up a job to do the actual work
+        @type jobqueue: L{bool}
+        """
+
+        # Attendee refreshes are already executed in a job (in batches) so don't create more
+        if jobqueue and config.Scheduling.Options.WorkQueues.Enabled and not hasattr(self.txn, "doing_attendee_refresh"):
+            # Create job for the work
+            yield ScheduleOrganizerSendWork.schedule(
+                self.txn,
+                self.action,
+                self.calendar_home,
+                self.resource,
+                self.organizerAddress.record.canonicalCalendarUserAddress(),
+                attendee,
+                itipmsg,
+                self.suppress_refresh,
+                count,
+            )
+        else:
+            # Execute the work right now
+            scheduler = self.makeScheduler()
+
+            # Do the PUT processing
+            log.info(
+                "Implicit {method} - organizer: '{organizer}' to attendee: '{attendee}', UID: '{uid}'",
+                method=itipmsg.propertyValue("METHOD"),
+                organizer=self.organizer,
+                attendee=attendee,
+                uid=self.uid,
+            )
+            response = (yield scheduler.doSchedulingViaPUT(self.originator, (attendee,), itipmsg, internal_request=True, suppress_refresh=self.suppress_refresh))
+            self.handleSchedulingResponse(response, True)
+
+
     def handleSchedulingResponse(self, response, is_organizer):
 
         # For a queued operation we stash the response away for the work item to deal with
@@ -1475,9 +1556,13 @@
                 if doScheduling:
                     # Check to see whether all instances are CANCELLED
                     if self.calendar.hasPropertyValueInAllComponents(Property("STATUS", "CANCELLED")):
-                        log.debug("Attendee '{attendee}' is creating CANCELLED event for missing UID: '{uid}' - removing entire event", attendee=self.attendee, uid=self.uid)
-                        self.return_status = ImplicitScheduler.STATUS_ORPHANED_CANCELLED_EVENT
-                        returnValue(None)
+                        if self.action == "create":
+                            log.debug("Attendee '{attendee}' is creating CANCELLED event for missing UID: '{uid}' - removing entire event", attendee=self.attendee, uid=self.uid)
+                            self.return_status = ImplicitScheduler.STATUS_ORPHANED_CANCELLED_EVENT
+                            returnValue(None)
+                        else:
+                            log.debug("Attendee '{attendee}' is modifying CANCELLED event for missing UID: '{uid}'", attendee=self.attendee, uid=self.uid)
+                            returnValue(None)
                     else:
                         # Check to see whether existing event is SCHEDULE-AGENT=CLIENT/NONE
                         if self.oldcalendar:

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/itip.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/itip.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/itip.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -174,10 +174,17 @@
                         hidden = component.hasProperty(Component.HIDDEN_INSTANCE_PROPERTY)
                         new_component = new_calendar.deriveInstance(rid, allowCancelled=allowCancelled and not hidden)
                         if new_component is not None:
-                            new_calendar.addComponent(new_component)
-                            iTipProcessing.transferItems(calendar, new_component, needs_action_rids, reschedule, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)
-                            if hidden:
-                                new_component.addProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
+                            # If the new component is not CANCELLED then add the one derived from the new master and
+                            # sync over attendee properties from the existing attendee data. However, if the new
+                            # component is cancelled, we need to preserve the original state of the attendee's
+                            # version as it may differ from the one derived from the new master.
+                            if allowCancelled:
+                                new_calendar.addComponent(component.duplicate())
+                            else:
+                                new_calendar.addComponent(new_component)
+                                iTipProcessing.transferItems(calendar, new_component, needs_action_rids, reschedule, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)
+                                if hidden:
+                                    new_component.addProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
 
             iTipProcessing.addTranspForNeedsAction(new_calendar.subcomponents(), recipient)
 
@@ -298,11 +305,12 @@
                         overridden.replaceProperty(Property("STATUS", "CANCELLED"))
                         calendar.addComponent(overridden)
                         newseq = component.propertyValue("SEQUENCE")
-                        overridden.replacePropertyInAllComponents(Property("SEQUENCE", newseq))
+                        overridden.replaceProperty(Property("SEQUENCE", newseq))
 
         # If we have any EXDATEs lets add them to the existing calendar object.
         if exdates and calendar_master:
-            calendar_master.addProperty(Property("EXDATE", exdates))
+            for exdate in exdates:
+                calendar_master.addExdate(exdate)
 
         # See if there are still components in the calendar - we might have deleted the last overridden instance
         # in which case the calendar object is empty (except for VTIMEZONEs) or has only hidden components.

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/processing.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/processing.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -463,7 +463,7 @@
             if send_reply:
                 # Track outstanding auto-reply processing
                 log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply queued" % (self.recipient.cuaddr, self.uid,))
-                ScheduleAutoReplyWork.autoReply(self.txn, new_resource, partstat)
+                yield ScheduleAutoReplyWork.autoReply(self.txn, new_resource, partstat)
 
             # Build the schedule-changes XML element
             changes = customxml.ScheduleChanges(
@@ -511,7 +511,7 @@
                 if send_reply:
                     # Track outstanding auto-reply processing
                     log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply queued" % (self.recipient.cuaddr, self.uid,))
-                    ScheduleAutoReplyWork.autoReply(self.txn, new_resource, partstat)
+                    yield ScheduleAutoReplyWork.autoReply(self.txn, new_resource, partstat)
 
                 # Build the schedule-changes XML element
                 update_details = []

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/test/test_implicit.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/test/test_implicit.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/test/test_implicit.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -34,6 +34,7 @@
 from txdav.caldav.datastore.scheduling.scheduler import ScheduleResponseQueue
 from txdav.caldav.icalendarstore import AttendeeAllowedError, \
     ComponentUpdateState
+from txdav.caldav.datastore.sql import CalendarObject
 from txdav.common.datastore.test.util import CommonCommonTests, populateCalendarsFrom
 
 from twext.enterprise.jobqueue import JobItem
@@ -1275,7 +1276,10 @@
         yield self._createCalendarObject(data1, "user01", "test.ics")
 
         cobj = yield self.calendarObjectUnderTest(home="user01", name="test.ics")
+        actualVersion = CalendarObject._currentDataVersion
+        self.patch(CalendarObject, "_currentDataVersion", 0)
         yield cobj._setComponentInternal(Component.fromString(data1), internal_state=ComponentUpdateState.RAW)
+        CalendarObject._currentDataVersion = actualVersion
         yield self.commit()
 
         cobj = yield self.calendarObjectUnderTest(home="user01", name="test.ics")
@@ -1285,7 +1289,10 @@
         self.assertFalse(comp.getOrganizerScheduleAgent())
 
         cobj = yield self.calendarObjectUnderTest(home="user01", name="test.ics")
+        actualVersion = CalendarObject._currentDataVersion
+        self.patch(CalendarObject, "_currentDataVersion", 0)
         yield cobj.setComponent(Component.fromString(data2))
+        CalendarObject._currentDataVersion = actualVersion
         yield self.commit()
 
         cobj = yield self.calendarObjectUnderTest(home="user01", name="test.ics")

Copied: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/test/test_work.py (from rev 13731, CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_work.py)
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/test/test_work.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/test/test_work.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,267 @@
+##
+# Copyright (c) 2013-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from twistedcaldav.ical import Component
+from twext.enterprise.jobqueue import JobItem, WorkItem
+from txdav.common.datastore.sql_tables import scheduleActionFromSQL
+from twisted.internet import reactor
+
+"""
+Tests for txdav.caldav.datastore.utils
+"""
+
+from twisted.internet.defer import inlineCallbacks
+from twisted.trial import unittest
+
+from txdav.caldav.datastore.scheduling.work import ScheduleOrganizerWork, \
+    ScheduleWorkMixin, ScheduleWork, ScheduleOrganizerSendWork
+from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
+
+
+
+class BaseWorkTests(CommonCommonTests, unittest.TestCase):
+    """
+    Tests for scheduling work.
+    """
+    @inlineCallbacks
+    def setUp(self):
+
+        yield super(BaseWorkTests, self).setUp()
+        yield self.buildStoreAndDirectory()
+        yield self.populate()
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+        self.notifierFactory.reset()
+
+    requirements = {
+        "user01": {
+            "calendar": {
+            },
+            "inbox": {
+            },
+        },
+        "user02": {
+            "calendar": {
+            },
+            "inbox": {
+            },
+        },
+        "user03": {
+            "calendar": {
+            },
+            "inbox": {
+            },
+        },
+    }
+
+
+    def storeUnderTest(self):
+        """
+        Create and return a L{CalendarStore} for testing.
+        """
+        return self._sqlCalendarStore
+
+
+
+class TestScheduleOrganizerWork(BaseWorkTests):
+    """
+    Test creation of L{ScheduleOrganizerWork} items.
+    """
+
+    calendar_old = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+END:VEVENT
+END:VCALENDAR
+""")
+
+    calendar_new = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T130000Z
+DURATION:PT1H
+ORGANIZER:urn:uuid:user01
+ATTENDEE:urn:uuid:user01
+ATTENDEE:urn:uuid:user02
+END:VEVENT
+END:VCALENDAR
+""")
+
+
+    @inlineCallbacks
+    def test_create(self):
+        """
+        Test that jobs associated with L{txdav.caldav.datastore.scheduling.work.ScheduleOrganizerWork}
+        can be created and correctly removed.
+        """
+
+        ScheduleWorkMixin._queued = 0
+        txn = self.transactionUnderTest()
+        home = yield self.homeUnderTest(name="user01")
+        yield ScheduleOrganizerWork.schedule(
+            txn,
+            "12345-67890",
+            "create",
+            home,
+            None,
+            None,
+            self.calendar_new,
+            "urn:uuid:user01",
+            2,
+            True,
+        )
+        yield self.commit()
+        self.assertEqual(ScheduleWorkMixin._queued, 1)
+
+        jobs = yield JobItem.all(self.transactionUnderTest())
+        self.assertEqual(len(jobs), 1)
+
+        work = yield jobs[0].workItem()
+        self.assertTrue(isinstance(work, ScheduleOrganizerWork))
+        self.assertEqual(work.icalendarUid, "12345-67890")
+        self.assertEqual(scheduleActionFromSQL[work.scheduleAction], "create")
+
+        yield work.delete()
+        yield jobs[0].delete()
+        yield self.commit()
+
+        jobs = yield JobItem.all(self.transactionUnderTest())
+        self.assertEqual(len(jobs), 0)
+        work = yield ScheduleOrganizerWork.all(self.transactionUnderTest())
+        self.assertEqual(len(work), 0)
+        baseWork = yield ScheduleWork.all(self.transactionUnderTest())
+        self.assertEqual(len(baseWork), 0)
+
+
+    @inlineCallbacks
+    def test_cascade_delete_cleanup(self):
+        """
+        Test that when work associated with L{txdav.caldav.datastore.scheduling.work.ScheduleWork}
+        is removed with the L{ScheduleWork} item being removed, the associated L{JobItem} runs and
+        removes itself and the L{ScheduleWork}.
+        """
+
+        ScheduleWorkMixin._queued = 0
+        txn = self.transactionUnderTest()
+        home = yield self.homeUnderTest(name="user01")
+        yield ScheduleOrganizerWork.schedule(
+            txn,
+            "12345-67890",
+            "create",
+            home,
+            None,
+            None,
+            self.calendar_new,
+            "urn:uuid:user01",
+            2,
+            True,
+        )
+        yield self.commit()
+        self.assertEqual(ScheduleWorkMixin._queued, 1)
+
+        jobs = yield JobItem.all(self.transactionUnderTest())
+        work = yield jobs[0].workItem()
+        yield WorkItem.delete(work)
+        yield self.commit()
+
+        jobs = yield JobItem.all(self.transactionUnderTest())
+        self.assertEqual(len(jobs), 1)
+        baseWork = yield ScheduleWork.all(self.transactionUnderTest())
+        self.assertEqual(len(baseWork), 1)
+        self.assertEqual(baseWork[0].jobID, jobs[0].jobID)
+
+        work = yield jobs[0].workItem()
+        self.assertTrue(work is None)
+        yield self.commit()
+
+        yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60)
+
+        jobs = yield JobItem.all(self.transactionUnderTest())
+        self.assertEqual(len(jobs), 0)
+        work = yield ScheduleOrganizerWork.all(self.transactionUnderTest())
+        self.assertEqual(len(work), 0)
+        baseWork = yield ScheduleWork.all(self.transactionUnderTest())
+        self.assertEqual(len(baseWork), 0)
+
+
+
+class TestScheduleOrganizerSendWork(BaseWorkTests):
+    """
+    Test creation of L{ScheduleOrganizerSendWork} items.
+    """
+
+    itip_new = Component.fromString("""BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T130000Z
+DURATION:PT1H
+ORGANIZER:urn:x-uid:user01
+{attendees}
+END:VEVENT
+END:VCALENDAR
+""".format(attendees="\n".join(["ATTENDEE:urn:x-uid:user%02d" % i for i in range(1, 100)]))
+)
+
+
+    @inlineCallbacks
+    def test_create(self):
+        """
+        Test that jobs associated with L{txdav.caldav.datastore.scheduling.work.ScheduleOrganizerSendWork}
+        can be created and correctly removed.
+        """
+
+        txn = self.transactionUnderTest()
+        home = yield self.homeUnderTest(name="user01")
+        yield ScheduleOrganizerSendWork.schedule(
+            txn,
+            "create",
+            home,
+            None,
+            "urn:x-uid:user01",
+            "urn:x-uid:user02",
+            self.itip_new,
+            True,
+            1000,
+        )
+
+        jobs = yield JobItem.all(self.transactionUnderTest())
+        self.assertEqual(len(jobs), 1)
+
+        work = yield jobs[0].workItem()
+        yield work.doWork()
+
+        home2 = yield self.calendarUnderTest(home="user02", name="calendar")
+        cobjs = yield home2.calendarObjects()
+        self.assertEqual(len(cobjs), 1)
+        #cal2 = yield cobjs[0].component()
+
+        yield work.delete()
+        yield jobs[0].delete()
+        yield self.commit()

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/work.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/work.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/scheduling/work.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -14,10 +14,10 @@
 # limitations under the License.
 ##
 
-from twext.enterprise.dal.record import fromTable
+from twext.enterprise.dal.record import fromTable, Record
 from twext.enterprise.dal.syntax import Select, Insert, Delete, Parameter
 from twext.enterprise.locking import NamedLock
-from twext.enterprise.jobqueue import WorkItem, WORK_PRIORITY_MEDIUM
+from twext.enterprise.jobqueue import WorkItem, WORK_PRIORITY_MEDIUM, JobItem
 from twext.python.log import Logger
 
 from twisted.internet.defer import inlineCallbacks, returnValue, Deferred
@@ -51,7 +51,10 @@
 
 class ScheduleWorkMixin(WorkItem):
     """
-    Base class for common schedule work item behavior.
+    Base class for common schedule work item behavior. Sub-classes have their own class specific data
+    stored in per-class tables. This class manages a SCHEDULE_WORK table that contains the work id, job id
+    and iCalendar UID. That table is used for locking all scheduling items with the same UID, as well as
+    allow smart re-scheduling/ordering etc of items with the same UID.
     """
 
     # Track when all work is complete (needed for unit tests)
@@ -59,12 +62,132 @@
     _queued = 0
 
     # Schedule work is grouped based on calendar object UID
-    group = property(lambda self: (self.table.ICALENDAR_UID == self.icalendarUid))
     default_priority = WORK_PRIORITY_MEDIUM
     default_weight = 5
 
+    @classmethod
+    @inlineCallbacks
+    def create(cls, transaction, **kwargs):
+        """
+        A new work item needs to be created. First we create a SCHEDULE_WORK record, then
+        we create the actual work item.
 
+        @param transaction: the transaction to use
+        @type transaction: L{IAsyncTransaction}
+        """
+
+        baseargs = {
+            "jobID": kwargs.pop("jobID"),
+            "icalendarUid": kwargs.pop("icalendarUid"),
+            "workType": cls.workType()
+        }
+
+        baseWork = yield ScheduleWork.create(transaction, **baseargs)
+
+        kwargs["workID"] = baseWork.workID
+        work = yield super(ScheduleWorkMixin, cls).create(transaction, **kwargs)
+        work.addBaseWork(baseWork)
+        returnValue(work)
+
+
     @classmethod
+    @inlineCallbacks
+    def loadForJob(cls, txn, jobID):
+        baseItems = yield ScheduleWork.query(txn, (ScheduleWork.jobID == jobID))
+        workItems = []
+        for baseItem in baseItems:
+            workItem = yield cls.query(txn, (cls.workID == baseItem.workID))
+            if len(workItem) == 0:
+                # This can happen if a cascade delete is done on the actual work item - that will not
+                # remove the corresponding L{JobItem} or L{ScheduleWork}
+                yield baseItem.delete()
+                continue
+            workItem[0].addBaseWork(baseItem)
+            workItems.append(workItem[0])
+        returnValue(workItems)
+
+
+    @inlineCallbacks
+    def runlock(self):
+        """
+        Lock the "group" which is all the base items with the same UID. Also make sure
+        to lock this item after.
+
+        @return: an L{Deferred} that fires with L{True} if the L{WorkItem} was locked,
+            L{False} if not.
+        @rtype: L{Deferred}
+        """
+
+        # Do the group lock first since this can impact multiple rows and thus could
+        # cause deadlocks if done in the wrong order
+
+        # Row level lock on this item
+        locked = yield self.baseWork.trylock(ScheduleWork.icalendarUid == self.icalendarUid)
+        if locked:
+            yield self.trylock()
+        returnValue(locked)
+
+
+    def addBaseWork(self, baseWork):
+        """
+        Add the base work fields into the sub-classes as non-record attributes.
+
+        @param baseWork: the base work item to add
+        @type baseWork: L{ScheduleWork}
+        """
+        self.__dict__["baseWork"] = baseWork
+        self.__dict__["jobID"] = baseWork.jobID
+        self.__dict__["icalendarUid"] = baseWork.icalendarUid
+
+
+    def delete(self):
+        """
+        Delete the base work item which will delete this one via cascade.
+
+        @return: a L{Deferred} which fires with C{None} when the underlying row
+            has been deleted, or fails with L{NoSuchRecord} if the underlying
+            row was already deleted.
+        """
+        return self.baseWork.delete()
+
+
+    @classmethod
+    @inlineCallbacks
+    def hasWork(cls, txn):
+        sch = cls.table
+        rows = (yield Select(
+            (sch.WORK_ID,),
+            From=sch,
+        ).on(txn))
+        returnValue(len(rows) > 0)
+
+
+    @inlineCallbacks
+    def afterWork(self):
+        """
+        A hook that gets called after the L{WorkItem} does its real work. This can be used
+        for common clean-up behaviors. The base implementation does nothing.
+        """
+        yield super(ScheduleWorkMixin, self).afterWork()
+
+        # Find the next item and schedule to run immediately after this.
+        # We only coalesce ScheduleOrganizerSendWork.
+        if self.workType() == ScheduleOrganizerSendWork.workType():
+            all = yield self.baseWork.query(
+                self.transaction,
+                (ScheduleWork.icalendarUid == self.icalendarUid).And(ScheduleWork.workID != self.workID),
+                order=ScheduleWork.workID,
+                limit=1,
+            )
+            if all:
+                work = all[0]
+                if work.workType == self.workType():
+                    job = yield JobItem.load(self.transaction, work.jobID)
+                    yield job.update(notBefore=datetime.datetime.utcnow())
+                    log.debug("ScheduleOrganizerSendWork - promoted job: {id}, UID: '{uid}'", id=work.workID, uid=self.icalendarUid)
+
+
+    @classmethod
     def allDone(cls):
         d = Deferred()
         cls._allDoneCallback = d.callback
@@ -94,6 +217,36 @@
                 self.transaction.postCommit(_post)
 
 
+    def extractSchedulingResponse(self, queuedResponses):
+        """
+        Extract a list of (recipient, status) pairs from a scheduling response, returning that list
+        and an indicator of whether any have a schedule status other than delivered.
+
+        @param queuedResponses: the scheduling response object
+        @type queuedResponses: L{list} of L{caldavxml.ScheduleResponse}
+
+        @return: a L{tuple} of the list and the status state
+        @rtype: L{tuple} of (L{list}, L{bool})
+        """
+
+        # Map each recipient in the response to a status code
+        results = []
+        all_delivered = True
+        for response in queuedResponses:
+            for item in response.responses:
+                recipient = str(item.recipient.children[0])
+                status = str(item.reqstatus)
+                statusCode = status.split(";")[0]
+
+                results.append((recipient, statusCode,))
+
+                # Now apply to each ATTENDEE/ORGANIZER in the original data only if not 1.2
+                if statusCode != iTIPRequestStatus.MESSAGE_DELIVERED_CODE:
+                    all_delivered = False
+
+        return results, all_delivered
+
+
     def handleSchedulingResponse(self, response, calendar, is_organizer):
         """
         Update a user's calendar object resource based on the results of a queued scheduling
@@ -101,8 +254,8 @@
         as we will already have updated the calendar object resource to make it look like scheduling
         worked prior to the work queue item being enqueued.
 
-        @param response: the scheduling response object
-        @type response: L{caldavxml.ScheduleResponse}
+        @param response: the scheduling response summary data
+        @type response: L{list} of L{tuple} of (L{str} - recipient, L{str} - status)
         @param calendar: original calendar component
         @type calendar: L{Component}
         @param is_organizer: whether or not iTIP message was sent by the organizer
@@ -112,11 +265,7 @@
         # Map each recipient in the response to a status code
         changed = False
         propname = calendar.mainComponent().recipientPropertyName() if is_organizer else "ORGANIZER"
-        for item in response.responses:
-            recipient = str(item.recipient.children[0])
-            status = str(item.reqstatus)
-            statusCode = status.split(";")[0]
-
+        for recipient, statusCode in response:
             # Now apply to each ATTENDEE/ORGANIZER in the original data only if not 1.2
             if statusCode != iTIPRequestStatus.MESSAGE_DELIVERED_CODE:
                 calendar.setParameterToValueForPropertyWithValue(
@@ -131,12 +280,22 @@
 
 
 
+class ScheduleWork(Record, fromTable(schema.SCHEDULE_WORK)):
+    """
+    A L{Record} based table whose rows are used for locking scheduling work by iCalendar UID value.
+    as well as helping to determine the next work for a particular UID.
+    """
+    pass
+
+
+
 class ScheduleOrganizerWork(ScheduleWorkMixin, fromTable(schema.SCHEDULE_ORGANIZER_WORK)):
     """
     The associated work item table is SCHEDULE_ORGANIZER_WORK.
 
-    This work item is used to send a iTIP request and cancel messages when an organizer changes
-    their calendar object resource.
+    This work item is used to generate a set of L{ScheduleOrganizerSendWork} work items for
+    each set of iTIP messages that need to be sent as the result of an organizer changing
+    their copy of the event.
     """
 
     @classmethod
@@ -177,18 +336,7 @@
         log.debug("ScheduleOrganizerWork - enqueued for ID: {id}, UID: {uid}, organizer: {org}", id=proposal.workItem.workID, uid=uid, org=organizer)
 
 
-    @classmethod
     @inlineCallbacks
-    def hasWork(cls, txn):
-        srw = schema.SCHEDULE_ORGANIZER_WORK
-        rows = (yield Select(
-            (srw.WORK_ID,),
-            From=srw,
-        ).on(txn))
-        returnValue(len(rows) > 0)
-
-
-    @inlineCallbacks
     def doWork(self):
 
         try:
@@ -217,31 +365,142 @@
                 self.smartMerge
             )
 
+            self._dequeued()
+
+        except Exception, e:
+            log.debug("ScheduleOrganizerWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=self.icalendarUid, err=str(e))
+            log.debug(traceback.format_exc())
+            raise
+        except:
+            log.debug("ScheduleOrganizerWork - bare exception ID: {id}, UID: '{uid}'", id=self.workID, uid=self.icalendarUid)
+            log.debug(traceback.format_exc())
+            raise
+
+        log.debug("ScheduleOrganizerWork - done for ID: {id}, UID: {uid}, organizer: {org}", id=self.workID, uid=self.icalendarUid, org=organizer)
+
+
+
+class ScheduleOrganizerSendWork(ScheduleWorkMixin, fromTable(schema.SCHEDULE_ORGANIZER_SEND_WORK)):
+    """
+    The associated work item table is SCHEDULE_ORGANIZER_SEND_WORK.
+
+    This work item is used to send iTIP request and cancel messages when an organizer changes
+    their calendar object resource. One of these will be created for each iTIP message that
+    L{ScheduleOrganizerWork} needs to have sent.
+    """
+
+    @classmethod
+    @inlineCallbacks
+    def schedule(cls, txn, action, home, resource, organizer, attendee, itipmsg, no_refresh, stagger):
+        """
+        Create the work item. Because there may be lots of these dumped onto the server in one go, we will
+        stagger them via notBefore. However, we are using a "chained" work item so when one completes, it
+        will reschedule the next one to run immediately after it, so if work is being done quickly, the
+        stagger interval is effectively ignored.
+
+        @param txn: the transaction to use
+        @type txn: L{CommonStoreTransaction}
+        @param organizer: the calendar user address of the organizer
+        @type organizer: L{str}
+        @param attendee: the calendar user address of the attendee to send the message to
+        @type attendee: L{str}
+        @param itipmsg: the iTIP message to send
+        @type itipmsg: L{Component}
+        @param no_refresh: whether or not refreshes are allowed
+        @type no_refresh: L{bool}
+        @param stagger: number of seconds into the future for notBefore
+        @type stagger: L{int}
+        """
+
+        # Always queue up new work - coalescing happens when work is executed
+        notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.RequestDelaySeconds + stagger)
+        uid = itipmsg.resourceUID()
+        proposal = (yield txn.enqueue(
+            cls,
+            notBefore=notBefore,
+            icalendarUid=uid,
+            scheduleAction=scheduleActionToSQL[action],
+            homeResourceID=home.id(),
+            resourceID=resource.id() if resource else None,
+            attendee=attendee,
+            itipMsg=itipmsg.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference),
+            noRefresh=no_refresh,
+        ))
+        cls._enqueued()
+        log.debug(
+            "ScheduleOrganizerSendWork - enqueued for ID: {id}, UID: {uid}, organizer: {org}, attendee: {att}",
+            id=proposal.workItem.workID,
+            uid=uid,
+            org=organizer,
+            att=attendee
+        )
+
+
+    @inlineCallbacks
+    def doWork(self):
+
+        try:
+            home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
+            resource = (yield home.objectResourceWithID(self.resourceID))
+            itipmsg = Component.fromString(self.itipMsg)
+
+            organizerAddress = yield calendarUserFromCalendarUserUID(home.uid(), self.transaction)
+            organizer = organizerAddress.record.canonicalCalendarUserAddress()
+            log.debug(
+                "ScheduleOrganizerSendWork - running for ID: {id}, UID: {uid}, organizer: {org}, attendee: {att}",
+                id=self.workID,
+                uid=self.icalendarUid,
+                org=organizer,
+                att=self.attendee
+            )
+
+            # We need to get the UID lock for implicit processing.
+            yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(self.icalendarUid).hexdigest(),))
+
+            from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
+            scheduler = ImplicitScheduler()
+            yield scheduler.queuedOrganizerSending(
+                self.transaction,
+                scheduleActionFromSQL[self.scheduleAction],
+                home,
+                resource,
+                self.icalendarUid,
+                organizer,
+                self.attendee,
+                itipmsg,
+                self.noRefresh
+            )
+
             # Handle responses - update the actual resource in the store. Note that for a create the resource did not previously
             # exist and is stored as None for the work item, but the scheduler will attempt to find the new resources and use
             # that. We need to grab the scheduler's resource for further processing.
             resource = scheduler.resource
             if resource is not None:
-                changed = False
-                calendar = (yield resource.componentForUser())
-                for response in scheduler.queuedResponses:
-                    changed |= yield self.handleSchedulingResponse(response, calendar, True)
+                responses, all_delivered = self.extractSchedulingResponse(scheduler.queuedResponses)
+                if not all_delivered:
+                    calendar = (yield resource.componentForUser())
+                    changed = self.handleSchedulingResponse(responses, calendar, True)
+                    if changed:
+                        yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ORGANIZER_ITIP_UPDATE)
 
-                if changed:
-                    yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ORGANIZER_ITIP_UPDATE)
-
             self._dequeued()
 
         except Exception, e:
-            log.debug("ScheduleOrganizerWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=self.icalendarUid, err=str(e))
+            log.debug("ScheduleOrganizerSendWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=self.icalendarUid, err=str(e))
             log.debug(traceback.format_exc())
             raise
         except:
-            log.debug("ScheduleOrganizerWork - bare exception ID: {id}, UID: '{uid}'", id=self.workID, uid=self.icalendarUid)
+            log.debug("ScheduleOrganizerSendWork - bare exception ID: {id}, UID: '{uid}'", id=self.workID, uid=self.icalendarUid)
             log.debug(traceback.format_exc())
             raise
 
-        log.debug("ScheduleOrganizerWork - done for ID: {id}, UID: {uid}, organizer: {org}", id=self.workID, uid=self.icalendarUid, org=organizer)
+        log.debug(
+            "ScheduleOrganizerSendWork - for ID: {id}, UID: {uid}, organizer: {org}, attendee: {att}",
+            id=self.workID,
+            uid=self.icalendarUid,
+            org=organizer,
+            att=self.attendee
+        )
 
 
 
@@ -298,18 +557,7 @@
         log.debug("ScheduleReplyWork - enqueued for ID: {id}, UID: {uid}, attendee: {att}", id=proposal.workItem.workID, uid=resource.uid(), att=attendee)
 
 
-    @classmethod
     @inlineCallbacks
-    def hasWork(cls, txn):
-        srw = schema.SCHEDULE_REPLY_WORK
-        rows = (yield Select(
-            (srw.WORK_ID,),
-            From=srw,
-        ).on(txn))
-        returnValue(len(rows) > 0)
-
-
-    @inlineCallbacks
     def doWork(self):
 
         try:
@@ -332,11 +580,12 @@
 
             # Send scheduling message and process response
             response = (yield self.sendToOrganizer(home, "REPLY", itipmsg, attendee, organizer))
-            changed = yield self.handleSchedulingResponse(response, calendar, False)
+            responses, all_delivered = self.extractSchedulingResponse((response,))
+            if not all_delivered:
+                changed = yield self.handleSchedulingResponse(responses, calendar, False)
+                if changed:
+                    yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTENDEE_ITIP_UPDATE)
 
-            if changed:
-                yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTENDEE_ITIP_UPDATE)
-
             self._dequeued()
 
         except Exception, e:
@@ -360,10 +609,6 @@
     of the original resource data.
     """
 
-    # Schedule work is grouped based on calendar object UID
-    group = property(lambda self: "ScheduleWork:%s" % (self.icalendarUid,))
-
-
     @classmethod
     @inlineCallbacks
     def replyCancel(cls, txn, home, calendar, attendee):

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/sql.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/sql.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -1365,13 +1365,13 @@
         # Initialize these for all shares
         for ename in self._shadowProperties:
             if ename not in self.properties() and ename.toString() in props:
-                self.properties()[ename] = WebDAVDocument.fromString(props[ename]).root_element
+                self.properties()[ename] = WebDAVDocument.fromString(props[ename.toString()]).root_element
 
         # Only initialize these for direct shares
         if self.direct():
             for ename in (PropertyName.fromElement(element.DisplayName),):
                 if ename not in self.properties() and ename.toString() in props:
-                    self.properties()[ename] = WebDAVDocument.fromString(props[ename]).root_element
+                    self.properties()[ename] = WebDAVDocument.fromString(props[ename.toString()]).root_element
 
 
     # FIXME: this is DAV-ish.  Data store calendar objects don't have
@@ -1781,7 +1781,7 @@
         record = yield self._txn.directoryService().recordWithUID(shareeUID.decode("utf-8"))
         if (
             record is None or
-            record.recordType != RecordType.group or not (False and
+            record.type() != RecordType.group or not (False and
                 config.Sharing.Enabled and
                 config.Sharing.Calendars.Enabled and
                 config.Sharing.Calendars.Groups.Enabled
@@ -1793,8 +1793,8 @@
             )
 
         # shareWith every member of group not already shared to
-        groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self._txn.groupByUID(record.uid)
-        memberUIDs = yield self._txn.membersOfGroup(groupID)
+        groupID = (yield self._txn.groupByUID(record.uid))[0]
+        memberUIDs = yield self._txn.groupMemberUIDs(groupID)
         for memberUID in memberUIDs:
             shareeHome = yield self._txn.calendarHomeWithUID(memberUID, create=True)
             if (yield shareeHome.childWithID(self._resourceID)) is None:
@@ -1814,8 +1814,8 @@
             record = (
                 yield self._txn.directoryService().recordWithUID(groupUID.decode("utf-8"))
             )
-            groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self._txn.groupByUID(record.uid)
-            memberUIDs = yield self._txn.membersOfGroup(groupID)
+            groupID = (yield self._txn.groupByUID(record.uid))[0]
+            memberUIDs = yield self._txn.groupMemberUIDs(groupID)
             boundUIDs = set()
 
             bind = schema.CALENDAR_BIND
@@ -1851,7 +1851,10 @@
         update schema.GROUP_SHAREE
         """
         changed = False
-        groupID, _ignore_name, membershipHash, _ignore_modDate = yield self._txn.groupByUID(groupUID)
+        (
+            groupID, _ignore_name, membershipHash, _ignore_modDate,
+            _ignore_extant
+        ) = yield self._txn.groupByUID(groupUID)
 
         gs = schema.GROUP_SHAREE
         rows = yield Select(
@@ -1923,33 +1926,26 @@
         """
 
         # see if after share is removed, user is still shared by a group sharee
-        effectiveShareMode = None
-        oldShareMode = shareeView.shareMode()
-        if oldShareMode in (_BIND_MODE_DIRECT, _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_MODE_GROUP,):
+        if shareeView.shareMode() in (_BIND_MODE_DIRECT, _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_MODE_GROUP):
 
             gs = schema.GROUP_SHAREE
             rows = yield Select(
-                [gs.GROUP_ID, gs.GROUP_BIND_MODE],
+                [gs.GROUP_ID],
                 From=gs,
                 Where=(gs.CALENDAR_HOME_ID == self.ownerHome()._resourceID).And(
                     gs.CALENDAR_ID == self._resourceID)
             ).on(self._txn)
+            groupIDs = [row[0] for row in rows]
+
             shareeHomeUID = shareeView.viewerHome().uid()
-            for groupID, groupShareMode in rows:
-                memberUIDs = yield self._txn.membersOfGroup(groupID)
+            for groupID in groupIDs:
+                memberUIDs = yield self._txn.groupMemberUIDs(groupID) # must be cached
                 if shareeHomeUID in memberUIDs:
-                    if effectiveShareMode is None:
-                        effectiveShareMode = groupShareMode
-                    elif groupShareMode > effectiveShareMode:
-                        effectiveShareMode = groupShareMode
+                    yield self.updateShare(shareeView, mode=_BIND_MODE_GROUP)
+                    returnValue(None)
 
-        if effectiveShareMode is None:
-            # no group sharee for this user so let super do work
-            returnValue((yield super(Calendar, self).removeShare(shareeView)))
-        elif oldShareMode != _BIND_MODE_GROUP:
-            # change to group share
-            yield self.updateShare(shareeView, mode=_BIND_MODE_GROUP)
-            returnValue(None)
+        # no group sharee for this user so let super do work
+        returnValue((yield super(Calendar, self).removeShare(shareeView)))
 
         #TODO: effectiveShareMode may change between _BIND_MODE_READ & _BIND_MODE_READ.
         #        Is that OK?
@@ -1975,8 +1971,8 @@
             returnValue(None)
 
         # get group membership
-        groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self._txn.groupByUID(record.uid)
-        memberUIDs = yield self._txn.membersOfGroup(groupID)
+        groupID = (yield self._txn.groupByUID(record.uid))[0]
+        memberUIDs = yield self._txn.groupMemberUIDs(groupID)
 
         # update groupsharee so that removeShare works
         gs = schema.GROUP_SHAREE
@@ -2116,6 +2112,8 @@
     _objectSchema = schema.CALENDAR_OBJECT
     _componentClass = VComponent
 
+    _currentDataVersion = 1
+
     def __init__(self, calendar, name, uid, resourceID=None, options=None):
 
         super(CalendarObject, self).__init__(calendar, name, uid, resourceID)
@@ -2177,7 +2175,8 @@
             obj.SCHEDULE_ETAGS,
             obj.PRIVATE_COMMENTS,
             obj.CREATED,
-            obj.MODIFIED
+            obj.MODIFIED,
+            obj.DATAVERSION,
         ]
 
 
@@ -2198,6 +2197,7 @@
             "_private_comments",
             "_created",
             "_modified",
+            "_dataversion",
          )
 
 
@@ -2221,7 +2221,7 @@
 
         # Possible timezone stripping
         if config.EnableTimezonesByReference:
-            component.stripKnownTimezones()
+            component.stripStandardTimezones()
 
         # Do validation on external requests
         if internal_state == ComponentUpdateState.NORMAL:
@@ -2250,7 +2250,7 @@
             yield self.validAttendeeListSizeCheck(component, inserting)
 
         # Check location/resource organizer requirement
-        self.validLocationResourceOrganizer(component, inserting, internal_state)
+        yield self.validLocationResourceOrganizer(component, inserting, internal_state)
 
         # Check access
         if config.EnablePrivateEvents:
@@ -2277,19 +2277,16 @@
         groupCUAToAttendeeMemberPropMap = {}
         for groupCUA in groupCUAs:
 
+            groupCUAToAttendeeMemberPropMap[groupCUA] = ()
             groupRecord = yield self.directoryService().recordWithCalendarUserAddress(groupCUA)
             if groupRecord:
-                # get members from cached membership
-                groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self._txn.groupByUID(groupRecord.uid)
-                members = [(yield self.directoryService().recordWithUID(memberUID))
-                    for memberUID in
-                    (yield self._txn.membersOfGroup(groupID))
-                ]
-                groupCUAToAttendeeMemberPropMap[groupRecord.canonicalCalendarUserAddress()] = tuple(
-                    [member.attendeeProperty(params={"MEMBER": groupCUA}) for member in sorted(members, key=lambda x: x.uid)]
-                )
-            else:
-                groupCUAToAttendeeMemberPropMap[groupCUA] = ()
+                # get members
+                groupID = (yield self._txn.groupByUID(groupRecord.uid))[0]
+                if groupID is not None:
+                    members = yield self._txn.groupMembers(groupID)
+                    groupCUAToAttendeeMemberPropMap[groupRecord.canonicalCalendarUserAddress()] = tuple(
+                        [member.attendeeProperty(params={"MEMBER": groupCUA}) for member in sorted(members, key=lambda x: x.uid)]
+                    )
 
         # sync group attendee members if inserting or group changed
         changed = False
@@ -2328,7 +2325,10 @@
                 groupUID = groupRecord.uid
             else:
                 groupUID = uidFromCalendarUserAddress(groupCUA)
-            groupID, _ignore_name, membershipHash, _ignore_modDate = yield self._txn.groupByUID(groupUID)
+            (
+                groupID, _ignore_name, membershipHash, _ignore_modDate,
+                _ignore_extant
+            ) = yield self._txn.groupByUID(groupUID)
 
             if groupID in groupIDToMembershipHashMap:
                 if groupIDToMembershipHashMap[groupID] != membershipHash:
@@ -3309,7 +3309,8 @@
                 co.SCHEDULE_TAG                    : self._schedule_tag,
                 co.SCHEDULE_ETAGS                  : self._schedule_etags,
                 co.PRIVATE_COMMENTS                : self._private_comments,
-                co.MD5                             : self._md5
+                co.MD5                             : self._md5,
+                co.DATAVERSION                     : self._currentDataVersion,
             }
 
             # Only needed if indexing being changed
@@ -3328,8 +3329,9 @@
                 values[co.MODIFIED] = utcNowSQL
                 self._modified = (
                     yield Update(
-                        values, Return=co.MODIFIED,
-                        Where=co.RESOURCE_ID == self._resourceID
+                        values,
+                        Where=co.RESOURCE_ID == self._resourceID,
+                        Return=co.MODIFIED,
                     ).on(txn)
                 )[0][0]
 
@@ -3483,14 +3485,6 @@
             # Fix any bogus data we can
             fixed, unfixed = component.validCalendarData(doFix=True, doRaise=False)
 
-            # Normalize CUAs:
-            # FIXME: update the DB copy as well so we don't keep going through
-            # this normalization?
-            yield component.normalizeCalendarUserAddresses(
-                normalizationLookup,
-                self.directoryService().recordWithCalendarUserAddress
-            )
-
             if unfixed:
                 self.log.error(
                     "Calendar data id={0} had unfixable problems:\n  {1}".format(
@@ -3505,6 +3499,10 @@
                     )
                 )
 
+            # Check for on-demand data upgrade
+            if self._dataversion < self._currentDataVersion:
+                yield self.upgradeData(component)
+
             self._cachedComponent = component
             self._cachedCommponentPerUser = {}
 
@@ -3533,6 +3531,25 @@
         returnValue(self._cachedCommponentPerUser[user_uuid])
 
 
+    @inlineCallbacks
+    def upgradeData(self, component):
+        """
+        Implement in sub-classes. If the data version of this item does not match
+        the current data version, call this method and implement a data upgrade,
+        writing back the new data and updating the data version.
+        """
+
+        if self._dataversion < 1:
+            # Normalize CUAs:
+            yield component.normalizeCalendarUserAddresses(
+                normalizationLookup,
+                self.directoryService().recordWithCalendarUserAddress
+            )
+
+        self._dataversion = self._currentDataVersion
+        yield self.updateDatabase(component)
+
+
     def moveValidation(self, destination, name):
         """
         Validate whether a move to the specified collection is allowed.

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/test/test_sql.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/test/test_sql.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -39,6 +39,7 @@
 from twistedcaldav.dateops import datetimeMktime
 from twistedcaldav.ical import Component, normalize_iCalStr, diff_iCalStrs
 from twistedcaldav.instance import InvalidOverriddenInstanceError
+from twistedcaldav.timezones import TimezoneCache
 
 from txdav.base.propertystore.base import PropertyName
 from txdav.caldav.datastore.query.filter import Filter
@@ -2017,7 +2018,195 @@
         yield self.abort()
 
 
+    @inlineCallbacks
+    def test_standardTimezone(self):
+        """
+        Make sure a standard timezone is not stored and not returned in the calendar data when timezones
+        by reference is on.
+        """
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+X-LIC-LOCATION:America/New_York
+BEGIN:STANDARD
+DTSTART:18000101T000000
+RDATE:18000101T000000
+TZNAME:GMT+1
+TZOFFSETFROM:-0100
+TZOFFSETTO:-0100
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;TZID=America/New_York:20130806T000000
+DURATION:PT1H
+DTSTAMP:20051222T210507Z
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
 
+        self.patch(config, "EnableTimezonesByReference", True)
+        TimezoneCache.create()
+        self.addCleanup(TimezoneCache.clear)
+
+        yield self.homeUnderTest(name="user01", create=True)
+        calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+        yield calendar.createCalendarObjectWithName("data1.ics", Component.fromString(data))
+        yield self.commit()
+
+        obj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="user01")
+        txt = yield obj._text()
+        self.assertTrue("BEGIN:VTIMEZONE" not in txt)
+        cal = yield obj.componentForUser("user01")
+        self.assertEqual(len(tuple(cal.subcomponents())), 1)
+        txt = cal.getTextWithTimezones(False)
+        self.assertTrue("BEGIN:VTIMEZONE" not in txt)
+
+
+    @inlineCallbacks
+    def test_nonStandardTimezone(self):
+        """
+        Make sure a non-standard timezone is stored and returned in the calendar data when timezones
+        by reference is on.
+        """
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:GMTPlusOne
+X-LIC-LOCATION:GMTPlusOne
+BEGIN:STANDARD
+DTSTART:18000101T000000
+RDATE:18000101T000000
+TZNAME:GMT+1
+TZOFFSETFROM:-0100
+TZOFFSETTO:-0100
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;TZID=GMTPlusOne:20130806T000000
+DURATION:PT1H
+DTSTAMP:20051222T210507Z
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        self.patch(config, "EnableTimezonesByReference", True)
+        TimezoneCache.create()
+        self.addCleanup(TimezoneCache.clear)
+
+        yield self.homeUnderTest(name="user01", create=True)
+        calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+        yield calendar.createCalendarObjectWithName("data1.ics", Component.fromString(data))
+        yield self.commit()
+
+        obj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="user01")
+        txt = yield obj._text()
+        self.assertTrue("TZID:GMTPlusOne" in txt)
+        cal = yield obj.componentForUser("user01")
+        self.assertEqual(len(tuple(cal.subcomponents())), 2)
+        txt = cal.getTextWithTimezones(False)
+        self.assertTrue("BEGIN:VTIMEZONE" in txt)
+
+
+    @inlineCallbacks
+    def test_dataVersion(self):
+        """
+        Make sure L{CalendarObject}'s data version is set when object is created.
+        """
+        olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-dataversion
+DTSTART:20130806T000000Z
+DURATION:PT1H
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        yield self.homeUnderTest(name="user01", create=True)
+        calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+        yield calendar.createCalendarObjectWithName("data1.ics", Component.fromString(olddata))
+        yield self.commit()
+
+        obj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="user01")
+        self.assertEqual(obj._dataversion, obj._currentDataVersion)
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_dataUpgrade(self):
+        """
+        Make sure L{CalendarObject.upgradeData} works correctly.
+        """
+        olddata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20130806T000000Z
+DURATION:PT1H
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE:mailto:user01 at example.com
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        yield self.homeUnderTest(name="user01", create=True)
+        calendar = yield self.calendarUnderTest(name="calendar", home="user01")
+        yield calendar.createCalendarObjectWithName("data1.ics", Component.fromString(olddata))
+        yield self.commit()
+
+        # Make it look old
+        obj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="user01")
+        co = schema.CALENDAR_OBJECT
+        yield Update(
+            {
+                co.ICALENDAR_TEXT: olddata,
+                co.DATAVERSION: 0,
+            },
+            Where=co.RESOURCE_ID == obj._resourceID,
+        ).on(self.transactionUnderTest())
+        yield self.commit()
+
+        # Still looks old
+        obj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="user01")
+        txt = yield obj._text()
+        self.assertTrue("mailto:user01 at example.com" in txt)
+        self.assertEqual(obj._dataversion, 0)
+        yield self.commit()
+
+        # Now it is new
+        obj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="user01")
+        cal = yield obj.componentForUser("user01")
+        txt = cal.getTextWithTimezones(False)
+        self.assertTrue("mailto:user01 at example.com" not in txt)
+        self.assertTrue("urn:x-uid:user01" in txt)
+        self.assertEqual(obj._dataversion, obj._currentDataVersion)
+        yield self.commit()
+
+        # Still new
+        obj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="user01")
+        txt = yield obj._text()
+        self.assertTrue("mailto:user01 at example.com" not in txt)
+        self.assertTrue("urn:x-uid:user01" in txt)
+        self.assertEqual(obj._dataversion, obj._currentDataVersion)
+        yield self.commit()
+
+
+
 class SchedulingTests(CommonCommonTests, unittest.TestCase):
     """
     CalendarObject splitting tests

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/carddav/datastore/sql.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/carddav/datastore/sql.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -224,7 +224,7 @@
         Unbinds any collections that have been shared to this home but not yet
         accepted.  Associated invite entries are also removed.
         """
-        super(AddressBookHome, self).removeUnacceptedShares()
+        yield super(AddressBookHome, self).removeUnacceptedShares()
 
         # Remove group binds too
         bind = AddressBookObject._bindSchema
@@ -358,7 +358,7 @@
 
 class AddressBookSharingMixIn(SharingMixIn):
     """
-    Sharing code shared between AddressBook and AddressBookObject
+        Sharing code shared between AddressBook and AddressBookObject
     """
 
     def sharedResourceType(self):
@@ -805,7 +805,7 @@
                 self.ownerHome()._addressbookPropertyStoreID,  # not ._resourceID as in CommonHomeChild._loadPropertyStore()
                 notifyCallback=self.notifyPropertyChanged
             )
-        super(AddressBook, self)._loadPropertyStore(props)
+        yield super(AddressBook, self)._loadPropertyStore(props)
 
 
     def initPropertyStore(self, props):
@@ -839,13 +839,13 @@
         # Initialize these for all shares
         for ename in self._shadowProperties:
             if ename not in self.properties() and ename.toString() in props:
-                self.properties()[ename] = WebDAVDocument.fromString(props[ename]).root_element
+                self.properties()[ename] = WebDAVDocument.fromString(props[ename.toString()]).root_element
 
         # Only initialize these for direct shares
         if self.direct():
             for ename in (PropertyName.fromElement(element.DisplayName),):
                 if ename not in self.properties() and ename.toString() in props:
-                    self.properties()[ename] = WebDAVDocument.fromString(props[ename]).root_element
+                    self.properties()[ename] = WebDAVDocument.fromString(props[ename.toString()]).root_element
 
 
     def contentType(self):
@@ -868,7 +868,7 @@
     @inlineCallbacks
     def removedObjectResource(self, child):
         """
-            just like CommonHomeChild.removedObjectResource() but does not call self._deleteRevision()
+        Just like CommonHomeChild.removedObjectResource() but does not call self._deleteRevision()
         """
         self._objects.pop(child.name(), None)
         self._objects.pop(child.uid(), None)
@@ -1856,6 +1856,8 @@
 
     _componentClass = VCard
 
+    _currentDataVersion = 0
+
     # used by CommonHomeChild._childrenAndMetadataForHomeID() only
     # _homeChildSchema = schema.ADDRESSBOOK_OBJECT
     # _homeChildMetaDataSchema = schema.ADDRESSBOOK_OBJECT
@@ -2232,7 +2234,8 @@
             obj.MD5,
             Len(obj.TEXT),
             obj.CREATED,
-            obj.MODIFIED
+            obj.MODIFIED,
+            obj.DATAVERSION,
         ]
 
 
@@ -2248,6 +2251,7 @@
             "_size",
             "_created",
             "_modified",
+            "_dataversion",
          )
 
 
@@ -2451,17 +2455,21 @@
         """
         abo = schema.ADDRESSBOOK_OBJECT
         return Insert(
-            {abo.RESOURCE_ID: schema.RESOURCE_ID_SEQ,
-             abo.ADDRESSBOOK_HOME_RESOURCE_ID: Parameter("addressbookResourceID"),
-             abo.RESOURCE_NAME: Parameter("name"),
-             abo.VCARD_TEXT: Parameter("text"),
-             abo.VCARD_UID: Parameter("uid"),
-             abo.KIND: Parameter("kind"),
-             abo.MD5: Parameter("md5"),
-             },
-            Return=(abo.RESOURCE_ID,
-                    abo.CREATED,
-                    abo.MODIFIED))
+            {
+                abo.RESOURCE_ID: schema.RESOURCE_ID_SEQ,
+                abo.ADDRESSBOOK_HOME_RESOURCE_ID: Parameter("addressbookResourceID"),
+                abo.RESOURCE_NAME: Parameter("name"),
+                abo.VCARD_TEXT: Parameter("text"),
+                abo.VCARD_UID: Parameter("uid"),
+                abo.KIND: Parameter("kind"),
+                abo.MD5: Parameter("md5"),
+                abo.DATAVERSION: Parameter("dataVersion"),
+            },
+            Return=(
+                abo.RESOURCE_ID,
+                abo.CREATED,
+                abo.MODIFIED
+        ))
 
 
     @classproperty
@@ -2578,6 +2586,7 @@
                     uid=self._uid,
                     md5=self._md5,
                     kind=self._kind,
+                    dataVersion=self._currentDataVersion,
                 )
             )[0]
 
@@ -2611,9 +2620,12 @@
 
         else:
             self._modified = (yield Update(
-                {abo.VCARD_TEXT: self._objectText,
-                 abo.MD5: self._md5,
-                 abo.MODIFIED: utcNowSQL},
+                {
+                    abo.VCARD_TEXT: self._objectText,
+                    abo.MD5: self._md5,
+                    abo.DATAVERSION: self._dataversion,
+                    abo.MODIFIED: utcNowSQL,
+                },
                 Where=abo.RESOURCE_ID == self._resourceID,
                 Return=abo.MODIFIED).on(self._txn))[0][0]
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/carddav/datastore/test/test_sql.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/carddav/datastore/test/test_sql.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -877,3 +877,31 @@
         obj = (yield self.addressbookObjectUnderTest())
         addressbookObject = (yield home.objectResourceWithID(obj._resourceID))
         self.assertNotEquals(addressbookObject, None)
+
+
+    @inlineCallbacks
+    def test_dataVersion(self):
+        """
+        Make sure L{AddressBookObject}'s data version is set when object is created.
+        """
+        olddata = """BEGIN:VCARD
+VERSION:3.0
+N:Thompson;Default1;;;
+FN:Default1 Thompson
+EMAIL;type=INTERNET;type=WORK;type=pref:lthompson1 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:uid-dataversion-test
+END:VCARD
+"""
+
+        yield self.homeUnderTest()
+        adbk = yield self.addressbookUnderTest(name="addressbook")
+        yield adbk.createAddressBookObjectWithName("data1.ics", VCard.fromString(olddata))
+        yield self.commit()
+
+        obj = yield self.addressbookObjectUnderTest(name="data1.ics", addressbook_name="addressbook")
+        self.assertEqual(obj._dataversion, obj._currentDataVersion)
+        yield self.commit()

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -81,6 +81,7 @@
 from txdav.common.inotifications import INotificationCollection, \
     INotificationObject
 from txdav.idav import ChangeCategory
+from twext.who.idirectory import RecordType
 from txdav.xml import element
 
 from uuid import uuid4, UUID
@@ -1033,8 +1034,8 @@
             {
                 gr.MEMBERSHIP_HASH: Parameter("membershipHash"),
                 gr.NAME: Parameter("name"),
-                gr.MODIFIED:
-                Parameter("timestamp")
+                gr.MODIFIED: Parameter("timestamp"),
+                gr.EXTANT: Parameter("extant"),
             },
             Where=(gr.GROUP_UID == Parameter("groupUID"))
         )
@@ -1044,7 +1045,7 @@
     def _groupByUID(cls):
         gr = schema.GROUPS
         return Select(
-            [gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH, gr.MODIFIED],
+            [gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH, gr.MODIFIED, gr.EXTANT],
             From=gr,
             Where=(gr.GROUP_UID == Parameter("groupUID"))
         )
@@ -1054,7 +1055,7 @@
     def _groupByID(cls):
         gr = schema.GROUPS
         return Select(
-            [gr.GROUP_UID, gr.NAME, gr.MEMBERSHIP_HASH],
+            [gr.GROUP_UID, gr.NAME, gr.MEMBERSHIP_HASH, gr.EXTANT],
             From=gr,
             Where=(gr.GROUP_ID == Parameter("groupID"))
         )
@@ -1069,25 +1070,33 @@
         )
 
 
+    @inlineCallbacks
     def addGroup(self, groupUID, name, membershipHash):
         """
         @type groupUID: C{unicode}
         @type name: C{unicode}
         @type membershipHash: C{str}
         """
-        return self._addGroupQuery.on(
+        groupID = yield self._addGroupQuery.on(
             self,
             name=name.encode("utf-8"),
             groupUID=groupUID.encode("utf-8"),
             membershipHash=membershipHash
         )
 
+        record = yield self.directoryService().recordWithUID(groupUID)
+        yield self._refreshGroup(
+            groupUID, record, groupID, name.encode("utf-8"), membershipHash
+        )
+        returnValue(groupID)
 
-    def updateGroup(self, groupUID, name, membershipHash):
+
+    def updateGroup(self, groupUID, name, membershipHash, extant=True):
         """
         @type groupUID: C{unicode}
         @type name: C{unicode}
         @type membershipHash: C{str}
+        @type extant: C{boolean}
         """
         timestamp = datetime.datetime.utcnow()
         return self._updateGroupQuery.on(
@@ -1095,7 +1104,8 @@
             name=name.encode("utf-8"),
             groupUID=groupUID.encode("utf-8"),
             timestamp=timestamp,
-            membershipHash=membershipHash
+            membershipHash=membershipHash,
+            extant=(1 if extant else 0)
         )
 
 
@@ -1107,7 +1117,8 @@
         @type groupUID: C{unicode}
 
         @return: Deferred firing with tuple of group ID C{str}, group name
-            C{unicode}, membership hash C{str}, and modified timestamp
+            C{unicode}, membership hash C{str}, modified timestamp, and
+            extant C{boolean}
         """
         results = (
             yield self._groupByUID.on(
@@ -1120,6 +1131,7 @@
                 results[0][1].decode("utf-8"),  # name
                 results[0][2],  # membership hash
                 results[0][3],  # modified timestamp
+                bool(results[0][4]),  # extant
             ))
         elif create:
             savepoint = SavepointAction("groupByUID")
@@ -1139,6 +1151,7 @@
                         results[0][1].decode("utf-8"),  # name
                         results[0][2],  # membership hash
                         results[0][3],  # modified timestamp
+                        bool(results[0][4]),  # extant
                     ))
                 else:
                     raise
@@ -1155,11 +1168,12 @@
                         results[0][1].decode("utf-8"),  # name
                         results[0][2],  # membership hash
                         results[0][3],  # modified timestamp
+                        bool(results[0][4]),  # extant
                     ))
                 else:
                     raise
         else:
-            returnValue((None, None, None, None))
+            returnValue((None, None, None, None, None))
 
 
     @inlineCallbacks
@@ -1169,7 +1183,7 @@
 
         @type groupID: C{str}
         @return: Deferred firing with a tuple of group UID C{unicode},
-            group name C{unicode}, and membership hash C{str}
+            group name C{unicode}, membership hash C{str}, and extant C{boolean}
         """
         try:
             results = (yield self._groupByID.on(self, groupID=groupID))[0]
@@ -1177,7 +1191,8 @@
                 results = (
                     results[0].decode("utf-8"),
                     results[1].decode("utf-8"),
-                    results[2]
+                    results[2],
+                    bool(results[3])
                 )
             returnValue(results)
         except IndexError:
@@ -1263,7 +1278,7 @@
 
 
     @inlineCallbacks
-    def membersOfGroup(self, groupID):
+    def groupMemberUIDs(self, groupID):
         """
         Returns the cached set of UIDs for members of the given groupID.
         Sub-groups are not returned in the results but their members are,
@@ -1283,6 +1298,131 @@
 
 
     @inlineCallbacks
+    def refreshGroup(self, groupUID):
+        """
+        Refreshes the group membership cache.
+
+        @param groupUID: the group UID
+        @type groupUID: C{unicode}
+
+        @return: Deferred firing with tuple of group ID C{str}, and
+            membershipChanged C{boolean}
+
+        """
+        log.debug("Faulting in group: {g}", g=groupUID)
+        record = (yield self.directoryService().recordWithUID(groupUID))
+        if record is None:
+            # the group has disappeared from the directory
+            log.info("Group is missing: {g}", g=groupUID)
+        else:
+            log.debug("Got group record: {u}", u=record.uid)
+
+        (
+            groupID, cachedName, cachedMembershipHash, _ignore_modified,
+            _ignore_extant
+        ) = yield self.groupByUID(
+            groupUID,
+            create=(record is not None)
+        )
+
+        membershipChanged = False
+        if groupID:
+            membershipChanged = yield self._refreshGroup(
+                groupUID, record, groupID, cachedName, cachedMembershipHash
+            )
+
+        returnValue((groupID, membershipChanged))
+
+
+    @inlineCallbacks
+    def _refreshGroup(self, groupUID, record, groupID, cachedName, cachedMembershipHash):
+        """
+        @param groupUID: the directory record
+        @type groupUID: C{unicode}
+        @param record: the directory record
+        @type record: C{iDirectoryRecord}
+        @param groupID: group resource id
+        @type groupID: C{str}
+        @param cachedName: group name in the database
+        @type cachedName: C{unicode}
+        @param cachedMembershipHash: membership hash in the database
+        @type cachedMembershipHash: C{str}
+
+        @return: Deferred firing with membershipChanged C{boolean}
+
+        """
+        if record is not None:
+            members = yield record.expandedMembers()
+            name = record.displayName
+            extant = True
+        else:
+            members = frozenset()
+            name = cachedName
+            extant = False
+
+        membershipHashContent = hashlib.md5()
+        members = list(members)
+        members.sort(key=lambda x: x.uid)
+        for member in members:
+            membershipHashContent.update(str(member.uid))
+        membershipHash = membershipHashContent.hexdigest()
+
+        if cachedMembershipHash != membershipHash:
+            membershipChanged = True
+            log.debug(
+                "Group '{group}' changed", group=name
+            )
+        else:
+            membershipChanged = False
+
+        if membershipChanged or record is not None:
+            # also updates group mod date
+            yield self.updateGroup(
+                groupUID, name, membershipHash, extant=extant
+            )
+
+        if membershipChanged:
+            newMemberUIDs = set()
+            for member in members:
+                newMemberUIDs.add(member.uid)
+            yield self.synchronizeMembers(groupID, newMemberUIDs)
+
+        returnValue(membershipChanged)
+
+
+    @inlineCallbacks
+    def synchronizeMembers(self, groupID, newMemberUIDs):
+        numRemoved = numAdded = 0
+        cachedMemberUIDs = (yield self.groupMemberUIDs(groupID))
+
+        for memberUID in cachedMemberUIDs:
+            if memberUID not in newMemberUIDs:
+                numRemoved += 1
+                yield self.removeMemberFromGroup(memberUID, groupID)
+
+        for memberUID in newMemberUIDs:
+            if memberUID not in cachedMemberUIDs:
+                numAdded += 1
+                yield self.addMemberToGroup(memberUID, groupID)
+
+        returnValue((numAdded, numRemoved))
+
+
+    @inlineCallbacks
+    def groupMembers(self, groupID):
+        """
+        The members of the given group as recorded in the db
+        """
+        members = set()
+        memberUIDs = (yield self.groupMemberUIDs(groupID))
+        for uid in memberUIDs:
+            record = (yield self.directoryService().recordWithUID(uid))
+            if record is not None:
+                members.add(record)
+        returnValue(members)
+
+
+    @inlineCallbacks
     def groupsFor(self, uid):
         """
         Returns the cached set of UIDs for the groups this given uid is
@@ -3475,6 +3615,11 @@
         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.
 
+        Now that we are truncating the revision table, we need to handle the full sync (revision == 0)
+        case a little differently as the revision table will not contain data for resources that exist,
+        but were last modified before the revision cut-off. Instead for revision == 0 we need to list
+        all existing child resources.  
+  
         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
@@ -3494,61 +3639,96 @@
         @type depth: C{str}
         """
 
-        changed = set()
-        deleted = set()
-        invalid = set()
         if revision:
             minValidRevision = yield self._txn.calendarserverValue("MIN-VALID-REVISION")
             if revision < int(minValidRevision):
                 raise SyncTokenValidException
+        else:
+            results = yield self.resourceNamesSinceRevisionZero(depth)
+            returnValue(results)
 
-            results = [
-                (
-                    path if path else (collection if collection else ""),
-                    name if name else "",
-                    wasdeleted
-                )
-                for path, collection, name, wasdeleted in
-                (yield self.doChangesQuery(revision))
-            ]
+        # Use revision table to find changes since the last revision - this will not include
+        # changes to child resources of shared collections - those we will get later
+        results = [
+            (
+                path if path else (collection if collection else ""),
+                name if name else "",
+                wasdeleted
+            )
+            for path, collection, name, wasdeleted in
+            (yield self.doChangesQuery(revision))
+        ]
 
-            deleted_collections = set()
-            for path, name, wasdeleted in results:
-                if wasdeleted:
-                    if name:
-                        # Resource deleted - for depth "1" report collection as changed,
-                        # otherwise report resource as deleted
-                        if depth == "1":
-                            changed.add("%s/" % (path,))
-                        else:
-                            deleted.add("%s/%s" % (path, name,))
+        changed = set()
+        deleted = set()
+        invalid = set()
+        deleted_collections = set()
+        for path, name, wasdeleted in results:
+            if wasdeleted:
+                if name:
+                    # Resource deleted - for depth "1" report collection as changed,
+                    # otherwise report resource as deleted
+                    if depth == "1":
+                        changed.add("%s/" % (path,))
                     else:
-                        # Collection was deleted
-                        deleted.add("%s/" % (path,))
-                        deleted_collections.add(path)
+                        deleted.add("%s/%s" % (path, name,))
+                else:
+                    # Collection was deleted
+                    deleted.add("%s/" % (path,))
+                    deleted_collections.add(path)
 
-                if path not in deleted_collections:
-                    # 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,))
+            if path not in deleted_collections:
+                # Always report collection as changed
+                changed.add("%s/" % (path,))
 
-        # Now deal with shared collections (and owned if revision == 0)
+                # Resource changed - for depth "infinity" report resource as changed
+                if name and depth != "1":
+                    changed.add("%s/%s" % (path, name,))
+
+        # Now deal with existing shared collections
+        # TODO: think about whether this can be done in one query rather than looping over each share
         for share in (yield self.children()):
-            if share.owned():
-                if not revision:
-                    path = share.name()
-                    # Always report collection as changed
-                    changed.add("%s/" % (path,))
+            if not share.owned():
+                sharedChanged, sharedDeleted, sharedInvalid = yield share.sharedChildResourceNamesSinceRevision(revision, depth)
+                changed |= sharedChanged
+                changed -= sharedInvalid
+                deleted |= sharedDeleted
+                deleted -= sharedInvalid
+                invalid |= sharedInvalid
 
-                    # Resource changed - for depth "infinity" report resource as changed
-                    if depth != "1":
-                        for name in (yield share.listObjectResources()):
-                            changed.add("%s/%s" % (path, name,))
+        changed = sorted(changed)
+        deleted = sorted(deleted)
+        invalid = sorted(invalid)
+        returnValue((changed, deleted, invalid,))
+
+
+
+
+    @inlineCallbacks
+    def resourceNamesSinceRevisionZero(self, depth):
+        """
+        Revision == 0 specialization of L{resourceNamesSinceRevision} .
+
+        @param depth: depth for determine what changed
+        @type depth: C{str}
+        """
+
+        # Scan each child
+        changed = set()
+        deleted = set()
+        invalid = set()
+        for child in (yield self.children()):
+            if child.owned():
+                path = child.name()
+                # Always report collection as changed
+                changed.add("%s/" % (path,))
+
+                # Resource changed - for depth "infinity" report resource as changed
+                if depth != "1":
+                    for name in (yield child.listObjectResources()):
+                        changed.add("%s/%s" % (path, name,))
             else:
-                sharedChanged, sharedDeleted, sharedInvalid = yield share.sharedChildResourceNamesSinceRevision(revision, depth)
+                sharedChanged, sharedDeleted, sharedInvalid = yield child.sharedChildResourceNamesSinceRevisionZero(depth)
                 changed |= sharedChanged
                 changed -= sharedInvalid
                 deleted |= sharedDeleted
@@ -6235,57 +6415,83 @@
             else:
                 invalid.add(self.name() + "/")
         else:
+            # If revision is prior to when the share was created, then treat as a full sync of the share
             if revision != 0 and revision < self._bindRevision:
                 if depth != "1":
+                    # This should never happen unless the client the share existed, was removed and then
+                    # re-added and the client has a token from before the remove. In that case the token is no
+                    # longer valid - a full sync has to be done.
                     raise SyncTokenValidException
                 else:
-                    revision = 0
+                    results = yield self.sharedChildResourceNamesSinceRevisionZero(depth)
+                    returnValue(results)
 
-            if revision:
-                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
-                ]
+            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 depth == "1":
-                            changed.add("%s/" % (path,))
-                        else:
-                            deleted.add("%s/%s" % (path, name,))
+            for path, name, wasdeleted in results:
+                if wasdeleted:
+                    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,))
-            else:
-                path = self.name()
                 # Always report collection as changed
                 changed.add("%s/" % (path,))
 
                 # Resource changed - for depth "infinity" report resource as changed
-                if depth != "1":
-                    for name in (yield self.listObjectResources()):
-                        changed.add("%s/%s" % (path, name,))
+                if name and depth != "1":
+                    changed.add("%s/%s" % (path, name,))
 
         returnValue((changed, deleted, invalid,))
 
 
     @inlineCallbacks
+    def sharedChildResourceNamesSinceRevisionZero(self, depth):
+        """
+        Revision == 0 specialization of L{sharedChildResourceNamesSinceRevision}. We report on all
+        existing resources -= this collection and children (if depth == infinite).
+
+        @param depth: depth for determine what changed
+        @type depth: C{str}
+        """
+        changed = set()
+        deleted = set()
+        invalid = set()
+        path = self.name()
+        if self.external():
+            if depth == "1":
+                changed.add("{}/".format(path))
+            else:
+                invalid.add("{}/".format(path))
+        else:
+            path = self.name()
+            # Always report collection as changed
+            changed.add(path + "/")
+
+            # Resource changed - for depth "infinity" report resource as changed
+            if depth != "1":
+                for name in (yield self.listObjectResources()):
+                    changed.add("%s/%s" % (path, name,))
+
+        returnValue((changed, deleted, invalid,))
+
+
+    @inlineCallbacks
     def _loadPropertyStore(self, props=None):
         if props is None:
             # Use any authz uid in place of the viewer uid so delegates have their own
@@ -6482,6 +6688,11 @@
     _objectSchema = None
     _componentClass = None
 
+    # Sub-classes must override and set their version number. This is used for
+    # on-demand data upgrades - i.e., any time old data is read it will be
+    # converted to the latest format and written back.
+    _currentDataVersion = 0
+
     BATCH_LOAD_SIZE = 50
 
 
@@ -6569,6 +6780,7 @@
         self._size = None
         self._created = None
         self._modified = None
+        self._dataversion = None
         self._textData = None
         self._cachedComponent = None
 
@@ -6814,7 +7026,8 @@
             obj.MD5,
             Len(obj.TEXT),
             obj.CREATED,
-            obj.MODIFIED
+            obj.MODIFIED,
+            obj.DATAVERSION,
         ]
 
 
@@ -6828,6 +7041,7 @@
             "_size",
             "_created",
             "_modified",
+            "_dataversion",
         )
 
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current-oracle-dialect-extras.sql
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current-oracle-dialect-extras.sql	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current-oracle-dialect-extras.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -1,11 +1,17 @@
-create or replace function next_job return integer is
-declare
-  cursor c1 is select JOB_ID from JOB for update skip locked;
-  result integer;
-begin
-  open c1;
-  fetch c1 into result;
-  select JOB_ID from JOB where ID = result for update;
-  return result;
-end;
-/
+----
+-- Copyright (c) 2010-2014 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.
+----
+
+-- Extra schema to add to current-oracle-dialect.sql

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current-oracle-dialect.sql	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current-oracle-dialect.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -116,7 +116,6 @@
 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);
-insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('group', 5);
 create table CALENDAR_BIND_STATUS (
     "ID" integer primary key,
     "DESCRIPTION" nvarchar2(16) unique
@@ -153,7 +152,8 @@
     "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', 
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "DATAVERSION" integer default 0 not null, 
     unique ("CALENDAR_RESOURCE_ID", "RESOURCE_NAME")
 );
 
@@ -269,7 +269,8 @@
     "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', 
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "DATAVERSION" integer default 0 not null, 
     unique ("ADDRESSBOOK_HOME_RESOURCE_ID", "RESOURCE_NAME"), 
     unique ("ADDRESSBOOK_HOME_RESOURCE_ID", "VCARD_UID")
 );
@@ -401,6 +402,13 @@
     "GROUP_UID" nvarchar2(255)
 );
 
+create table GROUP_ATTENDEE_RECONCILE_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "RESOURCE_ID" integer,
+    "GROUP_ID" integer
+);
+
 create table GROUPS (
     "GROUP_ID" integer primary key,
     "NAME" nvarchar2(255),
@@ -417,13 +425,6 @@
     primary key ("GROUP_ID", "MEMBER_UID")
 );
 
-create table GROUP_ATTENDEE_RECONCILE_WORK (
-    "WORK_ID" integer primary key not null,
-    "JOB_ID" integer not null references JOB,
-    "RESOURCE_ID" integer,
-    "GROUP_ID" integer
-);
-
 create table GROUP_ATTENDEE (
     "GROUP_ID" integer not null references GROUPS on delete cascade,
     "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
@@ -431,22 +432,6 @@
     primary key ("GROUP_ID", "RESOURCE_ID")
 );
 
-create table GROUP_SHAREE_RECONCILE_WORK (
-    "WORK_ID" integer primary key not null,
-    "JOB_ID" integer not null references JOB,
-    "CALENDAR_ID" integer not null references CALENDAR on delete cascade,
-    "GROUP_ID" integer not null references GROUPS on delete cascade
-);
-
-create table GROUP_SHAREE (
-    "GROUP_ID" integer not null references GROUPS on delete cascade,
-    "CALENDAR_HOME_ID" integer not null references CALENDAR_HOME on delete cascade,
-    "CALENDAR_ID" integer not null references CALENDAR on delete cascade,
-    "GROUP_BIND_MODE" integer not null,
-    "MEMBERSHIP_HASH" nvarchar2(255), 
-    primary key ("GROUP_ID", "CALENDAR_HOME_ID", "CALENDAR_ID")
-);
-
 create table DELEGATES (
     "DELEGATOR" nvarchar2(255),
     "DELEGATE" nvarchar2(255),
@@ -495,10 +480,15 @@
     "HOME_ID" integer not null unique references CALENDAR_HOME on delete cascade
 );
 
-create table SCHEDULE_REFRESH_WORK (
+create table SCHEDULE_WORK (
     "WORK_ID" integer primary key not null,
     "JOB_ID" integer not null references JOB,
     "ICALENDAR_UID" nvarchar2(255),
+    "WORK_TYPE" nvarchar2(255)
+);
+
+create table SCHEDULE_REFRESH_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
     "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
     "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
     "ATTENDEE_COUNT" integer
@@ -511,18 +501,14 @@
 );
 
 create table SCHEDULE_AUTO_REPLY_WORK (
-    "WORK_ID" integer primary key not null,
-    "JOB_ID" integer not null references JOB,
-    "ICALENDAR_UID" nvarchar2(255),
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
     "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
     "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
     "PARTSTAT" nvarchar2(255)
 );
 
 create table SCHEDULE_ORGANIZER_WORK (
-    "WORK_ID" integer primary key not null,
-    "JOB_ID" integer not null references JOB,
-    "ICALENDAR_UID" nvarchar2(255),
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
     "SCHEDULE_ACTION" integer not null,
     "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
     "RESOURCE_ID" integer,
@@ -541,19 +527,25 @@
 insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify', 1);
 insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify-cancelled', 2);
 insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('remove', 3);
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "SCHEDULE_ACTION" integer not null,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer,
+    "ATTENDEE" nvarchar2(255),
+    "ITIP_MSG" nclob,
+    "NO_REFRESH" integer
+);
+
 create table SCHEDULE_REPLY_WORK (
-    "WORK_ID" integer primary key not null,
-    "JOB_ID" integer not null references JOB,
-    "ICALENDAR_UID" nvarchar2(255),
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
     "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
     "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
     "CHANGED_RIDS" nclob
 );
 
 create table SCHEDULE_REPLY_CANCEL_WORK (
-    "WORK_ID" integer primary key not null,
-    "JOB_ID" integer not null references JOB,
-    "ICALENDAR_UID" nvarchar2(255),
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
     "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
     "ICALENDAR_TEXT" nclob
 );
@@ -586,7 +578,7 @@
     "VALUE" nvarchar2(255)
 );
 
-insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '44');
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '45');
 insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '6');
 insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
 insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
@@ -744,6 +736,10 @@
     JOB_ID
 );
 
+create index GROUP_ATTENDEE_RECONC_da73d3c2 on GROUP_ATTENDEE_RECONCILE_WORK (
+    JOB_ID
+);
+
 create index GROUPS_GROUP_UID_b35cce23 on GROUPS (
     GROUP_UID
 );
@@ -752,22 +748,10 @@
     MEMBER_UID
 );
 
-create index GROUP_ATTENDEE_RECONC_da73d3c2 on GROUP_ATTENDEE_RECONCILE_WORK (
-    JOB_ID
-);
-
-create index GROUP_ATTENDEE_ID_d497ffdb on GROUP_ATTENDEE (
+create index GROUP_ATTENDEE_RESOUR_855124dc on GROUP_ATTENDEE (
     RESOURCE_ID
 );
 
-create index GROUP_SHAREE_RECONCIL_9aad0858 on GROUP_SHAREE_RECONCILE_WORK (
-    JOB_ID
-);
-
-create index GROUP_SHAREE_CALENDAR_28a88850 on GROUP_SHAREE (
-    CALENDAR_ID
-);
-
 create index DELEGATE_TO_DELEGATOR_5e149b11 on DELEGATES (
     DELEGATE,
     READ_WRITE,
@@ -802,6 +786,14 @@
     JOB_ID
 );
 
+create index SCHEDULE_WORK_JOB_ID_65e810ee on SCHEDULE_WORK (
+    JOB_ID
+);
+
+create index SCHEDULE_WORK_ICALEND_089f33dc on SCHEDULE_WORK (
+    ICALENDAR_UID
+);
+
 create index SCHEDULE_REFRESH_WORK_26084c7b on SCHEDULE_REFRESH_WORK (
     HOME_RESOURCE_ID
 );
@@ -810,10 +802,6 @@
     RESOURCE_ID
 );
 
-create index SCHEDULE_REFRESH_WORK_3ffa2718 on SCHEDULE_REFRESH_WORK (
-    JOB_ID
-);
-
 create index SCHEDULE_REFRESH_ATTE_83053b91 on SCHEDULE_REFRESH_ATTENDEES (
     RESOURCE_ID,
     ATTENDEE
@@ -827,10 +815,6 @@
     RESOURCE_ID
 );
 
-create index SCHEDULE_AUTO_REPLY_W_4d7bb5a8 on SCHEDULE_AUTO_REPLY_WORK (
-    JOB_ID
-);
-
 create index SCHEDULE_ORGANIZER_WO_18ce4edd on SCHEDULE_ORGANIZER_WORK (
     HOME_RESOURCE_ID
 );
@@ -839,10 +823,14 @@
     RESOURCE_ID
 );
 
-create index SCHEDULE_ORGANIZER_WO_1e9f246d on SCHEDULE_ORGANIZER_WORK (
-    JOB_ID
+create index SCHEDULE_ORGANIZER_SE_9ec9f827 on SCHEDULE_ORGANIZER_SEND_WORK (
+    HOME_RESOURCE_ID
 );
 
+create index SCHEDULE_ORGANIZER_SE_699fefc4 on SCHEDULE_ORGANIZER_SEND_WORK (
+    RESOURCE_ID
+);
+
 create index SCHEDULE_REPLY_WORK_H_745af8cf on SCHEDULE_REPLY_WORK (
     HOME_RESOURCE_ID
 );
@@ -851,18 +839,10 @@
     RESOURCE_ID
 );
 
-create index SCHEDULE_REPLY_WORK_J_5913b4a4 on SCHEDULE_REPLY_WORK (
-    JOB_ID
-);
-
 create index SCHEDULE_REPLY_CANCEL_dab513ef on SCHEDULE_REPLY_CANCEL_WORK (
     HOME_RESOURCE_ID
 );
 
-create index SCHEDULE_REPLY_CANCEL_94a0c766 on SCHEDULE_REPLY_CANCEL_WORK (
-    JOB_ID
-);
-
 create index PRINCIPAL_PURGE_POLLI_6383e68a on PRINCIPAL_PURGE_POLLING_WORK (
     JOB_ID
 );
@@ -883,18 +863,4 @@
     HOME_RESOURCE_ID
 );
 
--- Skipped Function next_job
-
--- Extras
-
-create or replace function next_job return integer is
-declare
-  cursor c1 is select JOB_ID from JOB for update skip locked;
-  result integer;
-begin
-  open c1;
-  fetch c1 into result;
-  select JOB_ID from JOB where ID = result for update;
-  return result;
-end;
-/
+-- Extra schema to add to current-oracle-dialect.sql

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current.sql	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/current.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -61,18 +61,9 @@
   NOT_BEFORE  timestamp not null,
   ASSIGNED    timestamp default null,
   OVERDUE     timestamp default null,
-  FAILED	  integer default 0
+  FAILED      integer default 0
 );
 
-create or replace function next_job() returns integer as $$
-declare
-  result integer;
-begin
-  select JOB_ID into result from JOB where pg_try_advisory_xact_lock(JOB_ID) limit 1 for update;
-  return result;
-end
-$$ LANGUAGE plpgsql;
-
 -------------------
 -- Calendar Home --
 -------------------
@@ -264,6 +255,7 @@
   MD5                  char(32)     not null,
   CREATED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
   MODIFIED             timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  DATAVERSION          integer      default 0 not null,
 
   unique (CALENDAR_RESOURCE_ID, RESOURCE_NAME) -- implicit index
 
@@ -362,7 +354,7 @@
   TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
   USER_ID                     varchar(255) not null,
   TRANSPARENT                 boolean      not null,
-  ADJUSTED_START_DATE         timestamp	   default null,
+  ADJUSTED_START_DATE         timestamp    default null,
   ADJUSTED_END_DATE           timestamp    default null
 );
 
@@ -484,6 +476,7 @@
   MD5                           char(32)        not null,
   CREATED                       timestamp       default timezone('UTC', CURRENT_TIMESTAMP),
   MODIFIED                      timestamp       default timezone('UTC', CURRENT_TIMESTAMP),
+  DATAVERSION                   integer         default 0 not null,
 
   unique (ADDRESSBOOK_HOME_RESOURCE_ID, RESOURCE_NAME), -- implicit index
   unique (ADDRESSBOOK_HOME_RESOURCE_ID, VCARD_UID)      -- implicit index
@@ -808,7 +801,7 @@
   primary key (GROUP_ID, RESOURCE_ID)
 );
 
-create index GROUP_ATTENDEE_ID on
+create index GROUP_ATTENDEE_ID on  -- FIXME: Rename to GROUP_ATTENDEE_RESOURCE_ID
   GROUP_ATTENDEE(RESOURCE_ID);
 
   
@@ -923,14 +916,28 @@
 create index CLEANUP_ONE_INBOX_WORK_JOB_ID on
   CLEANUP_ONE_INBOX_WORK(JOB_ID);
 
+-------------------
+-- Schedule Work --
+-------------------
+
+create table SCHEDULE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ICALENDAR_UID                 varchar(255) not null,
+  WORK_TYPE                     varchar(255) not null
+);
+
+create index SCHEDULE_WORK_JOB_ID on
+  SCHEDULE_WORK(JOB_ID);
+create index SCHEDULE_WORK_ICALENDAR_UID on
+  SCHEDULE_WORK(ICALENDAR_UID);
+
 ---------------------------
 -- Schedule Refresh Work --
 ---------------------------
 
 create table SCHEDULE_REFRESH_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  JOB_ID                        integer      references JOB not null,
-  ICALENDAR_UID                 varchar(255) not null,
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
   HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
   RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
   ATTENDEE_COUNT                integer
@@ -940,8 +947,6 @@
   SCHEDULE_REFRESH_WORK(HOME_RESOURCE_ID);
 create index SCHEDULE_REFRESH_WORK_RESOURCE_ID on
   SCHEDULE_REFRESH_WORK(RESOURCE_ID);
-create index SCHEDULE_REFRESH_WORK_JOB_ID on
-  SCHEDULE_REFRESH_WORK(JOB_ID);
 
 create table SCHEDULE_REFRESH_ATTENDEES (
   RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
@@ -958,9 +963,7 @@
 ------------------------------
 
 create table SCHEDULE_AUTO_REPLY_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  JOB_ID                        integer      references JOB not null,
-  ICALENDAR_UID                 varchar(255) not null,
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
   HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
   RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
   PARTSTAT                      varchar(255) not null
@@ -970,17 +973,13 @@
   SCHEDULE_AUTO_REPLY_WORK(HOME_RESOURCE_ID);
 create index SCHEDULE_AUTO_REPLY_WORK_RESOURCE_ID on
   SCHEDULE_AUTO_REPLY_WORK(RESOURCE_ID);
-create index SCHEDULE_AUTO_REPLY_WORK_JOB_ID on
-  SCHEDULE_AUTO_REPLY_WORK(JOB_ID);
 
 -----------------------------
 -- Schedule Organizer Work --
 -----------------------------
 
 create table SCHEDULE_ORGANIZER_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  JOB_ID                        integer      references JOB not null,
-  ICALENDAR_UID                 varchar(255) not null,
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
   SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
   HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
   RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDR_OBJECT
@@ -994,8 +993,6 @@
   SCHEDULE_ORGANIZER_WORK(HOME_RESOURCE_ID);
 create index SCHEDULE_ORGANIZER_WORK_RESOURCE_ID on
   SCHEDULE_ORGANIZER_WORK(RESOURCE_ID);
-create index SCHEDULE_ORGANIZER_WORK_JOB_ID on
-  SCHEDULE_ORGANIZER_WORK(JOB_ID);
 
 -- Enumeration of schedule actions
 
@@ -1009,14 +1006,31 @@
 insert into SCHEDULE_ACTION values (2, 'modify-cancelled');
 insert into SCHEDULE_ACTION values (3, 'remove');
 
+----------------------------------
+-- Schedule Organizer Send Work --
+----------------------------------
+
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ATTENDEE                      varchar(255) not null,
+  ITIP_MSG                      text,
+  NO_REFRESH                    boolean
+);
+
+create index SCHEDULE_ORGANIZER_SEND_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_SEND_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(RESOURCE_ID);
+
 -------------------------
 -- Schedule Reply Work --
 -------------------------
 
 create table SCHEDULE_REPLY_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  JOB_ID                        integer      references JOB not null,
-  ICALENDAR_UID                 varchar(255) not null,
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
   HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
   RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
   CHANGED_RIDS                  text
@@ -1026,25 +1040,19 @@
   SCHEDULE_REPLY_WORK(HOME_RESOURCE_ID);
 create index SCHEDULE_REPLY_WORK_RESOURCE_ID on
   SCHEDULE_REPLY_WORK(RESOURCE_ID);
-create index SCHEDULE_REPLY_WORK_JOB_ID on
-  SCHEDULE_REPLY_WORK(JOB_ID);
 
 --------------------------------
 -- Schedule Reply Cancel Work --
 --------------------------------
 
 create table SCHEDULE_REPLY_CANCEL_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  JOB_ID                        integer      references JOB not null,
-  ICALENDAR_UID                 varchar(255) not null,
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
   HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
   ICALENDAR_TEXT                text         not null
 );
 
 create index SCHEDULE_REPLY_CANCEL_WORK_HOME_RESOURCE_ID on
   SCHEDULE_REPLY_CANCEL_WORK(HOME_RESOURCE_ID);
-create index SCHEDULE_REPLY_CANCEL_WORK_JOB_ID on
-  SCHEDULE_REPLY_CANCEL_WORK(JOB_ID);
 
 ----------------------------------
 -- Principal Purge Polling Work --
@@ -1110,7 +1118,7 @@
   VALUE                         varchar(255)
 );
 
-insert into CALENDARSERVER values ('VERSION', '44');
+insert into CALENDARSERVER values ('VERSION', '45');
 insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '6');
 insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
 insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');

Copied: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/oracle-dialect/v44.sql (from rev 13731, CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v44.sql)
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/oracle-dialect/v44.sql	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/oracle-dialect/v44.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,866 @@
+create sequence RESOURCE_ID_SEQ;
+create sequence JOB_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 JOB (
+    "JOB_ID" integer primary key not null,
+    "WORK_TYPE" nvarchar2(255),
+    "PRIORITY" integer default 0,
+    "WEIGHT" integer default 0,
+    "NOT_BEFORE" timestamp not null,
+    "ASSIGNED" timestamp default null,
+    "OVERDUE" timestamp default null,
+    "FAILED" integer default 0
+);
+
+create table CALENDAR_HOME (
+    "RESOURCE_ID" integer primary key,
+    "OWNER_UID" nvarchar2(255) unique,
+    "STATUS" integer default 0 not null,
+    "DATAVERSION" integer default 0 not null
+);
+
+create table HOME_STATUS (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into HOME_STATUS (DESCRIPTION, ID) values ('normal', 0);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('external', 1);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('purging', 2);
+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,
+    "STATUS" integer default 0 not null,
+    "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,
+    "EXTERNAL_ID" integer default null,
+    "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_OBJ_ATTACHMENTS_MODE (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (DESCRIPTION, ID) values ('none', 0);
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (DESCRIPTION, ID) values ('read', 1);
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (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 PERUSER (
+    "TIME_RANGE_INSTANCE_ID" integer not null references TIME_RANGE on delete cascade,
+    "USER_ID" nvarchar2(255),
+    "TRANSPARENT" integer not null,
+    "ADJUSTED_START_DATE" timestamp default null,
+    "ADJUSTED_END_DATE" timestamp default 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,
+    "STATUS" integer default 0 not null,
+    "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,
+    "EXTERNAL_ID" integer default null,
+    "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,
+    "ADDRESSBOOK_ID" integer not null references ADDRESSBOOK_HOME on delete cascade,
+    "MEMBER_ID" integer not null,
+    "REVISION" integer not null,
+    "REMOVED" integer default 0 not null,
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    primary key ("GROUP_ID", "MEMBER_ID", "REVISION")
+);
+
+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,
+    "EXTERNAL_ID" integer default null,
+    "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,
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+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,
+    "OBJECT_RESOURCE_ID" integer default 0,
+    "RESOURCE_NAME" nvarchar2(255),
+    "REVISION" integer not null,
+    "DELETED" integer not null,
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+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,
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    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,
+    "JOB_ID" integer not null references JOB,
+    "FROM_ADDR" nvarchar2(255),
+    "TO_ADDR" nvarchar2(255),
+    "ICALENDAR_TEXT" nclob
+);
+
+create table IMIP_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table IMIP_REPLY_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "ORGANIZER" nvarchar2(255),
+    "ATTENDEE" nvarchar2(255),
+    "ICALENDAR_TEXT" nclob
+);
+
+create table PUSH_NOTIFICATION_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "PUSH_ID" nvarchar2(255),
+    "PUSH_PRIORITY" integer not null
+);
+
+create table GROUP_CACHER_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table GROUP_REFRESH_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "GROUP_UID" nvarchar2(255)
+);
+
+create table GROUP_ATTENDEE_RECONCILE_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "RESOURCE_ID" integer,
+    "GROUP_ID" integer
+);
+
+create table GROUPS (
+    "GROUP_ID" integer primary key,
+    "NAME" nvarchar2(255),
+    "GROUP_UID" nvarchar2(255),
+    "MEMBERSHIP_HASH" nvarchar2(255),
+    "EXTANT" integer default 1,
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table GROUP_MEMBERSHIP (
+    "GROUP_ID" integer not null references GROUPS on delete cascade,
+    "MEMBER_UID" nvarchar2(255), 
+    primary key ("GROUP_ID", "MEMBER_UID")
+);
+
+create table GROUP_ATTENDEE (
+    "GROUP_ID" integer not null references GROUPS on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "MEMBERSHIP_HASH" nvarchar2(255), 
+    primary key ("GROUP_ID", "RESOURCE_ID")
+);
+
+create table DELEGATES (
+    "DELEGATOR" nvarchar2(255),
+    "DELEGATE" nvarchar2(255),
+    "READ_WRITE" integer not null, 
+    primary key ("DELEGATOR", "READ_WRITE", "DELEGATE")
+);
+
+create table DELEGATE_GROUPS (
+    "DELEGATOR" nvarchar2(255),
+    "GROUP_ID" integer not null references GROUPS on delete cascade,
+    "READ_WRITE" integer not null,
+    "IS_EXTERNAL" integer not null, 
+    primary key ("DELEGATOR", "READ_WRITE", "GROUP_ID")
+);
+
+create table EXTERNAL_DELEGATE_GROUPS (
+    "DELEGATOR" nvarchar2(255) primary key,
+    "GROUP_UID_READ" nvarchar2(255),
+    "GROUP_UID_WRITE" nvarchar2(255)
+);
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade
+);
+
+create table FIND_MIN_VALID_REVISION_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table REVISION_CLEANUP_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table INBOX_CLEANUP_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table CLEANUP_ONE_INBOX_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "HOME_ID" integer not null unique references CALENDAR_HOME on delete cascade
+);
+
+create table SCHEDULE_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "ICALENDAR_UID" nvarchar2(255),
+    "WORK_TYPE" nvarchar2(255)
+);
+
+create table SCHEDULE_REFRESH_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "ATTENDEE_COUNT" integer
+);
+
+create table SCHEDULE_REFRESH_ATTENDEES (
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "ATTENDEE" nvarchar2(255), 
+    primary key ("RESOURCE_ID", "ATTENDEE")
+);
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "PARTSTAT" nvarchar2(255)
+);
+
+create table SCHEDULE_ORGANIZER_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "SCHEDULE_ACTION" integer not null,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer,
+    "ICALENDAR_TEXT_OLD" nclob,
+    "ICALENDAR_TEXT_NEW" nclob,
+    "ATTENDEE_COUNT" integer,
+    "SMART_MERGE" integer
+);
+
+create table SCHEDULE_ACTION (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('create', 0);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify', 1);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify-cancelled', 2);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('remove', 3);
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "SCHEDULE_ACTION" integer not null,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer,
+    "ATTENDEE" nvarchar2(255),
+    "ITIP_MSG" nclob,
+    "NO_REFRESH" integer
+);
+
+create table SCHEDULE_REPLY_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "CHANGED_RIDS" nclob
+);
+
+create table SCHEDULE_REPLY_CANCEL_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "ICALENDAR_TEXT" nclob
+);
+
+create table PRINCIPAL_PURGE_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table PRINCIPAL_PURGE_CHECK_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "UID" nvarchar2(255)
+);
+
+create table PRINCIPAL_PURGE_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "UID" nvarchar2(255)
+);
+
+create table PRINCIPAL_PURGE_HOME_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade
+);
+
+create table CALENDARSERVER (
+    "NAME" nvarchar2(255) primary key,
+    "VALUE" nvarchar2(255)
+);
+
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '44');
+insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '6');
+insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
+insert into CALENDARSERVER (NAME, VALUE) values ('MIN-VALID-REVISION', '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 PERUSER_TIME_RANGE_IN_5468a226 on PERUSER (
+    TIME_RANGE_INSTANCE_ID
+);
+
+create index ATTACHMENT_CALENDAR_H_0078845c on ATTACHMENT (
+    CALENDAR_HOME_RESOURCE_ID
+);
+
+create index ATTACHMENT_DROPBOX_ID_5073cf23 on ATTACHMENT (
+    DROPBOX_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 IMIP_INVITATION_WORK__586d064c on IMIP_INVITATION_WORK (
+    JOB_ID
+);
+
+create index IMIP_POLLING_WORK_JOB_d5535891 on IMIP_POLLING_WORK (
+    JOB_ID
+);
+
+create index IMIP_REPLY_WORK_JOB_I_bf4ae73e on IMIP_REPLY_WORK (
+    JOB_ID
+);
+
+create index PUSH_NOTIFICATION_WOR_8bbab117 on PUSH_NOTIFICATION_WORK (
+    JOB_ID
+);
+
+create index GROUP_CACHER_POLLING__6eb3151c on GROUP_CACHER_POLLING_WORK (
+    JOB_ID
+);
+
+create index GROUP_REFRESH_WORK_JO_717ede20 on GROUP_REFRESH_WORK (
+    JOB_ID
+);
+
+create index GROUP_ATTENDEE_RECONC_da73d3c2 on GROUP_ATTENDEE_RECONCILE_WORK (
+    JOB_ID
+);
+
+create index GROUPS_GROUP_UID_b35cce23 on GROUPS (
+    GROUP_UID
+);
+
+create index GROUP_MEMBERSHIP_MEMB_0ca508e8 on GROUP_MEMBERSHIP (
+    MEMBER_UID
+);
+
+create index GROUP_ATTENDEE_RESOUR_855124dc on GROUP_ATTENDEE (
+    RESOURCE_ID
+);
+
+create index DELEGATE_TO_DELEGATOR_5e149b11 on DELEGATES (
+    DELEGATE,
+    READ_WRITE,
+    DELEGATOR
+);
+
+create index DELEGATE_GROUPS_GROUP_25117446 on DELEGATE_GROUPS (
+    GROUP_ID
+);
+
+create index CALENDAR_OBJECT_SPLIT_af71dcda on CALENDAR_OBJECT_SPLITTER_WORK (
+    RESOURCE_ID
+);
+
+create index CALENDAR_OBJECT_SPLIT_33603b72 on CALENDAR_OBJECT_SPLITTER_WORK (
+    JOB_ID
+);
+
+create index FIND_MIN_VALID_REVISI_78d17400 on FIND_MIN_VALID_REVISION_WORK (
+    JOB_ID
+);
+
+create index REVISION_CLEANUP_WORK_eb062686 on REVISION_CLEANUP_WORK (
+    JOB_ID
+);
+
+create index INBOX_CLEANUP_WORK_JO_799132bd on INBOX_CLEANUP_WORK (
+    JOB_ID
+);
+
+create index CLEANUP_ONE_INBOX_WOR_375dac36 on CLEANUP_ONE_INBOX_WORK (
+    JOB_ID
+);
+
+create index SCHEDULE_WORK_JOB_ID_65e810ee on SCHEDULE_WORK (
+    JOB_ID
+);
+
+create index SCHEDULE_WORK_ICALEND_089f33dc on SCHEDULE_WORK (
+    ICALENDAR_UID
+);
+
+create index SCHEDULE_REFRESH_WORK_26084c7b on SCHEDULE_REFRESH_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_REFRESH_WORK_989efe54 on SCHEDULE_REFRESH_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_REFRESH_ATTE_83053b91 on SCHEDULE_REFRESH_ATTENDEES (
+    RESOURCE_ID,
+    ATTENDEE
+);
+
+create index SCHEDULE_AUTO_REPLY_W_0256478d on SCHEDULE_AUTO_REPLY_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_AUTO_REPLY_W_0755e754 on SCHEDULE_AUTO_REPLY_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_ORGANIZER_WO_18ce4edd on SCHEDULE_ORGANIZER_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_ORGANIZER_WO_14702035 on SCHEDULE_ORGANIZER_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_ORGANIZER_SE_9ec9f827 on SCHEDULE_ORGANIZER_SEND_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_ORGANIZER_SE_699fefc4 on SCHEDULE_ORGANIZER_SEND_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_REPLY_WORK_H_745af8cf on SCHEDULE_REPLY_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_REPLY_WORK_R_11bd3fbb on SCHEDULE_REPLY_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_REPLY_CANCEL_dab513ef on SCHEDULE_REPLY_CANCEL_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index PRINCIPAL_PURGE_POLLI_6383e68a on PRINCIPAL_PURGE_POLLING_WORK (
+    JOB_ID
+);
+
+create index PRINCIPAL_PURGE_CHECK_b0c024c1 on PRINCIPAL_PURGE_CHECK_WORK (
+    JOB_ID
+);
+
+create index PRINCIPAL_PURGE_WORK__7a8141a3 on PRINCIPAL_PURGE_WORK (
+    JOB_ID
+);
+
+create index PRINCIPAL_PURGE_HOME__f35eea7a on PRINCIPAL_PURGE_HOME_WORK (
+    JOB_ID
+);
+
+create index PRINCIPAL_PURGE_HOME__967e4480 on PRINCIPAL_PURGE_HOME_WORK (
+    HOME_RESOURCE_ID
+);
+
+
+-- Extras
+

Added: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/oracle-dialect/v45.sql
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/oracle-dialect/v45.sql	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/oracle-dialect/v45.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,866 @@
+create sequence RESOURCE_ID_SEQ;
+create sequence JOB_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 JOB (
+    "JOB_ID" integer primary key not null,
+    "WORK_TYPE" nvarchar2(255),
+    "PRIORITY" integer default 0,
+    "WEIGHT" integer default 0,
+    "NOT_BEFORE" timestamp not null,
+    "ASSIGNED" timestamp default null,
+    "OVERDUE" timestamp default null,
+    "FAILED" integer default 0
+);
+
+create table CALENDAR_HOME (
+    "RESOURCE_ID" integer primary key,
+    "OWNER_UID" nvarchar2(255) unique,
+    "STATUS" integer default 0 not null,
+    "DATAVERSION" integer default 0 not null
+);
+
+create table HOME_STATUS (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into HOME_STATUS (DESCRIPTION, ID) values ('normal', 0);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('external', 1);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('purging', 2);
+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,
+    "STATUS" integer default 0 not null,
+    "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,
+    "EXTERNAL_ID" integer default null,
+    "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',
+    "DATAVERSION" integer default 0 not null, 
+    unique ("CALENDAR_RESOURCE_ID", "RESOURCE_NAME")
+);
+
+create table CALENDAR_OBJ_ATTACHMENTS_MODE (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (DESCRIPTION, ID) values ('none', 0);
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (DESCRIPTION, ID) values ('read', 1);
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (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 PERUSER (
+    "TIME_RANGE_INSTANCE_ID" integer not null references TIME_RANGE on delete cascade,
+    "USER_ID" nvarchar2(255),
+    "TRANSPARENT" integer not null,
+    "ADJUSTED_START_DATE" timestamp default null,
+    "ADJUSTED_END_DATE" timestamp default 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,
+    "STATUS" integer default 0 not null,
+    "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,
+    "EXTERNAL_ID" integer default null,
+    "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',
+    "DATAVERSION" integer default 0 not null, 
+    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,
+    "ADDRESSBOOK_ID" integer not null references ADDRESSBOOK_HOME on delete cascade,
+    "MEMBER_ID" integer not null,
+    "REVISION" integer not null,
+    "REMOVED" integer default 0 not null,
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    primary key ("GROUP_ID", "MEMBER_ID", "REVISION")
+);
+
+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,
+    "EXTERNAL_ID" integer default null,
+    "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,
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+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,
+    "OBJECT_RESOURCE_ID" integer default 0,
+    "RESOURCE_NAME" nvarchar2(255),
+    "REVISION" integer not null,
+    "DELETED" integer not null,
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+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,
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    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,
+    "JOB_ID" integer not null references JOB,
+    "FROM_ADDR" nvarchar2(255),
+    "TO_ADDR" nvarchar2(255),
+    "ICALENDAR_TEXT" nclob
+);
+
+create table IMIP_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table IMIP_REPLY_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "ORGANIZER" nvarchar2(255),
+    "ATTENDEE" nvarchar2(255),
+    "ICALENDAR_TEXT" nclob
+);
+
+create table PUSH_NOTIFICATION_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "PUSH_ID" nvarchar2(255),
+    "PUSH_PRIORITY" integer not null
+);
+
+create table GROUP_CACHER_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table GROUP_REFRESH_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "GROUP_UID" nvarchar2(255)
+);
+
+create table GROUP_ATTENDEE_RECONCILE_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "RESOURCE_ID" integer,
+    "GROUP_ID" integer
+);
+
+create table GROUPS (
+    "GROUP_ID" integer primary key,
+    "NAME" nvarchar2(255),
+    "GROUP_UID" nvarchar2(255),
+    "MEMBERSHIP_HASH" nvarchar2(255),
+    "EXTANT" integer default 1,
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table GROUP_MEMBERSHIP (
+    "GROUP_ID" integer not null references GROUPS on delete cascade,
+    "MEMBER_UID" nvarchar2(255), 
+    primary key ("GROUP_ID", "MEMBER_UID")
+);
+
+create table GROUP_ATTENDEE (
+    "GROUP_ID" integer not null references GROUPS on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "MEMBERSHIP_HASH" nvarchar2(255), 
+    primary key ("GROUP_ID", "RESOURCE_ID")
+);
+
+create table DELEGATES (
+    "DELEGATOR" nvarchar2(255),
+    "DELEGATE" nvarchar2(255),
+    "READ_WRITE" integer not null, 
+    primary key ("DELEGATOR", "READ_WRITE", "DELEGATE")
+);
+
+create table DELEGATE_GROUPS (
+    "DELEGATOR" nvarchar2(255),
+    "GROUP_ID" integer not null references GROUPS on delete cascade,
+    "READ_WRITE" integer not null,
+    "IS_EXTERNAL" integer not null, 
+    primary key ("DELEGATOR", "READ_WRITE", "GROUP_ID")
+);
+
+create table EXTERNAL_DELEGATE_GROUPS (
+    "DELEGATOR" nvarchar2(255) primary key,
+    "GROUP_UID_READ" nvarchar2(255),
+    "GROUP_UID_WRITE" nvarchar2(255)
+);
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade
+);
+
+create table FIND_MIN_VALID_REVISION_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table REVISION_CLEANUP_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table INBOX_CLEANUP_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table CLEANUP_ONE_INBOX_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "HOME_ID" integer not null unique references CALENDAR_HOME on delete cascade
+);
+
+create table SCHEDULE_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "ICALENDAR_UID" nvarchar2(255),
+    "WORK_TYPE" nvarchar2(255)
+);
+
+create table SCHEDULE_REFRESH_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "ATTENDEE_COUNT" integer
+);
+
+create table SCHEDULE_REFRESH_ATTENDEES (
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "ATTENDEE" nvarchar2(255), 
+    primary key ("RESOURCE_ID", "ATTENDEE")
+);
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "PARTSTAT" nvarchar2(255)
+);
+
+create table SCHEDULE_ORGANIZER_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "SCHEDULE_ACTION" integer not null,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer,
+    "ICALENDAR_TEXT_OLD" nclob,
+    "ICALENDAR_TEXT_NEW" nclob,
+    "ATTENDEE_COUNT" integer,
+    "SMART_MERGE" integer
+);
+
+create table SCHEDULE_ACTION (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('create', 0);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify', 1);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify-cancelled', 2);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('remove', 3);
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "SCHEDULE_ACTION" integer not null,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer,
+    "ATTENDEE" nvarchar2(255),
+    "ITIP_MSG" nclob,
+    "NO_REFRESH" integer
+);
+
+create table SCHEDULE_REPLY_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "CHANGED_RIDS" nclob
+);
+
+create table SCHEDULE_REPLY_CANCEL_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "ICALENDAR_TEXT" nclob
+);
+
+create table PRINCIPAL_PURGE_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB
+);
+
+create table PRINCIPAL_PURGE_CHECK_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "UID" nvarchar2(255)
+);
+
+create table PRINCIPAL_PURGE_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "UID" nvarchar2(255)
+);
+
+create table PRINCIPAL_PURGE_HOME_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade
+);
+
+create table CALENDARSERVER (
+    "NAME" nvarchar2(255) primary key,
+    "VALUE" nvarchar2(255)
+);
+
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '45');
+insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '6');
+insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
+insert into CALENDARSERVER (NAME, VALUE) values ('MIN-VALID-REVISION', '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 PERUSER_TIME_RANGE_IN_5468a226 on PERUSER (
+    TIME_RANGE_INSTANCE_ID
+);
+
+create index ATTACHMENT_CALENDAR_H_0078845c on ATTACHMENT (
+    CALENDAR_HOME_RESOURCE_ID
+);
+
+create index ATTACHMENT_DROPBOX_ID_5073cf23 on ATTACHMENT (
+    DROPBOX_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 IMIP_INVITATION_WORK__586d064c on IMIP_INVITATION_WORK (
+    JOB_ID
+);
+
+create index IMIP_POLLING_WORK_JOB_d5535891 on IMIP_POLLING_WORK (
+    JOB_ID
+);
+
+create index IMIP_REPLY_WORK_JOB_I_bf4ae73e on IMIP_REPLY_WORK (
+    JOB_ID
+);
+
+create index PUSH_NOTIFICATION_WOR_8bbab117 on PUSH_NOTIFICATION_WORK (
+    JOB_ID
+);
+
+create index GROUP_CACHER_POLLING__6eb3151c on GROUP_CACHER_POLLING_WORK (
+    JOB_ID
+);
+
+create index GROUP_REFRESH_WORK_JO_717ede20 on GROUP_REFRESH_WORK (
+    JOB_ID
+);
+
+create index GROUP_ATTENDEE_RECONC_da73d3c2 on GROUP_ATTENDEE_RECONCILE_WORK (
+    JOB_ID
+);
+
+create index GROUPS_GROUP_UID_b35cce23 on GROUPS (
+    GROUP_UID
+);
+
+create index GROUP_MEMBERSHIP_MEMB_0ca508e8 on GROUP_MEMBERSHIP (
+    MEMBER_UID
+);
+
+create index GROUP_ATTENDEE_RESOUR_855124dc on GROUP_ATTENDEE (
+    RESOURCE_ID
+);
+
+create index DELEGATE_TO_DELEGATOR_5e149b11 on DELEGATES (
+    DELEGATE,
+    READ_WRITE,
+    DELEGATOR
+);
+
+create index DELEGATE_GROUPS_GROUP_25117446 on DELEGATE_GROUPS (
+    GROUP_ID
+);
+
+create index CALENDAR_OBJECT_SPLIT_af71dcda on CALENDAR_OBJECT_SPLITTER_WORK (
+    RESOURCE_ID
+);
+
+create index CALENDAR_OBJECT_SPLIT_33603b72 on CALENDAR_OBJECT_SPLITTER_WORK (
+    JOB_ID
+);
+
+create index FIND_MIN_VALID_REVISI_78d17400 on FIND_MIN_VALID_REVISION_WORK (
+    JOB_ID
+);
+
+create index REVISION_CLEANUP_WORK_eb062686 on REVISION_CLEANUP_WORK (
+    JOB_ID
+);
+
+create index INBOX_CLEANUP_WORK_JO_799132bd on INBOX_CLEANUP_WORK (
+    JOB_ID
+);
+
+create index CLEANUP_ONE_INBOX_WOR_375dac36 on CLEANUP_ONE_INBOX_WORK (
+    JOB_ID
+);
+
+create index SCHEDULE_WORK_JOB_ID_65e810ee on SCHEDULE_WORK (
+    JOB_ID
+);
+
+create index SCHEDULE_WORK_ICALEND_089f33dc on SCHEDULE_WORK (
+    ICALENDAR_UID
+);
+
+create index SCHEDULE_REFRESH_WORK_26084c7b on SCHEDULE_REFRESH_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_REFRESH_WORK_989efe54 on SCHEDULE_REFRESH_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_REFRESH_ATTE_83053b91 on SCHEDULE_REFRESH_ATTENDEES (
+    RESOURCE_ID,
+    ATTENDEE
+);
+
+create index SCHEDULE_AUTO_REPLY_W_0256478d on SCHEDULE_AUTO_REPLY_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_AUTO_REPLY_W_0755e754 on SCHEDULE_AUTO_REPLY_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_ORGANIZER_WO_18ce4edd on SCHEDULE_ORGANIZER_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_ORGANIZER_WO_14702035 on SCHEDULE_ORGANIZER_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_ORGANIZER_SE_9ec9f827 on SCHEDULE_ORGANIZER_SEND_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_ORGANIZER_SE_699fefc4 on SCHEDULE_ORGANIZER_SEND_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_REPLY_WORK_H_745af8cf on SCHEDULE_REPLY_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index SCHEDULE_REPLY_WORK_R_11bd3fbb on SCHEDULE_REPLY_WORK (
+    RESOURCE_ID
+);
+
+create index SCHEDULE_REPLY_CANCEL_dab513ef on SCHEDULE_REPLY_CANCEL_WORK (
+    HOME_RESOURCE_ID
+);
+
+create index PRINCIPAL_PURGE_POLLI_6383e68a on PRINCIPAL_PURGE_POLLING_WORK (
+    JOB_ID
+);
+
+create index PRINCIPAL_PURGE_CHECK_b0c024c1 on PRINCIPAL_PURGE_CHECK_WORK (
+    JOB_ID
+);
+
+create index PRINCIPAL_PURGE_WORK__7a8141a3 on PRINCIPAL_PURGE_WORK (
+    JOB_ID
+);
+
+create index PRINCIPAL_PURGE_HOME__f35eea7a on PRINCIPAL_PURGE_HOME_WORK (
+    JOB_ID
+);
+
+create index PRINCIPAL_PURGE_HOME__967e4480 on PRINCIPAL_PURGE_HOME_WORK (
+    HOME_RESOURCE_ID
+);
+
+-- Extra schema to add to current-oracle-dialect.sql

Copied: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/postgres-dialect/v44.sql (from rev 13731, CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v44.sql)
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/postgres-dialect/v44.sql	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/postgres-dialect/v44.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,1096 @@
+-- -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
+
+----
+-- Copyright (c) 2010-2014 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
+);
+
+
+--------------------
+-- Jobs           --
+--------------------
+
+create sequence JOB_SEQ;
+
+create table JOB (
+  JOB_ID      integer primary key default nextval('JOB_SEQ') not null, --implicit index
+  WORK_TYPE   varchar(255) not null,
+  PRIORITY    integer default 0,
+  WEIGHT      integer default 0,
+  NOT_BEFORE  timestamp not null,
+  ASSIGNED    timestamp default null,
+  OVERDUE     timestamp default null,
+  FAILED      integer default 0
+);
+
+-------------------
+-- 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
+  STATUS           integer      default 0 not null,                             -- enum HOME_STATUS
+  DATAVERSION      integer      default 0 not null
+);
+
+-- Enumeration of statuses
+
+create table HOME_STATUS (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into HOME_STATUS values (0, 'normal' );
+insert into HOME_STATUS values (1, 'external');
+insert into HOME_STATUS values (2, 'purging');
+
+
+--------------
+-- 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
+  STATUS      integer      default 0 not null,                             -- enum HOME_STATUS
+  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,
+  EXTERNAL_ID               integer      default null,
+  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_OBJ_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_OBJ_ATTACHMENTS_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE values (0, 'none' );
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE values (1, 'read' );
+insert into CALENDAR_OBJ_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'  );
+
+
+-------------------
+-- Per-user data --
+-------------------
+
+create table PERUSER (
+  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
+  USER_ID                     varchar(255) not null,
+  TRANSPARENT                 boolean      not null,
+  ADJUSTED_START_DATE         timestamp    default null,
+  ADJUSTED_END_DATE           timestamp    default null
+);
+
+create index PERUSER_TIME_RANGE_INSTANCE_ID on
+  PERUSER(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);
+
+create index ATTACHMENT_DROPBOX_ID on
+  ATTACHMENT(DROPBOX_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
+  STATUS                        integer         default 0 not null,                             -- enum HOME_STATUS
+  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,
+  EXTERNAL_ID                           integer         default null,
+  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');
+
+
+----------------------------------
+-- 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
+  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
+  REVISION        integer     default nextval('REVISION_SEQ') not null,
+  REMOVED         boolean     default false not null,
+  MODIFIED        timestamp   default timezone('UTC', CURRENT_TIMESTAMP),
+
+    primary key (GROUP_ID, MEMBER_ID, REVISION) -- 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,
+  EXTERNAL_ID                       integer      default null,
+  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,
+  MODIFIED                  timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+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,
+  OBJECT_RESOURCE_ID            integer      default 0,
+  RESOURCE_NAME                 varchar(255),
+  REVISION                      integer      default nextval('REVISION_SEQ') not null,
+  DELETED                       boolean      not null,
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+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,
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  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
+  JOB_ID                        integer      references JOB not null,
+  FROM_ADDR                     varchar(255) not null,
+  TO_ADDR                       varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index IMIP_INVITATION_WORK_JOB_ID on
+  IMIP_INVITATION_WORK(JOB_ID);
+
+-----------------------
+-- IMIP Polling Work --
+-----------------------
+
+create table IMIP_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index IMIP_POLLING_WORK_JOB_ID on
+  IMIP_POLLING_WORK(JOB_ID);
+
+
+---------------------
+-- IMIP Reply Work --
+---------------------
+
+create table IMIP_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index IMIP_REPLY_WORK_JOB_ID on
+  IMIP_REPLY_WORK(JOB_ID);
+
+
+------------------------
+-- Push Notifications --
+------------------------
+
+create table PUSH_NOTIFICATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  PUSH_ID                       varchar(255) not null,
+  PUSH_PRIORITY                 integer      not null -- 1:low 5:medium 10:high
+);
+
+create index PUSH_NOTIFICATION_WORK_JOB_ID on
+  PUSH_NOTIFICATION_WORK(JOB_ID);
+
+-----------------
+-- GroupCacher --
+-----------------
+
+create table GROUP_CACHER_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index GROUP_CACHER_POLLING_WORK_JOB_ID on
+  GROUP_CACHER_POLLING_WORK(JOB_ID);
+
+create table GROUP_REFRESH_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  GROUP_UID                     varchar(255) not null
+);
+
+create index GROUP_REFRESH_WORK_JOB_ID on
+  GROUP_REFRESH_WORK(JOB_ID);
+
+create table GROUP_ATTENDEE_RECONCILE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  RESOURCE_ID                   integer,
+  GROUP_ID                      integer
+);
+
+create index GROUP_ATTENDEE_RECONCILE_WORK_JOB_ID on
+  GROUP_ATTENDEE_RECONCILE_WORK(JOB_ID);
+
+
+create table GROUPS (
+  GROUP_ID                      integer      primary key default nextval('RESOURCE_ID_SEQ'),    -- implicit index
+  NAME                          varchar(255) not null,
+  GROUP_UID                     varchar(255) not null,
+  MEMBERSHIP_HASH               varchar(255) not null,
+  EXTANT                        integer default 1,
+  CREATED                       timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                      timestamp default timezone('UTC', CURRENT_TIMESTAMP)
+);
+create index GROUPS_GROUP_UID on
+  GROUPS(GROUP_UID);
+
+create table GROUP_MEMBERSHIP (
+  GROUP_ID                     integer not null references GROUPS on delete cascade,
+  MEMBER_UID                   varchar(255) not null,
+  
+  primary key (GROUP_ID, MEMBER_UID)
+);
+
+create index GROUP_MEMBERSHIP_MEMBER on
+  GROUP_MEMBERSHIP(MEMBER_UID);
+
+create table GROUP_ATTENDEE (
+  GROUP_ID                      integer not null references GROUPS on delete cascade,
+  RESOURCE_ID                   integer not null references CALENDAR_OBJECT on delete cascade,
+  MEMBERSHIP_HASH               varchar(255) not null,
+  
+  primary key (GROUP_ID, RESOURCE_ID)
+);
+create index GROUP_ATTENDEE_RESOURCE_ID on
+  GROUP_ATTENDEE(RESOURCE_ID);
+
+---------------
+-- Delegates --
+---------------
+
+create table DELEGATES (
+  DELEGATOR                     varchar(255) not null,
+  DELEGATE                      varchar(255) not null,
+  READ_WRITE                    integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+
+  primary key (DELEGATOR, READ_WRITE, DELEGATE)
+);
+create index DELEGATE_TO_DELEGATOR on
+  DELEGATES(DELEGATE, READ_WRITE, DELEGATOR);
+
+create table DELEGATE_GROUPS (
+  DELEGATOR                     varchar(255) not null,
+  GROUP_ID                      integer      not null references GROUPS on delete cascade,
+  READ_WRITE                    integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+  IS_EXTERNAL                   integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+
+  primary key (DELEGATOR, READ_WRITE, GROUP_ID)
+);
+create index DELEGATE_GROUPS_GROUP_ID on
+  DELEGATE_GROUPS(GROUP_ID);
+
+create table EXTERNAL_DELEGATE_GROUPS (
+  DELEGATOR                     varchar(255) primary key not null,
+  GROUP_UID_READ                varchar(255),
+  GROUP_UID_WRITE               varchar(255)
+);
+
+--------------------------
+-- Object Splitter Work --
+--------------------------
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  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);
+create index CALENDAR_OBJECT_SPLITTER_WORK_JOB_ID on
+  CALENDAR_OBJECT_SPLITTER_WORK(JOB_ID);
+
+---------------------------
+-- Revision Cleanup Work --
+---------------------------
+
+create table FIND_MIN_VALID_REVISION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index FIND_MIN_VALID_REVISION_WORK_JOB_ID on
+  FIND_MIN_VALID_REVISION_WORK(JOB_ID);
+
+create table REVISION_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index REVISION_CLEANUP_WORK_JOB_ID on
+  REVISION_CLEANUP_WORK(JOB_ID);
+
+------------------------
+-- Inbox Cleanup Work --
+------------------------
+
+create table INBOX_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index INBOX_CLEANUP_WORK_JOB_ID on
+   INBOX_CLEANUP_WORK(JOB_ID);
+
+create table CLEANUP_ONE_INBOX_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_ID                       integer      not null unique references CALENDAR_HOME on delete cascade
+);
+
+create index CLEANUP_ONE_INBOX_WORK_JOB_ID on
+  CLEANUP_ONE_INBOX_WORK(JOB_ID);
+
+-------------------
+-- Schedule Work --
+-------------------
+
+create table SCHEDULE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ICALENDAR_UID                 varchar(255) not null,
+  WORK_TYPE                     varchar(255) not null
+);
+
+create index SCHEDULE_WORK_JOB_ID on
+  SCHEDULE_WORK(JOB_ID);
+create index SCHEDULE_WORK_ICALENDAR_UID on
+  SCHEDULE_WORK(ICALENDAR_UID);
+
+---------------------------
+-- Schedule Refresh Work --
+---------------------------
+
+create table SCHEDULE_REFRESH_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE_COUNT                integer
+);
+
+create index SCHEDULE_REFRESH_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REFRESH_WORK_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(RESOURCE_ID);
+
+create table SCHEDULE_REFRESH_ATTENDEES (
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE                      varchar(255) not null,
+  
+  primary key (RESOURCE_ID, ATTENDEE)
+);
+
+create index SCHEDULE_REFRESH_ATTENDEES_RESOURCE_ID_ATTENDEE on
+  SCHEDULE_REFRESH_ATTENDEES(RESOURCE_ID, ATTENDEE);
+
+------------------------------
+-- Schedule Auto Reply Work --
+------------------------------
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  PARTSTAT                      varchar(255) not null
+);
+
+create index SCHEDULE_AUTO_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_AUTO_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(RESOURCE_ID);
+
+-----------------------------
+-- Schedule Organizer Work --
+-----------------------------
+
+create table SCHEDULE_ORGANIZER_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDR_OBJECT
+  ICALENDAR_TEXT_OLD            text,
+  ICALENDAR_TEXT_NEW            text,
+  ATTENDEE_COUNT                integer,
+  SMART_MERGE                   boolean
+);
+
+create index SCHEDULE_ORGANIZER_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(RESOURCE_ID);
+
+-- Enumeration of schedule actions
+
+create table SCHEDULE_ACTION (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into SCHEDULE_ACTION values (0, 'create');
+insert into SCHEDULE_ACTION values (1, 'modify');
+insert into SCHEDULE_ACTION values (2, 'modify-cancelled');
+insert into SCHEDULE_ACTION values (3, 'remove');
+
+----------------------------------
+-- Schedule Organizer Send Work --
+----------------------------------
+
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ATTENDEE                      varchar(255) not null,
+  ITIP_MSG                      text,
+  NO_REFRESH                    boolean
+);
+
+create index SCHEDULE_ORGANIZER_SEND_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_SEND_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(RESOURCE_ID);
+
+-------------------------
+-- Schedule Reply Work --
+-------------------------
+
+create table SCHEDULE_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  CHANGED_RIDS                  text
+);
+
+create index SCHEDULE_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(RESOURCE_ID);
+
+--------------------------------
+-- Schedule Reply Cancel Work --
+--------------------------------
+
+create table SCHEDULE_REPLY_CANCEL_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index SCHEDULE_REPLY_CANCEL_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REPLY_CANCEL_WORK(HOME_RESOURCE_ID);
+
+----------------------------------
+-- Principal Purge Polling Work --
+----------------------------------
+
+create table PRINCIPAL_PURGE_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index PRINCIPAL_PURGE_POLLING_WORK_JOB_ID on
+  PRINCIPAL_PURGE_POLLING_WORK(JOB_ID);
+
+--------------------------------
+-- Principal Purge Check Work --
+--------------------------------
+
+create table PRINCIPAL_PURGE_CHECK_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  UID                           varchar(255) not null
+);
+
+create index PRINCIPAL_PURGE_CHECK_WORK_JOB_ID on
+  PRINCIPAL_PURGE_CHECK_WORK(JOB_ID);
+
+--------------------------
+-- Principal Purge Work --
+--------------------------
+
+create table PRINCIPAL_PURGE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  UID                           varchar(255) not null
+);
+
+create index PRINCIPAL_PURGE_WORK_JOB_ID on
+  PRINCIPAL_PURGE_WORK(JOB_ID);
+
+
+--------------------------------
+-- Principal Home Remove Work --
+--------------------------------
+
+create table PRINCIPAL_PURGE_HOME_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade
+);
+
+create index PRINCIPAL_PURGE_HOME_WORK_JOB_ID on
+  PRINCIPAL_PURGE_HOME_WORK(JOB_ID);
+create index PRINCIPAL_PURGE_HOME_HOME_RESOURCE_ID on
+  PRINCIPAL_PURGE_HOME_WORK(HOME_RESOURCE_ID);
+
+
+--------------------
+-- Schema Version --
+--------------------
+
+create table CALENDARSERVER (
+  NAME                          varchar(255) primary key, -- implicit index
+  VALUE                         varchar(255)
+);
+
+insert into CALENDARSERVER values ('VERSION', '44');
+insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '6');
+insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');
+insert into CALENDARSERVER values ('MIN-VALID-REVISION', '1');

Added: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/postgres-dialect/v45.sql
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/postgres-dialect/v45.sql	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/old/postgres-dialect/v45.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,1098 @@
+-- -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
+
+----
+-- Copyright (c) 2010-2014 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
+);
+
+
+--------------------
+-- Jobs           --
+--------------------
+
+create sequence JOB_SEQ;
+
+create table JOB (
+  JOB_ID      integer primary key default nextval('JOB_SEQ') not null, --implicit index
+  WORK_TYPE   varchar(255) not null,
+  PRIORITY    integer default 0,
+  WEIGHT      integer default 0,
+  NOT_BEFORE  timestamp not null,
+  ASSIGNED    timestamp default null,
+  OVERDUE     timestamp default null,
+  FAILED      integer default 0
+);
+
+-------------------
+-- 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
+  STATUS           integer      default 0 not null,                             -- enum HOME_STATUS
+  DATAVERSION      integer      default 0 not null
+);
+
+-- Enumeration of statuses
+
+create table HOME_STATUS (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into HOME_STATUS values (0, 'normal' );
+insert into HOME_STATUS values (1, 'external');
+insert into HOME_STATUS values (2, 'purging');
+
+
+--------------
+-- 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
+  STATUS      integer      default 0 not null,                             -- enum HOME_STATUS
+  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,
+  EXTERNAL_ID               integer      default null,
+  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_OBJ_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),
+  DATAVERSION          integer      default 0 not null,
+
+  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_OBJ_ATTACHMENTS_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE values (0, 'none' );
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE values (1, 'read' );
+insert into CALENDAR_OBJ_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'  );
+
+
+-------------------
+-- Per-user data --
+-------------------
+
+create table PERUSER (
+  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
+  USER_ID                     varchar(255) not null,
+  TRANSPARENT                 boolean      not null,
+  ADJUSTED_START_DATE         timestamp    default null,
+  ADJUSTED_END_DATE           timestamp    default null
+);
+
+create index PERUSER_TIME_RANGE_INSTANCE_ID on
+  PERUSER(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);
+
+create index ATTACHMENT_DROPBOX_ID on
+  ATTACHMENT(DROPBOX_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
+  STATUS                        integer         default 0 not null,                             -- enum HOME_STATUS
+  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,
+  EXTERNAL_ID                           integer         default null,
+  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),
+  DATAVERSION                   integer         default 0 not null,
+
+  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');
+
+
+----------------------------------
+-- 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
+  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
+  REVISION        integer     default nextval('REVISION_SEQ') not null,
+  REMOVED         boolean     default false not null,
+  MODIFIED        timestamp   default timezone('UTC', CURRENT_TIMESTAMP),
+
+    primary key (GROUP_ID, MEMBER_ID, REVISION) -- 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,
+  EXTERNAL_ID                       integer      default null,
+  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,
+  MODIFIED                  timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+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,
+  OBJECT_RESOURCE_ID            integer      default 0,
+  RESOURCE_NAME                 varchar(255),
+  REVISION                      integer      default nextval('REVISION_SEQ') not null,
+  DELETED                       boolean      not null,
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+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,
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  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
+  JOB_ID                        integer      references JOB not null,
+  FROM_ADDR                     varchar(255) not null,
+  TO_ADDR                       varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index IMIP_INVITATION_WORK_JOB_ID on
+  IMIP_INVITATION_WORK(JOB_ID);
+
+-----------------------
+-- IMIP Polling Work --
+-----------------------
+
+create table IMIP_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index IMIP_POLLING_WORK_JOB_ID on
+  IMIP_POLLING_WORK(JOB_ID);
+
+
+---------------------
+-- IMIP Reply Work --
+---------------------
+
+create table IMIP_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index IMIP_REPLY_WORK_JOB_ID on
+  IMIP_REPLY_WORK(JOB_ID);
+
+
+------------------------
+-- Push Notifications --
+------------------------
+
+create table PUSH_NOTIFICATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  PUSH_ID                       varchar(255) not null,
+  PUSH_PRIORITY                 integer      not null -- 1:low 5:medium 10:high
+);
+
+create index PUSH_NOTIFICATION_WORK_JOB_ID on
+  PUSH_NOTIFICATION_WORK(JOB_ID);
+
+-----------------
+-- GroupCacher --
+-----------------
+
+create table GROUP_CACHER_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index GROUP_CACHER_POLLING_WORK_JOB_ID on
+  GROUP_CACHER_POLLING_WORK(JOB_ID);
+
+create table GROUP_REFRESH_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  GROUP_UID                     varchar(255) not null
+);
+
+create index GROUP_REFRESH_WORK_JOB_ID on
+  GROUP_REFRESH_WORK(JOB_ID);
+
+create table GROUP_ATTENDEE_RECONCILE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  RESOURCE_ID                   integer,
+  GROUP_ID                      integer
+);
+
+create index GROUP_ATTENDEE_RECONCILE_WORK_JOB_ID on
+  GROUP_ATTENDEE_RECONCILE_WORK(JOB_ID);
+
+
+create table GROUPS (
+  GROUP_ID                      integer      primary key default nextval('RESOURCE_ID_SEQ'),    -- implicit index
+  NAME                          varchar(255) not null,
+  GROUP_UID                     varchar(255) not null,
+  MEMBERSHIP_HASH               varchar(255) not null,
+  EXTANT                        integer default 1,
+  CREATED                       timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                      timestamp default timezone('UTC', CURRENT_TIMESTAMP)
+);
+create index GROUPS_GROUP_UID on
+  GROUPS(GROUP_UID);
+
+create table GROUP_MEMBERSHIP (
+  GROUP_ID                     integer not null references GROUPS on delete cascade,
+  MEMBER_UID                   varchar(255) not null,
+  
+  primary key (GROUP_ID, MEMBER_UID)
+);
+
+create index GROUP_MEMBERSHIP_MEMBER on
+  GROUP_MEMBERSHIP(MEMBER_UID);
+
+create table GROUP_ATTENDEE (
+  GROUP_ID                      integer not null references GROUPS on delete cascade,
+  RESOURCE_ID                   integer not null references CALENDAR_OBJECT on delete cascade,
+  MEMBERSHIP_HASH               varchar(255) not null,
+  
+  primary key (GROUP_ID, RESOURCE_ID)
+);
+create index GROUP_ATTENDEE_RESOURCE_ID on
+  GROUP_ATTENDEE(RESOURCE_ID);
+
+---------------
+-- Delegates --
+---------------
+
+create table DELEGATES (
+  DELEGATOR                     varchar(255) not null,
+  DELEGATE                      varchar(255) not null,
+  READ_WRITE                    integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+
+  primary key (DELEGATOR, READ_WRITE, DELEGATE)
+);
+create index DELEGATE_TO_DELEGATOR on
+  DELEGATES(DELEGATE, READ_WRITE, DELEGATOR);
+
+create table DELEGATE_GROUPS (
+  DELEGATOR                     varchar(255) not null,
+  GROUP_ID                      integer      not null references GROUPS on delete cascade,
+  READ_WRITE                    integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+  IS_EXTERNAL                   integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+
+  primary key (DELEGATOR, READ_WRITE, GROUP_ID)
+);
+create index DELEGATE_GROUPS_GROUP_ID on
+  DELEGATE_GROUPS(GROUP_ID);
+
+create table EXTERNAL_DELEGATE_GROUPS (
+  DELEGATOR                     varchar(255) primary key not null,
+  GROUP_UID_READ                varchar(255),
+  GROUP_UID_WRITE               varchar(255)
+);
+
+--------------------------
+-- Object Splitter Work --
+--------------------------
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  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);
+create index CALENDAR_OBJECT_SPLITTER_WORK_JOB_ID on
+  CALENDAR_OBJECT_SPLITTER_WORK(JOB_ID);
+
+---------------------------
+-- Revision Cleanup Work --
+---------------------------
+
+create table FIND_MIN_VALID_REVISION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index FIND_MIN_VALID_REVISION_WORK_JOB_ID on
+  FIND_MIN_VALID_REVISION_WORK(JOB_ID);
+
+create table REVISION_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index REVISION_CLEANUP_WORK_JOB_ID on
+  REVISION_CLEANUP_WORK(JOB_ID);
+
+------------------------
+-- Inbox Cleanup Work --
+------------------------
+
+create table INBOX_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index INBOX_CLEANUP_WORK_JOB_ID on
+   INBOX_CLEANUP_WORK(JOB_ID);
+
+create table CLEANUP_ONE_INBOX_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_ID                       integer      not null unique references CALENDAR_HOME on delete cascade
+);
+
+create index CLEANUP_ONE_INBOX_WORK_JOB_ID on
+  CLEANUP_ONE_INBOX_WORK(JOB_ID);
+
+-------------------
+-- Schedule Work --
+-------------------
+
+create table SCHEDULE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ICALENDAR_UID                 varchar(255) not null,
+  WORK_TYPE                     varchar(255) not null
+);
+
+create index SCHEDULE_WORK_JOB_ID on
+  SCHEDULE_WORK(JOB_ID);
+create index SCHEDULE_WORK_ICALENDAR_UID on
+  SCHEDULE_WORK(ICALENDAR_UID);
+
+---------------------------
+-- Schedule Refresh Work --
+---------------------------
+
+create table SCHEDULE_REFRESH_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE_COUNT                integer
+);
+
+create index SCHEDULE_REFRESH_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REFRESH_WORK_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(RESOURCE_ID);
+
+create table SCHEDULE_REFRESH_ATTENDEES (
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE                      varchar(255) not null,
+  
+  primary key (RESOURCE_ID, ATTENDEE)
+);
+
+create index SCHEDULE_REFRESH_ATTENDEES_RESOURCE_ID_ATTENDEE on
+  SCHEDULE_REFRESH_ATTENDEES(RESOURCE_ID, ATTENDEE);
+
+------------------------------
+-- Schedule Auto Reply Work --
+------------------------------
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  PARTSTAT                      varchar(255) not null
+);
+
+create index SCHEDULE_AUTO_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_AUTO_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(RESOURCE_ID);
+
+-----------------------------
+-- Schedule Organizer Work --
+-----------------------------
+
+create table SCHEDULE_ORGANIZER_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDR_OBJECT
+  ICALENDAR_TEXT_OLD            text,
+  ICALENDAR_TEXT_NEW            text,
+  ATTENDEE_COUNT                integer,
+  SMART_MERGE                   boolean
+);
+
+create index SCHEDULE_ORGANIZER_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(RESOURCE_ID);
+
+-- Enumeration of schedule actions
+
+create table SCHEDULE_ACTION (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into SCHEDULE_ACTION values (0, 'create');
+insert into SCHEDULE_ACTION values (1, 'modify');
+insert into SCHEDULE_ACTION values (2, 'modify-cancelled');
+insert into SCHEDULE_ACTION values (3, 'remove');
+
+----------------------------------
+-- Schedule Organizer Send Work --
+----------------------------------
+
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ATTENDEE                      varchar(255) not null,
+  ITIP_MSG                      text,
+  NO_REFRESH                    boolean
+);
+
+create index SCHEDULE_ORGANIZER_SEND_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_SEND_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(RESOURCE_ID);
+
+-------------------------
+-- Schedule Reply Work --
+-------------------------
+
+create table SCHEDULE_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  CHANGED_RIDS                  text
+);
+
+create index SCHEDULE_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(RESOURCE_ID);
+
+--------------------------------
+-- Schedule Reply Cancel Work --
+--------------------------------
+
+create table SCHEDULE_REPLY_CANCEL_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index SCHEDULE_REPLY_CANCEL_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REPLY_CANCEL_WORK(HOME_RESOURCE_ID);
+
+----------------------------------
+-- Principal Purge Polling Work --
+----------------------------------
+
+create table PRINCIPAL_PURGE_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index PRINCIPAL_PURGE_POLLING_WORK_JOB_ID on
+  PRINCIPAL_PURGE_POLLING_WORK(JOB_ID);
+
+--------------------------------
+-- Principal Purge Check Work --
+--------------------------------
+
+create table PRINCIPAL_PURGE_CHECK_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  UID                           varchar(255) not null
+);
+
+create index PRINCIPAL_PURGE_CHECK_WORK_JOB_ID on
+  PRINCIPAL_PURGE_CHECK_WORK(JOB_ID);
+
+--------------------------
+-- Principal Purge Work --
+--------------------------
+
+create table PRINCIPAL_PURGE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  UID                           varchar(255) not null
+);
+
+create index PRINCIPAL_PURGE_WORK_JOB_ID on
+  PRINCIPAL_PURGE_WORK(JOB_ID);
+
+
+--------------------------------
+-- Principal Home Remove Work --
+--------------------------------
+
+create table PRINCIPAL_PURGE_HOME_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade
+);
+
+create index PRINCIPAL_PURGE_HOME_WORK_JOB_ID on
+  PRINCIPAL_PURGE_HOME_WORK(JOB_ID);
+create index PRINCIPAL_PURGE_HOME_HOME_RESOURCE_ID on
+  PRINCIPAL_PURGE_HOME_WORK(HOME_RESOURCE_ID);
+
+
+--------------------
+-- Schema Version --
+--------------------
+
+create table CALENDARSERVER (
+  NAME                          varchar(255) primary key, -- implicit index
+  VALUE                         varchar(255)
+);
+
+insert into CALENDARSERVER values ('VERSION', '45');
+insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '6');
+insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');
+insert into CALENDARSERVER values ('MIN-VALID-REVISION', '1');

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_43_to_44.sql
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_43_to_44.sql	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_43_to_44.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -18,33 +18,153 @@
 -- Upgrade database schema from VERSION 43 to 44 --
 ---------------------------------------------------
 
-insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('group', 5);
+-----------------
+-- Job Changes --
+-----------------
 
-create table GROUP_SHAREE_RECONCILE_WORK (
-  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  JOB_ID                        integer not null references JOB,
-  CALENDAR_ID                   integer	not null references CALENDAR on delete cascade,
-  GROUP_ID                      integer not null references GROUPS on delete cascade
+drop function next_job;
+
+-- The scheduling work schema has changed a lot - to avoid a complex migration process this
+-- script just drops all the existing tables and adds back the new set
+
+-------------------
+-- Schedule Work --
+-------------------
+
+create table SCHEDULE_WORK (
+    "WORK_ID" integer primary key not null,
+    "JOB_ID" integer not null references JOB,
+    "ICALENDAR_UID" nvarchar2(255),
+    "WORK_TYPE" nvarchar2(255)
 );
 
-create index GROUP_SHAREE_RECONCILE_WORK_JOB_ID on GROUP_SHAREE_RECONCILE_WORK(
-	JOB_ID
+create index SCHEDULE_WORK_JOB_ID_65e810ee on SCHEDULE_WORK (
+    JOB_ID
 );
+create index SCHEDULE_WORK_ICALEND_089f33dc on SCHEDULE_WORK (
+    ICALENDAR_UID
+);
 
+---------------------------
+-- Schedule Refresh Work --
+---------------------------
 
-create table GROUP_SHAREE (
-  GROUP_ID                      integer not null references GROUPS on delete cascade,
-  CALENDAR_HOME_ID 				integer not null references CALENDAR_HOME on delete cascade,
-  CALENDAR_ID      				integer not null references CALENDAR on delete cascade,
-  GROUP_BIND_MODE               integer not null, -- enum CALENDAR_BIND_MODE
-  MEMBERSHIP_HASH               varchar(255) not null,
-  
-  primary key (GROUP_ID, CALENDAR_HOME_ID, CALENDAR_ID) -- implicit index
+drop table SCHEDULE_REFRESH_WORK;
+
+create table SCHEDULE_REFRESH_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "ATTENDEE_COUNT" integer
 );
 
-create index GROUP_SHAREE_CALENDAR_ID on GROUP_SHAREE(
-	CALENDAR_ID
+create index SCHEDULE_REFRESH_WORK_26084c7b on SCHEDULE_REFRESH_WORK (
+    HOME_RESOURCE_ID
 );
+create index SCHEDULE_REFRESH_WORK_989efe54 on SCHEDULE_REFRESH_WORK (
+    RESOURCE_ID
+);
 
+------------------------------
+-- Schedule Auto Reply Work --
+------------------------------
+
+drop table SCHEDULE_AUTO_REPLY_WORK;
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "PARTSTAT" nvarchar2(255)
+);
+
+create index SCHEDULE_AUTO_REPLY_W_0256478d on SCHEDULE_AUTO_REPLY_WORK (
+    HOME_RESOURCE_ID
+);
+create index SCHEDULE_AUTO_REPLY_W_0755e754 on SCHEDULE_AUTO_REPLY_WORK (
+    RESOURCE_ID
+);
+
+-----------------------------
+-- Schedule Organizer Work --
+-----------------------------
+
+drop table SCHEDULE_ORGANIZER_WORK;
+
+create table SCHEDULE_ORGANIZER_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "SCHEDULE_ACTION" integer not null,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer,
+    "ICALENDAR_TEXT_OLD" nclob,
+    "ICALENDAR_TEXT_NEW" nclob,
+    "ATTENDEE_COUNT" integer,
+    "SMART_MERGE" integer
+);
+
+create index SCHEDULE_ORGANIZER_WO_18ce4edd on SCHEDULE_ORGANIZER_WORK (
+    HOME_RESOURCE_ID
+);
+create index SCHEDULE_ORGANIZER_WO_14702035 on SCHEDULE_ORGANIZER_WORK (
+    RESOURCE_ID
+);
+
+----------------------------------
+-- Schedule Organizer Send Work --
+----------------------------------
+
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "SCHEDULE_ACTION" integer not null,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer,
+    "ATTENDEE" nvarchar2(255),
+    "ITIP_MSG" nclob,
+    "NO_REFRESH" integer
+);
+
+create index SCHEDULE_ORGANIZER_SE_9ec9f827 on SCHEDULE_ORGANIZER_SEND_WORK (
+    HOME_RESOURCE_ID
+);
+create index SCHEDULE_ORGANIZER_SE_699fefc4 on SCHEDULE_ORGANIZER_SEND_WORK (
+    RESOURCE_ID
+);
+
+-------------------------
+-- Schedule Reply Work --
+-------------------------
+
+drop table SCHEDULE_REPLY_WORK;
+
+create table SCHEDULE_REPLY_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "CHANGED_RIDS" nclob
+);
+
+create index SCHEDULE_REPLY_WORK_H_745af8cf on SCHEDULE_REPLY_WORK (
+    HOME_RESOURCE_ID
+);
+create index SCHEDULE_REPLY_WORK_R_11bd3fbb on SCHEDULE_REPLY_WORK (
+    RESOURCE_ID
+);
+
+--------------------------------
+-- Schedule Reply Cancel Work --
+--------------------------------
+
+drop table SCHEDULE_REPLY_CANCEL_WORK;
+
+create table SCHEDULE_REPLY_CANCEL_WORK (
+    "WORK_ID" integer primary key references SCHEDULE_WORK on delete cascade,
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "ICALENDAR_TEXT" nclob
+);
+
+create index SCHEDULE_REPLY_CANCEL_dab513ef on SCHEDULE_REPLY_CANCEL_WORK (
+    HOME_RESOURCE_ID
+);
+
 -- update the version
 update CALENDARSERVER set VALUE = '44' where NAME = 'VERSION';

Copied: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_44_to_45.sql (from rev 13731, CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_44_to_45.sql)
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_44_to_45.sql	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_44_to_45.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,32 @@
+----
+-- Copyright (c) 2012-2014 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 44 to 45 --
+---------------------------------------------------
+
+-------------------
+-- Data Versions --
+-------------------
+
+alter table CALENDAR_OBJECT
+  add ("DATAVERSION" integer default 0 not null);
+
+alter table ADDRESSBOOK_OBJECT
+  add ("DATAVERSION" integer default 0 not null);
+
+-- update the version
+update CALENDARSERVER set VALUE = '45' where NAME = 'VERSION';

Copied: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_45_to_46.sql (from rev 13684, CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_43_to_44.sql)
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_45_to_46.sql	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_45_to_46.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,50 @@
+----
+-- Copyright (c) 2012-2014 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 45 to 46 --
+---------------------------------------------------
+
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('group', 5);
+
+create table GROUP_SHAREE_RECONCILE_WORK (
+  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer not null references JOB,
+  CALENDAR_ID                   integer	not null references CALENDAR on delete cascade,
+  GROUP_ID                      integer not null references GROUPS on delete cascade
+);
+
+create index GROUP_SHAREE_RECONCILE_WORK_JOB_ID on GROUP_SHAREE_RECONCILE_WORK(
+	JOB_ID
+);
+
+
+create table GROUP_SHAREE (
+  GROUP_ID                      integer not null references GROUPS on delete cascade,
+  CALENDAR_HOME_ID 				integer not null references CALENDAR_HOME on delete cascade,
+  CALENDAR_ID      				integer not null references CALENDAR on delete cascade,
+  GROUP_BIND_MODE               integer not null, -- enum CALENDAR_BIND_MODE
+  MEMBERSHIP_HASH               varchar(255) not null,
+  
+  primary key (GROUP_ID, CALENDAR_HOME_ID, CALENDAR_ID) -- implicit index
+);
+
+create index GROUP_SHAREE_CALENDAR_ID on GROUP_SHAREE(
+	CALENDAR_ID
+);
+
+-- update the version
+update CALENDARSERVER set VALUE = '44' where NAME = 'VERSION';

Deleted: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -1,48 +0,0 @@
-----
--- Copyright (c) 2012-2014 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 43 to 44 --
----------------------------------------------------
-
-insert into CALENDAR_BIND_MODE values (5, 'group');
-
-create table GROUP_SHAREE_RECONCILE_WORK (
-  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  JOB_ID                        integer not null references JOB,
-  CALENDAR_ID                   integer	not null references CALENDAR on delete cascade,
-  GROUP_ID                      integer not null references GROUPS on delete cascade
-);
-
-create index GROUP_SHAREE_RECONCILE_WORK_JOB_ID on
-  GROUP_SHAREE_RECONCILE_WORK(JOB_ID);
-
-
-create table GROUP_SHAREE (
-  GROUP_ID                      integer not null references GROUPS on delete cascade,
-  CALENDAR_HOME_ID 				integer not null references CALENDAR_HOME on delete cascade,
-  CALENDAR_ID      				integer not null references CALENDAR on delete cascade,
-  GROUP_BIND_MODE               integer not null, -- enum CALENDAR_BIND_MODE
-  MEMBERSHIP_HASH               varchar(255) not null,
-  
-  primary key (GROUP_ID, CALENDAR_HOME_ID, CALENDAR_ID) -- implicit index
-);
-
-create index GROUP_SHAREE_CALENDAR_ID on
-  GROUP_SHAREE(CALENDAR_ID);
-
--- update the version
-update CALENDARSERVER set VALUE = '44' where NAME = 'VERSION';

Copied: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql (from rev 13731, CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql)
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,157 @@
+----
+-- Copyright (c) 2012-2014 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 43 to 44 --
+---------------------------------------------------
+
+-----------------
+-- Job Changes --
+-----------------
+
+drop function next_job();
+
+-- The scheduling work schema has changed a lot - to avoid a complex migration process this
+-- script just drops all the existing tables and adds back the new set
+
+-------------------
+-- Schedule Work --
+-------------------
+
+create table SCHEDULE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ICALENDAR_UID                 varchar(255) not null,
+  WORK_TYPE                     varchar(255) not null
+);
+
+create index SCHEDULE_WORK_JOB_ID on
+  SCHEDULE_WORK(JOB_ID);
+create index SCHEDULE_WORK_ICALENDAR_UID on
+  SCHEDULE_WORK(ICALENDAR_UID);
+
+---------------------------
+-- Schedule Refresh Work --
+---------------------------
+
+drop table SCHEDULE_REFRESH_WORK;
+
+create table SCHEDULE_REFRESH_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE_COUNT                integer
+);
+
+create index SCHEDULE_REFRESH_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REFRESH_WORK_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(RESOURCE_ID);
+
+------------------------------
+-- Schedule Auto Reply Work --
+------------------------------
+
+drop table SCHEDULE_AUTO_REPLY_WORK;
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  PARTSTAT                      varchar(255) not null
+);
+
+create index SCHEDULE_AUTO_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_AUTO_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(RESOURCE_ID);
+
+-----------------------------
+-- Schedule Organizer Work --
+-----------------------------
+
+drop table SCHEDULE_ORGANIZER_WORK;
+
+create table SCHEDULE_ORGANIZER_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDR_OBJECT
+  ICALENDAR_TEXT_OLD            text,
+  ICALENDAR_TEXT_NEW            text,
+  ATTENDEE_COUNT                integer,
+  SMART_MERGE                   boolean
+);
+
+create index SCHEDULE_ORGANIZER_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(RESOURCE_ID);
+
+----------------------------------
+-- Schedule Organizer Send Work --
+----------------------------------
+
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ATTENDEE                      varchar(255) not null,
+  ITIP_MSG                      text,
+  NO_REFRESH                    boolean
+);
+
+create index SCHEDULE_ORGANIZER_SEND_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_SEND_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(RESOURCE_ID);
+
+-------------------------
+-- Schedule Reply Work --
+-------------------------
+
+drop table SCHEDULE_REPLY_WORK;
+
+create table SCHEDULE_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  CHANGED_RIDS                  text
+);
+
+create index SCHEDULE_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(RESOURCE_ID);
+
+--------------------------------
+-- Schedule Reply Cancel Work --
+--------------------------------
+
+drop table SCHEDULE_REPLY_CANCEL_WORK;
+
+create table SCHEDULE_REPLY_CANCEL_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index SCHEDULE_REPLY_CANCEL_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REPLY_CANCEL_WORK(HOME_RESOURCE_ID);
+
+-- update the version
+update CALENDARSERVER set VALUE = '44' where NAME = 'VERSION';

Copied: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_44_to_45.sql (from rev 13731, CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_44_to_45.sql)
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_44_to_45.sql	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_44_to_45.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,32 @@
+----
+-- Copyright (c) 2012-2014 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 44 to 45 --
+---------------------------------------------------
+
+-------------------
+-- Data Versions --
+-------------------
+
+alter table CALENDAR_OBJECT
+  add column DATAVERSION integer default 0 not null;
+
+alter table ADDRESSBOOK_OBJECT
+  add column DATAVERSION integer default 0 not null;
+
+-- update the version
+update CALENDARSERVER set VALUE = '45' where NAME = 'VERSION';

Copied: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_45_to_46.sql (from rev 13684, CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_43_to_44.sql)
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_45_to_46.sql	                        (rev 0)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_45_to_46.sql	2014-07-08 07:05:29 UTC (rev 13732)
@@ -0,0 +1,48 @@
+----
+-- Copyright (c) 2012-2014 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 45 to 46 --
+---------------------------------------------------
+
+insert into CALENDAR_BIND_MODE values (5, 'group');
+
+create table GROUP_SHAREE_RECONCILE_WORK (
+  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  JOB_ID                        integer not null references JOB,
+  CALENDAR_ID                   integer	not null references CALENDAR on delete cascade,
+  GROUP_ID                      integer not null references GROUPS on delete cascade
+);
+
+create index GROUP_SHAREE_RECONCILE_WORK_JOB_ID on
+  GROUP_SHAREE_RECONCILE_WORK(JOB_ID);
+
+
+create table GROUP_SHAREE (
+  GROUP_ID                      integer not null references GROUPS on delete cascade,
+  CALENDAR_HOME_ID 				integer not null references CALENDAR_HOME on delete cascade,
+  CALENDAR_ID      				integer not null references CALENDAR on delete cascade,
+  GROUP_BIND_MODE               integer not null, -- enum CALENDAR_BIND_MODE
+  MEMBERSHIP_HASH               varchar(255) not null,
+  
+  primary key (GROUP_ID, CALENDAR_HOME_ID, CALENDAR_ID) -- implicit index
+);
+
+create index GROUP_SHAREE_CALENDAR_ID on
+  GROUP_SHAREE(CALENDAR_ID);
+
+-- update the version
+update CALENDARSERVER set VALUE = '46' where NAME = 'VERSION';

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_tables.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_tables.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/common/datastore/sql_tables.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -31,6 +31,7 @@
 from sqlparse import parse
 from re import compile
 import hashlib
+import itertools
 
 def _schemaFiles(version=None):
     """
@@ -70,8 +71,7 @@
     """
 
     if extras.exists():
-        out.write("\n-- Extras\n\n")
-        out.write(extras.getContent())
+        out.write("\n".join(itertools.dropwhile(lambda x: not x.startswith("-- Extra schema to add"), extras.getContent().splitlines())) + "\n")
 
 
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/dps/client.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/dps/client.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/dps/client.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -15,6 +15,7 @@
 ##
 
 import cPickle as pickle
+import time
 import uuid
 
 from twext.python.log import Logger
@@ -25,7 +26,7 @@
 import twext.who.idirectory
 from twext.who.util import ConstantsContainer
 from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.internet.protocol import ClientCreator
 from twisted.protocols import amp
 from twisted.python.constants import Names, NamedConstant
@@ -41,6 +42,7 @@
     VerifyPlaintextPasswordCommand, VerifyHTTPDigestCommand,
     WikiAccessForUIDCommand, ContinuationCommand
 )
+from txdav.who.delegates import RecordType as DelegatesRecordType
 from txdav.who.directory import (
     CalendarDirectoryRecordMixin, CalendarDirectoryServiceMixin
 )
@@ -59,13 +61,9 @@
 
 
 ## MOVE2WHO TODOs:
-## SACLs
 ## LDAP
-## Tests from old twistedcaldav/directory
-## Cmd line tools
 ## Store based directory service (records in the store, i.e.
 ##    locations/resources)
-## Separate store for DPS (augments and delegates separate from calendar data)
 ## Store autoAcceptGroups in the group db?
 
 @implementer(IDirectoryService, IStoreDirectoryService)
@@ -189,6 +187,20 @@
         returnValue(results)
 
 
+    def _logResultTiming(self, command, startTime, results):
+        duration = time.time() - startTime
+        numResults = 0
+        if "fields" in results:
+            numResults = 1
+        if "fieldsList" in results:
+            numResults = len(results["fieldsList"])
+        log.debug(
+            "DPS call {command} duration={duration:.2f}s, results={numResults}",
+            command=command, duration=duration, numResults=numResults
+        )
+
+
+
     @inlineCallbacks
     def _call(self, command, postProcess, **kwds):
         """
@@ -204,9 +216,11 @@
             L{Deferred} which fires with the post-processed results
         @type postProcess: callable
         """
+        startTime = time.time()
         results = yield self._sendCommand(command, **kwds)
         if results.get("continuation", None) is None:
             # We have all the results
+            self._logResultTiming(command, startTime, results)
             returnValue(postProcess(results))
 
         # There are more results to fetch, so loop until the continuation
@@ -225,6 +239,7 @@
         for result in multi:
             results["fieldsList"].extend(result["fieldsList"])
 
+        self._logResultTiming(command, startTime, results)
         returnValue(postProcess(results))
 
 
@@ -367,11 +382,20 @@
 
 
     def members(self):
-        return self.service._call(
-            MembersCommand,
-            self.service._processMultipleRecords,
-            uid=self.uid.encode("utf-8")
-        )
+        if self.recordType in (
+            RecordType.group,
+            DelegatesRecordType.readDelegateGroup,
+            DelegatesRecordType.writeDelegateGroup,
+            DelegatesRecordType.readDelegatorGroup,
+            DelegatesRecordType.writeDelegatorGroup,
+        ):
+            return self.service._call(
+                MembersCommand,
+                self.service._processMultipleRecords,
+                uid=self.uid.encode("utf-8")
+            )
+        else:
+            return succeed([])
 
 
     def groups(self):

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/dps/server.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/dps/server.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/dps/server.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -100,7 +100,7 @@
         @return: an iterable of records, or None if the token does not exist
         """
         if token in self._continuations:
-            timestamp, records = self._continuations[token]
+            _ignore_timestamp, records = self._continuations[token]
             del self._continuations[token]
         else:
             records = None
@@ -119,7 +119,7 @@
         log.debug("Continuation: {c}", c=continuation)
         records = self._retrieveContinuation(continuation)
         response = self._recordsToResponse(records)
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         return response
 
 
@@ -194,7 +194,7 @@
         response = {
             "fields": pickle.dumps(fields),
         }
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -212,7 +212,7 @@
         response = {
             "fields": pickle.dumps(fields),
         }
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -226,7 +226,7 @@
         response = {
             "fields": pickle.dumps(fields),
         }
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -239,7 +239,7 @@
             self._directory.recordType.lookupByName(recordType))
         )
         response = self._recordsToResponse(records)
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -250,7 +250,7 @@
         log.debug("RecordsWithEmailAddress: {e}", e=emailAddress)
         records = (yield self._directory.recordsWithEmailAddress(emailAddress))
         response = self._recordsToResponse(records)
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -265,7 +265,7 @@
             tokens, context=context
         )
         response = self._recordsToResponse(records)
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -306,7 +306,7 @@
             newFields, operand=operand, recordType=recordType
         )
         response = self._recordsToResponse(records)
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -327,7 +327,7 @@
                 records.append(member)
 
         response = self._recordsToResponse(records)
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -357,7 +357,7 @@
         response = {
             "success": success,
         }
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -376,7 +376,7 @@
         for group in (yield record.groups()):
             records.append(group)
         response = self._recordsToResponse(records)
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -392,7 +392,7 @@
         response = {
             "authenticated": authenticated,
         }
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -426,7 +426,7 @@
         response = {
             "authenticated": authenticated,
         }
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 
@@ -444,7 +444,7 @@
         response = {
             "access": access.name.encode("utf-8"),
         }
-        log.debug("Responding with: {response}", response=response)
+        # log.debug("Responding with: {response}", response=response)
         returnValue(response)
 
 

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/augment.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/augment.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/augment.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -320,9 +320,10 @@
         return (baseFields, augmentFields)
 
 
+    @inlineCallbacks
     def removeRecords(self, uids):
-        self._augmentDB.removeAugmentRecords(uids)
-        return self._directory.removeRecords(uids)
+        yield self._augmentDB.removeAugmentRecords(uids)
+        yield self._directory.removeRecords(uids)
 
 
     def _assignToField(self, fields, name, value):

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/delegates.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/delegates.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/delegates.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -236,7 +236,10 @@
     """
     if delegate.recordType == BaseRecordType.group:
         # find the groupID
-        groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = yield txn.groupByUID(
+        (
+            groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
+            _ignore_extant
+        ) = yield txn.groupByUID(
             delegate.uid
         )
         yield txn.addDelegateGroup(delegator.uid, groupID, readWrite)
@@ -260,7 +263,10 @@
     """
     if delegate.recordType == BaseRecordType.group:
         # find the groupID
-        groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = yield txn.groupByUID(
+        (
+            groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
+            _ignore_extant
+        ) = yield txn.groupByUID(
             delegate.uid
         )
         yield txn.removeDelegateGroup(delegator.uid, groupID, readWrite)

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/directory.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/directory.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/directory.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -175,8 +175,6 @@
         else:
             expression = CompoundExpression(outer, Operand.AND)
 
-        results = []
-
         if context is not None:
             recordTypes = self.recordTypesForSearchContext(context)
         else:

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/groups.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/groups.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/groups.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -157,12 +157,14 @@
             if (component.masterComponent() is None or not component.isRecurring()):
 
                 # skip non-recurring old events, no instances
-                if (yield calendarObject.removeOldEventGroupLink(
-                    component,
-                    instances=None,
-                    inserting=False,
-                    txn=self.transaction
-                )):
+                if (
+                    yield calendarObject.removeOldEventGroupLink(
+                        component,
+                        instances=None,
+                        inserting=False,
+                        txn=self.transaction
+                    )
+                ):
                     returnValue(None)
             else:
                 # skip recurring old events
@@ -180,12 +182,14 @@
                     lowerLimit=truncateLowerLimit,
                     ignoreInvalidInstances=True
                 )
-                if (yield calendarObject.removeOldEventGroupLink(
-                    component,
-                    instances=instances,
-                    inserting=False,
-                    txn=self.transaction
-                )):
+                if (
+                    yield calendarObject.removeOldEventGroupLink(
+                        component,
+                        instances=instances,
+                        inserting=False,
+                        txn=self.transaction
+                    )
+                ):
                     returnValue(None)
 
                 # split spanning events and only update present-future split result
@@ -345,11 +349,17 @@
             ) in changed:
                 readDelegateGroupID = writeDelegateGroupID = None
                 if readDelegateUID:
-                    readDelegateGroupID, _ignore_name, _ignore_hash, _ignore_modified = (
+                    (
+                        readDelegateGroupID, _ignore_name, _ignore_hash,
+                        _ignore_modified, _ignore_extant
+                    ) = (
                         yield txn.groupByUID(readDelegateUID)
                     )
                 if writeDelegateUID:
-                    writeDelegateGroupID, _ignore_name, _ignore_hash, _ignore_modified = (
+                    (
+                        writeDelegateGroupID, _ignore_name, _ignore_hash,
+                        _ignore_modified, _ignore_extant
+                    ) = (
                         yield txn.groupByUID(writeDelegateUID)
                     )
                 yield txn.assignExternalDelegates(
@@ -371,90 +381,25 @@
             and updates the GROUP_MEMBERSHIP table
             WorkProposal is returned for tests
         """
-        self.log.debug("Faulting in group: {g}", g=groupUID)
-        record = (yield self.directory.recordWithUID(groupUID))
-        if record is None:
-            # the group has disappeared from the directory
-            self.log.info("Group is missing: {g}", g=groupUID)
+        groupID, membershipChanged = yield txn.refreshGroup(groupUID)
+
+        if membershipChanged:
+            wps = yield self.scheduleGroupAttendeeReconciliations(txn, groupID)
         else:
-            self.log.debug("Got group record: {u}", u=record.uid)
+            wps = ()
 
-        groupID, cachedName, cachedMembershipHash, _ignore_modified = (
-            yield txn.groupByUID(
-                groupUID,
-                create=(record is not None)
-            )
-        )
-        wps = tuple()
-        if groupID:
-            if record is not None:
-                members = yield record.expandedMembers()
-                name = record.fullNames[0]
-            else:
-                members = frozenset()
-                name = cachedName
-
-            membershipHashContent = hashlib.md5()
-            members = list(members)
-            members.sort(key=lambda x: x.uid)
-            for member in members:
-                membershipHashContent.update(str(member.uid))
-            membershipHash = membershipHashContent.hexdigest()
-
-            if cachedMembershipHash != membershipHash:
-                membershipChanged = True
-                self.log.debug(
-                    "Group '{group}' changed", group=name
-                )
-            else:
-                membershipChanged = False
-
-            if membershipChanged or record is not None:
-                # also updates group mod date
-                yield txn.updateGroup(groupUID, name, membershipHash)
-
-            if membershipChanged:
-                newMemberUIDs = set()
-                for member in members:
-                    newMemberUIDs.add(member.uid)
-                yield self.synchronizeMembers(txn, groupID, newMemberUIDs)
-
-                wps = yield self.scheduleGroupAttendeeReconciliations(txn, groupID)
-                wps = wps + (yield self.scheduleGroupShareeReconciliations(txn, groupID))
-
         returnValue(wps)
 
 
-    @inlineCallbacks
     def synchronizeMembers(self, txn, groupID, newMemberUIDs):
-        numRemoved = numAdded = 0
-        cachedMemberUIDs = (yield txn.membersOfGroup(groupID))
+        return txn.synchronizeMembers(groupID, newMemberUIDs)
 
-        for memberUID in cachedMemberUIDs:
-            if memberUID not in newMemberUIDs:
-                numRemoved += 1
-                yield txn.removeMemberFromGroup(memberUID, groupID)
 
-        for memberUID in newMemberUIDs:
-            if memberUID not in cachedMemberUIDs:
-                numAdded += 1
-                yield txn.addMemberToGroup(memberUID, groupID)
-
-        returnValue((numAdded, numRemoved))
-
-
-    @inlineCallbacks
     def cachedMembers(self, txn, groupID):
         """
         The members of the given group as recorded in the db
         """
-        members = set()
-        memberUIDs = (yield txn.membersOfGroup(groupID))
-        for uid in memberUIDs:
-            record = (yield self.directory.recordWithUID(uid))
-            if record is not None:
-                members.add(record)
-        returnValue(members)
+        return txn.groupMembers(groupID)
 
 
     def cachedGroupsFor(self, txn, uid):

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/opendirectory.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/opendirectory.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/opendirectory.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -28,6 +28,7 @@
 
 from twext.who.opendirectory import DirectoryService
 
+DirectoryService    # Something has to use the import
 
 # Hoorj OMG haxx
 from twext.who.opendirectory._constants import ODRecordType as _ODRecordType

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_delegates.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_delegates.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_delegates.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -207,7 +207,10 @@
                 yield self.directory.recordWithShortName(RecordType.user, name)
             )
             newSet.add(record.uid)
-        groupID, name, _ignore_membershipHash, _ignore_modified = (yield txn.groupByUID(group1.uid))
+        (
+            groupID, name, _ignore_membershipHash, _ignore_modified,
+            _ignore_extant
+        ) = (yield txn.groupByUID(group1.uid))
         _ignore_numAdded, _ignore_numRemoved = (
             yield self.groupCacher.synchronizeMembers(txn, groupID, newSet)
         )

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_group_attendees.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_group_attendees.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_group_attendees.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -841,8 +841,7 @@
 
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
         yield self.commit()
-        self.assertEqual(len(wps), 1)
-        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+        self.assertEqual(len(wps), 0)
 
         cobj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
         vcalendar = yield cobj.component()
@@ -868,7 +867,10 @@
         #finally, simulate an event that has become old
         self.patch(CalendarDirectoryRecordMixin, "expandedMembers", unpatchedExpandedMembers)
 
-        groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
+        (
+            groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate,
+            _ignore_extant
+        ) = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
         ga = schema.GROUP_ATTENDEE
         yield Insert({
                 ga.RESOURCE_ID: cobj._resourceID,
@@ -1001,8 +1003,7 @@
 
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
         yield self.commit()
-        self.assertEqual(len(wps), 1)
-        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+        self.assertEqual(len(wps), 0)
 
         cobj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
         vcalendar = yield cobj.component()
@@ -1029,7 +1030,10 @@
         #finally, simulate an event that has become old
         self.patch(CalendarDirectoryRecordMixin, "expandedMembers", unpatchedExpandedMembers)
 
-        groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
+        (
+            groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate,
+            _ignore_extant
+        ) = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
         ga = schema.GROUP_ATTENDEE
         yield Insert({
                 ga.RESOURCE_ID: cobj._resourceID,
@@ -1478,8 +1482,7 @@
 
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000002")
         yield self.commit()
-        self.assertEqual(len(wps), 1)
-        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+        self.assertEqual(len(wps), 0)
 
         cobj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000001")
         vcalendar = yield cobj.component()
@@ -1637,12 +1640,10 @@
 
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000002")
         yield self.commit()
-        self.assertEqual(len(wps), 1)
-        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+        self.assertEqual(len(wps), 0)
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000003")
         yield self.commit()
-        self.assertEqual(len(wps), 1)
-        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+        self.assertEqual(len(wps), 0)
 
         cobj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000001")
         vcalendar = yield cobj.component()

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_groups.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_groups.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/test/test_groups.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -18,11 +18,12 @@
 Group membership caching implementation tests
 """
 
-from txdav.who.groups import GroupCacher, diffAssignments
 from twext.who.idirectory import RecordType
 from twisted.internet.defer import inlineCallbacks
 from twistedcaldav.test.util import StoreTestCase
 from txdav.common.icommondatastore import NotFoundError
+from txdav.who.groups import GroupCacher, diffAssignments
+from txdav.who.test.support import TestRecord, CalendarInMemoryDirectoryService
 
 
 
@@ -44,8 +45,8 @@
         txn = store.newTransaction()
 
         record = yield self.directory.recordWithUID(u"__top_group_1__")
-        _ignore_groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = (yield txn.groupByUID(record.uid))
-        _ignore_groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = (yield txn.groupByUID(record.uid))
+        yield txn.groupByUID(record.uid)
+        yield txn.groupByUID(record.uid)
 
         yield txn.commit()
 
@@ -63,16 +64,21 @@
         record = yield self.directory.recordWithUID(u"__top_group_1__")
         yield self.groupCacher.refreshGroup(txn, record.uid)
 
-        groupID, _ignore_name, membershipHash, _ignore_modified = (yield txn.groupByUID(record.uid))
+        (
+            groupID, _ignore_name, membershipHash, _ignore_modified,
+            extant
+        ) = (yield txn.groupByUID(record.uid))
 
+        self.assertEquals(extant, True)
         self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")
 
-        groupUID, name, membershipHash = (yield txn.groupByID(groupID))
+        groupUID, name, membershipHash, extant = (yield txn.groupByID(groupID))
         self.assertEquals(groupUID, record.uid)
         self.assertEquals(name, u"Top Group 1")
         self.assertEquals(membershipHash, "553eb54e3bbb26582198ee04541dbee4")
+        self.assertEquals(extant, True)
 
-        members = (yield txn.membersOfGroup(groupID))
+        members = (yield txn.groupMemberUIDs(groupID))
         self.assertEquals(
             set([u'__cdaboo1__', u'__glyph1__', u'__sagen1__', u'__wsanchez1__']),
             members
@@ -107,7 +113,10 @@
         # Refresh the group so it's assigned a group_id
         uid = u"__top_group_1__"
         yield self.groupCacher.refreshGroup(txn, uid)
-        groupID, name, _ignore_membershipHash, _ignore_modified = (yield txn.groupByUID(uid))
+        (
+            groupID, name, _ignore_membershipHash, _ignore_modified,
+            _ignore_extant
+        ) = yield txn.groupByUID(uid)
 
         # Remove two members, and add one member
         newSet = set()
@@ -156,9 +165,12 @@
         uid = u"__top_group_1__"
         hash = "553eb54e3bbb26582198ee04541dbee4"
         yield self.groupCacher.refreshGroup(txn, uid)
-        groupID, _ignore_name, _ignore_membershipHash, _ignore_modified = yield txn.groupByUID(uid)
-        results = (yield txn.groupByID(groupID))
-        self.assertEquals((uid, u"Top Group 1", hash), results)
+        (
+            groupID, _ignore_name, _ignore_membershipHash, _ignore_modified,
+            extant
+        ) = yield txn.groupByUID(uid)
+        results = yield txn.groupByID(groupID)
+        self.assertEquals((uid, u"Top Group 1", hash, True), results)
 
         yield txn.commit()
 
@@ -414,3 +426,125 @@
                 {"D": ("7", "8"), "C": ("4", "5"), "A": ("1", "2")},
             )
         )
+
+
+class DynamicGroupTest(StoreTestCase):
+
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(DynamicGroupTest, self).setUp()
+
+        self.directory = CalendarInMemoryDirectoryService(None)
+        self.store.setDirectoryService(self.directory)
+        self.groupCacher = GroupCacher(self.directory)
+
+        self.numUsers = 100
+
+        # Add users
+        records = []
+        fieldName = self.directory.fieldName
+        for i in xrange(self.numUsers):
+            records.append(
+                TestRecord(
+                    self.directory,
+                    {
+                        fieldName.uid: u"foo{ctr:05d}".format(ctr=i),
+                        fieldName.shortNames: (u"foo{ctr:05d}".format(ctr=i),),
+                        fieldName.fullNames: (u"foo{ctr:05d}".format(ctr=i),),
+                        fieldName.recordType: RecordType.user,
+                    }
+                )
+            )
+
+        # Add a group
+        records.append(
+            TestRecord(
+                self.directory,
+                {
+                    fieldName.uid: u"testgroup",
+                    fieldName.recordType: RecordType.group,
+                }
+            )
+        )
+
+        yield self.directory.updateRecords(records, create=True)
+
+        group = yield self.directory.recordWithUID(u"testgroup")
+        members = yield self.directory.recordsWithRecordType(RecordType.user)
+        yield group.setMembers(members)
+
+
+    @inlineCallbacks
+    def test_extant(self):
+        """
+        Verify that once a group is removed from the directory, the next call
+        to refreshGroup() will set the "extent" to False.  Add the group back
+        to the directory and "extent" becomes True.
+        """
+        store = self.storeUnderTest()
+
+        txn = store.newTransaction()
+        yield self.groupCacher.refreshGroup(txn, u"testgroup")
+        (
+            groupID, _ignore_name, membershipHash, _ignore_modified,
+            extant
+        ) = (yield txn.groupByUID(u"testgroup"))
+        yield txn.commit()
+
+        self.assertTrue(extant)
+
+        # Remove the group
+        yield self.directory.removeRecords([u"testgroup"])
+
+        txn = store.newTransaction()
+        yield self.groupCacher.refreshGroup(txn, u"testgroup")
+        (
+            groupID, _ignore_name, membershipHash, _ignore_modified,
+            extant
+        ) = (yield txn.groupByUID(u"testgroup"))
+        yield txn.commit()
+
+        # Extant = False
+        self.assertFalse(extant)
+
+        # The list of members stored in the DB for this group is now empty
+        txn = store.newTransaction()
+        members = yield txn.groupMemberUIDs(groupID)
+        yield txn.commit()
+        self.assertEquals(members, set())
+
+        # Add the group back into the directory
+        fieldName = self.directory.fieldName
+        yield self.directory.updateRecords(
+            (
+                TestRecord(
+                    self.directory,
+                    {
+                        fieldName.uid: u"testgroup",
+                        fieldName.recordType: RecordType.group,
+                    }
+                ),
+            ),
+            create=True
+        )
+        group = yield self.directory.recordWithUID(u"testgroup")
+        members = yield self.directory.recordsWithRecordType(RecordType.user)
+        yield group.setMembers(members)
+
+        txn = store.newTransaction()
+        yield self.groupCacher.refreshGroup(txn, u"testgroup")
+        (
+            groupID, _ignore_name, membershipHash, _ignore_modified,
+            extant
+        ) = (yield txn.groupByUID(u"testgroup"))
+        yield txn.commit()
+
+        # Extant = True
+        self.assertTrue(extant)
+
+        # The list of members stored in the DB for this group has 100 users
+        txn = store.newTransaction()
+        members = yield txn.groupMemberUIDs(groupID)
+        yield txn.commit()
+        self.assertEquals(len(members), 100)

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/util.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/util.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/util.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -168,12 +168,30 @@
                         ),
                     ),
 
+                    CalRecordType.location: RecordTypeSchema(
+                        relativeDN=u"ou=places",
+
+                        attributes=(),
+                    ),
+
+                    CalRecordType.resource: RecordTypeSchema(
+                        relativeDN=u"ou=resources",
+
+                        attributes=(),
+                    ),
+
+                    CalRecordType.address: RecordTypeSchema(
+                        relativeDN=u"ou=buildings",
+
+                        attributes=(),
+                    ),
+
                 })
             )
 
         elif "inmemory" in directoryType:
-            from txdav.who.test.support import InMemoryDirectoryService
-            directory = InMemoryDirectoryService()
+            from txdav.who.test.support import CalendarInMemoryDirectoryService
+            directory = CalendarInMemoryDirectoryService()
 
         else:
             log.error("Invalid DirectoryType: {dt}", dt=directoryType)

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/who/wiki.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/who/wiki.py	2014-07-07 23:41:26 UTC (rev 13731)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/who/wiki.py	2014-07-08 07:05:29 UTC (rev 13732)
@@ -194,6 +194,7 @@
                 "in wiki {log_source}: {error}",
                 record=record, error=e
             )
+            returnValue(WikiAccessLevel.none)
 
         except WebError as e:
             status = int(e.status)
@@ -216,6 +217,7 @@
                 "Unable to look up wiki access: {error}",
                 record=record, error=e
             )
+            returnValue(WikiAccessLevel.none)
 
         try:
             returnValue({
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140708/9fd36d88/attachment-0001.html>


More information about the calendarserver-changes mailing list