[CalendarServer-changes] [12565] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:20:09 PDT 2014


Revision: 12565
          http://trac.calendarserver.org//changeset/12565
Author:   cdaboo at apple.com
Date:     2014-02-04 15:58:26 -0800 (Tue, 04 Feb 2014)
Log Message:
-----------
Merge schedule-q to trunk.

Modified Paths:
--------------
    CalendarServer/trunk/conf/caldavd-test.plist
    CalendarServer/trunk/contrib/performance/loadtest/ampsim.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/twistedcaldav/test/util.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_implicit.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_pocessing.py
    CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
    CalendarServer/trunk/txdav/common/datastore/sql_tables.py
    CalendarServer/trunk/txdav/common/datastore/test/util.py
    CalendarServer/trunk/txdav/xml/base.py
    CalendarServer/trunk/txdav/xml/element.py
    CalendarServer/trunk/txdav/xml/parser.py
    CalendarServer/trunk/txdav/xml/rfc2518.py
    CalendarServer/trunk/txdav/xml/rfc3744.py
    CalendarServer/trunk/txdav/xml/xmlext.py

Added Paths:
-----------
    CalendarServer/trunk/calendarserver/tools/schedule_workitems.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_queue_scheduling.py
    CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v25.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v33.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v25.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v33.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_25_to_26.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_33_to_34.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_25_to_26.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_33_to_34.sql

Removed Paths:
-------------
    CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v25.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v25.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_25_to_26.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_25_to_26.sql

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


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

Copied: CalendarServer/trunk/calendarserver/tools/schedule_workitems.py (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/calendarserver/tools/schedule_workitems.py)
===================================================================
--- CalendarServer/trunk/calendarserver/tools/schedule_workitems.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/tools/schedule_workitems.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+
+##
+# 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.
+##
+from __future__ import print_function
+
+from getopt import getopt, GetoptError
+import os
+import sys
+import curses
+import datetime
+
+from twisted.internet.defer import inlineCallbacks, succeed
+from calendarserver.tools.cmdline import utilityMain, WorkerService
+from calendarserver.push.notifier import PushNotificationWork
+from txdav.caldav.datastore.scheduling.work import ScheduleOrganizerWork, \
+    ScheduleReplyWork, ScheduleRefreshWork
+
+useCurses = True
+
+def usage(e=None):
+
+    name = os.path.basename(sys.argv[0])
+    print("usage: %s [options]" % (name,))
+    print("")
+    print("  TODO: describe usage")
+    print("")
+    print("options:")
+    print("  -h --help: print this help and exit")
+    print("  -e --error: send stderr to stdout")
+    print("  -f --config <path>: Specify caldavd.plist configuration path")
+    print("")
+
+    if e:
+        sys.exit(64)
+    else:
+        sys.exit(0)
+
+
+
+def main():
+
+    try:
+        (optargs, _ignore_args) = getopt(
+            sys.argv[1:], "hef:", [
+                "help",
+                "error",
+                "config=",
+            ],
+        )
+    except GetoptError, e:
+        usage(e)
+
+    #
+    # Get configuration
+    #
+    configFileName = None
+    debug = False
+
+    for opt, arg in optargs:
+        if opt in ("-h", "--help"):
+            usage()
+
+        if opt in ("-e", "--error"):
+            debug = True
+
+        elif opt in ("-f", "--config"):
+            configFileName = arg
+
+        else:
+            raise NotImplementedError(opt)
+
+    utilityMain(configFileName, WorkItemMonitorService, verbose=debug)
+
+
+
+class WorkItemMonitorService(WorkerService, object):
+
+    def __init__(self, store):
+        super(WorkItemMonitorService, self).__init__(store)
+        from twisted.internet import reactor
+        self.reactor = reactor
+
+
+    def doWork(self):
+        self.screen = curses.initscr() if useCurses else None
+        self.windows = []
+        self.updateScreenGeometry()
+        self.reactor.callLater(0, self.updateDisplay)
+        return succeed(None)
+
+
+    def postStartService(self):
+        """
+        Don't quit right away
+        """
+        pass
+
+
+    def updateScreenGeometry(self):
+        for win in self.windows:
+            del win
+        winY, winX = self.screen.getmaxyx() if useCurses else (100, 100)
+        seencolumns = [1]
+        seenrows = [1]
+        heightSoFar = 0
+        begin_x = 0
+        begin_y = 0
+        # Specify height and width of each window as one of:
+        #    absolute value (int), e.g.: 42
+        #    percentage of window height / width (string), e.g.: "42%"
+        # Specify row and column for each window as though it is a cell in an invisible html table
+        # Itemize windows in ascending order by row, col
+        for title, height, width, row, col, workItemClass, fmt, attrs in (
+            ("Organizer Requests", "100%", "25%", 1, 1, ScheduleOrganizerWork, "%s: %d", ("icalendarUid", "attendeeCount")),
+            ("Attendee Replies", "100%", "25%", 1, 2, ScheduleReplyWork, "%s", ("icalendarUid",)),
+            ("Attendee Refresh", "100%", "25%", 1, 3, ScheduleRefreshWork, "%s: %d", ("icalendarUid", "attendeeCount")),
+#            ("Auto Reply", "100%", "25%", 1, 4, ScheduleAutoReplyWork, "%s", ("icalendarUid")),
+            ("Push Notifications", "100%", "25%", 1, 4, PushNotificationWork, "%s: %d", ("pushID", "priority")),
+        ):
+            if (isinstance(height, basestring)):
+                height = max(int(winY * (float(height.strip("%")) / 100.0)), 3)
+            if (isinstance(width, basestring)):
+                width = max(int(winX * (float(width.strip("%")) / 100.0)), 10)
+            if col not in seencolumns:
+                heightSoFar = max(height, heightSoFar)
+                seencolumns.append(col)
+            if row not in seenrows:
+                begin_y = heightSoFar
+                heightSoFar += height
+                begin_x = 0
+                seenrows.append(row)
+                seencolumns = [col]
+            window = WorkWindow(height, width, begin_y, begin_x,
+                self.store, title, workItemClass, fmt, attrs)
+            self.windows.append(window)
+            begin_x += width
+
+
+    @inlineCallbacks
+    def updateDisplay(self):
+        for window in self.windows:
+            try:
+                yield window.update()
+            except Exception as e:
+                print(str(e))
+        if not useCurses:
+            print("-------------")
+
+        self.reactor.callLater(0.1, self.updateDisplay)
+
+
+
+class WorkWindow(object):
+    def __init__(self, nlines, ncols, begin_y, begin_x,
+        store, title, workItemClass, fmt, attrs):
+        self.window = curses.newwin(nlines, ncols, begin_y, begin_x) if useCurses else None
+        self.ncols = ncols
+        self.store = store
+        self.title = title
+        self.workItemClass = workItemClass
+        self.fmt = fmt
+        self.attrs = attrs
+        self.iter = 0
+
+
+    @inlineCallbacks
+    def update(self):
+        txn = self.store.newTransaction()
+        records = (yield self.workItemClass.all(txn))
+        self.iter += 1
+
+        if useCurses:
+            self.window.erase()
+            self.window.border()
+            self.window.addstr(0, 2, self.title + " %d (%d)" % (len(records), self.iter,))
+
+        x = 1
+        y = 1
+        for record in records:
+            txt = ""
+            seconds = record.notBefore - datetime.datetime.utcnow()
+            try:
+                if useCurses:
+                    self.window.addstr(y, x, "%d seconds" % int(seconds.total_seconds()))
+                else:
+                    txt = "%s:" % (self.title,)
+            except curses.error:
+                continue
+            y += 1
+            if self.attrs:
+                try:
+                    s = self.fmt % tuple([getattr(record, str(a)) for a in self.attrs])
+                except Exception, e:
+                    s = "Error: %s" % (str(e),)
+                try:
+                    if useCurses:
+                        self.window.addnstr(y, x, s, self.ncols - 2)
+                    else:
+                        txt += " " + s
+                except curses.error:
+                    pass
+                y += 1
+
+            if not useCurses:
+                print(txt)
+
+        if useCurses:
+            self.window.refresh()
+
+        yield txn.commit()
+
+
+if __name__ == "__main__":
+    main()

Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/conf/caldavd-test.plist	2014-02-04 23:58:26 UTC (rev 12565)
@@ -792,6 +792,22 @@
 	        <key>DefaultMode</key>
 	        <string>automatic</string>
 		</dict>
+		
+		<key>WorkQueues</key>
+		<dict>
+			<key>Enabled</key>
+			<true/>
+            <key>RequestDelaySeconds</key>
+            <integer>1</integer>
+            <key>ReplyDelaySeconds</key>
+            <integer>2</integer>
+            <key>AutoReplyDelaySeconds</key>
+            <integer>1</integer>
+            <key>AttendeeRefreshBatchDelaySeconds</key>
+            <integer>2</integer>
+            <key>AttendeeRefreshBatchIntervalSeconds</key>
+            <integer>2</integer>
+		</dict>
       </dict>
     </dict>
 

Modified: CalendarServer/trunk/contrib/performance/loadtest/ampsim.py
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/ampsim.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/contrib/performance/loadtest/ampsim.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -34,7 +34,7 @@
             from twisted.internet import reactor
             from twisted.internet.stdio import StandardIO
 
-            from contrib.performance.loadtest.ampsim import Worker
+            #from contrib.performance.loadtest.ampsim import Worker
             from contrib.performance.loadtest.sim import LagTrackingReactor
 
             StandardIO(Worker(LagTrackingReactor(reactor)))

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -3652,7 +3652,12 @@
 
     icalstr = str(icalstr).replace("\r\n ", "")
     icalstr = icalstr.replace("\n ", "")
-    icalstr = "\r\n".join([line for line in icalstr.splitlines() if not line.startswith("DTSTAMP")])
+    lines = [line for line in icalstr.splitlines() if not line.startswith("DTSTAMP")]
+    for ctr, line in enumerate(lines[:]):
+        pos = line.find(";X-CALENDARSERVER-DTSTAMP=")
+        if pos != -1:
+            lines[ctr] = line[:pos] + line[pos + len(";X-CALENDARSERVER-DTSTAMP=") + 16:]
+    icalstr = "\r\n".join(lines)
     return icalstr + "\r\n"
 
 

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -739,8 +739,6 @@
             "TrackUnscheduledResourceData"        : True, # Track who the last modifier of an unscheduled resource event is
             "LimitFreeBusyAttendees"              : 30, # Maximum number of attendees to request freebusy for
             "AttendeeRefreshBatch"                : 5, # Number of attendees to do batched refreshes: 0 - no batching
-            "AttendeeRefreshBatchDelaySeconds"    : 5, # Time after an iTIP REPLY for first batched attendee refresh
-            "AttendeeRefreshBatchIntervalSeconds" : 5, # Time between attendee batch refreshes
             "AttendeeRefreshCountLimit"           : 50, # Number of attendees above which attendee refreshes are suppressed: 0 - no limit
             "UIDLockTimeoutSeconds"               : 60, # Time for implicit UID lock timeout
             "UIDLockExpirySeconds"                : 300, # Expiration time for UID lock,
@@ -764,6 +762,15 @@
                 "FutureFreeBusyDays"              : 3 * 365,       # How far into the future to check for booking conflicts
             },
 
+            "WorkQueues" : {
+                "Enabled"                             : True,       # Work queues for scheduling enabled
+                "RequestDelaySeconds"                 : 5,          # Number of seconds delay for a queued scheduling request/cancel
+                "ReplyDelaySeconds"                   : 1,          # Number of seconds delay for a queued scheduling reply
+                "AutoReplyDelaySeconds"               : 5,          # Time delay for sending an auto reply iTIP message
+                "AttendeeRefreshBatchDelaySeconds"    : 5,          # Time after an iTIP REPLY for first batched attendee refresh
+                "AttendeeRefreshBatchIntervalSeconds" : 5,          # Time between attendee batch refreshes
+            },
+
             "Splitting": {
                 "Enabled"                         : False,          # False for now whilst we experiment with this
                 "Size"                            : 100 * 1024,     # Consider splitting when greater than 100KB

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -2872,6 +2872,8 @@
                 request.addResponseFilter(_removeEtag, atEnd=True)
 
             # Handle Prefer header
+            if request.headers.getHeader("accept") is None:
+                request.headers.setHeader("accept", dict(((MimeType.fromString(format), 1.0,),)))
             response = yield self._processPrefer(request, response)
 
             returnValue(response)
@@ -3587,6 +3589,8 @@
                 request.addResponseFilter(_removeEtag, atEnd=True)
 
             # Look for Prefer header
+            if request.headers.getHeader("accept") is None:
+                request.headers.setHeader("accept", dict(((MimeType.fromString(format), 1.0,),)))
             response = yield self._processPrefer(request, response)
 
             returnValue(response)

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -231,6 +231,7 @@
         os.mkdir(self.serverRoot)
 
         config.reset()
+        self.configInit()
 
         config.ServerRoot = os.path.abspath(self.serverRoot)
         config.ConfigRoot = "config"

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -774,6 +774,75 @@
         return rids
 
 
+    def attendeeNeedsAction(self, diffs):
+        """
+        Given a set of results from L{whatIsDifferent}, determine which recurrence-id's
+        have changed in such a way that we need to force the attendee to NEEDS-ACTION
+        state. We need this because with async processing of iTIP messages, it is possible
+        that the incoming iTIP message does not gave the most recent partstat change an
+        attendee made, but we need to preserve the attendee partstat whenever possible.
+
+        @param diffs: mapping of rid to property changes
+        @type diffs: C{dict}
+        """
+
+        date_changed_rids = set()
+        recurrence_reschedule = False
+
+        for rid, props in diffs.iteritems():
+            if any([testprop in props for testprop in (
+                "DTSTART",
+                "DTEND",
+                "DURATION",
+                "DUE",
+                "RECURRENCE-ID",
+            )]):
+                date_changed_rids.add(rid)
+
+            # Check to see whether a change to R-ID's happened
+            if rid is None:
+
+                if "DTSTART" in props and self.newcalendar.masterComponent().hasProperty("RRULE"):
+                    # DTSTART change with RRULE present is always a reschedule
+                    recurrence_reschedule = True
+
+                elif "RRULE" in props:
+
+                    # Need to see if the RRULE change is a simple truncation or expansion - i.e. a change to
+                    # COUNT or UNTIL only. If so we don't need to treat this as a complete re-schedule.
+
+                    # Start off assuming they are different
+                    recurrence_reschedule = True
+
+                    # Get each RRULE (can be only one in the master)
+                    oldrrule = tuple(self.oldcalendar.masterComponent().properties("RRULE"))
+                    oldrrule = oldrrule[0].value() if len(oldrrule) else None
+                    newrrule = tuple(self.newcalendar.masterComponent().properties("RRULE"))
+                    newrrule = newrrule[0].value() if len(newrrule) else None
+
+                    if newrrule is not None and oldrrule is not None:
+
+                        # Normalize the rrules by removing COUNT/UNTIL and then compare
+                        oldrrule = oldrrule.duplicate()
+                        newrrule = newrrule.duplicate()
+
+                        oldrrule.setUseUntil(False)
+                        oldrrule.setUntil(None)
+                        oldrrule.setUseCount(False)
+                        oldrrule.setCount(0)
+
+                        newrrule.setUseUntil(False)
+                        newrrule.setUntil(None)
+                        newrrule.setUseCount(False)
+                        newrrule.setCount(0)
+
+                        # If they are equal we have a simple change - no overall reschedule
+                        if newrrule == oldrrule:
+                            recurrence_reschedule = False
+
+        return (date_changed_rids, recurrence_reschedule,)
+
+
     def _componentDuplicateAndNormalize(self, comp):
         comp = comp.duplicate()
         comp.normalizePropertyValueLists("EXDATE")

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -33,6 +33,8 @@
 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
 
 import collections
 
@@ -42,6 +44,12 @@
 
 log = Logger()
 
+
+class ImplicitSchedulingWorkError(Exception):
+    pass
+
+
+
 # TODO:
 #
 # Handle the case where a PUT removes the ORGANIZER property. That should be equivalent to cancelling the entire meeting.
@@ -387,14 +395,81 @@
 
 
     @inlineCallbacks
-    def sendAttendeeReply(self, txn, resource, calendar, attendee):
+    def queuedOrganizerProcessing(self, txn, action, home, resource, uid, calendar_old, calendar_new, smart_merge):
+        """
+        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.do_smart_merge = smart_merge
+        self.queuedResponses = []
 
+        cal_uid = calendar_old.resourceUID() if calendar_old is not None else (calendar_new.resourceUID() if calendar_new is not None else "unknown")
+
+        # Handle different action scenarios
+        if action == "create":
+            # resource is None, calendar_old is None
+            # Find the newly created resource
+            resources = (yield self.calendar_home.objectResourcesWithUID(uid, ignore_children=["inbox"], allowShared=False))
+            if len(resources) != 1:
+                # Ughh - what has happened? It is possible the resource was created then deleted before we could start work processing,
+                # so simply ignore this
+                log.debug("ImplicitScheduler - queuedOrganizerProcessing 'create' cannot find organizer resource for UID: {uid}", uid=cal_uid)
+                returnValue(None)
+            self.resource = resources[0]
+            self.calendar = calendar_new
+
+        elif action in ("modify", "modify-cancelled"):
+            # Check that the resource still exists - it may have been deleted after this work item was queued, in which
+            # case we have to ignore this (on the assumption that the "remove" action will have queued some work that will
+            # execute soon).
+            if self.resource is None:
+                log.debug("ImplicitScheduler - queuedOrganizerProcessing 'modify' cannot find organizer resource for UID: {uid}", uid=cal_uid)
+                returnValue(None)
+
+            # The new calendar_old data is what is currently stored - other modifications may have causes coalescing.
+            # Old calendar_old data is what was stored int he work item
+            self.calendar = calendar_new
+            self.oldcalendar = calendar_old
+
+        elif action == "remove":
+            # A remove can happen when the underlying resource is deleted, or when all scheduling properties
+            # (organizer and attendees) are removed from its content. So sometimes the resource will not exist, other
+            # times it might. Thus we cannot make any assumptions about resource existence.
+
+            # The "new" calendar_old data is in fact the calendar_old data at the time of the remove - which is the data stored
+            # in the work item.
+            self.calendar = calendar_old
+
+        yield self.extractCalendarData()
+        self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
+        self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
+
+        # Originator is the organizer in this case
+        self.originatorPrincipal = self.organizerPrincipal
+        self.originator = self.organizer
+
+        self.except_attendees = ()
+        self.only_refresh_attendees = None
+        self.split_details = None
+
+        yield self.doImplicitOrganizer(queued=True)
+
+
+    @inlineCallbacks
+    def sendAttendeeReply(self, txn, resource):
+
+        self.txn = txn
+        self.resource = resource
+
         self.calendar_home = self.resource.parentCollection().ownerHome()
 
-        self.calendar = calendar
+        self.calendar = (yield self.resource.componentForUser())
         self.action = "modify"
         self.state = "attendee"
 
@@ -404,8 +479,8 @@
         # Get some useful information from the calendar
         yield self.extractCalendarData()
 
-        self.originator = self.attendee = attendee.principal.canonicalCalendarUserAddress()
-        self.attendeePrincipal = attendee.principal
+        self.attendeePrincipal = self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
+        self.originator = self.attendee = self.attendeePrincipal.canonicalCalendarUserAddress()
 
         result = (yield self.scheduleWithOrganizer())
 
@@ -517,9 +592,10 @@
 
 
     @inlineCallbacks
-    def doImplicitOrganizer(self):
+    def doImplicitOrganizer(self, queued=False):
 
-        self.oldcalendar = None
+        if not queued or not config.Scheduling.Options.WorkQueues.Enabled:
+            self.oldcalendar = None
         self.changed_rids = None
         self.cancelledAttendees = ()
         self.reinvites = None
@@ -540,16 +616,17 @@
             self.needs_sequence_change = True
 
         # Check for a new resource or an update
-        elif self.action == "modify":
+        elif self.action in ("modify", "modify-cancelled"):
 
             # Read in existing data
-            self.oldcalendar = (yield self.resource.componentForUser())
+            if not queued or not config.Scheduling.Options.WorkQueues.Enabled:
+                self.oldcalendar = (yield self.resource.componentForUser())
             self.oldAttendeesByInstance = self.oldcalendar.getAttendeesByInstance(True, onlyScheduleAgentServer=True)
             self.oldInstances = set(self.oldcalendar.getComponentInstances())
             self.coerceAttendeesPartstatOnModify()
 
             # Don't allow any SEQUENCE to decrease
-            if self.oldcalendar:
+            if self.oldcalendar and (not queued or not config.Scheduling.Options.WorkQueues.Enabled):
                 self.calendar.sequenceInSync(self.oldcalendar)
 
             # Significant change
@@ -613,10 +690,15 @@
             if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION").upper() == "NEEDS-ACTION":
                 attendee.setParameter("RSVP", "TRUE")
 
-        if self.needs_sequence_change:
-            self.calendar.bumpiTIPInfo(oldcalendar=self.oldcalendar, doSequence=True)
+        # If processing a queue item, actually execute the scheduling operations, else queue it.
+        # Note a split is always queued, so we do not need to re-queue
+        if queued or not config.Scheduling.Options.WorkQueues.Enabled or self.split_details is not None:
+            if self.needs_sequence_change:
+                self.calendar.bumpiTIPInfo(oldcalendar=self.oldcalendar, doSequence=True)
 
-        yield self.scheduleWithAttendees()
+            yield self.scheduleWithAttendees()
+        else:
+            yield self.queuedScheduleWithAttendees()
 
         # Always clear SCHEDULE-FORCE-SEND from all attendees after scheduling
         for attendee in self.calendar.getAllAttendeeProperties():
@@ -942,6 +1024,141 @@
 
 
     @inlineCallbacks
+    def queuedScheduleWithAttendees(self):
+
+        # First make sure we are allowed to schedule
+        self.testSchedulingAllowed()
+
+        yield ScheduleOrganizerWork.schedule(
+            self.txn,
+            self.oldcalendar.resourceUID() if self.oldcalendar else self.calendar.resourceUID(),
+            self.action,
+            self.calendar_home,
+            self.resource,
+            self.oldcalendar,
+            self.calendar,
+            self.organizerPrincipal.canonicalCalendarUserAddress(),
+            len(self.calendar.getAllUniqueAttendees()) - 1,
+            self.do_smart_merge,
+        )
+
+        # We bump the sequence AFTER storing the work item data to make sure that the sequence
+        # change does not cause unchanged components to be treated as changed when the work
+        # item executes.
+        if self.needs_sequence_change:
+            self.calendar.bumpiTIPInfo(oldcalendar=self.oldcalendar, doSequence=True)
+
+        # First process cancelled attendees
+        total = (yield self.processQueuedCancels())
+
+        # Process regular requests next
+        if self.action in ("create", "modify",):
+            total += (yield self.processQueuedRequests())
+
+        self.logItems["itip.requests"] = total
+
+
+    @inlineCallbacks
+    def processQueuedCancels(self):
+        """
+        Set each ATTENDEE who would be scheduled to status to 1.2.
+        """
+
+        # Do one per attendee
+        aggregated = {}
+        for attendee, rid in self.cancelledAttendees:
+            aggregated.setdefault(attendee, []).append(rid)
+
+        count = 0
+        for attendee, rids in aggregated.iteritems():
+
+            # Don't send message back to the ORGANIZER
+            if attendee in self.organizerPrincipal.calendarUserAddresses:
+                continue
+
+            # Handle split by not scheduling local attendees
+            if self.split_details is not None:
+                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+                attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
+                if type(attendeeAddress) is LocalCalendarUser:
+                    continue
+
+            # Test whether an iTIP CANCEL message for this attendee would be generated
+            if None in rids:
+                # One big CANCEL will do
+                itipmsg = iTipGenerator.generateCancel(self.oldcalendar, (attendee,), None, self.action == "remove", test_only=True)
+            else:
+                # Multiple CANCELs
+                itipmsg = iTipGenerator.generateCancel(self.oldcalendar, (attendee,), rids, test_only=True)
+
+            # Send scheduling message
+            if itipmsg:
+
+                # Always make it look like scheduling succeeded when queuing
+                self.calendar.setParameterToValueForPropertyWithValue(
+                    "SCHEDULE-STATUS",
+                    iTIPRequestStatus.MESSAGE_DELIVERED_CODE,
+                    "ATTENDEE",
+                    attendee,
+                )
+
+                count += 1
+
+        returnValue(count)
+
+
+    @inlineCallbacks
+    def processQueuedRequests(self):
+        """
+        Set each ATTENDEE who would be scheduled to status to 1.2.
+        """
+
+        # Do one per attendee
+        count = 0
+        for attendee in self.attendees:
+
+            # Don't send message back to the ORGANIZER
+            if attendee in self.organizerPrincipal.calendarUserAddresses:
+                continue
+
+            # Don't send message to specified attendees
+            if attendee in self.except_attendees:
+                continue
+
+            # Only send to specified attendees
+            if self.only_refresh_attendees is not None and attendee not in self.only_refresh_attendees:
+                continue
+
+            # If SCHEDULE-FORCE-SEND only change, only send message to those Attendees
+            if self.reinvites and attendee not in self.reinvites:
+                continue
+
+            # Handle split by not scheduling local attendees
+            if self.split_details is not None:
+                attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+                attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
+                if type(attendeeAddress) is LocalCalendarUser:
+                    continue
+
+            itipmsg = iTipGenerator.generateAttendeeRequest(self.calendar, (attendee,), self.changed_rids, test_only=True)
+
+            # Send scheduling message
+            if itipmsg is not None:
+
+                # Always make it look like scheduling succeeded when queuing
+                self.calendar.setParameterToValueForPropertyWithValue(
+                    "SCHEDULE-STATUS",
+                    iTIPRequestStatus.MESSAGE_DELIVERED_CODE,
+                    "ATTENDEE",
+                    attendee,
+                )
+
+                count += 1
+
+        returnValue(count)
+
+
+    @inlineCallbacks
     def scheduleWithAttendees(self):
 
         # First make sure we are allowed to schedule
@@ -1074,20 +1291,24 @@
 
     def handleSchedulingResponse(self, response, is_organizer):
 
-        # Map each recipient in the response to a status code
-        responses = {}
-        propname = self.calendar.mainComponent().recipientPropertyName() if is_organizer else "ORGANIZER"
-        for item in response.responses:
-            recipient = str(item.recipient.children[0])
-            status = str(item.reqstatus)
-            responses[recipient] = status
+        # For a queued operation we stash the response away for the work item to deal with
+        if hasattr(self, "queuedResponses"):
+            self.queuedResponses.append(response)
+        else:
+            # Map each recipient in the response to a status code
+            responses = {}
+            propname = self.calendar.mainComponent().recipientPropertyName() if is_organizer else "ORGANIZER"
+            for item in response.responses:
+                recipient = str(item.recipient.children[0])
+                status = str(item.reqstatus)
+                responses[recipient] = status
 
-            # Now apply to each ATTENDEE/ORGANIZER in the original data
-            self.calendar.setParameterToValueForPropertyWithValue(
-                "SCHEDULE-STATUS",
-                status.split(";")[0],
-                propname,
-                recipient)
+                # Now apply to each ATTENDEE/ORGANIZER in the original data
+                self.calendar.setParameterToValueForPropertyWithValue(
+                    "SCHEDULE-STATUS",
+                    status.split(";")[0],
+                    propname,
+                    recipient)
 
 
     @inlineCallbacks
@@ -1337,12 +1558,24 @@
         if self.logItems is not None:
             self.logItems["itip.reply"] = "reply"
 
-        itipmsg = iTipGenerator.generateAttendeeReply(self.calendar, self.attendee, changedRids=changedRids)
+        if config.Scheduling.Options.WorkQueues.Enabled:
+            # Always make it look like scheduling succeeded when queuing
+            self.calendar.setParameterToValueForPropertyWithValue(
+                "SCHEDULE-STATUS",
+                iTIPRequestStatus.MESSAGE_DELIVERED_CODE,
+                "ORGANIZER",
+                self.organizer,
+            )
 
-        # Send scheduling message
-        return self.sendToOrganizer("REPLY", itipmsg)
+            return ScheduleReplyWork.reply(self.txn, self.calendar_home, self.resource, changedRids, self.attendee)
 
+        else:
+            itipmsg = iTipGenerator.generateAttendeeReply(self.calendar, self.attendee, changedRids=changedRids)
 
+            # Send scheduling message
+            return self.sendToOrganizer("REPLY", itipmsg)
+
+
     def scheduleCancelWithOrganizer(self):
 
         # First make sure we are allowed to schedule
@@ -1351,12 +1584,17 @@
         if self.logItems is not None:
             self.logItems["itip.reply"] = "cancel"
 
-        itipmsg = iTipGenerator.generateAttendeeReply(self.calendar, self.attendee, force_decline=True)
+        if config.Scheduling.Options.WorkQueues.Enabled:
+            return ScheduleReplyCancelWork.replyCancel(self.txn, self.calendar_home, self.calendar, self.attendee)
 
-        # Send scheduling message
-        return self.sendToOrganizer("CANCEL", itipmsg)
+        else:
+            itipmsg = iTipGenerator.generateAttendeeReply(self.calendar, self.attendee, force_decline=True)
 
+            # Send scheduling message
+            return self.sendToOrganizer("CANCEL", itipmsg)
 
+
+    @inlineCallbacks
     def sendToOrganizer(self, action, itipmsg):
 
         # Send scheduling message
@@ -1365,10 +1603,6 @@
         scheduler = self.makeScheduler()
 
         # Do the PUT processing
-        def _gotResponse(response):
-            self.handleSchedulingResponse(response, False)
-
         log.info("Implicit {action} - attendee: '{attendee}' to organizer: '{organizer}', UID: '{uid}'", action=action, attendee=self.attendee, organizer=self.organizer, uid=self.uid)
-        d = scheduler.doSchedulingViaPUT(self.originator, (self.organizer,), itipmsg, internal_request=True)
-        d.addCallback(_gotResponse)
-        return d
+        response = (yield scheduler.doSchedulingViaPUT(self.originator, (self.organizer,), itipmsg, internal_request=True))
+        self.handleSchedulingResponse(response, False)

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -67,10 +67,11 @@
             calendar.removeProperty(method)
 
         if recipient:
-            iTipProcessing.addTranspForNeedsAction(calendar.subcomponents(), recipient)
 
             # Check for incoming DECLINED
             if creating:
+                iTipProcessing.addTranspForNeedsAction(calendar.subcomponents(), recipient)
+
                 master = calendar.masterComponent()
                 for component in tuple(calendar.subcomponents()):
                     if component in ignoredComponents or component is master:
@@ -111,14 +112,16 @@
 
         # Merge Organizer data with Attendee's own changes (VALARMs, Comment only for now).
         from txdav.caldav.datastore.scheduling.icaldiff import iCalDiff
-        rids = iCalDiff(calendar, itip_message, False).whatIsDifferent()
+        differ = iCalDiff(calendar, itip_message, False)
+        rids = differ.whatIsDifferent()
+        needs_action_rids, reschedule = differ.attendeeNeedsAction(rids)
 
         # Different behavior depending on whether a master component is present or not
         # Here we cache per-attendee data from the existing master that we need to use in any new
         # overridden components that the organizer added
         current_master = calendar.masterComponent()
         if current_master:
-            master_valarms = [comp for comp in current_master.subcomponents() if comp.name() == "VALARM"]
+            valarms = [comp for comp in current_master.subcomponents() if comp.name() == "VALARM"]
             private_comments = current_master.properties("X-CALENDARSERVER-PRIVATE-COMMENT")
             transps = current_master.properties("TRANSP")
             completeds = current_master.properties("COMPLETED")
@@ -132,11 +135,12 @@
                 if props:
                     other_props[pname] = props
         else:
-            master_valarms = ()
+            valarms = ()
             private_comments = ()
             transps = ()
             completeds = ()
             organizer_schedule_status = None
+            attendee = None
             attendee_dtstamp = None
             other_props = {}
 
@@ -145,31 +149,16 @@
             # Get a new calendar object first
             new_calendar = iTipProcessing.processNewRequest(itip_message, recipient)
 
-            # Copy over master alarms, comments
+            # Copy over master alarms, comments etc
             master_component = new_calendar.masterComponent()
-            for alarm in master_valarms:
-                master_component.addComponent(alarm)
-            for comment in private_comments:
-                master_component.addProperty(comment)
-            for transp in transps:
-                master_component.replaceProperty(transp)
-            for completed in completeds:
-                master_component.replaceProperty(completed)
-            if organizer_schedule_status:
-                organizer = master_component.getProperty("ORGANIZER")
-                if organizer:
-                    organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
-            if attendee_dtstamp:
-                attendee = master_component.getAttendeeProperty((recipient,))
-                if attendee:
-                    attendee.setParameter("X-CALENDARSERVER-DTSTAMP", attendee_dtstamp)
-            for props in other_props.values():
-                [master_component.replaceProperty(prop) for prop in props]
+            transfer_partstat = None not in needs_action_rids and not reschedule
+            seq_change = Component.compareComponentsForITIP(master_component, current_master, use_dtstamp=False) <= 0
+            iTipProcessing._transferItems(master_component, transfer_partstat and seq_change, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)
 
             # Now try to match recurrences in the new calendar
             for component in tuple(new_calendar.subcomponents()):
                 if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
-                    iTipProcessing.transferItems(calendar, component, master_valarms, private_comments, transps, completeds, organizer_schedule_status, attendee_dtstamp, other_props, recipient)
+                    iTipProcessing.transferItems(calendar, component, needs_action_rids, reschedule, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)
 
             # Now try to match recurrences from the old calendar
             for component in calendar.subcomponents():
@@ -181,10 +170,12 @@
                         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, master_valarms, private_comments, transps, completeds, organizer_schedule_status, attendee_dtstamp, other_props, recipient)
+                            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)
+
             # Replace the entire object
             return new_calendar, rids
 
@@ -200,12 +191,12 @@
                         calendar.addComponent(component)
                 else:
                     component = component.duplicate()
-                    missingDeclined = iTipProcessing.transferItems(calendar, component, master_valarms, private_comments, transps, completeds, organizer_schedule_status, attendee_dtstamp, other_props, recipient, remove_matched=True)
+                    missingDeclined = iTipProcessing.transferItems(calendar, component, needs_action_rids, reschedule, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient, remove_matched=True)
                     if not missingDeclined:
                         calendar.addComponent(component)
-                        if recipient:
-                            iTipProcessing.addTranspForNeedsAction((component,), recipient)
 
+            iTipProcessing.addTranspForNeedsAction(calendar.subcomponents(), recipient)
+
             # Write back the modified object
             return calendar, rids
 
@@ -558,7 +549,7 @@
 
 
     @staticmethod
-    def transferItems(from_calendar, to_component, master_valarms, private_comments, transps, completeds, organizer_schedule_status, attendee_dtstamp, other_props, recipient, remove_matched=False):
+    def transferItems(from_calendar, to_component, needs_action_rids, reschedule, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient, remove_matched=False):
         """
         Transfer properties from a calendar to a component by first trying to match the component in the original calendar and
         use the properties from that, or use the values provided as arguments (which have been derived from the original calendar's
@@ -568,8 +559,8 @@
         @type from_calendar: L{Component}
         @param to_component: the new component to transfer items to
         @type to_component: L{Component}
-        @param master_valarms: a C{list} of VALARM components from the old master to use
-        @type master_valarms: C{list}
+        @param valarms: a C{list} of VALARM components from the old master to use
+        @type valarms: C{list}
         @param private_comments: a C{list} of private comment properties from the old master to use
         @type private_comments: C{list}
         @param transps: a C{list} of TRANSP properties from the old master to use
@@ -592,44 +583,43 @@
 
         rid = to_component.getRecurrenceIDUTC()
 
+        transfer_partstat = rid not in needs_action_rids and not reschedule
+
         # Is there a matching component
         matched = from_calendar.overriddenComponent(rid)
         if matched:
-            # Copy over VALARMs from existing component
-            [to_component.addComponent(comp) for comp in matched.subcomponents() if comp.name() == "VALARM"]
-            [to_component.addProperty(prop) for prop in matched.properties("X-CALENDARSERVER-ATTENDEE-COMMENT")]
-            [to_component.replaceProperty(prop) for prop in matched.properties("TRANSP")]
-            [to_component.replaceProperty(prop) for prop in matched.properties("COMPLETED")]
-
+            valarms = [comp for comp in matched.subcomponents() if comp.name() == "VALARM"]
+            private_comments = matched.properties("X-CALENDARSERVER-PRIVATE-COMMENT")
+            transps = matched.properties("TRANSP")
+            completeds = matched.properties("COMPLETED")
             organizer = matched.getProperty("ORGANIZER")
             organizer_schedule_status = organizer.parameterValue("SCHEDULE-STATUS", None) if organizer else None
-            if organizer_schedule_status:
-                organizer = to_component.getProperty("ORGANIZER")
-                if organizer:
-                    organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
+            attendee = matched.getAttendeeProperty((recipient,))
+            attendee_dtstamp = attendee.parameterValue("X-CALENDARSERVER-DTSTAMP") if attendee else None
+            other_props = {}
+            for pname in config.Scheduling.CalDAV.PerAttendeeProperties:
+                props = tuple(matched.properties(pname))
+                if props:
+                    other_props[pname] = props
 
-            # Remove the old one
-            if remove_matched:
-                from_calendar.removeComponent(matched)
+            seq_change = Component.compareComponentsForITIP(to_component, matched, use_dtstamp=False) <= 0
+            iTipProcessing._transferItems(to_component, transfer_partstat and seq_change, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)
 
             # Check for incoming DECLINED
-            attendee = to_component.getAttendeeProperty((recipient,))
-            if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
+            to_attendee = to_component.getAttendeeProperty((recipient,))
+            if to_attendee and to_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
                 # If existing item has HIDDEN property copy that over
                 if matched.hasProperty(Component.HIDDEN_INSTANCE_PROPERTY):
                     to_component.addProperty(Property(Component.HIDDEN_INSTANCE_PROPERTY, "T"))
 
-            if attendee and attendee_dtstamp:
-                attendee.setParameter("X-CALENDARSERVER-DTSTAMP", attendee_dtstamp)
+            # Remove the old one
+            if remove_matched:
+                from_calendar.removeComponent(matched)
 
-            for pname in config.Scheduling.CalDAV.PerAttendeeProperties:
-                [to_component.replaceProperty(prop) for prop in matched.properties(pname)]
-
             # Check to see if the new component is cancelled as that could mean we are copying in the wrong attendee state
             if to_component.propertyValue("STATUS") == "CANCELLED":
-                from_attendee = matched.getAttendeeProperty((recipient,))
-                if attendee and from_attendee:
-                    attendee.setParameter("PARTSTAT", from_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION"))
+                if attendee and to_attendee:
+                    to_attendee.setParameter("PARTSTAT", attendee.parameterValue("PARTSTAT", "NEEDS-ACTION"))
 
         else:
             # Check for incoming DECLINED
@@ -637,29 +627,89 @@
             if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
                 return True
 
-            # It is a new override - copy any valarms on the existing master component
-            # into the new one.
-            [to_component.addComponent(alarm) for alarm in master_valarms]
-            [to_component.addProperty(comment) for comment in private_comments]
-            [to_component.replaceProperty(transp) for transp in transps]
-            [to_component.replaceProperty(completed) for completed in completeds]
+            master_component = from_calendar.masterComponent()
+            seq_change = (Component.compareComponentsForITIP(to_component, master_component, use_dtstamp=False) <= 0) if master_component is not None else True
+            iTipProcessing._transferItems(to_component, transfer_partstat and seq_change, valarms, private_comments, transps, completeds, organizer_schedule_status, attendee, attendee_dtstamp, other_props, recipient)
 
-            if organizer_schedule_status:
-                organizer = to_component.getProperty("ORGANIZER")
-                if organizer:
-                    organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
-            if attendee_dtstamp:
-                attendee = to_component.getAttendeeProperty((recipient,))
-                if attendee:
-                    attendee.setParameter("X-CALENDARSERVER-DTSTAMP", attendee_dtstamp)
+        return False
 
-            for props in other_props.values():
-                [to_component.replaceProperty(prop) for prop in props]
 
+    @staticmethod
+    def _transferItems(to_component, transfer_partstat, valarms, private_comments, transps, completeds, organizer_schedule_status, old_attendee, attendee_dtstamp, other_props, recipient):
+        """
+        Transfer properties the key per-attendee properties from one component to another. Note that the key properties are pulled out into separate items, because they
+        may have been derived from the master.
+
+        @param to_component: the new component to transfer items to
+        @type to_component: L{Component}
+        @param partstat_change: whether not to transfer the old PARTSTAT over
+        @type partstat_change: C{bool}
+        @param valarms: a C{list} of VALARM components from the old master to use
+        @type valarms: C{list}
+        @param private_comments: a C{list} of private comment properties from the old master to use
+        @type private_comments: C{list}
+        @param transps: a C{list} of TRANSP properties from the old master to use
+        @type transps: C{list}
+        @param completeds: a C{list} of COMPLETED properties from the old master to use
+        @type completeds: C{list}
+        @param organizer_schedule_status: a the SCHEDULE-STATUS value for the organizer from the old master to use
+        @type organizer_schedule_status: C{str}
+        @param attendee_dtstamp: an the ATTENDEE DTSTAMP parameter value from the old master to use
+        @type attendee_dtstamp: C{str}
+        @param other_props: other properties from the old master to use
+        @type other_props: C{list}
+        @param recipient: the calendar user address of the attendee whose data is being processed
+        @type recipient: C{str}
+
+        @return: C{True} if an EXDATE match occurred requiring the incoming component to be removed.
+        """
+
+        # It is a new override - copy any valarms on the existing master component
+        # into the new one.
+        [to_component.addComponent(alarm) for alarm in valarms]
+        [to_component.addProperty(comment) for comment in private_comments]
+        [to_component.replaceProperty(transp) for transp in transps]
+        [to_component.replaceProperty(completed) for completed in completeds]
+
+        if organizer_schedule_status:
+            organizer = to_component.getProperty("ORGANIZER")
+            if organizer:
+                organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
+
+        # ATTENDEE property merge
+        attendee = to_component.getAttendeeProperty((recipient,))
+        if old_attendee and attendee and transfer_partstat:
+            iTipProcessing.mergePartStat(old_attendee, attendee)
+
+        if attendee_dtstamp and attendee:
+            attendee.setParameter("X-CALENDARSERVER-DTSTAMP", attendee_dtstamp)
+
+        for props in other_props.values():
+            [to_component.replaceProperty(prop) for prop in props]
+
         return False
 
 
     @staticmethod
+    def mergePartStat(from_attendee, to_attendee):
+        """
+        Make sure the existing attendee PARTSTAT is preserved and also get rid of any RSVP
+        if the new PARTSTAT is not NEEDS-ACTION.
+
+        @param from_attendee: attendee property to copy PARTSTAT from
+        @type from_attendee: L{twistedcaldav.ical.Property}
+        @param to_attendee: attendee property to copy PARTSTAT to
+        @type to_attendee: L{twistedcaldav.ical.Property}
+        """
+
+        preserve = from_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
+        if preserve != to_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION"):
+            to_attendee.setParameter("PARTSTAT", preserve)
+        if preserve != "NEEDS-ACTION":
+            to_attendee.removeParameter("RSVP")
+
+
+    @staticmethod
     def addTranspForNeedsAction(components, recipient):
         """
         For each component where the ATTENDEE property of the recipient has PARTSTAT
@@ -770,7 +820,7 @@
     """
 
     @staticmethod
-    def generateCancel(original, attendees, instances=None, full_cancel=False):
+    def generateCancel(original, attendees, instances=None, full_cancel=False, test_only=False):
         """
         This assumes that SEQUENCE is not already at its new value in the original calendar data. This
         is because the component passed in is the one that originally contained the attendee that is
@@ -789,9 +839,6 @@
         added = False
         for instance_rid in instances:
 
-            # Create a new component matching the type of the original
-            comp = Component(original.mainType())
-
             # Use the master component when the instance is None
             if not instance_rid:
                 instance = original.masterComponent()
@@ -806,6 +853,14 @@
                 if instance is None:
                     continue
 
+            # If testing, skip the rest
+            if test_only:
+                added = True
+                continue
+
+            # Create a new component matching the type of the original
+            comp = Component(original.mainType())
+
             # Add some required properties extracted from the original
             comp.addProperty(Property("DTSTAMP", instance.propertyValue("DTSTAMP")))
             comp.addProperty(Property("UID", instance.propertyValue("UID")))
@@ -843,6 +898,11 @@
             itip.addComponent(comp)
             added = True
 
+        # When testing only need to return whether an itip would have been created or not
+        if test_only:
+            return added
+
+        # Handle actual iTIP message
         if added:
             # Now include any referenced tzids
             for comp in original.subcomponents():
@@ -860,7 +920,7 @@
 
 
     @staticmethod
-    def generateAttendeeRequest(original, attendees, filter_rids):
+    def generateAttendeeRequest(original, attendees, filter_rids, test_only=False):
         """
         This assumes that SEQUENCE is already at its new value in the original calendar data.
         """
@@ -876,7 +936,8 @@
         # Now filter out components except the ones specified
         if itip.filterComponents(filter_rids):
             # Strip out unwanted bits
-            iTipGenerator.prepareSchedulingMessage(itip)
+            if not test_only:
+                iTipGenerator.prepareSchedulingMessage(itip)
             return itip
 
         else:

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -22,27 +22,24 @@
 from txweb2.dav.method.report import NumberOfMatchesWithinLimits
 from txweb2.http import HTTPError
 
-from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, returnValue
 
 from twistedcaldav import customxml, caldavxml
 from twistedcaldav.config import config
 from twistedcaldav.ical import Property
 from twistedcaldav.instance import InvalidOverriddenInstanceError
-from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
-from twistedcaldav.memcacher import Memcacher
 
 from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
+from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
 from txdav.caldav.datastore.scheduling.itip import iTipProcessing, iTIPRequestStatus
 from txdav.caldav.datastore.scheduling.utils import getCalendarObjectForRecord
+from txdav.caldav.datastore.scheduling.work import ScheduleRefreshWork, \
+    ScheduleAutoReplyWork
+from txdav.caldav.icalendarstore import ComponentUpdateState, ComponentRemoveState
 
 import collections
 import hashlib
 import uuid
-from txdav.caldav.icalendarstore import ComponentUpdateState, \
-    ComponentRemoveState
-from twext.enterprise.locking import NamedLock
-from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
 
 """
 CalDAV implicit processing.
@@ -80,12 +77,12 @@
         Do implicit processing of a scheduling message, and possibly also auto-process it
         if the recipient has auto-accept on.
 
-        @param message:
-        @type message:
-        @param originator:
-        @type originator:
-        @param recipient:
-        @type recipient:
+        @param message: the iTIP message
+        @type message: L{twistedcaldav.ical.Component}
+        @param originator: calendar user sending the message
+        @type originator: C{str}
+        @param recipient: calendar user receiving the message
+        @type recipient: C{str}
 
         @return: a C{tuple} of (C{bool}, C{bool}) indicating whether the message was processed, and if it was whether
             auto-processing has taken place.
@@ -167,6 +164,9 @@
 
     @inlineCallbacks
     def doImplicitOrganizer(self):
+        """
+        Process an iTIP message sent to the organizer.
+        """
 
         # Locate the organizer's copy of the event.
         yield self.getRecipientsCopy()
@@ -187,8 +187,12 @@
 
     @inlineCallbacks
     def doImplicitOrganizerUpdate(self):
+        """
+        An iTIP REPLY has been sent by an attendee to an organizer and the attendee state needs to be sync'd
+        to the organizer's copy of the event.
+        """
 
-        # Check to see if this is a valid reply
+        # Check to see if this is a valid reply - this will also merge the changes to the organizer's copy
         result, processed = iTipProcessing.processReply(self.message, self.recipient_calendar)
         if result:
 
@@ -250,72 +254,41 @@
     @inlineCallbacks
     def queueAttendeeUpdate(self, exclude_attendees):
         """
-        Queue up an update to attendees and use a memcache lock to ensure we don't update too frequently.
+        Queue up a background update to attendees.
 
         @param exclude_attendees: list of attendees who should not be refreshed (e.g., the one that triggered the refresh)
         @type exclude_attendees: C{list}
         """
 
-        # When doing auto-processing of replies, only refresh attendees when the last auto-accept is done.
-        # Note that when we do this we also need to refresh the attendee that is generating the reply because they
-        # are no longer up to date with changes of other auto-accept attendees. See docstr for sendAttendeeAutoReply
-        # below for more details of what is going on here.
-        if getattr(self.txn, "auto_reply_processing_count", 0) > 1:
-            log.debug("ImplicitProcessing - refreshing UID: '%s', Suppressed: %s" % (self.uid, self.txn.auto_reply_processing_count,))
-            self.txn.auto_reply_suppressed = True
-            returnValue(None)
-        if getattr(self.txn, "auto_reply_suppressed", False):
-            log.debug("ImplicitProcessing - refreshing UID: '%s', Suppression lifted" % (self.uid,))
-            exclude_attendees = ()
-
         self.uid = self.recipient_calendar.resourceUID()
 
         # Check for batched refreshes
         if config.Scheduling.Options.AttendeeRefreshBatch:
+            # Batch refresh those attendees that need it.
+            allAttendees = sorted(list(self.recipient_calendar.getAllUniqueAttendees()))
+            allAttendees = filter(lambda x: x not in exclude_attendees, allAttendees)
+            if allAttendees:
+                yield self._enqueueBatchRefresh(allAttendees)
+        else:
+            yield self._doRefresh(self.organizer_calendar_resource, exclude_attendees)
 
-            # Need to lock whilst manipulating the batch list
-            lock = MemcacheLock(
-                "BatchRefreshUIDLock",
-                self.uid,
-                timeout=config.Scheduling.Options.UIDLockTimeoutSeconds,
-                expire_time=config.Scheduling.Options.UIDLockExpirySeconds,
-            )
-            try:
-                yield lock.acquire()
-            except MemcacheLockTimeoutError:
-                # If we could not lock then just fail the refresh - not sure what else to do
-                returnValue(None)
 
-            try:
-                # Get all attendees to refresh
-                allAttendees = sorted(list(self.recipient_calendar.getAllUniqueAttendees()))
-                allAttendees = filter(lambda x: x not in exclude_attendees, allAttendees)
+    def _enqueueBatchRefresh(self, attendees):
+        """
+        Create a batch refresh work item. Do this in a separate method to allow for easy
+        unit testing.
 
-                if allAttendees:
-                    # See if there is already a pending refresh and merge current attendees into that list,
-                    # otherwise just mark all attendees as pending
-                    cache = Memcacher("BatchRefreshAttendees", pickle=True)
-                    pendingAttendees = yield cache.get(self.uid)
-                    firstTime = False
-                    if pendingAttendees:
-                        for attendee in allAttendees:
-                            if attendee not in pendingAttendees:
-                                pendingAttendees.append(attendee)
-                    else:
-                        firstTime = True
-                        pendingAttendees = allAttendees
-                    yield cache.set(self.uid, pendingAttendees)
+        @param attendees: the list of attendees to refresh
+        @type attendees: C{list}
+        """
+        return ScheduleRefreshWork.refreshAttendees(
+            self.txn,
+            self.recipient_calendar_resource,
+            self.recipient_calendar,
+            attendees,
+        )
 
-                    # Now start the first batch off
-                    if firstTime:
-                        self._enqueueBatchRefresh()
-            finally:
-                yield lock.clean()
 
-        else:
-            yield self._doRefresh(self.organizer_calendar_resource, exclude_attendees)
-
-
     @inlineCallbacks
     def _doRefresh(self, organizer_resource, exclude_attendees=(), only_attendees=None):
         """
@@ -340,107 +313,17 @@
 
 
     @inlineCallbacks
-    def _doDelayedRefresh(self, attendeesToProcess):
+    def doImplicitAttendee(self):
         """
-        Do an attendee refresh that has been delayed until after processing of the request that called it. That
-        requires that we create a new transaction to work with.
-
-        @param attendeesToProcess: list of attendees to refresh.
-        @type attendeesToProcess: C{list}
+        Process an iTIP message sent to an attendee.
         """
 
-        # The original transaction is still around but likely committed at this point, so we need a brand new
-        # transaction to do this work.
-        txn = yield self.txn.store().newTransaction("Delayed attendee refresh for UID: %s" % (self.uid,))
-
-        try:
-            # We need to get the UID lock for implicit processing whilst we send the auto-reply
-            # as the Organizer processing will attempt to write out data to other attendees to
-            # refresh them. To prevent a race we need a lock.
-            yield NamedLock.acquire(txn, "ImplicitUIDLock:%s" % (hashlib.md5(self.uid).hexdigest(),))
-
-            organizer_home = (yield txn.calendarHomeWithUID(self.organizer_uid))
-            organizer_resource = (yield organizer_home.objectResourceWithID(self.organizer_calendar_resource_id))
-            if organizer_resource is not None:
-                yield self._doRefresh(organizer_resource, only_attendees=attendeesToProcess)
-            else:
-                log.debug("ImplicitProcessing - skipping refresh of missing UID: '%s'" % (self.uid,))
-        except Exception, e:
-            log.debug("ImplicitProcessing - refresh exception UID: '%s', %s" % (self.uid, str(e)))
-            yield txn.abort()
-        except:
-            log.debug("ImplicitProcessing - refresh bare exception UID: '%s'" % (self.uid,))
-            yield txn.abort()
-        else:
-            yield txn.commit()
-
-
-    def _enqueueBatchRefresh(self):
-        """
-        Mostly here to help unit test by being able to stub this out.
-        """
-        reactor.callLater(config.Scheduling.Options.AttendeeRefreshBatchDelaySeconds, self._doBatchRefresh)
-
-
-    @inlineCallbacks
-    def _doBatchRefresh(self):
-        """
-        Do refresh of attendees in batches until the batch list is empty.
-        """
-
-        # Need to lock whilst manipulating the batch list
-        log.debug("ImplicitProcessing - batch refresh for UID: '%s'" % (self.uid,))
-        lock = MemcacheLock(
-            "BatchRefreshUIDLock",
-            self.uid,
-            timeout=config.Scheduling.Options.UIDLockTimeoutSeconds,
-            expire_time=config.Scheduling.Options.UIDLockExpirySeconds,
-        )
-        try:
-            yield lock.acquire()
-        except MemcacheLockTimeoutError:
-            # If we could not lock then just fail the refresh - not sure what else to do
-            returnValue(None)
-
-        try:
-            # Get the batch list
-            cache = Memcacher("BatchRefreshAttendees", pickle=True)
-            pendingAttendees = yield cache.get(self.uid)
-            if pendingAttendees:
-
-                # Get the next batch of attendees to process and update the cache value or remove it if
-                # no more processing is needed
-                attendeesToProcess = pendingAttendees[:config.Scheduling.Options.AttendeeRefreshBatch]
-                pendingAttendees = pendingAttendees[config.Scheduling.Options.AttendeeRefreshBatch:]
-                if pendingAttendees:
-                    yield cache.set(self.uid, pendingAttendees)
-                else:
-                    yield cache.delete(self.uid)
-
-                # Make sure we release this here to avoid potential deadlock when grabbing the ImplicitUIDLock in the next call
-                yield lock.release()
-
-                # Now do the batch refresh
-                yield self._doDelayedRefresh(attendeesToProcess)
-
-                # Queue the next refresh if needed
-                if pendingAttendees:
-                    self._enqueueBatchRefresh()
-            else:
-                yield cache.delete(self.uid)
-                yield lock.release()
-        finally:
-            yield lock.clean()
-
-
-    @inlineCallbacks
-    def doImplicitAttendee(self):
-
         # Locate the attendee's copy of the event if it exists.
         yield self.getRecipientsCopy()
         self.new_resource = self.recipient_calendar is None
 
-        # Handle new items differently than existing ones.
+        # If we get a CANCEL and we don't have a matching resource already stored, simply
+        # ignore the CANCEL.
         if self.new_resource and self.method == "CANCEL":
             result = (True, True, False, None)
         else:
@@ -451,6 +334,10 @@
 
     @inlineCallbacks
     def doImplicitAttendeeUpdate(self):
+        """
+        An iTIP message has been sent by to an attendee by the organizer. We need to update the attendee state
+        based on the nature of the iTIP message.
+        """
 
         # Do security check: ORGANZIER in iTIP MUST match existing resource value
         if self.recipient_calendar:
@@ -516,6 +403,10 @@
     @inlineCallbacks
     def doImplicitAttendeeRequest(self):
         """
+        An iTIP REQUEST message has been sent to an attendee. If there is no existing resource, we will simply
+        create a new one. If there is an existing resource we need to reconcile the changes between it and the
+        iTIP message.
+
         @return: C{tuple} of (processed, auto-processed, store inbox item, changes)
         """
 
@@ -555,9 +446,8 @@
 
             if send_reply:
                 # Track outstanding auto-reply processing
-                self.txn.auto_reply_processing_count = getattr(self.txn, "auto_reply_processing_count", 0) + 1
-                log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply queued: %s" % (self.recipient.cuaddr, self.uid, self.txn.auto_reply_processing_count,))
-                reactor.callLater(2.0, self.sendAttendeeAutoReply, *(new_calendar, new_resource, partstat))
+                log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply queued" % (self.recipient.cuaddr, self.uid,))
+                ScheduleAutoReplyWork.autoReply(self.txn, new_resource, partstat)
 
             # Build the schedule-changes XML element
             changes = customxml.ScheduleChanges(
@@ -596,9 +486,8 @@
 
                 if send_reply:
                     # Track outstanding auto-reply processing
-                    self.txn.auto_reply_processing_count = getattr(self.txn, "auto_reply_processing_count", 0) + 1
-                    log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply queued: %s" % (self.recipient.cuaddr, self.uid, self.txn.auto_reply_processing_count,))
-                    reactor.callLater(2.0, self.sendAttendeeAutoReply, *(new_calendar, new_resource, partstat))
+                    log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply queued" % (self.recipient.cuaddr, self.uid,))
+                    ScheduleAutoReplyWork.autoReply(self.txn, new_resource, partstat)
 
                 # Build the schedule-changes XML element
                 update_details = []
@@ -638,11 +527,18 @@
 
     @inlineCallbacks
     def doImplicitAttendeeCancel(self):
+        """
+        An iTIP CANCEL message has been sent to an attendee. If there is no existing resource, we will simply
+        ignore the message. If there is an existing resource we need to reconcile the changes between it and the
+        iTIP message.
 
+        @return: C{tuple} of (processed, auto-processed, store inbox item, changes)
+        """
+
         # If there is no existing copy, then ignore
         if self.recipient_calendar is None:
             log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring METHOD:CANCEL, UID: '%s' - attendee has no copy" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
-            result = (True, True, None)
+            result = (True, True, True, None)
         else:
             # Need to check for auto-respond attendees. These need to suppress the inbox message
             # if the cancel is processed. However, if the principal is a user we always force the
@@ -696,66 +592,6 @@
 
 
     @inlineCallbacks
-    def sendAttendeeAutoReply(self, calendar, resource, partstat):
-        """
-        Auto-process the calendar option to generate automatic accept/decline status and
-        send a reply if needed.
-
-        There is some tricky behavior here: when multiple auto-accept attendees are present in a
-        calendar object, we want to suppress the processing of other attendee refreshes until all
-        auto-accepts have replied, to avoid a flood of refreshes. We do that by tracking the pending
-        auto-replies via a "auto_reply_processing_count" attribute on the original txn objection (even
-        though that has been committed). We also use a "auto_reply_suppressed" attribute on that txn
-        to indicate when suppression has occurred, to ensure that when the refresh is finally sent, we
-        send it to everyone to make sure all are in sync. In order for the actual refreshes to be
-        suppressed we have to "transfer" those two attributes from the original txn to the new one
-        used to send the reply. Then we transfer "auto_reply_suppressed" back when done, and decrement
-        "auto_reply_processing_count" (all done under a UID lock to prevent race conditions).
-
-        @param calendar: calendar data to examine
-        @type calendar: L{Component}
-
-        @return: L{Component} for the new calendar data to write
-        """
-
-        # The original transaction is still around but likely committed at this point, so we need a brand new
-        # transaction to do this work.
-        txn = yield self.txn.store().newTransaction("Attendee (%s) auto-reply for UID: %s" % (self.recipient.cuaddr, self.uid,))
-
-        aborted = False
-        try:
-            # We need to get the UID lock for implicit processing whilst we send the auto-reply
-            # as the Organizer processing will attempt to write out data to other attendees to
-            # refresh them. To prevent a race we need a lock.
-            yield NamedLock.acquire(txn, "ImplicitUIDLock:%s" % (hashlib.md5(calendar.resourceUID()).hexdigest(),))
-
-            # Must be done after acquiring the lock to avoid a race-condition
-            txn.auto_reply_processing_count = getattr(self.txn, "auto_reply_processing_count", 0)
-            txn.auto_reply_suppressed = getattr(self.txn, "auto_reply_suppressed", False)
-
-            # Send out a reply
-            log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply: %s" % (self.recipient.cuaddr, self.uid, partstat))
-            from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
-            scheduler = ImplicitScheduler()
-            yield scheduler.sendAttendeeReply(txn, resource, calendar, self.recipient)
-        except Exception, e:
-            log.debug("ImplicitProcessing - auto-reply exception UID: '%s', %s" % (self.uid, str(e)))
-            aborted = True
-        except:
-            log.debug("ImplicitProcessing - auto-reply bare exception UID: '%s'" % (self.uid,))
-            aborted = True
-
-        # Track outstanding auto-reply processing - must be done before commit/abort which releases the lock
-        self.txn.auto_reply_processing_count = getattr(self.txn, "auto_reply_processing_count", 0) - 1
-        self.txn.auto_reply_suppressed = txn.auto_reply_suppressed
-
-        if aborted:
-            yield txn.abort()
-        else:
-            yield txn.commit()
-
-
-    @inlineCallbacks
     def checkAttendeeAutoReply(self, calendar, automode):
         """
         Check whether a reply to the given iTIP message is needed and if so make the

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -4292,6 +4292,576 @@
             self.assertEqual(got_rids, rids, msg="%s expected R-IDs: '%s', got: '%s'" % (description, rids, got_rids,))
 
 
+    def test_attendee_needs_action(self):
+
+        data = (
+            (
+                "#1.1 Simple component, no change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                set(),
+                False,
+            ),
+            (
+                "#1.2 Simple component, one property change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test1
+END:VEVENT
+END:VCALENDAR
+""",
+                set(),
+                False,
+            ),
+            (
+                "#1.3 Simple component, date property change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T130000Z
+DTEND:20080601T140000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                set((None,)),
+                False,
+            ),
+            (
+                "#1.4 Simple component, duration property change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT2H
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                set((None,)),
+                False,
+            ),
+            (
+                "#2.1 Recurring component, no change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                set(),
+                False,
+            ),
+            (
+                "#2.2 Recurring component, change property",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test1
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                set(),
+                False,
+            ),
+            (
+                "#2.3 Recurring component, change date",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T130000Z
+DTEND:20080601T140000Z
+SUMMARY:Test1
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                set((None,)),
+                True,
+            ),
+            (
+                "#2.4 Recurring component, change rule",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=WEEKLY
+END:VEVENT
+END:VCALENDAR
+""",
+                set(),
+                True,
+            ),
+            (
+                "#2.5 Recurring component, truncate rule",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+END:VCALENDAR
+""",
+                set(),
+                False,
+            ),
+            (
+                "#2.6 Recurring component, expand rule",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+END:VCALENDAR
+""",
+                set(),
+                False,
+            ),
+            (
+                "#3.1 Recurring component with override, no change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                set(),
+                False,
+            ),
+            (
+                "#3.2 Recurring component with override, property change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test1
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+SUMMARY:Test2
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                set(),
+                False,
+            ),
+            (
+                "#3.3 Recurring component with override, date change in override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test1
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+SUMMARY:Test2
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                set(("20080602T120000Z",)),
+                False,
+            ),
+            (
+                "#3.4 Recurring component with override, date change in override and master",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+SUMMARY:Test
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T130000Z
+DTEND:20080601T140000Z
+SUMMARY:Test1
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T130000Z
+DTSTART:20080602T130000Z
+DTEND:20080602T140000Z
+SUMMARY:Test2
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                set((None,)),
+                True,
+            ),
+        )
+
+        for description, calendar1, calendar2, rids, rescheduled in data:
+            differ = iCalDiff(Component.fromString(calendar1), Component.fromString(calendar2), False)
+            diffs = differ.whatIsDifferent()
+            got_rids, got_rescheduled = differ.attendeeNeedsAction(diffs)
+            rids = set([DateTime.parseText(k) if k else None for k in rids])
+            self.assertEqual(got_rids, rids, msg="%s expected R-IDs: '%s', got: '%s'" % (description, rids, got_rids,))
+            self.assertEqual(got_rescheduled, rescheduled, msg="%s expected rescheduled: '%s', got: '%s'" % (description, rescheduled, got_rescheduled,))
+
+
     def test_organizer_smart_merge(self):
 
         data1 = (

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_implicit.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_implicit.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_implicit.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -29,7 +29,8 @@
 from twistedcaldav.config import config
 from twistedcaldav.ical import Component
 
-from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
+from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler, \
+    ScheduleReplyWork
 from txdav.caldav.datastore.scheduling.scheduler import ScheduleResponseQueue
 from txdav.caldav.datastore.test.util import buildCalendarStore, \
     buildDirectoryRecord
@@ -1326,6 +1327,11 @@
 
         yield self._setCalendarData(data2, "user02")
 
+        while True:
+            work = (yield ScheduleReplyWork.hasWork(self.transactionUnderTest()))
+            if not work:
+                break
+
         list1 = (yield self._listCalendarObjects("user01", "inbox"))
         self.assertEqual(len(list1), 1)
 
@@ -1396,6 +1402,11 @@
 
         yield self._setCalendarData(data2, "user02")
 
+        while True:
+            work = (yield ScheduleReplyWork.hasWork(self.transactionUnderTest()))
+            if not work:
+                break
+
         list1 = (yield self._listCalendarObjects("user01", "inbox"))
         self.assertEqual(len(list1), 1)
 
@@ -1456,7 +1467,7 @@
 
         # Need refreshes to occur immediately, not via reactor.callLater
         self.patch(config.Scheduling.Options, "AttendeeRefreshBatch", 5)
-        self.patch(config.Scheduling.Options, "AttendeeRefreshBatchDelaySeconds", 1)
+        self.patch(config.Scheduling.Options.WorkQueues, "AttendeeRefreshBatchDelaySeconds", 1)
 
         yield self._createCalendarObject(data1, "user01", "test.ics")
 
@@ -1480,6 +1491,11 @@
 
         yield self._setCalendarData(data2, "user02")
 
+        while True:
+            work = (yield ScheduleReplyWork.hasWork(self.transactionUnderTest()))
+            if not work:
+                break
+
         list1 = (yield self._listCalendarObjects("user01", "inbox"))
         self.assertEqual(len(list1), 1)
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -34,6 +34,224 @@
     iCalendar support tests
     """
 
+    def test_processRequest_mergeAttendeePartstat(self):
+        """
+        Test iTIPProcessing.processRequest properly preserves attendee PARTSTAT when there is no date change
+        """
+
+        data = (
+            (
+                "1.1 Simple Request - summary change only, partstats match",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test1
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test1
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.2 Simple Request - summary change only, partstat mismatch",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test1
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test1
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.3 Simple Request - date change, partstats match",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "1.4 Simple Request - date change, partstat mismatch",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DURATION:PT1H
+DTSTAMP:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Test
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+        )
+
+        for title, calendar_txt, itip_txt, changed_txt in data:
+            calendar = Component.fromString(calendar_txt)
+            itip = Component.fromString(itip_txt)
+            changed = Component.fromString(changed_txt)
+
+            result, _ignore = iTipProcessing.processRequest(itip, calendar, "mailto:user02 at example.com")
+            self.assertEqual(result, changed, msg="Calendar mismatch: %s" % (title,))
+
+
     def test_processReply(self):
         """
         Test iTIPProcessing.processReply
@@ -1077,11 +1295,6 @@
             calendar = Component.fromString(calendar_txt)
             itipmsg = Component.fromString(itipmsg_txt)
             reply_success, reply_processed = iTipProcessing.processReply(itipmsg, calendar)
-#            if not description.startswith("#3.1"):
-#                continue
-#            print(description)
-#            print(str(calendar))
-#            print(str(result))
             self.assertEqual(
                 str(calendar).replace("\r", "").replace("\n ", ""),
                 str(result).replace("\n ", ""),

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_pocessing.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_pocessing.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_pocessing.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -34,7 +34,7 @@
         self.batches = 0
 
 
-    def _enqueueBatchRefresh(self):
+    def _enqueueBatchRefresh(self, exclude_attendees):
         self.batches += 1
 
 
@@ -69,7 +69,7 @@
 
 
     def id(self):
-        return None
+        return 1
 
 
 
@@ -107,6 +107,7 @@
         processor = FakeImplicitProcessor()
         processor.txn = ""
         processor.uid = "12345-67890"
+        processor.recipient_calendar_resource = FakeResource()
         processor.recipient_calendar = calendar
         yield processor.queueAttendeeUpdate(("urn:uuid:user02", "urn:uuid:user01",))
         self.assertEqual(processor.batches, 0)
@@ -134,6 +135,7 @@
         processor = FakeImplicitProcessor()
         processor.txn = ""
         processor.uid = "12345-67890"
+        processor.recipient_calendar_resource = FakeResource()
         processor.recipient_calendar = calendar
         yield processor.queueAttendeeUpdate(("urn:uuid:user02", "urn:uuid:user01",))
         self.assertEqual(processor.batches, 1)
@@ -219,7 +221,7 @@
             processor.txn = ""
             processor.recipient_calendar = calendar.duplicate()
             processor.uid = processor.recipient_calendar.newUID()
-            processor.recipient_calendar_resource = None
+            processor.recipient_calendar_resource = FakeResource()
             processor.message = itip.duplicate()
             processor.message.newUID(processor.uid)
             processor.originator = LocalCalendarUser(None, None)

Copied: CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/scheduling/work.py)
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -0,0 +1,682 @@
+#
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.enterprise.dal.record import fromTable
+from twext.enterprise.dal.syntax import Select, Insert, Delete, Parameter
+from twext.enterprise.locking import NamedLock
+from twext.enterprise.queue import WorkItem
+from twext.python.log import Logger
+
+from twisted.internet.defer import inlineCallbacks, returnValue, Deferred
+
+from twistedcaldav.config import config
+from twistedcaldav.ical import Component
+
+from txdav.caldav.datastore.scheduling.itip import iTipGenerator, iTIPRequestStatus
+from txdav.caldav.icalendarstore import ComponentUpdateState
+from txdav.common.datastore.sql_tables import schema, \
+    scheduleActionToSQL, scheduleActionFromSQL
+
+import datetime
+import hashlib
+from pycalendar.datetime import DateTime
+import traceback
+
+__all__ = [
+    "ScheduleOrganizerWork",
+    "ScheduleReplyWork",
+    "ScheduleReplyCancelWork",
+    "ScheduleRefreshWork",
+    "ScheduleAutoReplyWork",
+]
+
+log = Logger()
+
+class ScheduleWorkMixin(object):
+    """
+    Base class for common schedule work item behavior.
+    """
+
+    # Track when all work is complete (needed for unit tests)
+    _allDoneCallback = None
+    _queued = 0
+
+    # Schedule work is grouped based on calendar object UID
+    group = property(lambda self: "ScheduleWork:%s" % (self.icalendarUid,))
+
+
+    @classmethod
+    def allDone(cls):
+        d = Deferred()
+        cls._allDoneCallback = d.callback
+        cls._queued = 0
+        return d
+
+
+    @classmethod
+    def _enqueued(cls):
+        """
+        Called when a new item is enqueued - using for tracking purposes.
+        """
+        ScheduleWorkMixin._queued += 1
+
+
+    def _dequeued(self):
+        """
+        Called when an item is dequeued - using for tracking purposes. We call
+        the callback when the last item is dequeued.
+        """
+        ScheduleWorkMixin._queued -= 1
+        if ScheduleWorkMixin._queued == 0:
+            if ScheduleWorkMixin._allDoneCallback:
+                def _post():
+                    ScheduleWorkMixin._allDoneCallback(None)
+                    ScheduleWorkMixin._allDoneCallback = None
+                self.transaction.postCommit(_post)
+
+
+    def handleSchedulingResponse(self, response, calendar, is_organizer):
+        """
+        Update a user's calendar object resource based on the results of a queued scheduling
+        message response. Note we only need to update in the case where there is an error response
+        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 calendar: original calendar component
+        @type calendar: L{Component}
+        @param is_organizer: whether or not iTIP message was sent by the organizer
+        @type is_organizer: C{bool}
+        """
+
+        # 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]
+
+            # Now apply to each ATTENDEE/ORGANIZER in the original data only if not 1.2
+            if statusCode != iTIPRequestStatus.MESSAGE_DELIVERED_CODE:
+                calendar.setParameterToValueForPropertyWithValue(
+                    "SCHEDULE-STATUS",
+                    statusCode,
+                    propname,
+                    recipient,
+                )
+                changed = True
+
+        return changed
+
+
+
+class ScheduleOrganizerWork(WorkItem, fromTable(schema.SCHEDULE_ORGANIZER_WORK), ScheduleWorkMixin):
+    """
+    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.
+    """
+
+    @classmethod
+    @inlineCallbacks
+    def schedule(cls, txn, uid, action, home, resource, calendar_old, calendar_new, organizer, attendee_count, smart_merge):
+        """
+        The actual arguments depend on the action:
+
+        1) If action is "create", resource is None, calendar_old is None, calendar_new is the new data
+        2) If action is "modify", resource is existing resource, calendar_old is the old calendar_old data, and
+            calendar_new is the new data
+        3) If action is "remove", resource is the existing resource, calendar_old is the old calendar_old data,
+            and calendar_new is None
+
+        Right now we will also create the iTIP message based on the diff of calendar_old and calendar_new rather than
+        looking at the current state of the orgnaizer's resource (which may have changed since this work item was
+        filed). That means that we are basically NOT doing any coalescing of changes - instead every change results
+        in its own iTIP message (pretty much as it would without the queue). Ultimately we need to support coalescing
+        for performance benefit, but the logic involved in doing that is tricky (e.g., certain properties like
+        SCHEDULE-FORCE-SEND are not preserved in the saved data, yet need to be accounted for because they change the
+        nature of the iTIP processing).
+        """
+        # Always queue up new work - coalescing happens when work is executed
+        notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.RequestDelaySeconds)
+        proposal = (yield txn.enqueue(
+            cls,
+            notBefore=notBefore,
+            icalendarUid=uid,
+            scheduleAction=scheduleActionToSQL[action],
+            homeResourceID=home.id(),
+            resourceID=resource.id() if resource else None,
+            icalendarTextOld=calendar_old.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference) if calendar_old else None,
+            icalendarTextNew=calendar_new.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference) if calendar_new else None,
+            attendeeCount=attendee_count,
+            smartMerge=smart_merge
+        ))
+        cls._enqueued()
+        yield proposal.whenProposed()
+        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:
+            home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
+            resource = (yield home.objectResourceWithID(self.resourceID))
+            organizerPrincipal = home.directoryService().recordWithUID(home.uid())
+            organizer = organizerPrincipal.canonicalCalendarUserAddress()
+            calendar_old = Component.fromString(self.icalendarTextOld) if self.icalendarTextOld else None
+            calendar_new = Component.fromString(self.icalendarTextNew) if self.icalendarTextNew else None
+
+            log.debug("ScheduleOrganizerWork - running for ID: {id}, UID: {uid}, organizer: {org}", id=self.workID, uid=self.icalendarUid, org=organizer)
+
+            # 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.queuedOrganizerProcessing(
+                self.transaction,
+                scheduleActionFromSQL[self.scheduleAction],
+                home,
+                resource,
+                self.icalendarUid,
+                calendar_old,
+                calendar_new,
+                self.smartMerge
+            )
+
+            # 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)
+
+                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(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 ScheduleReplyWorkMixin(ScheduleWorkMixin):
+
+
+    def makeScheduler(self, home):
+        """
+        Convenience method which we can override in unit tests to make testing easier.
+        """
+        from txdav.caldav.datastore.scheduling.caldav.scheduler import CalDAVScheduler
+        return CalDAVScheduler(self.transaction, home.uid())
+
+
+    @inlineCallbacks
+    def sendToOrganizer(self, home, action, itipmsg, originator, recipient):
+
+        # Send scheduling message
+
+        # This is a local CALDAV scheduling operation.
+        scheduler = self.makeScheduler(home)
+
+        # Do the PUT processing
+        log.info("Implicit %s - attendee: '%s' to organizer: '%s', UID: '%s'" % (action, originator, recipient, itipmsg.resourceUID(),))
+        response = (yield scheduler.doSchedulingViaPUT(originator, (recipient,), itipmsg, internal_request=True))
+        returnValue(response)
+
+
+
+class ScheduleReplyWork(WorkItem, fromTable(schema.SCHEDULE_REPLY_WORK), ScheduleReplyWorkMixin):
+    """
+    The associated work item table is SCHEDULE_REPLY_WORK.
+
+    This work item is used to send an iTIP reply message when an attendee changes
+    their partstat in the calendar object resource.
+    """
+
+    @classmethod
+    @inlineCallbacks
+    def reply(cls, txn, home, resource, changedRids, attendee):
+        # Always queue up new work - coalescing happens when work is executed
+        notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.ReplyDelaySeconds)
+        proposal = (yield txn.enqueue(
+            cls,
+            notBefore=notBefore,
+            icalendarUid=resource.uid(),
+            homeResourceID=home.id(),
+            resourceID=resource.id(),
+
+            # Serialize None as ""
+            changedRids=",".join(map(lambda x: "" if x is None else str(x), changedRids)) if changedRids else None,
+        ))
+        cls._enqueued()
+        yield proposal.whenProposed()
+        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:
+            home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
+            resource = (yield home.objectResourceWithID(self.resourceID))
+            attendeePrincipal = home.directoryService().recordWithUID(home.uid())
+            attendee = attendeePrincipal.canonicalCalendarUserAddress()
+            calendar = (yield resource.componentForUser())
+            organizer = calendar.validOrganizerForScheduling()
+
+            # Deserialize "" as None
+            changedRids = map(lambda x: DateTime.parseText(x) if x else None, self.changedRids.split(",")) if self.changedRids else None
+
+            log.debug("ScheduleReplyWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee)
+
+            # We need to get the UID lock for implicit processing.
+            yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(calendar.resourceUID()).hexdigest(),))
+
+            itipmsg = iTipGenerator.generateAttendeeReply(calendar, attendee, changedRids=changedRids)
+
+            # Send scheduling message and process response
+            response = (yield self.sendToOrganizer(home, "REPLY", itipmsg, attendee, organizer))
+            changed = yield self.handleSchedulingResponse(response, calendar, False)
+
+            if changed:
+                yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTENDEE_ITIP_UPDATE)
+
+            self._dequeued()
+
+        except Exception, e:
+            log.debug("ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e))
+            raise
+        except:
+            log.debug("ScheduleReplyWork - bare exception ID: {id}, UID: '{uid}'", id=self.workID, uid=calendar.resourceUID())
+            raise
+
+        log.debug("ScheduleReplyWork - done for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee)
+
+
+
+class ScheduleReplyCancelWork(WorkItem, fromTable(schema.SCHEDULE_REPLY_CANCEL_WORK), ScheduleReplyWorkMixin):
+    """
+    The associated work item table is SCHEDULE_REPLY_CANCEL_WORK.
+
+    This work item is used to send an iTIP reply message when an attendee deletes
+    their copy of the calendar object resource. For this to work we need to store a copy
+    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):
+        # Always queue up new work - coalescing happens when work is executed
+        notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.ReplyDelaySeconds)
+        proposal = (yield txn.enqueue(
+            cls,
+            notBefore=notBefore,
+            icalendarUid=calendar.resourceUID(),
+            homeResourceID=home.id(),
+            icalendarText=calendar.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference),
+        ))
+        cls._enqueued()
+        yield proposal.whenProposed()
+        log.debug("ScheduleReplyCancelWork - enqueued for ID: {id}, UID: {uid}, attendee: {att}", id=proposal.workItem.workID, uid=calendar.resourceUID(), att=attendee)
+
+
+    @inlineCallbacks
+    def doWork(self):
+
+        try:
+            home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
+            attendeePrincipal = home.directoryService().recordWithUID(home.uid())
+            attendee = attendeePrincipal.canonicalCalendarUserAddress()
+            calendar = Component.fromString(self.icalendarText)
+            organizer = calendar.validOrganizerForScheduling()
+
+            log.debug("ScheduleReplyCancelWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee)
+
+            # We need to get the UID lock for implicit processing.
+            yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(calendar.resourceUID()).hexdigest(),))
+
+            itipmsg = iTipGenerator.generateAttendeeReply(calendar, attendee, force_decline=True)
+
+            # Send scheduling message - no need to process response as original resource is gone
+            yield self.sendToOrganizer(home, "CANCEL", itipmsg, attendee, organizer)
+
+            self._dequeued()
+
+        except Exception, e:
+            log.debug("ScheduleReplyCancelWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e))
+            raise
+        except:
+            log.debug("ScheduleReplyCancelWork - bare exception ID: {id}, UID: '{uid}'", id=self.workID, uid=calendar.resourceUID())
+            raise
+
+        log.debug("ScheduleReplyCancelWork - done for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee)
+
+
+
+class ScheduleRefreshWork(WorkItem, fromTable(schema.SCHEDULE_REFRESH_WORK), ScheduleWorkMixin):
+    """
+    The associated work item table is SCHEDULE_REFRESH_WORK.
+
+    This work item is used to trigger an iTIP refresh of attendees. This happens when one attendee
+    replies to an invite, and we want to have the others attendees see that change - eventually. We
+    are going to use the SCHEDULE_REFRESH_ATTENDEES table to track the list of attendees needing
+    a refresh for each calendar object resource (identified by the organizer's resource-id for that
+    calendar object). We want to do refreshes in batches with a configurable time between each batch.
+
+    The tricky part here is handling race conditions, where two or more attendee replies happen at the
+    same time, or happen whilst a previously queued refresh has started batch processing. Here is how
+    we will handle that:
+
+    1) Each time a refresh is needed we will add all attendees to the SCHEDULE_REFRESH_ATTENDEES table.
+    This will happen even if those attendees are currently listed in that table. We ensure the table is
+    not unique wrt to attendees - this means that two simultaneous refreshes can happily insert the
+    same set of attendees without running into unique constraints and thus without having to use
+    savepoints to cope with that. This will mean duplicate attendees listed in the table, but we take
+    care of that when executing the work item, as per the next point. We also always schedule a new work
+    item for the refresh - even if others are present. The work items are coalesced when executed, with
+    the actual refresh only running at the time of the latest enqueued item. That ensures there is always
+    a pause between a change that causes a refresh and then next actual refresh batch being done, giving
+    some breathing space in case rapid changes are happening to the iCalendar data.
+
+    2) When a work item is triggered we get the set of unique attendees needing a refresh from the
+    SCHEDULE_REFRESH_ATTENDEES table. We split out a batch of those to actually refresh - with the
+    others being left in the table as-is. We then remove the batch of attendees from the
+    SCHEDULE_REFRESH_ATTENDEES table - this will remove duplicates. The refresh is then done and a
+    new work item scheduled to do the next batch. We only stop rescheduling work items when nothing
+    is found during the initial query. Note that if any refresh is done we will always reschedule work
+    even if we know none remain. That should handle the case where a new refresh occurs whilst
+    processing the last batch from a previous refresh.
+
+    Hopefully the above methodology will deal with concurrency issues, preventing any excessive locking
+    or failed inserts etc.
+    """
+
+    @classmethod
+    @inlineCallbacks
+    def refreshAttendees(cls, txn, organizer_resource, organizer_calendar, attendees):
+        # See if there is already a pending refresh and merge current attendees into that list,
+        # otherwise just mark all attendees as pending
+        sra = schema.SCHEDULE_REFRESH_ATTENDEES
+        pendingAttendees = (yield Select(
+            [sra.ATTENDEE, ],
+            From=sra,
+            Where=sra.RESOURCE_ID == organizer_resource.id(),
+        ).on(txn))
+        pendingAttendees = [row[0] for row in pendingAttendees]
+        attendeesToRefresh = set(attendees) - set(pendingAttendees)
+        for attendee in attendeesToRefresh:
+            yield Insert(
+                {
+                    sra.RESOURCE_ID: organizer_resource.id(),
+                    sra.ATTENDEE: attendee,
+                }
+            ).on(txn)
+
+        # Always queue up new work - coalescing happens when work is executed
+        notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.AttendeeRefreshBatchDelaySeconds)
+        proposal = (yield txn.enqueue(
+            cls,
+            icalendarUid=organizer_resource.uid(),
+            homeResourceID=organizer_resource._home.id(),
+            resourceID=organizer_resource.id(),
+            attendeeCount=len(attendees),
+            notBefore=notBefore,
+        ))
+        cls._enqueued()
+        yield proposal.whenProposed()
+        log.debug("ScheduleRefreshWork - enqueued for ID: {id}, UID: {uid}, attendees: {att}", id=proposal.workItem.workID, uid=organizer_resource.uid(), att=",".join(attendeesToRefresh))
+
+
+    @inlineCallbacks
+    def doWork(self):
+
+        # Look for other work items for this resource and ignore this one if other later ones exist
+        srw = schema.SCHEDULE_REFRESH_WORK
+        rows = (yield Select(
+            (srw.WORK_ID,),
+            From=srw,
+            Where=(srw.HOME_RESOURCE_ID == self.homeResourceID).And(
+                   srw.RESOURCE_ID == self.resourceID),
+        ).on(self.transaction))
+        if rows:
+            log.debug("Schedule refresh for resource-id: {rid} - ignored", rid=self.resourceID)
+            returnValue(None)
+
+        log.debug("ScheduleRefreshWork - running for ID: {id}, UID: {uid}", id=self.workID, uid=self.icalendarUid)
+
+        # Get the unique list of pending attendees and split into batch to process
+        # TODO: do a DELETE ... and rownum <= N returning attendee - but have to fix Oracle to
+        # handle multi-row returning. Would be better than entire select + delete of each one,
+        # but need to make sure to use UNIQUE as there may be duplicate attendees.
+        sra = schema.SCHEDULE_REFRESH_ATTENDEES
+        pendingAttendees = (yield Select(
+            [sra.ATTENDEE, ],
+            From=sra,
+            Where=sra.RESOURCE_ID == self.resourceID,
+        ).on(self.transaction))
+        pendingAttendees = list(set([row[0] for row in pendingAttendees]))
+
+        # Nothing left so done
+        if len(pendingAttendees) == 0:
+            returnValue(None)
+
+        attendeesToProcess = pendingAttendees[:config.Scheduling.Options.AttendeeRefreshBatch]
+        pendingAttendees = pendingAttendees[config.Scheduling.Options.AttendeeRefreshBatch:]
+
+        yield Delete(
+            From=sra,
+            Where=(sra.RESOURCE_ID == self.resourceID).And(sra.ATTENDEE.In(Parameter("attendeesToProcess", len(attendeesToProcess))))
+        ).on(self.transaction, attendeesToProcess=attendeesToProcess)
+
+        # Reschedule work item if pending attendees remain.
+        if len(pendingAttendees) != 0:
+            notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.AttendeeRefreshBatchIntervalSeconds)
+            yield self.transaction.enqueue(
+                self.__class__,
+                icalendarUid=self.icalendarUid,
+                homeResourceID=self.homeResourceID,
+                resourceID=self.resourceID,
+                attendeeCount=len(pendingAttendees),
+                notBefore=notBefore
+            )
+
+            self._enqueued()
+
+        # Do refresh
+        yield self._doDelayedRefresh(attendeesToProcess)
+
+        self._dequeued()
+
+        log.debug("ScheduleRefreshWork - done for ID: {id}, UID: {uid}", id=self.workID, uid=self.icalendarUid)
+
+
+    @inlineCallbacks
+    def _doDelayedRefresh(self, attendeesToProcess):
+        """
+        Do an attendee refresh that has been delayed until after processing of the request that called it. That
+        requires that we create a new transaction to work with.
+
+        @param attendeesToProcess: list of attendees to refresh.
+        @type attendeesToProcess: C{list}
+        """
+
+        organizer_home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
+        organizer_resource = (yield organizer_home.objectResourceWithID(self.resourceID))
+        if organizer_resource is not None:
+            try:
+                # We need to get the UID lock for implicit processing whilst we send the auto-reply
+                # as the Organizer processing will attempt to write out data to other attendees to
+                # refresh them. To prevent a race we need a lock.
+                yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(organizer_resource.uid()).hexdigest(),))
+
+                yield self._doRefresh(organizer_resource, attendeesToProcess)
+            except Exception, e:
+                log.debug("ImplicitProcessing - refresh exception UID: '{uid}', {exc}", uid=organizer_resource.uid(), exc=str(e))
+                raise
+            except:
+                log.debug("ImplicitProcessing - refresh bare exception UID: '{uid}'", uid=organizer_resource.uid())
+                raise
+        else:
+            log.debug("ImplicitProcessing - skipping refresh of missing ID: '{rid}'", rid=self.resourceID)
+
+
+    @inlineCallbacks
+    def _doRefresh(self, organizer_resource, only_attendees):
+        """
+        Do a refresh of attendees.
+
+        @param organizer_resource: the resource for the organizer's calendar data
+        @type organizer_resource: L{DAVResource}
+        @param only_attendees: list of attendees to refresh (C{None} - refresh all)
+        @type only_attendees: C{tuple}
+        """
+        log.debug("ImplicitProcessing - refreshing UID: '{uid}', Attendees: {att}", uid=organizer_resource.uid(), att=", ".join(only_attendees) if only_attendees else "all")
+        from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
+        scheduler = ImplicitScheduler()
+        yield scheduler.refreshAllAttendeesExceptSome(
+            self.transaction,
+            organizer_resource,
+            only_attendees=only_attendees,
+        )
+
+
+
+class ScheduleAutoReplyWork(WorkItem, fromTable(schema.SCHEDULE_AUTO_REPLY_WORK), ScheduleWorkMixin):
+    """
+    The associated work item table is SCHEDULE_AUTO_REPLY_WORK.
+
+    This work item is used to send auto-reply iTIP messages after the calendar data for the
+    auto-accept user has been written to the user calendar.
+    """
+
+    @classmethod
+    @inlineCallbacks
+    def autoReply(cls, txn, resource, partstat):
+        # Always queue up new work - coalescing happens when work is executed
+        notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.AutoReplyDelaySeconds)
+        proposal = (yield txn.enqueue(
+            cls,
+            icalendarUid=resource.uid(),
+            homeResourceID=resource._home.id(),
+            resourceID=resource.id(),
+            partstat=partstat,
+            notBefore=notBefore,
+        ))
+        cls._enqueued()
+        yield proposal.whenProposed()
+        log.debug("ScheduleAutoReplyWork - enqueued for ID: {id}, UID: {uid}", id=proposal.workItem.workID, uid=resource.uid())
+
+
+    @inlineCallbacks
+    def doWork(self):
+
+        log.debug("ScheduleAutoReplyWork - running for ID: {id}, UID: {uid}", id=self.workID, uid=self.icalendarUid)
+
+        # Delete all other work items with the same pushID
+        yield Delete(From=self.table,
+            Where=self.table.RESOURCE_ID == self.resourceID
+        ).on(self.transaction)
+
+        # Do reply
+        yield self._sendAttendeeAutoReply()
+
+        self._dequeued()
+
+        log.debug("ScheduleAutoReplyWork - done for ID: {id}, UID: {uid}", id=self.workID, uid=self.icalendarUid)
+
+
+    @inlineCallbacks
+    def _sendAttendeeAutoReply(self):
+        """
+        Auto-process the calendar option to generate automatic accept/decline status and
+        send a reply if needed.
+
+        We used to have logic to suppress attendee refreshes until after all auto-replies have
+        been processed. We can't do that with the work queue (easily) so we are going to ignore
+        that for now. It may not be a big deal given that the refreshes are themselves done in the
+        queue and we only do the refresh when the last queued work item is processed.
+
+        @param resource: calendar resource to process
+        @type resource: L{CalendarObject}
+        @param partstat: new partstat value
+        @type partstat: C{str}
+        """
+
+        home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
+        resource = (yield home.objectResourceWithID(self.resourceID))
+        if resource is not None:
+            try:
+                # We need to get the UID lock for implicit processing whilst we send the auto-reply
+                # as the Organizer processing will attempt to write out data to other attendees to
+                # refresh them. To prevent a race we need a lock.
+                yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(resource.uid()).hexdigest(),))
+
+                # Send out a reply
+                log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply: %s" % (home.uid(), resource.uid(), self.partstat))
+                from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
+                scheduler = ImplicitScheduler()
+                yield scheduler.sendAttendeeReply(self.transaction, resource)
+            except Exception, e:
+                log.debug("ImplicitProcessing - auto-reply exception UID: '%s', %s" % (resource.uid(), str(e)))
+                raise
+            except:
+                log.debug("ImplicitProcessing - auto-reply bare exception UID: '%s'" % (resource.uid(),))
+                raise
+        else:
+            log.debug("ImplicitProcessing - skipping auto-reply of missing ID: '{rid}'", rid=self.resourceID)

Copied: CalendarServer/trunk/txdav/caldav/datastore/test/test_queue_scheduling.py (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/test/test_queue_scheduling.py)
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_queue_scheduling.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_queue_scheduling.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -0,0 +1,254 @@
+##
+# Copyright (c) 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 twisted.internet.defer import inlineCallbacks, returnValue
+
+from twext.python.clsprop import classproperty
+from twistedcaldav.config import config
+from txdav.caldav.datastore.scheduling.work import ScheduleWorkMixin
+from txdav.caldav.datastore.test.util import CommonStoreTests
+from txdav.common.datastore.test.util import componentUpdate
+from twistedcaldav.ical import normalize_iCalStr
+
+class BaseQueueSchedulingTests(CommonStoreTests):
+
+    """
+    Test store-based calendar sharing.
+    """
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(BaseQueueSchedulingTests, self).setUp()
+
+        # Enable the queue and make it fast
+        self.patch(config.Scheduling.Options.WorkQueues, "Enabled", True)
+        self.patch(config.Scheduling.Options.WorkQueues, "RequestDelaySeconds", 1)
+        self.patch(config.Scheduling.Options.WorkQueues, "ReplyDelaySeconds", 1)
+        self.patch(config.Scheduling.Options.WorkQueues, "AutoReplyDelaySeconds", 1)
+        self.patch(config.Scheduling.Options.WorkQueues, "AttendeeRefreshBatchDelaySeconds", 1)
+        self.patch(config.Scheduling.Options.WorkQueues, "AttendeeRefreshBatchIntervalSeconds", 1)
+
+
+    @classproperty(cache=False)
+    def requirements(cls): #@NoSelf
+        return {
+        "user01": {
+            "calendar": {
+            },
+            "inbox": {
+            },
+        },
+        "user02": {
+            "calendar": {
+            },
+            "inbox": {
+            },
+        },
+        "user03": {
+            "calendar": {
+            },
+            "inbox": {
+            },
+        },
+    }
+
+
+    @inlineCallbacks
+    def _getOneResource(self, home, calendar_name):
+        """
+        Get the one resources expected in a collection
+        """
+        inbox = yield self.calendarUnderTest(home=home, name=calendar_name)
+        objs = yield inbox.objectResources()
+        self.assertEqual(len(objs), 1)
+        returnValue(objs[0])
+
+
+    @inlineCallbacks
+    def _testOneResource(self, home, calendar_name, data):
+        """
+        Get the one resources expected in a collection
+        """
+        inbox = yield self.calendarUnderTest(home=home, name=calendar_name)
+        objs = yield inbox.objectResources()
+        self.assertEqual(len(objs), 1)
+
+        caldata = yield objs[0].componentForUser()
+        self.assertEqual(normalize_iCalStr(caldata), normalize_iCalStr(componentUpdate(data)))
+
+
+
+class SimpleSchedulingTests(BaseQueueSchedulingTests):
+
+    @inlineCallbacks
+    def test_invite_reply(self):
+        """
+        Test simple invite/reply roundtrip.
+        """
+
+        data1 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:{now}T000000Z
+DURATION:PT1H
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        data2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:{now}T000000Z
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:uuid:user02
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        data3 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:{now}T000000Z
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        data4 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:{now}T000000Z
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:user02
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+SUMMARY:1
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+"""
+
+        data5 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:{now}T000000Z
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user02
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        data6 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:{now}T000000Z
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:urn:uuid:user02
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        data7 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:{now}T000000Z
+DURATION:PT1H
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user02
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:user01
+SUMMARY:1
+REQUEST-STATUS:2.0;Success
+END:VEVENT
+END:VCALENDAR
+"""
+
+        data8 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:{now}T000000Z
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user01
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;PARTSTAT=ACCEPTED:urn:uuid:user02
+DTSTAMP:20051222T210507Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com;SCHEDULE-STATUS=1.2:urn:uuid:user01
+SUMMARY:1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        waitForWork = ScheduleWorkMixin.allDone()
+        calendar = yield self.calendarUnderTest(home="user01", name="calendar")
+        yield calendar.createCalendarObjectWithName("data1.ics", componentUpdate(data1))
+        yield self.commit()
+
+        yield waitForWork
+
+        yield self._testOneResource("user01", "calendar", data2)
+        yield self._testOneResource("user02", "inbox", data3)
+        yield self._testOneResource("user02", "calendar", data4)
+        yield self.commit()
+
+        waitForWork = ScheduleWorkMixin.allDone()
+        cobj = yield self._getOneResource("user02", "calendar")
+        yield cobj.setComponent(componentUpdate(data5))
+        yield self.commit()
+
+        yield waitForWork
+
+        yield self._testOneResource("user01", "calendar", data6)
+        yield self._testOneResource("user01", "inbox", data7)
+        yield self._testOneResource("user02", "calendar", data8)

Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -395,12 +395,74 @@
     "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
 );
 
+create table SCHEDULE_REFRESH_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "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)
+);
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "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,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "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_REPLY_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "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,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "HOME_RESOURCE_ID" integer not null references CALENDAR_HOME on delete cascade,
+    "ICALENDAR_TEXT" nclob
+);
+
 create table CALENDARSERVER (
     "NAME" nvarchar2(255) primary key,
     "VALUE" nvarchar2(255)
 );
 
-insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '33');
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '34');
 insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '5');
 insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
 insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
@@ -538,3 +600,44 @@
     RESOURCE_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
+);
+
+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_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
+);
+

Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -742,6 +742,117 @@
   NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
 );
 
+---------------------------
+-- Schedule Refresh Work --
+---------------------------
+
+create table SCHEDULE_REFRESH_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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
+);
+
+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 default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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 default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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 Reply Work --
+-------------------------
+
+create table SCHEDULE_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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 default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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);
+
 --------------------
 -- Schema Version --
 --------------------
@@ -751,7 +862,7 @@
   VALUE                         varchar(255)
 );
 
-insert into CALENDARSERVER values ('VERSION', '33');
+insert into CALENDARSERVER values ('VERSION', '34');
 insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
 insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
 insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');

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

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

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v33.sql (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/old/oracle-dialect/v33.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v33.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v33.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -0,0 +1,540 @@
+create sequence RESOURCE_ID_SEQ;
+create sequence INSTANCE_ID_SEQ;
+create sequence ATTACHMENT_ID_SEQ;
+create sequence REVISION_SEQ;
+create sequence WORKITEM_SEQ;
+create table NODE_INFO (
+    "HOSTNAME" nvarchar2(255),
+    "PID" integer not null,
+    "PORT" integer not null,
+    "TIME" timestamp default CURRENT_TIMESTAMP at time zone 'UTC' not null, 
+    primary key("HOSTNAME", "PORT")
+);
+
+create table NAMED_LOCK (
+    "LOCK_NAME" nvarchar2(255) primary key
+);
+
+create table CALENDAR_HOME (
+    "RESOURCE_ID" integer primary key,
+    "OWNER_UID" nvarchar2(255) unique,
+    "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);
+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_OBJECT_ATTACHMENTS_MO (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into CALENDAR_OBJECT_ATTACHMENTS_MO (DESCRIPTION, ID) values ('none', 0);
+insert into CALENDAR_OBJECT_ATTACHMENTS_MO (DESCRIPTION, ID) values ('read', 1);
+insert into CALENDAR_OBJECT_ATTACHMENTS_MO (DESCRIPTION, ID) values ('write', 2);
+create table CALENDAR_ACCESS_TYPE (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(32) unique
+);
+
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('', 0);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('public', 1);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('private', 2);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('confidential', 3);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('restricted', 4);
+create table TIME_RANGE (
+    "INSTANCE_ID" integer primary key,
+    "CALENDAR_RESOURCE_ID" integer not null references CALENDAR on delete cascade,
+    "CALENDAR_OBJECT_RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "FLOATING" integer not null,
+    "START_DATE" timestamp not null,
+    "END_DATE" timestamp not null,
+    "FBTYPE" integer not null,
+    "TRANSPARENT" integer not null
+);
+
+create table FREE_BUSY_TYPE (
+    "ID" integer primary key,
+    "DESCRIPTION" nvarchar2(16) unique
+);
+
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('unknown', 0);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('free', 1);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('busy', 2);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('busy-unavailable', 3);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('busy-tentative', 4);
+create table TRANSPARENCY (
+    "TIME_RANGE_INSTANCE_ID" integer not null references TIME_RANGE on delete cascade,
+    "USER_ID" nvarchar2(255),
+    "TRANSPARENT" integer not null
+);
+
+create table ATTACHMENT (
+    "ATTACHMENT_ID" integer primary key,
+    "CALENDAR_HOME_RESOURCE_ID" integer not null references CALENDAR_HOME,
+    "DROPBOX_ID" nvarchar2(255),
+    "CONTENT_TYPE" nvarchar2(255),
+    "SIZE" integer not null,
+    "MD5" nchar(32),
+    "CREATED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "MODIFIED" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "PATH" nvarchar2(1024)
+);
+
+create table ATTACHMENT_CALENDAR_OBJECT (
+    "ATTACHMENT_ID" integer not null references ATTACHMENT on delete cascade,
+    "MANAGED_ID" nvarchar2(255),
+    "CALENDAR_OBJECT_RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade, 
+    primary key("ATTACHMENT_ID", "CALENDAR_OBJECT_RESOURCE_ID"), 
+    unique("MANAGED_ID", "CALENDAR_OBJECT_RESOURCE_ID")
+);
+
+create table RESOURCE_PROPERTY (
+    "RESOURCE_ID" integer not null,
+    "NAME" nvarchar2(255),
+    "VALUE" nclob,
+    "VIEWER_UID" nvarchar2(255), 
+    primary key("RESOURCE_ID", "NAME", "VIEWER_UID")
+);
+
+create table ADDRESSBOOK_HOME (
+    "RESOURCE_ID" integer primary key,
+    "ADDRESSBOOK_PROPERTY_STORE_ID" integer not null,
+    "OWNER_UID" nvarchar2(255) unique,
+    "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,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "FROM_ADDR" nvarchar2(255),
+    "TO_ADDR" nvarchar2(255),
+    "ICALENDAR_TEXT" nclob
+);
+
+create table IMIP_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table IMIP_REPLY_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ORGANIZER" nvarchar2(255),
+    "ATTENDEE" nvarchar2(255),
+    "ICALENDAR_TEXT" nclob
+);
+
+create table PUSH_NOTIFICATION_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "PUSH_ID" nvarchar2(255),
+    "PRIORITY" integer not null
+);
+
+create table GROUP_CACHER_POLLING_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade
+);
+
+create table FIND_MIN_VALID_REVISION_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table REVISION_CLEANUP_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table CALENDARSERVER (
+    "NAME" nvarchar2(255) primary key,
+    "VALUE" nvarchar2(255)
+);
+
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '33');
+insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '5');
+insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
+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 TRANSPARENCY_TIME_RAN_5f34467f on TRANSPARENCY (
+    TIME_RANGE_INSTANCE_ID
+);
+
+create index ATTACHMENT_CALENDAR_H_0078845c on ATTACHMENT (
+    CALENDAR_HOME_RESOURCE_ID
+);
+
+create index ATTACHMENT_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 CALENDAR_OBJECT_SPLIT_af71dcda on CALENDAR_OBJECT_SPLITTER_WORK (
+    RESOURCE_ID
+);
+

Deleted: CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v25.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v25.sql	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v25.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -1,700 +0,0 @@
--- -*- 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
-);
-
-
--------------------
--- Calendar Home --
--------------------
-
-create table CALENDAR_HOME (
-  RESOURCE_ID      integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
-  OWNER_UID        varchar(255) not null unique,                                 -- implicit index
-  DATAVERSION      integer      default 0 not null
-);
-
---------------
--- Calendar --
---------------
-
-create table CALENDAR (
-  RESOURCE_ID integer   primary key default nextval('RESOURCE_ID_SEQ') -- implicit index
-);
-
-----------------------------
--- Calendar Home Metadata --
-----------------------------
-
-create table CALENDAR_HOME_METADATA (
-  RESOURCE_ID              integer     primary key references CALENDAR_HOME on delete cascade, -- implicit index
-  QUOTA_USED_BYTES         integer     default 0 not null,
-  DEFAULT_EVENTS           integer     default null references CALENDAR on delete set null,
-  DEFAULT_TASKS            integer     default null references CALENDAR on delete set null,
-  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);
-
------------------------
--- 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
-);
-
-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,
-  XML_TYPE                      varchar(255) not null,
-  XML_DATA                      text         not null,
-  MD5                           char(32)     not null,
-  CREATED                       timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-
-  unique(NOTIFICATION_UID, NOTIFICATION_HOME_RESOURCE_ID) -- implicit index
-);
-
-create index NOTIFICATION_NOTIFICATION_HOME_RESOURCE_ID on
-	NOTIFICATION(NOTIFICATION_HOME_RESOURCE_ID);
-
-
--------------------
--- Calendar Bind --
--------------------
-
--- Joins CALENDAR_HOME and CALENDAR
-
-create table CALENDAR_BIND (
-  CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
-  CALENDAR_RESOURCE_ID      integer      not null references CALENDAR on delete cascade,
-  CALENDAR_RESOURCE_NAME    varchar(255) not null,
-  BIND_MODE                 integer      not null, -- enum CALENDAR_BIND_MODE
-  BIND_STATUS               integer      not null, -- enum CALENDAR_BIND_STATUS
-  BIND_REVISION				integer      default 0 not null,
-  MESSAGE                   text,
-  TRANSP                    integer      default 0 not null, -- enum CALENDAR_TRANSP
-  ALARM_VEVENT_TIMED        text         default null,
-  ALARM_VEVENT_ALLDAY       text         default null,
-  ALARM_VTODO_TIMED         text         default null,
-  ALARM_VTODO_ALLDAY        text         default null,
-  TIMEZONE                  text         default null,
-
-  primary key(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID), -- implicit index
-  unique(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_NAME)     -- implicit index
-);
-
-create index CALENDAR_BIND_RESOURCE_ID on
-	CALENDAR_BIND(CALENDAR_RESOURCE_ID);
-
--- Enumeration of calendar bind modes
-
-create table CALENDAR_BIND_MODE (
-  ID          integer     primary key,
-  DESCRIPTION varchar(16) not null unique
-);
-
-insert into CALENDAR_BIND_MODE values (0, 'own'  );
-insert into CALENDAR_BIND_MODE values (1, 'read' );
-insert into CALENDAR_BIND_MODE values (2, 'write');
-insert into CALENDAR_BIND_MODE values (3, 'direct');
-
--- 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');
-
-
--- Enumeration of transparency
-
-create table CALENDAR_TRANSP (
-  ID          integer     primary key,
-  DESCRIPTION varchar(16) not null unique
-);
-
-insert into CALENDAR_TRANSP values (0, 'opaque' );
-insert into CALENDAR_TRANSP values (1, 'transparent');
-
-
----------------------
--- Calendar Object --
----------------------
-
-create table CALENDAR_OBJECT (
-  RESOURCE_ID          integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
-  CALENDAR_RESOURCE_ID integer      not null references CALENDAR on delete cascade,
-  RESOURCE_NAME        varchar(255) not null,
-  ICALENDAR_TEXT       text         not null,
-  ICALENDAR_UID        varchar(255) not null,
-  ICALENDAR_TYPE       varchar(255) not null,
-  ATTACHMENTS_MODE     integer      default 0 not null, -- enum CALENDAR_OBJECT_ATTACHMENTS_MODE
-  DROPBOX_ID           varchar(255),
-  ORGANIZER            varchar(255),
-  RECURRANCE_MIN       date,        -- minimum date that recurrences have been expanded to.
-  RECURRANCE_MAX       date,        -- maximum date that recurrences have been expanded to.
-  ACCESS               integer      default 0 not null,
-  SCHEDULE_OBJECT      boolean      default false,
-  SCHEDULE_TAG         varchar(36)  default null,
-  SCHEDULE_ETAGS       text         default null,
-  PRIVATE_COMMENTS     boolean      default false not null,
-  MD5                  char(32)     not null,
-  CREATED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED             timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-
-  unique (CALENDAR_RESOURCE_ID, RESOURCE_NAME) -- implicit index
-
-  -- since the 'inbox' is a 'calendar resource' for the purpose of storing
-  -- calendar objects, this constraint has to be selectively enforced by the
-  -- application layer.
-
-  -- unique(CALENDAR_RESOURCE_ID, ICALENDAR_UID)
-);
-
-create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_AND_ICALENDAR_UID on
-  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, ICALENDAR_UID);
-
-create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_RECURRANCE_MAX on
-  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, RECURRANCE_MAX);
-
-create index CALENDAR_OBJECT_ICALENDAR_UID on
-  CALENDAR_OBJECT(ICALENDAR_UID);
-
-create index CALENDAR_OBJECT_DROPBOX_ID on
-  CALENDAR_OBJECT(DROPBOX_ID);
-
--- Enumeration of attachment modes
-
-create table CALENDAR_OBJECT_ATTACHMENTS_MODE (
-  ID          integer     primary key,
-  DESCRIPTION varchar(16) not null unique
-);
-
-insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (0, 'none' );
-insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (1, 'read' );
-insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (2, 'write');
-
-
--- Enumeration of calendar access types
-
-create table CALENDAR_ACCESS_TYPE (
-  ID          integer     primary key,
-  DESCRIPTION varchar(32) not null unique
-);
-
-insert into CALENDAR_ACCESS_TYPE values (0, ''             );
-insert into CALENDAR_ACCESS_TYPE values (1, 'public'       );
-insert into CALENDAR_ACCESS_TYPE values (2, 'private'      );
-insert into CALENDAR_ACCESS_TYPE values (3, 'confidential' );
-insert into CALENDAR_ACCESS_TYPE values (4, 'restricted'   );
-
-
------------------
--- Instance ID --
------------------
-
-create sequence INSTANCE_ID_SEQ;
-
-
-----------------
--- Time Range --
-----------------
-
-create table TIME_RANGE (
-  INSTANCE_ID                 integer        primary key default nextval('INSTANCE_ID_SEQ'), -- implicit index
-  CALENDAR_RESOURCE_ID        integer        not null references CALENDAR on delete cascade,
-  CALENDAR_OBJECT_RESOURCE_ID integer        not null references CALENDAR_OBJECT on delete cascade,
-  FLOATING                    boolean        not null,
-  START_DATE                  timestamp      not null,
-  END_DATE                    timestamp      not null,
-  FBTYPE                      integer        not null,
-  TRANSPARENT                 boolean        not null
-);
-
-create index TIME_RANGE_CALENDAR_RESOURCE_ID on
-  TIME_RANGE(CALENDAR_RESOURCE_ID);
-create index TIME_RANGE_CALENDAR_OBJECT_RESOURCE_ID on
-  TIME_RANGE(CALENDAR_OBJECT_RESOURCE_ID);
-
-
--- Enumeration of free/busy types
-
-create table FREE_BUSY_TYPE (
-  ID          integer     primary key,
-  DESCRIPTION varchar(16) not null unique
-);
-
-insert into FREE_BUSY_TYPE values (0, 'unknown'         );
-insert into FREE_BUSY_TYPE values (1, 'free'            );
-insert into FREE_BUSY_TYPE values (2, 'busy'            );
-insert into FREE_BUSY_TYPE values (3, 'busy-unavailable');
-insert into FREE_BUSY_TYPE values (4, 'busy-tentative'  );
-
-
-------------------
--- Transparency --
-------------------
-
-create table TRANSPARENCY (
-  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
-  USER_ID                     varchar(255) not null,
-  TRANSPARENT                 boolean      not null
-);
-
-create index TRANSPARENCY_TIME_RANGE_INSTANCE_ID on
-  TRANSPARENCY(TIME_RANGE_INSTANCE_ID);
-
-
-----------------
--- Attachment --
-----------------
-
-create sequence ATTACHMENT_ID_SEQ;
-
-create table ATTACHMENT (
-  ATTACHMENT_ID               integer           primary key default nextval('ATTACHMENT_ID_SEQ'), -- implicit index
-  CALENDAR_HOME_RESOURCE_ID   integer           not null references CALENDAR_HOME,
-  DROPBOX_ID                  varchar(255),
-  CONTENT_TYPE                varchar(255)      not null,
-  SIZE                        integer           not null,
-  MD5                         char(32)          not null,
-  CREATED                     timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED                    timestamp default timezone('UTC', CURRENT_TIMESTAMP),
-  PATH                        varchar(1024)     not null
-);
-
-create index ATTACHMENT_CALENDAR_HOME_RESOURCE_ID on
-  ATTACHMENT(CALENDAR_HOME_RESOURCE_ID);
-
--- Many-to-many relationship between attachments and calendar objects
-create table ATTACHMENT_CALENDAR_OBJECT (
-  ATTACHMENT_ID                  integer      not null references ATTACHMENT on delete cascade,
-  MANAGED_ID                     varchar(255) not null,
-  CALENDAR_OBJECT_RESOURCE_ID    integer      not null references CALENDAR_OBJECT on delete cascade,
-
-  primary key (ATTACHMENT_ID, CALENDAR_OBJECT_RESOURCE_ID), -- implicit index
-  unique (MANAGED_ID, CALENDAR_OBJECT_RESOURCE_ID) --implicit index
-);
-
-create index ATTACHMENT_CALENDAR_OBJECT_CALENDAR_OBJECT_RESOURCE_ID on
-	ATTACHMENT_CALENDAR_OBJECT(CALENDAR_OBJECT_RESOURCE_ID);
-
------------------------
--- Resource Property --
------------------------
-
-create table RESOURCE_PROPERTY (
-  RESOURCE_ID integer      not null, -- foreign key: *.RESOURCE_ID
-  NAME        varchar(255) not null,
-  VALUE       text         not null, -- FIXME: xml?
-  VIEWER_UID  varchar(255),
-
-  primary key (RESOURCE_ID, NAME, VIEWER_UID) -- implicit index
-);
-
-
-----------------------
--- AddressBook Home --
-----------------------
-
-create table ADDRESSBOOK_HOME (
-  RESOURCE_ID      				integer			primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
-  ADDRESSBOOK_PROPERTY_STORE_ID	integer      	default nextval('RESOURCE_ID_SEQ') not null, 	-- implicit index
-  OWNER_UID        				varchar(255) 	not null unique,                                -- implicit index
-  DATAVERSION      				integer      	default 0 not null
-);
-
-
--------------------------------
--- AddressBook Home Metadata --
--------------------------------
-
-create table ADDRESSBOOK_HOME_METADATA (
-  RESOURCE_ID      integer      primary key references ADDRESSBOOK_HOME on delete cascade, -- implicit index
-  QUOTA_USED_BYTES integer      default 0 not null,
-  CREATED          timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED         timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
-);
-
-
------------------------------
--- Shared AddressBook Bind --
------------------------------
-
--- Joins sharee ADDRESSBOOK_HOME and owner ADDRESSBOOK_HOME
-
-create table SHARED_ADDRESSBOOK_BIND (
-  ADDRESSBOOK_HOME_RESOURCE_ID			integer			not null references ADDRESSBOOK_HOME,
-  OWNER_HOME_RESOURCE_ID    			integer      	not null references ADDRESSBOOK_HOME on delete cascade,
-  ADDRESSBOOK_RESOURCE_NAME    			varchar(255) 	not null,
-  BIND_MODE                    			integer      	not null,	-- enum CALENDAR_BIND_MODE
-  BIND_STATUS                  			integer      	not null,	-- enum CALENDAR_BIND_STATUS
-  BIND_REVISION				   			integer      	default 0 not null,
-  MESSAGE                      			text,                  		-- FIXME: xml?
-
-  primary key (ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID), -- implicit index
-  unique (ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME)     -- implicit index
-);
-
-create index SHARED_ADDRESSBOOK_BIND_RESOURCE_ID on
-  SHARED_ADDRESSBOOK_BIND(OWNER_HOME_RESOURCE_ID);
-
-
-------------------------
--- AddressBook Object --
-------------------------
-
-create table ADDRESSBOOK_OBJECT (
-  RESOURCE_ID             		integer   		primary key default nextval('RESOURCE_ID_SEQ'),    -- implicit index
-  ADDRESSBOOK_HOME_RESOURCE_ID 	integer      	not null references ADDRESSBOOK_HOME on delete cascade,
-  RESOURCE_NAME           		varchar(255) 	not null,
-  VCARD_TEXT              		text         	not null,
-  VCARD_UID               		varchar(255) 	not null,
-  KIND 			  		  		integer      	not null,  -- enum ADDRESSBOOK_OBJECT_KIND
-  MD5                     		char(32)     	not null,
-  CREATED                 		timestamp    	default timezone('UTC', CURRENT_TIMESTAMP),
-  MODIFIED                		timestamp    	default timezone('UTC', CURRENT_TIMESTAMP),
-
-  unique (ADDRESSBOOK_HOME_RESOURCE_ID, RESOURCE_NAME), -- implicit index
-  unique (ADDRESSBOOK_HOME_RESOURCE_ID, VCARD_UID)      -- implicit index
-);
-
-
------------------------------
--- AddressBook Object kind --
------------------------------
-
-create table ADDRESSBOOK_OBJECT_KIND (
-  ID          integer     primary key,
-  DESCRIPTION varchar(16) not null unique
-);
-
-insert into ADDRESSBOOK_OBJECT_KIND values (0, 'person');
-insert into ADDRESSBOOK_OBJECT_KIND values (1, 'group' );
-insert into ADDRESSBOOK_OBJECT_KIND values (2, 'resource');
-insert into ADDRESSBOOK_OBJECT_KIND values (3, 'location');
-
-
----------------------------------
--- Address Book Object Members --
----------------------------------
-
-create table ABO_MEMBERS (
-    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') RESOURCE_ID
- 	ADDRESSBOOK_ID		  integer      not null references ADDRESSBOOK_HOME on delete cascade,
-    MEMBER_ID             integer      not null references ADDRESSBOOK_OBJECT,						-- member AddressBook Object's RESOURCE_ID
-
-    primary key (GROUP_ID, MEMBER_ID) -- implicit index
-);
-
-create index ABO_MEMBERS_ADDRESSBOOK_ID on
-	ABO_MEMBERS(ADDRESSBOOK_ID);
-create index ABO_MEMBERS_MEMBER_ID on
-	ABO_MEMBERS(MEMBER_ID);
-
-------------------------------------------
--- Address Book Object Foreign Members  --
-------------------------------------------
-
-create table ABO_FOREIGN_MEMBERS (
-    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') RESOURCE_ID
- 	ADDRESSBOOK_ID		  integer      not null references ADDRESSBOOK_HOME on delete cascade,
-    MEMBER_ADDRESS  	  varchar(255) not null, 													-- member AddressBook Object's 'calendar' address
-
-    primary key (GROUP_ID, MEMBER_ADDRESS) -- implicit index
-);
-
-create index ABO_FOREIGN_MEMBERS_ADDRESSBOOK_ID on
-	ABO_FOREIGN_MEMBERS(ADDRESSBOOK_ID);
-
------------------------
--- Shared Group Bind --
------------------------
-
--- Joins ADDRESSBOOK_HOME and ADDRESSBOOK_OBJECT (kind == group)
-
-create table SHARED_GROUP_BIND (	
-  ADDRESSBOOK_HOME_RESOURCE_ID 		integer      not null references ADDRESSBOOK_HOME,
-  GROUP_RESOURCE_ID      			integer      not null references ADDRESSBOOK_OBJECT on delete cascade,
-  GROUP_ADDRESSBOOK_NAME			varchar(255) not null,
-  BIND_MODE                    		integer      not null, -- enum CALENDAR_BIND_MODE
-  BIND_STATUS                  		integer      not null, -- enum CALENDAR_BIND_STATUS
-  BIND_REVISION				   		integer      default 0 not null,
-  MESSAGE                      		text,                  -- FIXME: xml?
-
-  primary key (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_RESOURCE_ID), -- implicit index
-  unique (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_ADDRESSBOOK_NAME)     -- implicit index
-);
-
-create index SHARED_GROUP_BIND_RESOURCE_ID on
-  SHARED_GROUP_BIND(GROUP_RESOURCE_ID);
-
-
----------------
--- Revisions --
----------------
-
-create sequence REVISION_SEQ;
-
-
--------------------------------
--- Calendar Object Revisions --
--------------------------------
-
-create table CALENDAR_OBJECT_REVISIONS (
-  CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
-  CALENDAR_RESOURCE_ID      integer      references CALENDAR,
-  CALENDAR_NAME             varchar(255) default null,
-  RESOURCE_NAME             varchar(255),
-  REVISION                  integer      default nextval('REVISION_SEQ') not null,
-  DELETED                   boolean      not null
-);
-
-create index CALENDAR_OBJECT_REVISIONS_HOME_RESOURCE_ID_CALENDAR_RESOURCE_ID
-  on CALENDAR_OBJECT_REVISIONS(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID);
-
-create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_RESOURCE_NAME
-  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, RESOURCE_NAME);
-
-create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_REVISION
-  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, REVISION);
-
-
-----------------------------------
--- AddressBook Object Revisions --
-----------------------------------
-
-create table ADDRESSBOOK_OBJECT_REVISIONS (
-  ADDRESSBOOK_HOME_RESOURCE_ID 			integer			not null references ADDRESSBOOK_HOME,
-  OWNER_HOME_RESOURCE_ID    			integer     	references ADDRESSBOOK_HOME,
-  ADDRESSBOOK_NAME             			varchar(255) 	default null,
-  RESOURCE_NAME                			varchar(255),
-  REVISION                     			integer     	default nextval('REVISION_SEQ') not null,
-  DELETED                      			boolean      	not null
-);
-
-create index ADDRESSBOOK_OBJECT_REVISIONS_HOME_RESOURCE_ID_OWNER_HOME_RESOURCE_ID
-  on ADDRESSBOOK_OBJECT_REVISIONS(ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID);
-
-create index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_RESOURCE_NAME
-  on ADDRESSBOOK_OBJECT_REVISIONS(OWNER_HOME_RESOURCE_ID, RESOURCE_NAME);
-
-create index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_REVISION
-  on ADDRESSBOOK_OBJECT_REVISIONS(OWNER_HOME_RESOURCE_ID, REVISION);
-
-
------------------------------------
--- Notification Object Revisions --
------------------------------------
-
-create table NOTIFICATION_OBJECT_REVISIONS (
-  NOTIFICATION_HOME_RESOURCE_ID integer      not null references NOTIFICATION_HOME on delete cascade,
-  RESOURCE_NAME                 varchar(255),
-  REVISION                      integer      default nextval('REVISION_SEQ') not null,
-  DELETED                       boolean      not null,
-
-  unique(NOTIFICATION_HOME_RESOURCE_ID, RESOURCE_NAME) -- implicit index
-);
-
-create index NOTIFICATION_OBJECT_REVISIONS_RESOURCE_ID_REVISION
-  on NOTIFICATION_OBJECT_REVISIONS(NOTIFICATION_HOME_RESOURCE_ID, REVISION);
-
-
--------------------------------------------
--- Apple Push Notification Subscriptions --
--------------------------------------------
-
-create table APN_SUBSCRIPTIONS (
-  TOKEN                         varchar(255) not null,
-  RESOURCE_KEY                  varchar(255) not null,
-  MODIFIED                      integer      not null,
-  SUBSCRIBER_GUID               varchar(255) not null,
-  USER_AGENT                    varchar(255) default null,
-  IP_ADDR                       varchar(255) default null,
-
-  primary key (TOKEN, RESOURCE_KEY) -- implicit index
-);
-
-create index APN_SUBSCRIPTIONS_RESOURCE_KEY
-   on APN_SUBSCRIPTIONS(RESOURCE_KEY);
-
-   
------------------
--- IMIP Tokens --
------------------
-
-create table IMIP_TOKENS (
-  TOKEN                         varchar(255) not null,
-  ORGANIZER                     varchar(255) not null,
-  ATTENDEE                      varchar(255) not null,
-  ICALUID                       varchar(255) not null,
-  ACCESSED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-
-  primary key (ORGANIZER, ATTENDEE, ICALUID) -- implicit index
-);
-
-create index IMIP_TOKENS_TOKEN
-   on IMIP_TOKENS(TOKEN);
-
-   
-----------------
--- Work Items --
-----------------
-
-create sequence WORKITEM_SEQ;
-
-
----------------------------
--- IMIP Inivitation Work --
----------------------------
-
-create table IMIP_INVITATION_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-  FROM_ADDR                     varchar(255) not null,
-  TO_ADDR                       varchar(255) not null,
-  ICALENDAR_TEXT                text         not null
-);
-
-
------------------------
--- IMIP Polling Work --
------------------------
-
-create table IMIP_POLLING_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
-);
-
-
----------------------
--- IMIP Reply Work --
----------------------
-
-create table IMIP_REPLY_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-  ORGANIZER                     varchar(255) not null,
-  ATTENDEE                      varchar(255) not null,
-  ICALENDAR_TEXT                text         not null
-);
-
-
-------------------------
--- Push Notifications --
-------------------------
-
-create table PUSH_NOTIFICATION_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-  PUSH_ID                       varchar(255) not null
-);
-
------------------
--- GroupCacher --
------------------
-
-create table GROUP_CACHER_POLLING_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
-);
-
-
---------------------------
--- Object Splitter Work --
---------------------------
-
-create table CALENDAR_OBJECT_SPLITTER_WORK (
-  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
-  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade
-);
-
-create index CALENDAR_OBJECT_SPLITTER_WORK_RESOURCE_ID on
-	CALENDAR_OBJECT_SPLITTER_WORK(RESOURCE_ID);
-
---------------------
--- Schema Version --
---------------------
-
-create table CALENDARSERVER (
-  NAME                          varchar(255) primary key, -- implicit index
-  VALUE                         varchar(255)
-);
-
-insert into CALENDARSERVER values ('VERSION', '25');
-insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
-insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v25.sql (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/old/postgres-dialect/v25.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v25.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v25.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -0,0 +1,700 @@
+-- -*- 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
+);
+
+
+-------------------
+-- Calendar Home --
+-------------------
+
+create table CALENDAR_HOME (
+  RESOURCE_ID      integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  OWNER_UID        varchar(255) not null unique,                                 -- implicit index
+  DATAVERSION      integer      default 0 not null
+);
+
+--------------
+-- Calendar --
+--------------
+
+create table CALENDAR (
+  RESOURCE_ID integer   primary key default nextval('RESOURCE_ID_SEQ') -- implicit index
+);
+
+----------------------------
+-- Calendar Home Metadata --
+----------------------------
+
+create table CALENDAR_HOME_METADATA (
+  RESOURCE_ID              integer     primary key references CALENDAR_HOME on delete cascade, -- implicit index
+  QUOTA_USED_BYTES         integer     default 0 not null,
+  DEFAULT_EVENTS           integer     default null references CALENDAR on delete set null,
+  DEFAULT_TASKS            integer     default null references CALENDAR on delete set null,
+  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);
+
+-----------------------
+-- 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
+);
+
+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,
+  XML_TYPE                      varchar(255) not null,
+  XML_DATA                      text         not null,
+  MD5                           char(32)     not null,
+  CREATED                       timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique(NOTIFICATION_UID, NOTIFICATION_HOME_RESOURCE_ID) -- implicit index
+);
+
+create index NOTIFICATION_NOTIFICATION_HOME_RESOURCE_ID on
+	NOTIFICATION(NOTIFICATION_HOME_RESOURCE_ID);
+
+
+-------------------
+-- Calendar Bind --
+-------------------
+
+-- Joins CALENDAR_HOME and CALENDAR
+
+create table CALENDAR_BIND (
+  CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
+  CALENDAR_RESOURCE_ID      integer      not null references CALENDAR on delete cascade,
+  CALENDAR_RESOURCE_NAME    varchar(255) not null,
+  BIND_MODE                 integer      not null, -- enum CALENDAR_BIND_MODE
+  BIND_STATUS               integer      not null, -- enum CALENDAR_BIND_STATUS
+  BIND_REVISION				integer      default 0 not null,
+  MESSAGE                   text,
+  TRANSP                    integer      default 0 not null, -- enum CALENDAR_TRANSP
+  ALARM_VEVENT_TIMED        text         default null,
+  ALARM_VEVENT_ALLDAY       text         default null,
+  ALARM_VTODO_TIMED         text         default null,
+  ALARM_VTODO_ALLDAY        text         default null,
+  TIMEZONE                  text         default null,
+
+  primary key(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID), -- implicit index
+  unique(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_NAME)     -- implicit index
+);
+
+create index CALENDAR_BIND_RESOURCE_ID on
+	CALENDAR_BIND(CALENDAR_RESOURCE_ID);
+
+-- Enumeration of calendar bind modes
+
+create table CALENDAR_BIND_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_BIND_MODE values (0, 'own'  );
+insert into CALENDAR_BIND_MODE values (1, 'read' );
+insert into CALENDAR_BIND_MODE values (2, 'write');
+insert into CALENDAR_BIND_MODE values (3, 'direct');
+
+-- 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');
+
+
+-- Enumeration of transparency
+
+create table CALENDAR_TRANSP (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_TRANSP values (0, 'opaque' );
+insert into CALENDAR_TRANSP values (1, 'transparent');
+
+
+---------------------
+-- Calendar Object --
+---------------------
+
+create table CALENDAR_OBJECT (
+  RESOURCE_ID          integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  CALENDAR_RESOURCE_ID integer      not null references CALENDAR on delete cascade,
+  RESOURCE_NAME        varchar(255) not null,
+  ICALENDAR_TEXT       text         not null,
+  ICALENDAR_UID        varchar(255) not null,
+  ICALENDAR_TYPE       varchar(255) not null,
+  ATTACHMENTS_MODE     integer      default 0 not null, -- enum CALENDAR_OBJECT_ATTACHMENTS_MODE
+  DROPBOX_ID           varchar(255),
+  ORGANIZER            varchar(255),
+  RECURRANCE_MIN       date,        -- minimum date that recurrences have been expanded to.
+  RECURRANCE_MAX       date,        -- maximum date that recurrences have been expanded to.
+  ACCESS               integer      default 0 not null,
+  SCHEDULE_OBJECT      boolean      default false,
+  SCHEDULE_TAG         varchar(36)  default null,
+  SCHEDULE_ETAGS       text         default null,
+  PRIVATE_COMMENTS     boolean      default false not null,
+  MD5                  char(32)     not null,
+  CREATED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED             timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique (CALENDAR_RESOURCE_ID, RESOURCE_NAME) -- implicit index
+
+  -- since the 'inbox' is a 'calendar resource' for the purpose of storing
+  -- calendar objects, this constraint has to be selectively enforced by the
+  -- application layer.
+
+  -- unique(CALENDAR_RESOURCE_ID, ICALENDAR_UID)
+);
+
+create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_AND_ICALENDAR_UID on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, ICALENDAR_UID);
+
+create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_RECURRANCE_MAX on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, RECURRANCE_MAX);
+
+create index CALENDAR_OBJECT_ICALENDAR_UID on
+  CALENDAR_OBJECT(ICALENDAR_UID);
+
+create index CALENDAR_OBJECT_DROPBOX_ID on
+  CALENDAR_OBJECT(DROPBOX_ID);
+
+-- Enumeration of attachment modes
+
+create table CALENDAR_OBJECT_ATTACHMENTS_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (0, 'none' );
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (1, 'read' );
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (2, 'write');
+
+
+-- Enumeration of calendar access types
+
+create table CALENDAR_ACCESS_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(32) not null unique
+);
+
+insert into CALENDAR_ACCESS_TYPE values (0, ''             );
+insert into CALENDAR_ACCESS_TYPE values (1, 'public'       );
+insert into CALENDAR_ACCESS_TYPE values (2, 'private'      );
+insert into CALENDAR_ACCESS_TYPE values (3, 'confidential' );
+insert into CALENDAR_ACCESS_TYPE values (4, 'restricted'   );
+
+
+-----------------
+-- Instance ID --
+-----------------
+
+create sequence INSTANCE_ID_SEQ;
+
+
+----------------
+-- Time Range --
+----------------
+
+create table TIME_RANGE (
+  INSTANCE_ID                 integer        primary key default nextval('INSTANCE_ID_SEQ'), -- implicit index
+  CALENDAR_RESOURCE_ID        integer        not null references CALENDAR on delete cascade,
+  CALENDAR_OBJECT_RESOURCE_ID integer        not null references CALENDAR_OBJECT on delete cascade,
+  FLOATING                    boolean        not null,
+  START_DATE                  timestamp      not null,
+  END_DATE                    timestamp      not null,
+  FBTYPE                      integer        not null,
+  TRANSPARENT                 boolean        not null
+);
+
+create index TIME_RANGE_CALENDAR_RESOURCE_ID on
+  TIME_RANGE(CALENDAR_RESOURCE_ID);
+create index TIME_RANGE_CALENDAR_OBJECT_RESOURCE_ID on
+  TIME_RANGE(CALENDAR_OBJECT_RESOURCE_ID);
+
+
+-- Enumeration of free/busy types
+
+create table FREE_BUSY_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into FREE_BUSY_TYPE values (0, 'unknown'         );
+insert into FREE_BUSY_TYPE values (1, 'free'            );
+insert into FREE_BUSY_TYPE values (2, 'busy'            );
+insert into FREE_BUSY_TYPE values (3, 'busy-unavailable');
+insert into FREE_BUSY_TYPE values (4, 'busy-tentative'  );
+
+
+------------------
+-- Transparency --
+------------------
+
+create table TRANSPARENCY (
+  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
+  USER_ID                     varchar(255) not null,
+  TRANSPARENT                 boolean      not null
+);
+
+create index TRANSPARENCY_TIME_RANGE_INSTANCE_ID on
+  TRANSPARENCY(TIME_RANGE_INSTANCE_ID);
+
+
+----------------
+-- Attachment --
+----------------
+
+create sequence ATTACHMENT_ID_SEQ;
+
+create table ATTACHMENT (
+  ATTACHMENT_ID               integer           primary key default nextval('ATTACHMENT_ID_SEQ'), -- implicit index
+  CALENDAR_HOME_RESOURCE_ID   integer           not null references CALENDAR_HOME,
+  DROPBOX_ID                  varchar(255),
+  CONTENT_TYPE                varchar(255)      not null,
+  SIZE                        integer           not null,
+  MD5                         char(32)          not null,
+  CREATED                     timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                    timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  PATH                        varchar(1024)     not null
+);
+
+create index ATTACHMENT_CALENDAR_HOME_RESOURCE_ID on
+  ATTACHMENT(CALENDAR_HOME_RESOURCE_ID);
+
+-- Many-to-many relationship between attachments and calendar objects
+create table ATTACHMENT_CALENDAR_OBJECT (
+  ATTACHMENT_ID                  integer      not null references ATTACHMENT on delete cascade,
+  MANAGED_ID                     varchar(255) not null,
+  CALENDAR_OBJECT_RESOURCE_ID    integer      not null references CALENDAR_OBJECT on delete cascade,
+
+  primary key (ATTACHMENT_ID, CALENDAR_OBJECT_RESOURCE_ID), -- implicit index
+  unique (MANAGED_ID, CALENDAR_OBJECT_RESOURCE_ID) --implicit index
+);
+
+create index ATTACHMENT_CALENDAR_OBJECT_CALENDAR_OBJECT_RESOURCE_ID on
+	ATTACHMENT_CALENDAR_OBJECT(CALENDAR_OBJECT_RESOURCE_ID);
+
+-----------------------
+-- Resource Property --
+-----------------------
+
+create table RESOURCE_PROPERTY (
+  RESOURCE_ID integer      not null, -- foreign key: *.RESOURCE_ID
+  NAME        varchar(255) not null,
+  VALUE       text         not null, -- FIXME: xml?
+  VIEWER_UID  varchar(255),
+
+  primary key (RESOURCE_ID, NAME, VIEWER_UID) -- implicit index
+);
+
+
+----------------------
+-- AddressBook Home --
+----------------------
+
+create table ADDRESSBOOK_HOME (
+  RESOURCE_ID      				integer			primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  ADDRESSBOOK_PROPERTY_STORE_ID	integer      	default nextval('RESOURCE_ID_SEQ') not null, 	-- implicit index
+  OWNER_UID        				varchar(255) 	not null unique,                                -- implicit index
+  DATAVERSION      				integer      	default 0 not null
+);
+
+
+-------------------------------
+-- AddressBook Home Metadata --
+-------------------------------
+
+create table ADDRESSBOOK_HOME_METADATA (
+  RESOURCE_ID      integer      primary key references ADDRESSBOOK_HOME on delete cascade, -- implicit index
+  QUOTA_USED_BYTES integer      default 0 not null,
+  CREATED          timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED         timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+-----------------------------
+-- Shared AddressBook Bind --
+-----------------------------
+
+-- Joins sharee ADDRESSBOOK_HOME and owner ADDRESSBOOK_HOME
+
+create table SHARED_ADDRESSBOOK_BIND (
+  ADDRESSBOOK_HOME_RESOURCE_ID			integer			not null references ADDRESSBOOK_HOME,
+  OWNER_HOME_RESOURCE_ID    			integer      	not null references ADDRESSBOOK_HOME on delete cascade,
+  ADDRESSBOOK_RESOURCE_NAME    			varchar(255) 	not null,
+  BIND_MODE                    			integer      	not null,	-- enum CALENDAR_BIND_MODE
+  BIND_STATUS                  			integer      	not null,	-- enum CALENDAR_BIND_STATUS
+  BIND_REVISION				   			integer      	default 0 not null,
+  MESSAGE                      			text,                  		-- FIXME: xml?
+
+  primary key (ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID), -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME)     -- implicit index
+);
+
+create index SHARED_ADDRESSBOOK_BIND_RESOURCE_ID on
+  SHARED_ADDRESSBOOK_BIND(OWNER_HOME_RESOURCE_ID);
+
+
+------------------------
+-- AddressBook Object --
+------------------------
+
+create table ADDRESSBOOK_OBJECT (
+  RESOURCE_ID             		integer   		primary key default nextval('RESOURCE_ID_SEQ'),    -- implicit index
+  ADDRESSBOOK_HOME_RESOURCE_ID 	integer      	not null references ADDRESSBOOK_HOME on delete cascade,
+  RESOURCE_NAME           		varchar(255) 	not null,
+  VCARD_TEXT              		text         	not null,
+  VCARD_UID               		varchar(255) 	not null,
+  KIND 			  		  		integer      	not null,  -- enum ADDRESSBOOK_OBJECT_KIND
+  MD5                     		char(32)     	not null,
+  CREATED                 		timestamp    	default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                		timestamp    	default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, RESOURCE_NAME), -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, VCARD_UID)      -- implicit index
+);
+
+
+-----------------------------
+-- AddressBook Object kind --
+-----------------------------
+
+create table ADDRESSBOOK_OBJECT_KIND (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into ADDRESSBOOK_OBJECT_KIND values (0, 'person');
+insert into ADDRESSBOOK_OBJECT_KIND values (1, 'group' );
+insert into ADDRESSBOOK_OBJECT_KIND values (2, 'resource');
+insert into ADDRESSBOOK_OBJECT_KIND values (3, 'location');
+
+
+---------------------------------
+-- Address Book Object Members --
+---------------------------------
+
+create table ABO_MEMBERS (
+    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') RESOURCE_ID
+ 	ADDRESSBOOK_ID		  integer      not null references ADDRESSBOOK_HOME on delete cascade,
+    MEMBER_ID             integer      not null references ADDRESSBOOK_OBJECT,						-- member AddressBook Object's RESOURCE_ID
+
+    primary key (GROUP_ID, MEMBER_ID) -- implicit index
+);
+
+create index ABO_MEMBERS_ADDRESSBOOK_ID on
+	ABO_MEMBERS(ADDRESSBOOK_ID);
+create index ABO_MEMBERS_MEMBER_ID on
+	ABO_MEMBERS(MEMBER_ID);
+
+------------------------------------------
+-- Address Book Object Foreign Members  --
+------------------------------------------
+
+create table ABO_FOREIGN_MEMBERS (
+    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') RESOURCE_ID
+ 	ADDRESSBOOK_ID		  integer      not null references ADDRESSBOOK_HOME on delete cascade,
+    MEMBER_ADDRESS  	  varchar(255) not null, 													-- member AddressBook Object's 'calendar' address
+
+    primary key (GROUP_ID, MEMBER_ADDRESS) -- implicit index
+);
+
+create index ABO_FOREIGN_MEMBERS_ADDRESSBOOK_ID on
+	ABO_FOREIGN_MEMBERS(ADDRESSBOOK_ID);
+
+-----------------------
+-- Shared Group Bind --
+-----------------------
+
+-- Joins ADDRESSBOOK_HOME and ADDRESSBOOK_OBJECT (kind == group)
+
+create table SHARED_GROUP_BIND (	
+  ADDRESSBOOK_HOME_RESOURCE_ID 		integer      not null references ADDRESSBOOK_HOME,
+  GROUP_RESOURCE_ID      			integer      not null references ADDRESSBOOK_OBJECT on delete cascade,
+  GROUP_ADDRESSBOOK_NAME			varchar(255) not null,
+  BIND_MODE                    		integer      not null, -- enum CALENDAR_BIND_MODE
+  BIND_STATUS                  		integer      not null, -- enum CALENDAR_BIND_STATUS
+  BIND_REVISION				   		integer      default 0 not null,
+  MESSAGE                      		text,                  -- FIXME: xml?
+
+  primary key (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_RESOURCE_ID), -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_ADDRESSBOOK_NAME)     -- implicit index
+);
+
+create index SHARED_GROUP_BIND_RESOURCE_ID on
+  SHARED_GROUP_BIND(GROUP_RESOURCE_ID);
+
+
+---------------
+-- Revisions --
+---------------
+
+create sequence REVISION_SEQ;
+
+
+-------------------------------
+-- Calendar Object Revisions --
+-------------------------------
+
+create table CALENDAR_OBJECT_REVISIONS (
+  CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
+  CALENDAR_RESOURCE_ID      integer      references CALENDAR,
+  CALENDAR_NAME             varchar(255) default null,
+  RESOURCE_NAME             varchar(255),
+  REVISION                  integer      default nextval('REVISION_SEQ') not null,
+  DELETED                   boolean      not null
+);
+
+create index CALENDAR_OBJECT_REVISIONS_HOME_RESOURCE_ID_CALENDAR_RESOURCE_ID
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID);
+
+create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_RESOURCE_NAME
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, RESOURCE_NAME);
+
+create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_REVISION
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, REVISION);
+
+
+----------------------------------
+-- AddressBook Object Revisions --
+----------------------------------
+
+create table ADDRESSBOOK_OBJECT_REVISIONS (
+  ADDRESSBOOK_HOME_RESOURCE_ID 			integer			not null references ADDRESSBOOK_HOME,
+  OWNER_HOME_RESOURCE_ID    			integer     	references ADDRESSBOOK_HOME,
+  ADDRESSBOOK_NAME             			varchar(255) 	default null,
+  RESOURCE_NAME                			varchar(255),
+  REVISION                     			integer     	default nextval('REVISION_SEQ') not null,
+  DELETED                      			boolean      	not null
+);
+
+create index ADDRESSBOOK_OBJECT_REVISIONS_HOME_RESOURCE_ID_OWNER_HOME_RESOURCE_ID
+  on ADDRESSBOOK_OBJECT_REVISIONS(ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID);
+
+create index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_RESOURCE_NAME
+  on ADDRESSBOOK_OBJECT_REVISIONS(OWNER_HOME_RESOURCE_ID, RESOURCE_NAME);
+
+create index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_REVISION
+  on ADDRESSBOOK_OBJECT_REVISIONS(OWNER_HOME_RESOURCE_ID, REVISION);
+
+
+-----------------------------------
+-- Notification Object Revisions --
+-----------------------------------
+
+create table NOTIFICATION_OBJECT_REVISIONS (
+  NOTIFICATION_HOME_RESOURCE_ID integer      not null references NOTIFICATION_HOME on delete cascade,
+  RESOURCE_NAME                 varchar(255),
+  REVISION                      integer      default nextval('REVISION_SEQ') not null,
+  DELETED                       boolean      not null,
+
+  unique(NOTIFICATION_HOME_RESOURCE_ID, RESOURCE_NAME) -- implicit index
+);
+
+create index NOTIFICATION_OBJECT_REVISIONS_RESOURCE_ID_REVISION
+  on NOTIFICATION_OBJECT_REVISIONS(NOTIFICATION_HOME_RESOURCE_ID, REVISION);
+
+
+-------------------------------------------
+-- Apple Push Notification Subscriptions --
+-------------------------------------------
+
+create table APN_SUBSCRIPTIONS (
+  TOKEN                         varchar(255) not null,
+  RESOURCE_KEY                  varchar(255) not null,
+  MODIFIED                      integer      not null,
+  SUBSCRIBER_GUID               varchar(255) not null,
+  USER_AGENT                    varchar(255) default null,
+  IP_ADDR                       varchar(255) default null,
+
+  primary key (TOKEN, RESOURCE_KEY) -- implicit index
+);
+
+create index APN_SUBSCRIPTIONS_RESOURCE_KEY
+   on APN_SUBSCRIPTIONS(RESOURCE_KEY);
+
+   
+-----------------
+-- IMIP Tokens --
+-----------------
+
+create table IMIP_TOKENS (
+  TOKEN                         varchar(255) not null,
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALUID                       varchar(255) not null,
+  ACCESSED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  primary key (ORGANIZER, ATTENDEE, ICALUID) -- implicit index
+);
+
+create index IMIP_TOKENS_TOKEN
+   on IMIP_TOKENS(TOKEN);
+
+   
+----------------
+-- Work Items --
+----------------
+
+create sequence WORKITEM_SEQ;
+
+
+---------------------------
+-- IMIP Inivitation Work --
+---------------------------
+
+create table IMIP_INVITATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  FROM_ADDR                     varchar(255) not null,
+  TO_ADDR                       varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+
+-----------------------
+-- IMIP Polling Work --
+-----------------------
+
+create table IMIP_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+---------------------
+-- IMIP Reply Work --
+---------------------
+
+create table IMIP_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+
+------------------------
+-- Push Notifications --
+------------------------
+
+create table PUSH_NOTIFICATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  PUSH_ID                       varchar(255) not null
+);
+
+-----------------
+-- GroupCacher --
+-----------------
+
+create table GROUP_CACHER_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+--------------------------
+-- Object Splitter Work --
+--------------------------
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade
+);
+
+create index CALENDAR_OBJECT_SPLITTER_WORK_RESOURCE_ID on
+	CALENDAR_OBJECT_SPLITTER_WORK(RESOURCE_ID);
+
+--------------------
+-- Schema Version --
+--------------------
+
+create table CALENDARSERVER (
+  NAME                          varchar(255) primary key, -- implicit index
+  VALUE                         varchar(255)
+);
+
+insert into CALENDARSERVER values ('VERSION', '25');
+insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
+insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v33.sql (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/old/postgres-dialect/v33.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v33.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v33.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -0,0 +1,758 @@
+-- -*- 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
+);
+
+
+-------------------
+-- 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');
+
+
+--------------
+-- 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_OBJECT_ATTACHMENTS_MODE
+  DROPBOX_ID           varchar(255),
+  ORGANIZER            varchar(255),
+  RECURRANCE_MIN       date,        -- minimum date that recurrences have been expanded to.
+  RECURRANCE_MAX       date,        -- maximum date that recurrences have been expanded to.
+  ACCESS               integer      default 0 not null,
+  SCHEDULE_OBJECT      boolean      default false,
+  SCHEDULE_TAG         varchar(36)  default null,
+  SCHEDULE_ETAGS       text         default null,
+  PRIVATE_COMMENTS     boolean      default false not null,
+  MD5                  char(32)     not null,
+  CREATED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED             timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique (CALENDAR_RESOURCE_ID, RESOURCE_NAME) -- implicit index
+
+  -- since the 'inbox' is a 'calendar resource' for the purpose of storing
+  -- calendar objects, this constraint has to be selectively enforced by the
+  -- application layer.
+
+  -- unique(CALENDAR_RESOURCE_ID, ICALENDAR_UID)
+);
+
+create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_AND_ICALENDAR_UID on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, ICALENDAR_UID);
+
+create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_RECURRANCE_MAX on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, RECURRANCE_MAX);
+
+create index CALENDAR_OBJECT_ICALENDAR_UID on
+  CALENDAR_OBJECT(ICALENDAR_UID);
+
+create index CALENDAR_OBJECT_DROPBOX_ID on
+  CALENDAR_OBJECT(DROPBOX_ID);
+
+-- Enumeration of attachment modes
+
+create table CALENDAR_OBJECT_ATTACHMENTS_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (0, 'none' );
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (1, 'read' );
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (2, 'write');
+
+
+-- Enumeration of calendar access types
+
+create table CALENDAR_ACCESS_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(32) not null unique
+);
+
+insert into CALENDAR_ACCESS_TYPE values (0, ''             );
+insert into CALENDAR_ACCESS_TYPE values (1, 'public'       );
+insert into CALENDAR_ACCESS_TYPE values (2, 'private'      );
+insert into CALENDAR_ACCESS_TYPE values (3, 'confidential' );
+insert into CALENDAR_ACCESS_TYPE values (4, 'restricted'   );
+
+
+-----------------
+-- Instance ID --
+-----------------
+
+create sequence INSTANCE_ID_SEQ;
+
+
+----------------
+-- Time Range --
+----------------
+
+create table TIME_RANGE (
+  INSTANCE_ID                 integer        primary key default nextval('INSTANCE_ID_SEQ'), -- implicit index
+  CALENDAR_RESOURCE_ID        integer        not null references CALENDAR on delete cascade,
+  CALENDAR_OBJECT_RESOURCE_ID integer        not null references CALENDAR_OBJECT on delete cascade,
+  FLOATING                    boolean        not null,
+  START_DATE                  timestamp      not null,
+  END_DATE                    timestamp      not null,
+  FBTYPE                      integer        not null,
+  TRANSPARENT                 boolean        not null
+);
+
+create index TIME_RANGE_CALENDAR_RESOURCE_ID on
+  TIME_RANGE(CALENDAR_RESOURCE_ID);
+create index TIME_RANGE_CALENDAR_OBJECT_RESOURCE_ID on
+  TIME_RANGE(CALENDAR_OBJECT_RESOURCE_ID);
+
+
+-- Enumeration of free/busy types
+
+create table FREE_BUSY_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into FREE_BUSY_TYPE values (0, 'unknown'         );
+insert into FREE_BUSY_TYPE values (1, 'free'            );
+insert into FREE_BUSY_TYPE values (2, 'busy'            );
+insert into FREE_BUSY_TYPE values (3, 'busy-unavailable');
+insert into FREE_BUSY_TYPE values (4, 'busy-tentative'  );
+
+
+------------------
+-- Transparency --
+------------------
+
+create table TRANSPARENCY (
+  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
+  USER_ID                     varchar(255) not null,
+  TRANSPARENT                 boolean      not null
+);
+
+create index TRANSPARENCY_TIME_RANGE_INSTANCE_ID on
+  TRANSPARENCY(TIME_RANGE_INSTANCE_ID);
+
+
+----------------
+-- Attachment --
+----------------
+
+create sequence ATTACHMENT_ID_SEQ;
+
+create table ATTACHMENT (
+  ATTACHMENT_ID               integer           primary key default nextval('ATTACHMENT_ID_SEQ'), -- implicit index
+  CALENDAR_HOME_RESOURCE_ID   integer           not null references CALENDAR_HOME,
+  DROPBOX_ID                  varchar(255),
+  CONTENT_TYPE                varchar(255)      not null,
+  SIZE                        integer           not null,
+  MD5                         char(32)          not null,
+  CREATED                     timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                    timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  PATH                        varchar(1024)     not null
+);
+
+create index ATTACHMENT_CALENDAR_HOME_RESOURCE_ID on
+  ATTACHMENT(CALENDAR_HOME_RESOURCE_ID);
+
+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
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  FROM_ADDR                     varchar(255) not null,
+  TO_ADDR                       varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+
+-----------------------
+-- IMIP Polling Work --
+-----------------------
+
+create table IMIP_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+---------------------
+-- IMIP Reply Work --
+---------------------
+
+create table IMIP_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+
+------------------------
+-- Push Notifications --
+------------------------
+
+create table PUSH_NOTIFICATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  PUSH_ID                       varchar(255) not null,
+  PRIORITY                      integer      not null -- 1:low 5:medium 10:high
+);
+
+-----------------
+-- GroupCacher --
+-----------------
+
+create table GROUP_CACHER_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+--------------------------
+-- Object Splitter Work --
+--------------------------
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade
+);
+
+create index CALENDAR_OBJECT_SPLITTER_WORK_RESOURCE_ID on
+	CALENDAR_OBJECT_SPLITTER_WORK(RESOURCE_ID);
+
+---------------------------
+-- Revision Cleaner Work --
+---------------------------
+
+create table FIND_MIN_VALID_REVISION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+create table REVISION_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+--------------------
+-- Schema Version --
+--------------------
+
+create table CALENDARSERVER (
+  NAME                          varchar(255) primary key, -- implicit index
+  VALUE                         varchar(255)
+);
+
+insert into CALENDARSERVER values ('VERSION', '33');
+insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
+insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');
+insert into CALENDARSERVER values ('MIN-VALID-REVISION', '1');

Deleted: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_25_to_26.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_25_to_26.sql	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_25_to_26.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -1,43 +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 25 to 26 --
----------------------------------------------------
-
--- Replace index
-
-drop index CALENDAR_OBJECT_REVIS_2643d556;
-create index CALENDAR_OBJECT_REVIS_6d9d929c on CALENDAR_OBJECT_REVISIONS (
-    CALENDAR_RESOURCE_ID,
-    RESOURCE_NAME,
-    DELETED,
-    REVISION
-);
-
-
-drop index ADDRESSBOOK_OBJECT_RE_980b9872;
-create index ADDRESSBOOK_OBJECT_RE_00fe8288 on ADDRESSBOOK_OBJECT_REVISIONS (
-    OWNER_HOME_RESOURCE_ID,
-    RESOURCE_NAME,
-    DELETED,
-    REVISION
-);
-
-
--- Now update the version
--- No data upgrades
-update CALENDARSERVER set VALUE = '26' where NAME = 'VERSION';

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_25_to_26.sql (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_25_to_26.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_25_to_26.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_25_to_26.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -0,0 +1,43 @@
+----
+-- 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 25 to 26 --
+---------------------------------------------------
+
+-- Replace index
+
+drop index CALENDAR_OBJECT_REVIS_2643d556;
+create index CALENDAR_OBJECT_REVIS_6d9d929c on CALENDAR_OBJECT_REVISIONS (
+    CALENDAR_RESOURCE_ID,
+    RESOURCE_NAME,
+    DELETED,
+    REVISION
+);
+
+
+drop index ADDRESSBOOK_OBJECT_RE_980b9872;
+create index ADDRESSBOOK_OBJECT_RE_00fe8288 on ADDRESSBOOK_OBJECT_REVISIONS (
+    OWNER_HOME_RESOURCE_ID,
+    RESOURCE_NAME,
+    DELETED,
+    REVISION
+);
+
+
+-- Now update the version
+-- No data upgrades
+update CALENDARSERVER set VALUE = '26' where NAME = 'VERSION';

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_33_to_34.sql (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_33_to_34.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_33_to_34.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_33_to_34.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -0,0 +1,133 @@
+----
+-- Copyright (c) 2012-2013 Apple Inc. All rights reserved.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+----
+
+---------------------------------------------------
+-- Upgrade database schema from VERSION 33 to 34 --
+---------------------------------------------------
+
+-- New tables
+
+create table SCHEDULE_REFRESH_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "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_26084c7b on SCHEDULE_REFRESH_WORK (
+    HOME_RESOURCE_ID
+);
+create index SCHEDULE_REFRESH_WORK_989efe54 on SCHEDULE_REFRESH_WORK (
+    RESOURCE_ID
+);
+
+
+create table SCHEDULE_REFRESH_ATTENDEES (
+    "RESOURCE_ID" integer not null references CALENDAR_OBJECT on delete cascade,
+    "ATTENDEE" nvarchar2(255)
+);
+
+create index SCHEDULE_REFRESH_ATTE_83053b91 on SCHEDULE_REFRESH_ATTENDEES (
+    RESOURCE_ID,
+    ATTENDEE
+);
+
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "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
+);
+
+
+create table SCHEDULE_ORGANIZER_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "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
+);
+
+
+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_REPLY_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "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
+);
+
+
+create table SCHEDULE_REPLY_CANCEL_WORK (
+    "WORK_ID" integer primary key not null,
+    "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    "ICALENDAR_UID" nvarchar2(255),
+    "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
+);
+
+
+-- Now update the version
+-- No data upgrades
+update CALENDARSERVER set VALUE = '34' where NAME = 'VERSION';

Deleted: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_25_to_26.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_25_to_26.sql	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_25_to_26.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -1,32 +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 25 to 26 --
----------------------------------------------------
-
--- Replace index
-drop index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_RESOURCE_NAME;
-create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_RESOURCE_NAME_DELETED_REVISION
-  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, RESOURCE_NAME, DELETED, REVISION);
-
-drop index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_RESOURCE_NAME;
-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);
-
--- Now update the version
--- No data upgrades
-update CALENDARSERVER set VALUE = '26' where NAME = 'VERSION';

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_25_to_26.sql (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_25_to_26.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_25_to_26.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_25_to_26.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -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 25 to 26 --
+---------------------------------------------------
+
+-- Replace index
+drop index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_RESOURCE_NAME;
+create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_RESOURCE_NAME_DELETED_REVISION
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, RESOURCE_NAME, DELETED, REVISION);
+
+drop index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_RESOURCE_NAME;
+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);
+
+-- Now update the version
+-- No data upgrades
+update CALENDARSERVER set VALUE = '26' where NAME = 'VERSION';

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_33_to_34.sql (from rev 12557, CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_33_to_34.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_33_to_34.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_33_to_34.sql	2014-02-04 23:58:26 UTC (rev 12565)
@@ -0,0 +1,137 @@
+----
+-- Copyright (c) 2012-2013 Apple Inc. All rights reserved.
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+----
+
+---------------------------------------------------
+-- Upgrade database schema from VERSION 33 to 34 --
+---------------------------------------------------
+
+-- New tables
+
+---------------------------
+-- Schedule Refresh Work --
+---------------------------
+
+create table SCHEDULE_REFRESH_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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
+);
+
+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 default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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 default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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 Reply Work --
+-------------------------
+
+create table SCHEDULE_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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 default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ICALENDAR_UID        			varchar(255) not null,
+  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);
+
+
+-- Now update the version
+-- No data upgrades
+update CALENDARSERVER set VALUE = '34' where NAME = 'VERSION';

Modified: CalendarServer/trunk/txdav/common/datastore/sql_tables.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_tables.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/common/datastore/sql_tables.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -131,6 +131,22 @@
 
 
 
+def _schemaConstantsMaps(nameColumn, valueColumn):
+    """
+    Generate two dicts that map back and forth between SQL enum values and their
+    programmatic values.
+    """
+
+    toSQL = {}
+    fromSQL = {}
+    for row in nameColumn.model.table.schemaRows:
+        toSQL[row[nameColumn.model]] = row[valueColumn.model]
+        fromSQL[row[valueColumn.model]] = row[nameColumn.model]
+
+    return (toSQL, fromSQL)
+
+
+
 # Various constants
 
 _homeStatus = _schemaConstants(
@@ -196,6 +212,12 @@
 _ABO_KIND_RESOURCE = _addressBookObjectKind('resource')
 _ABO_KIND_LOCATION = _addressBookObjectKind('location')
 
+scheduleActionToSQL, scheduleActionFromSQL = _schemaConstantsMaps(
+    schema.SCHEDULE_ACTION.DESCRIPTION,
+    schema.SCHEDULE_ACTION.ID
+)
+
+
 class SchemaBroken(Exception):
     """
     The schema is broken and cannot be translated.

Modified: CalendarServer/trunk/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/util.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/common/datastore/test/util.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -38,7 +38,7 @@
 
 from twext.python.log import Logger
 from twext.python.filepath import CachingFilePath
-from twistedcaldav.ical import Component as VComponent
+from twistedcaldav.ical import Component as VComponent, Component
 from twext.enterprise.adbapi2 import ConnectionPool
 from twext.enterprise.ienterprise import AlreadyFinishedError
 from txweb2.dav.resource import TwistedGETContentMD5
@@ -529,7 +529,35 @@
     return data % {"now": nowYear}
 
 
+relativeDateSubstitutions = {}
 
+
+def componentUpdate(data):
+    """
+    Update the supplied iCalendar data so that all dates are updated to the current year.
+    """
+
+    if len(relativeDateSubstitutions) == 0:
+        now = DateTime.getToday()
+
+        relativeDateSubstitutions["now"] = now
+
+        for i in range(30):
+            attrname = "now_back%s" % (i + 1,)
+            dt = now.duplicate()
+            dt.offsetDay(-(i + 1))
+            relativeDateSubstitutions[attrname] = dt
+
+        for i in range(30):
+            attrname = "now_fwd%s" % (i + 1,)
+            dt = now.duplicate()
+            dt.offsetDay(i + 1)
+            relativeDateSubstitutions[attrname] = dt
+
+    return Component.fromString(data.format(**relativeDateSubstitutions))
+
+
+
 @inlineCallbacks
 def resetCalendarMD5s(md5s, store):
     """
@@ -713,8 +741,21 @@
     def setUp(self):
         self.counter = 0
         self.notifierFactory = StubNotifierFactory()
+        self.configInit()
 
 
+    def configInit(self):
+        """
+        Hard code some config options
+        """
+
+        # Work queues for implicit scheduling slow down tests a lot and require them all to add
+        # "waits" for work to complete. Rewriting all the current tests to do that is not practical
+        # right now, so we will turn this off by default. Instead we will have a set of tests dedicated
+        # to work queue-based scheduling which will patch this option to True.
+        config.Scheduling.Options.WorkQueues.Enabled = False
+
+
     def storeUnderTest(self):
         """
         Subclasses must implement this method.

Modified: CalendarServer/trunk/txdav/xml/base.py
===================================================================
--- CalendarServer/trunk/txdav/xml/base.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/xml/base.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -609,6 +609,7 @@
     allowed_children = {}
     children = ()
 
+
     def __hash__(self):
         """
         Define a hash method, so that an empty element can serve as dictionary

Modified: CalendarServer/trunk/txdav/xml/element.py
===================================================================
--- CalendarServer/trunk/txdav/xml/element.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/xml/element.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -104,3 +104,17 @@
     Return the element class for the element with the given qname.
     """
     return _elements_by_qname[qname]
+
+# Shhh unused imports
+WebDAVDocument
+dav_namespace
+twisted_dav_namespace
+twisted_private_namespace
+WebDAVElement
+PCDATAElement
+WebDAVOneShotElement
+WebDAVUnknownElement
+WebDAVEmptyElement
+WebDAVTextElement
+WebDAVDateTimeElement
+DateTimeHeaderElement

Modified: CalendarServer/trunk/txdav/xml/parser.py
===================================================================
--- CalendarServer/trunk/txdav/xml/parser.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/xml/parser.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -33,3 +33,6 @@
 ]
 
 from txdav.xml.parser_etree import WebDAVDocument
+
+# Shh unused import
+WebDAVDocument

Modified: CalendarServer/trunk/txdav/xml/rfc2518.py
===================================================================
--- CalendarServer/trunk/txdav/xml/rfc2518.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/xml/rfc2518.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -257,6 +257,7 @@
 LockType.write = LockType(Write())
 
 
+
 @registerElement
 @registerElementClass
 class MultiStatus (WebDAVElement):
@@ -386,7 +387,7 @@
     name = "status"
 
     @classmethod
-    def fromResponseCode(clazz, code):
+    def fromResponseCode(cls, code):
         """
         code must be an integer response code in
         txweb2.responsecode.RESPONSES.keys()
@@ -394,7 +395,7 @@
         if code not in responsecode.RESPONSES:
             raise ValueError("Invalid response code: %r" % (code,))
 
-        return clazz(PCDATAElement("HTTP/1.1 %d %s" % (code, responsecode.RESPONSES[code])))
+        return cls(PCDATAElement("HTTP/1.1 %d %s" % (code, responsecode.RESPONSES[code])))
 
 
     def __init__(self, *children, **attributes):
@@ -468,6 +469,7 @@
         (dav_namespace, "keepalive"): (0, 1),
     }
 
+
     def __init__(self, *children, **attributes):
         super(PropertyBehavior, self).__init__(*children, **attributes)
 
@@ -495,6 +497,7 @@
         PCDATAElement: (0, 1),
     }
 
+
     def validate(self):
         super(KeepAlive, self).validate()
 
@@ -583,6 +586,7 @@
         (dav_namespace, "prop"): (0, 1),
     }
 
+
     def validate(self):
         super(PropertyFind, self).validate()
 

Modified: CalendarServer/trunk/txdav/xml/rfc3744.py
===================================================================
--- CalendarServer/trunk/txdav/xml/rfc3744.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/xml/rfc3744.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -56,6 +56,7 @@
 # rfc2518.py.
 
 
+
 @registerElement
 @registerElementClass
 class WriteProperties (WebDAVEmptyElement):
@@ -260,6 +261,7 @@
 # rfc2518.py.
 
 
+
 @registerElement
 @registerElementClass
 class Group (WebDAVElement):
@@ -445,6 +447,7 @@
 # For DAV:privilege element (RFC 3744, section 5.4) see Privilege class above.
 
 
+
 @registerElement
 @registerElementClass
 class ACL (WebDAVElement):
@@ -553,6 +556,7 @@
 # For DAV:all element (RFC 3744, section 5.5.1) see All class above.
 
 
+
 @registerElement
 @registerElementClass
 class Authenticated (WebDAVEmptyElement):
@@ -577,6 +581,7 @@
 # class above.
 
 
+
 @registerElement
 @registerElementClass
 class Self (WebDAVEmptyElement):
@@ -629,6 +634,7 @@
 # class above.
 
 
+
 @registerElement
 @registerElementClass
 class Protected (WebDAVEmptyElement):
@@ -895,6 +901,7 @@
 # For DAV:self element (RFC 3744, section 9.3) see Self class above.
 
 
+
 @registerElement
 @registerElementClass
 class PrincipalPropertySearch (WebDAVElement):

Modified: CalendarServer/trunk/txdav/xml/xmlext.py
===================================================================
--- CalendarServer/trunk/txdav/xml/xmlext.py	2014-02-04 23:38:34 UTC (rev 12564)
+++ CalendarServer/trunk/txdav/xml/xmlext.py	2014-02-04 23:58:26 UTC (rev 12565)
@@ -3,6 +3,7 @@
 # File Name:            __init__.py
 #
 #
+from types import UnicodeType
 """
 WWW: http://4suite.com/4DOM         e-mail: support at 4suite.com
 
@@ -13,14 +14,20 @@
 """Some Helper functions: 4DOM/PyXML-specific Extensions to the DOM,
 and DOM-related utilities."""
 
-__all__ = [ "Print", "PrettyPrint" ]
+__all__ = ["Print", "PrettyPrint"]
 
-import sys,string
+import string
+import sys
 import re
 
 from xml.dom import Node
-from xml.dom import XML_NAMESPACE, XMLNS_NAMESPACE, DOMException
+from xml.dom import XML_NAMESPACE, XMLNS_NAMESPACE
 
+HTML_4_TRANSITIONAL_INLINE = ['TT', 'I', 'B', 'U', 'S', 'STRIKE', 'BIG', 'SMALL', 'EM', 'STRONG', 'DFN', 'CODE', 'SAMP', 'KBD', 'VAR', 'CITE', 'ABBR', 'ACRONYM', 'A', 'IMG', 'APPLET', 'OBJECT', 'FONT', 'BASEFONT', 'SCRIPT', 'MAP', 'Q', 'SUB', 'SUP', 'SPAN', 'BDO', 'IFRAME', 'INPUT', 'SELECT', 'TEXTAREA', 'LABEL', 'BUTTON']
+HTML_FORBIDDEN_END = ['AREA', 'BASE', 'BASEFONT', 'BR', 'COL', 'FRAME', 'HR', 'IMG', 'INPUT', 'ISINDEX', 'LINK', 'META', 'PARAM']
+XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"
+
+
 def Print(root, stream=sys.stdout, encoding='UTF-8'):
     if not hasattr(root, "nodeType"):
         return
@@ -29,6 +36,8 @@
     PrintWalker(visitor, root).run()
     return
 
+
+
 def PrettyPrint(root, stream=sys.stdout, encoding='UTF-8', indent='  ',
                 preserveElements=None):
     if not hasattr(root, "nodeType"):
@@ -45,6 +54,8 @@
     stream.write('\n')
     return
 
+
+
 def GetAllNs(node):
     #The xml namespace is implicit
     nss = {'xml': XML_NAMESPACE}
@@ -68,6 +79,8 @@
         nss = parent_nss
     return nss
 
+
+
 def SeekNss(node, nss=None):
     '''traverses the tree to seek an approximate set of defined namespaces'''
     nss = nss or {}
@@ -86,6 +99,8 @@
             SeekNss(child, nss)
     return nss
 
+
+
 class PrintVisitor:
     def __init__(self, stream, encoding, indent='', plainElements=None,
                  nsHints=None, isXhtml=0, force8bit=0):
@@ -105,6 +120,7 @@
         self.force8bit = force8bit
         return
 
+
     def _write(self, text):
         if self.force8bit:
             obj = strobj_to_utf8str(text, self.encoding)
@@ -113,17 +129,18 @@
         self.stream.write(obj)
         return
 
+
     def _tryIndent(self):
         if not self._inText and self._indent:
-            self._write('\n' + self._indent*self._depth)
+            self._write('\n' + self._indent * self._depth)
         return
 
+
     def visit(self, node):
         if self._html is None:
             # Set HTMLDocument flag here for speed
             self._html = hasattr(node.ownerDocument, 'getElementsByName')
 
-        nodeType = node.nodeType
         if node.nodeType == Node.ELEMENT_NODE:
             return self.visitElement(node)
 
@@ -163,16 +180,20 @@
         # It has a node type, but we don't know how to handle it
         raise Exception("Unknown node type: %s" % repr(node))
 
+
     def visitNodeList(self, node, exclude=None):
         for curr in node:
-            curr is not exclude and self.visit(curr)
+            if curr is not exclude:
+                self.visit(curr)
         return
 
+
     def visitNamedNodeMap(self, node):
         for item in node.values():
             self.visit(item)
         return
 
+
     def visitAttr(self, node):
         if node.namespaceURI == XMLNS_NAMESPACE:
             # Skip namespace declarations
@@ -185,6 +206,7 @@
             self.stream.write("=%s%s%s" % (delimiter, text, delimiter))
         return
 
+
     def visitProlog(self):
         self._write("<?xml version='1.0' encoding='%s'?>" % (
             self.encoding or 'utf-8'
@@ -192,16 +214,19 @@
         self._inText = 0
         return
 
+
     def visitDocument(self, node):
         not self._html and self.visitProlog()
         node.doctype and self.visitDocumentType(node.doctype)
         self.visitNodeList(node.childNodes, exclude=node.doctype)
         return
 
+
     def visitDocumentFragment(self, node):
         self.visitNodeList(node.childNodes)
         return
 
+
     def visitElement(self, node):
         self._namespaces.append(self._namespaces[-1].copy())
         inline = node.tagName in self._plainElements
@@ -219,12 +244,12 @@
                 self._nsHints = {}
             del nss['xml']
             for prefix in nss.keys():
-                if not self._namespaces[-1].has_key(prefix) or self._namespaces[-1][prefix] != nss[prefix]:
+                if prefix not in self._namespaces[-1] or self._namespaces[-1][prefix] != nss[prefix]:
                     nsuri, delimiter = TranslateCdataAttr(nss[prefix])
                     if prefix:
-                        xmlns = " xmlns:%s=%s%s%s" % (prefix, delimiter,nsuri,delimiter)
+                        xmlns = " xmlns:%s=%s%s%s" % (prefix, delimiter, nsuri, delimiter)
                     else:
-                        xmlns = " xmlns=%s%s%s" % (delimiter,nsuri,delimiter)
+                        xmlns = " xmlns=%s%s%s" % (delimiter, nsuri, delimiter)
                     namespaces = namespaces + xmlns
 
                 self._namespaces[-1][prefix] = nss[prefix]
@@ -249,6 +274,7 @@
         self._inText = 0
         return
 
+
     def visitText(self, node):
         text = node.data
         if self._indent:
@@ -262,8 +288,10 @@
             self._inText = 1
         return
 
+
     def visitDocumentType(self, doctype):
-        if not doctype.systemId and not doctype.publicId: return
+        if not doctype.systemId and not doctype.publicId:
+            return
         self._tryIndent()
         self._write('<!DOCTYPE %s' % doctype.name)
         if doctype.systemId and '"' in doctype.systemId:
@@ -294,6 +322,7 @@
         self._inText = 0
         return
 
+
     def visitEntity(self, node):
         """Visited from a NamedNodeMap in DocumentType"""
         self._tryIndent()
@@ -304,6 +333,7 @@
         self._write('>')
         return
 
+
     def visitNotation(self, node):
         """Visited from a NamedNodeMap in DocumentType"""
         self._tryIndent()
@@ -313,40 +343,48 @@
         self._write('>')
         return
 
+
     def visitCDATASection(self, node):
         self._tryIndent()
         self._write('<![CDATA[%s]]>' % (node.data))
         self._inText = 0
         return
 
+
     def visitComment(self, node):
         self._tryIndent()
         self._write('<!--%s-->' % (node.data))
         self._inText = 0
         return
 
+
     def visitEntityReference(self, node):
         self._write('&%s;' % node.nodeName)
         self._inText = 1
         return
 
+
     def visitProcessingInstruction(self, node):
         self._tryIndent()
         self._write('<?%s %s?>' % (node.target, node.data))
         self._inText = 0
         return
 
+
+
 class PrintWalker:
     def __init__(self, visitor, startNode):
         self.visitor = visitor
         self.start_node = startNode
         return
 
+
     def step(self):
         """There is really no step to printing.  It prints the whole thing"""
         self.visitor.visit(self.start_node)
         return
 
+
     def run(self):
         return self.step()
 
@@ -354,7 +392,7 @@
 SURROGATE_BLOCK = '[\xF0-\xF7][\x80-\xBF][\x80-\xBF][\x80-\xBF]'
 ILLEGAL_HIGH_CHARS = '\xEF\xBF[\xBE\xBF]'
 #Note: Prolly fuzzy on this, but it looks as if characters from the surrogate block are allowed if in scalar form, which is encoded in UTF8 the same was as in surrogate block form
-XML_ILLEGAL_CHAR_PATTERN = re.compile('%s|%s'%(ILLEGAL_LOW_CHARS, ILLEGAL_HIGH_CHARS))
+XML_ILLEGAL_CHAR_PATTERN = re.compile('%s|%s' % (ILLEGAL_LOW_CHARS, ILLEGAL_HIGH_CHARS))
 
 g_utf8TwoBytePattern = re.compile('([\xC0-\xC3])([\x80-\xBF])')
 g_cdataCharPattern = re.compile('[&<]|]]>')
@@ -371,15 +409,20 @@
     if type(text) is not unicode:
         text = unicode(text, "utf-8")
     return encoder(text)[0] # result,size
+
+
+
 def strobj_to_utf8str(text, encoding):
     if string.upper(encoding) not in ["UTF-8", "ISO-8859-1", "LATIN-1"]:
-        raise ValueError("Invalid encoding: %s"%encoding)
+        raise ValueError("Invalid encoding: %s" % encoding)
     encoder = codecs.lookup(encoding)[0] # encode,decode,reader,writer
     if type(text) is not unicode:
         text = unicode(text, "utf-8")
     #FIXME
     return str(encoder(text)[0])
 
+
+
 def TranslateCdataAttr(characters):
     '''Handles normalization and some intelligence about quoting'''
     if not characters:
@@ -397,12 +440,14 @@
         new_chars = re.sub('\n', '&#10;', new_chars)
     return new_chars, delimiter
 
+
+
 #Note: Unicode object only for now
 def TranslateCdata(characters, encoding='UTF-8', prev_chars='', markupSafe=0,
                    charsetHandler=utf8_to_code):
     """
     charsetHandler is a function that takes a string or unicode object as the
-    first argument, representing the string to be procesed, and an encoding
+    first argument, representing the string to be processed, and an encoding
     specifier as the second argument.  It must return a string or unicode
     object
     """
@@ -428,3 +473,312 @@
             new_string)[0]
     new_string = charsetHandler(new_string, encoding)
     return new_string
+
+
+
+def TranslateHtmlCdata(characters, encoding='UTF-8', prev_chars=''):
+    #Translate numerical char entity references with HTML entity equivalents
+    new_string, _ignore_num_subst = re.subn(
+        g_cdataCharPattern,
+        lambda m, d=g_charToEntity: d[m.group()],
+        characters
+        )
+    if prev_chars[-2:] == ']]' and new_string[0] == '>':
+        new_string = '&gt;' + new_string[1:]
+    new_string = UseHtmlCharEntities(new_string)
+    try:
+        new_string = utf8_to_code(new_string, encoding)
+    except:
+        #FIXME: This is a work-around, contributed by Mike Brown, that
+        #Deals with escaping output, until we have XML/HTML aware codecs
+        tmp_new_string = ""
+        for c in new_string:
+            try:
+                new_c = utf8_to_code(c, encoding)
+            except:
+                new_c = '&#%i;' % ord(c)
+            tmp_new_string = tmp_new_string + new_c
+        new_string = tmp_new_string
+    #new_string, num_subst = re.subn(g_xmlIllegalCharPattern, lambda m: '&#%i;'%ord(m.group()), new_string)
+    #Note: use decimal char entity rep because some browsers are broken
+    return new_string
+
+
+HTML_CHARACTER_ENTITIES = {
+    # Sect 24.2 -- ISO 8859-1
+    160: 'nbsp',
+    161: 'iexcl',
+    162: 'cent',
+    163: 'pound',
+    164: 'curren',
+    165: 'yen',
+    166: 'brvbar',
+    167: 'sect',
+    168: 'uml',
+    169: 'copy',
+    170: 'ordf',
+    171: 'laquo',
+    172: 'not',
+    173: 'shy',
+    174: 'reg',
+    175: 'macr',
+    176: 'deg',
+    177: 'plusmn',
+    178: 'sup2',
+    179: 'sup3',
+    180: 'acute',
+    181: 'micro',
+    182: 'para',
+    183: 'middot',
+    184: 'cedil',
+    185: 'sup1',
+    186: 'ordm',
+    187: 'raquo',
+    188: 'frac14',
+    189: 'frac12',
+    190: 'frac34',
+    191: 'iquest',
+    192: 'Agrave',
+    193: 'Aacute',
+    194: 'Acirc',
+    195: 'Atilde',
+    196: 'Auml',
+    197: 'Aring',
+    198: 'AElig',
+    199: 'Ccedil',
+    200: 'Egrave',
+    201: 'Eacute',
+    202: 'Ecirc',
+    203: 'Euml',
+    204: 'Igrave',
+    205: 'Iacute',
+    206: 'Icirc',
+    207: 'Iuml',
+    208: 'ETH',
+    209: 'Ntilde',
+    210: 'Ograve',
+    211: 'Oacute',
+    212: 'Ocirc',
+    213: 'Otilde',
+    214: 'Ouml',
+    215: 'times',
+    216: 'Oslash',
+    217: 'Ugrave',
+    218: 'Uacute',
+    219: 'Ucirc',
+    220: 'Uuml',
+    221: 'Yacute',
+    222: 'THORN',
+    223: 'szlig',
+    224: 'agrave',
+    225: 'aacute',
+    226: 'acirc',
+    227: 'atilde',
+    228: 'auml',
+    229: 'aring',
+    230: 'aelig',
+    231: 'ccedil',
+    232: 'egrave',
+    233: 'eacute',
+    234: 'ecirc',
+    235: 'euml',
+    236: 'igrave',
+    237: 'iacute',
+    238: 'icirc',
+    239: 'iuml',
+    240: 'eth',
+    241: 'ntilde',
+    242: 'ograve',
+    243: 'oacute',
+    244: 'ocirc',
+    245: 'otilde',
+    246: 'ouml',
+    247: 'divide',
+    248: 'oslash',
+    249: 'ugrave',
+    250: 'uacute',
+    251: 'ucirc',
+    252: 'uuml',
+    253: 'yacute',
+    254: 'thorn',
+    255: 'yuml',
+
+    # Sect 24.3 -- Symbols, Mathematical Symbols, and Greek Letters
+    # Latin Extended-B
+    402: 'fnof',
+    # Greek
+    913: 'Alpha',
+    914: 'Beta',
+    915: 'Gamma',
+    916: 'Delta',
+    917: 'Epsilon',
+    918: 'Zeta',
+    919: 'Eta',
+    920: 'Theta',
+    921: 'Iota',
+    922: 'Kappa',
+    923: 'Lambda',
+    924: 'Mu',
+    925: 'Nu',
+    926: 'Xi',
+    927: 'Omicron',
+    928: 'Pi',
+    929: 'Rho',
+    931: 'Sigma',
+    932: 'Tau',
+    933: 'Upsilon',
+    934: 'Phi',
+    935: 'Chi',
+    936: 'Psi',
+    937: 'Omega',
+    945: 'alpha',
+    946: 'beta',
+    947: 'gamma',
+    948: 'delta',
+    949: 'epsilon',
+    950: 'zeta',
+    951: 'eta',
+    952: 'theta',
+    953: 'iota',
+    954: 'kappa',
+    955: 'lambda',
+    956: 'mu',
+    957: 'nu',
+    958: 'xi',
+    959: 'omicron',
+    960: 'pi',
+    961: 'rho',
+    962: 'sigmaf',
+    963: 'sigma',
+    964: 'tau',
+    965: 'upsilon',
+    966: 'phi',
+    967: 'chi',
+    968: 'psi',
+    969: 'omega',
+    977: 'thetasym',
+    978: 'upsih',
+    982: 'piv',
+    # General Punctuation
+    8226: 'bull',      # bullet
+    8230: 'hellip',    # horizontal ellipsis
+    8242: 'prime',     # prime (minutes/feet)
+    8243: 'Prime',     # double prime (seconds/inches)
+    8254: 'oline',     # overline (spacing overscore)
+    8250: 'frasl',     # fractional slash
+    # Letterlike Symbols
+    8472: 'weierp',    # script capital P (power set/Weierstrass p)
+    8465: 'image',     # blackletter capital I (imaginary part)
+    8476: 'real',      # blackletter capital R (real part)
+    8482: 'trade',     # trademark
+    8501: 'alefsym',   # alef symbol (first transfinite cardinal)
+    # Arrows
+    8592: 'larr',      # leftwards arrow
+    8593: 'uarr',      # upwards arrow
+    8594: 'rarr',      # rightwards arrow
+    8595: 'darr',      # downwards arrow
+    8596: 'harr',      # left right arrow
+    8629: 'crarr',     # downwards arrow with corner leftwards (carriage return)
+    8656: 'lArr',      # leftwards double arrow
+    8657: 'uArr',      # upwards double arrow
+    8658: 'rArr',      # rightwards double arrow
+    8659: 'dArr',      # downwards double arrow
+    8660: 'hArr',      # left right double arrow
+    # Mathematical Operators
+    8704: 'forall',    # for all
+    8706: 'part',      # partial differential
+    8707: 'exist',     # there exists
+    8709: 'empty',     # empty set, null set, diameter
+    8711: 'nabla',     # nabla, backward difference
+    8712: 'isin',      # element of
+    8713: 'notin',     # not an element of
+    8715: 'ni',        # contains as member
+    8719: 'prod',      # n-ary product, product sign
+    8721: 'sum',       # n-ary sumation
+    8722: 'minus',     # minus sign
+    8727: 'lowast',    # asterisk operator
+    8730: 'radic',     # square root, radical sign
+    8733: 'prop',      # proportional to
+    8734: 'infin',     # infinity
+    8736: 'ang',       # angle
+    8743: 'and',       # logical and, wedge
+    8744: 'or',        # logical or, vee
+    8745: 'cap',       # intersection, cap
+    8746: 'cup',       # union, cup
+    8747: 'int',       # integral
+    8756: 'there4',    # therefore
+    8764: 'sim',       # tilde operator, varies with, similar to
+    8773: 'cong',      # approximately equal to
+    8776: 'asymp',     # almost equal to, asymptotic to
+    8800: 'ne',        # not equal to
+    8801: 'equiv',     # identical to
+    8804: 'le',        # less-than or equal to
+    8805: 'ge',        # greater-than or equal to
+    8834: 'sub',       # subset of
+    8835: 'sup',       # superset of
+    8836: 'nsub',      # not subset of
+    8838: 'sube',      # subset of or equal to
+    8839: 'supe',      # superset of or equal to
+    8853: 'oplus',     # circled plus, direct sum
+    8855: 'otimes',    # circled times, vector product
+    8869: 'perp',      # up tack, orthogonal to, perpendicular
+    8901: 'sdot',      # dot operator
+    8968: 'lceil',     # left ceiling, apl upstile
+    8969: 'rceil',     # right ceiling
+    8970: 'lfloor',    # left floor, apl downstile
+    8971: 'rfloor',    # right floor
+    9001: 'lang',      # left-pointing angle bracket, bra
+    9002: 'rang',      # right-pointing angle bracket, ket
+    9674: 'loz',       # lozenge
+    # Miscellaneous Symbols
+    9824: 'spades',
+    9827: 'clubs',
+    9829: 'hearts',
+    9830: 'diams',
+
+    # Sect 24.4 -- Markup Significant and Internationalization
+    # Latin Extended-A
+    338: 'OElig',      # capital ligature OE
+    339: 'oelig',      # small ligature oe
+    352: 'Scaron',     # capital S with caron
+    353: 'scaron',     # small s with caron
+    376: 'Yuml',       # capital Y with diaeresis
+    # Spacing Modifier Letters
+    710: 'circ',       # circumflexx accent
+    732: 'tidle',      # small tilde
+    # General Punctuation
+    8194: 'ensp',      # en space
+    8195: 'emsp',      # em space
+    8201: 'thinsp',    # thin space
+    8204: 'zwnj',      # zero-width non-joiner
+    8205: 'zwj',       # zero-width joiner
+    8206: 'lrm',       # left-to-right mark
+    8207: 'rlm',       # right-to-left mark
+    8211: 'ndash',     # en dash
+    8212: 'mdash',     # em dash
+    8216: 'lsquo',     # left single quotation mark
+    8217: 'rsquo',     # right single quotation mark
+    8218: 'sbquo',     # single low-9 quotation mark
+    8220: 'ldquo',     # left double quotation mark
+    8221: 'rdquo',     # right double quotation mark
+    8222: 'bdquo',     # double low-9 quotation mark
+    8224: 'dagger',    # dagger
+    8225: 'Dagger',    # double dagger
+    8240: 'permil',    # per mille sign
+    8249: 'lsaquo',    # single left-pointing angle quotation mark
+    8250: 'rsaquo',    # single right-pointing angle quotation mark
+    8364: 'euro',      # euro sign
+}
+
+g_htmlUniCharEntityPattern = re.compile('[\xa0-\xff]')
+
+def ConvertChar(m):
+    return '&' + HTML_CHARACTER_ENTITIES[ord(m.group())] + ';'
+
+
+
+def UseHtmlCharEntities(text):
+    if type(text) is not UnicodeType:
+        text = unicode(text, "utf-8")
+    new_text, _ignore_num_subst = re.subn(g_htmlUniCharEntityPattern, ConvertChar, text)
+    return new_text
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/b0c05b64/attachment.html>


More information about the calendarserver-changes mailing list