<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[12549] CalendarServer/branches/users/cdaboo/scheduling-queue-refresh</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/12549">12549</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-02-03 17:26:39 -0800 (Mon, 03 Feb 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Fully enable scheduling queues. This currently does a &quot;monolithic&quot; organizer work item (all attendees in one txn),
but the organizer PUT is not blocked on that. A later revision will split the organizer work into smaller chunks.
There is also a schedule work queue visualization tool showing the active work items.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdabooschedulingqueuerefreshconfcaldavdtestplist">CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/conf/caldavd-test.plist</a></li>
<li><a href="#CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcaldavdatastoreschedulingimplicitpy">CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/scheduling/implicit.py</a></li>
<li><a href="#CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcaldavdatastoreschedulingworkpy">CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/scheduling/work.py</a></li>
<li><a href="#CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcommondatastoresql_schemacurrentoracledialectsql">CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/current-oracle-dialect.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcommondatastoresql_schemacurrentsql">CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/current.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_33_to_34sql">CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_33_to_34.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_33_to_34sql">CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_33_to_34.sql</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdabooschedulingqueuerefreshcalendarservertoolsschedule_workitemspy">CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/calendarserver/tools/schedule_workitems.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdabooschedulingqueuerefreshcalendarservertoolsschedule_workitemspy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/calendarserver/tools/schedule_workitems.py (0 => 12549)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/calendarserver/tools/schedule_workitems.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/calendarserver/tools/schedule_workitems.py        2014-02-04 01:26:39 UTC (rev 12549)
</span><span class="lines">@@ -0,0 +1,228 @@
</span><ins>+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# 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 &quot;AS IS&quot; 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(&quot;usage: %s [options]&quot; % (name,))
+    print(&quot;&quot;)
+    print(&quot;  TODO: describe usage&quot;)
+    print(&quot;&quot;)
+    print(&quot;options:&quot;)
+    print(&quot;  -h --help: print this help and exit&quot;)
+    print(&quot;  -e --error: send stderr to stdout&quot;)
+    print(&quot;  -f --config &lt;path&gt;: Specify caldavd.plist configuration path&quot;)
+    print(&quot;&quot;)
+
+    if e:
+        sys.exit(64)
+    else:
+        sys.exit(0)
+
+
+
+def main():
+
+    try:
+        (optargs, _ignore_args) = getopt(
+            sys.argv[1:], &quot;hef:&quot;, [
+                &quot;help&quot;,
+                &quot;error&quot;,
+                &quot;config=&quot;,
+            ],
+        )
+    except GetoptError, e:
+        usage(e)
+
+    #
+    # Get configuration
+    #
+    configFileName = None
+    debug = False
+
+    for opt, arg in optargs:
+        if opt in (&quot;-h&quot;, &quot;--help&quot;):
+            usage()
+
+        if opt in (&quot;-e&quot;, &quot;--error&quot;):
+            debug = True
+
+        elif opt in (&quot;-f&quot;, &quot;--config&quot;):
+            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):
+        &quot;&quot;&quot;
+        Don't quit right away
+        &quot;&quot;&quot;
+        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.: &quot;42%&quot;
+        # 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 (
+            (&quot;Organizer Requests&quot;, &quot;100%&quot;, &quot;25%&quot;, 1, 1, ScheduleOrganizerWork, &quot;%s: %d&quot;, (&quot;icalendarUid&quot;, &quot;attendeeCount&quot;)),
+            (&quot;Attendee Replies&quot;, &quot;100%&quot;, &quot;25%&quot;, 1, 2, ScheduleReplyWork, &quot;%s&quot;, (&quot;icalendarUid&quot;,)),
+            (&quot;Attendee Refresh&quot;, &quot;100%&quot;, &quot;25%&quot;, 1, 3, ScheduleRefreshWork, &quot;%s: %d&quot;, (&quot;icalendarUid&quot;, &quot;attendeeCount&quot;)),
+#            (&quot;Auto Reply&quot;, &quot;100%&quot;, &quot;25%&quot;, 1, 4, ScheduleAutoReplyWork, &quot;%s&quot;, (&quot;icalendarUid&quot;)),
+            (&quot;Push Notifications&quot;, &quot;100%&quot;, &quot;25%&quot;, 1, 4, PushNotificationWork, &quot;%s: %d&quot;, (&quot;pushID&quot;, &quot;priority&quot;)),
+        ):
+            if (isinstance(height, basestring)):
+                height = max(int(winY * (float(height.strip(&quot;%&quot;)) / 100.0)), 3)
+            if (isinstance(width, basestring)):
+                width = max(int(winX * (float(width.strip(&quot;%&quot;)) / 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(&quot;-------------&quot;)
+
+        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 + &quot; %d (%d)&quot; % (len(records), self.iter,))
+
+        x = 1
+        y = 1
+        for record in records:
+            txt = &quot;&quot;
+            seconds = record.notBefore - datetime.datetime.utcnow()
+            try:
+                if useCurses:
+                    self.window.addstr(y, x, &quot;%d seconds&quot; % int(seconds.total_seconds()))
+                else:
+                    txt = &quot;%s:&quot; % (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 = &quot;Error: %s&quot; % (str(e),)
+                try:
+                    if useCurses:
+                        self.window.addnstr(y, x, s, self.ncols - 2)
+                    else:
+                        txt += &quot; &quot; + s
+                except curses.error:
+                    pass
+                y += 1
+
+            if not useCurses:
+                print(txt)
+
+        if useCurses:
+            self.window.refresh()
+
+        yield txn.commit()
+
+
+if __name__ == &quot;__main__&quot;:
+    main()
</ins><span class="cx">Property changes on: CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/calendarserver/tools/schedule_workitems.py
</span><span class="cx">___________________________________________________________________
</span></span></pre></div>
<a id="svnexecutable"></a>
<div class="addfile"><h4>Added: svn:executable</h4></div>
<a id="CalendarServerbranchesuserscdabooschedulingqueuerefreshconfcaldavdtestplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/conf/caldavd-test.plist (12548 => 12549)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/conf/caldavd-test.plist        2014-02-04 01:25:14 UTC (rev 12548)
+++ CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/conf/caldavd-test.plist        2014-02-04 01:26:39 UTC (rev 12549)
</span><span class="lines">@@ -796,7 +796,7 @@
</span><span class="cx">                 &lt;key&gt;WorkQueues&lt;/key&gt;
</span><span class="cx">                 &lt;dict&gt;
</span><span class="cx">                         &lt;key&gt;Enabled&lt;/key&gt;
</span><del>-                        &lt;false/&gt;
</del><ins>+                        &lt;true/&gt;
</ins><span class="cx">             &lt;key&gt;RequestDelaySeconds&lt;/key&gt;
</span><span class="cx">             &lt;integer&gt;1&lt;/integer&gt;
</span><span class="cx">             &lt;key&gt;ReplyDelaySeconds&lt;/key&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcaldavdatastoreschedulingimplicitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/scheduling/implicit.py (12548 => 12549)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/scheduling/implicit.py        2014-02-04 01:25:14 UTC (rev 12548)
+++ CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/scheduling/implicit.py        2014-02-04 01:26:39 UTC (rev 12549)
</span><span class="lines">@@ -395,7 +395,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def queuedOrganizerProcessing(self, txn, action, home, resource, uid, calendar, smart_merge):
</del><ins>+    def queuedOrganizerProcessing(self, txn, action, home, resource, uid, calendar_old, calendar_new, smart_merge):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Process an organizer scheduling work queue item. The basic goal here is to setup the ImplicitScheduler as if
</span><span class="cx">         this operation were the equivalent of the PUT that enqueued the work, and then do the actual work.
</span><span class="lines">@@ -407,46 +407,44 @@
</span><span class="cx">         self.calendar_home = home
</span><span class="cx">         self.resource = resource
</span><span class="cx">         self.do_smart_merge = smart_merge
</span><ins>+        self.queuedResponses = []
</ins><span class="cx"> 
</span><ins>+        cal_uid = calendar_old.resourceUID() if calendar_old is not None else (calendar_new.resourceUID() if calendar_new is not None else &quot;unknown&quot;)
+
</ins><span class="cx">         # Handle different action scenarios
</span><span class="cx">         if action == &quot;create&quot;:
</span><del>-            # resource is None, calendar is None
</del><ins>+            # resource is None, calendar_old is None
</ins><span class="cx">             # Find the newly created resource
</span><span class="cx">             resources = (yield self.calendar_home.objectResourcesWithUID(uid, ignore_children=[&quot;inbox&quot;], allowShared=False))
</span><span class="cx">             if len(resources) != 1:
</span><span class="cx">                 # Ughh - what has happened? It is possible the resource was created then deleted before we could start work processing,
</span><span class="cx">                 # so simply ignore this
</span><del>-                log.debug(&quot;ImplicitScheduler - queuedOrganizerProcessing 'create' cannot find organizer resource for UID: {uid}&quot;, uid=calendar.resourceUID())
</del><ins>+                log.debug(&quot;ImplicitScheduler - queuedOrganizerProcessing 'create' cannot find organizer resource for UID: {uid}&quot;, uid=cal_uid)
</ins><span class="cx">                 returnValue(None)
</span><span class="cx">             self.resource = resources[0]
</span><ins>+            self.calendar = calendar_new
</ins><span class="cx"> 
</span><del>-            # The calendar data to use is the current calendar data, not what was stored in the work item, since it might have been
-            # updated a few times after the create, but those modifications are effectively coalesced into the create
-            self.calendar = (yield self.resource.componentForUser())
-
-        elif action == &quot;modify&quot;:
</del><ins>+        elif action in (&quot;modify&quot;, &quot;modify-cancelled&quot;):
</ins><span class="cx">             # Check that the resource still exists - it may have been deleted after this work item was queued, in which
</span><span class="cx">             # case we have to ignore this (on the assumption that the &quot;remove&quot; action will have queued some work that will
</span><span class="cx">             # execute soon).
</span><span class="cx">             if self.resource is None:
</span><del>-                log.debug(&quot;ImplicitScheduler - queuedOrganizerProcessing 'modify' cannot find organizer resource for UID: {uid}&quot;, uid=calendar.resourceUID())
</del><ins>+                log.debug(&quot;ImplicitScheduler - queuedOrganizerProcessing 'modify' cannot find organizer resource for UID: {uid}&quot;, uid=cal_uid)
</ins><span class="cx">                 returnValue(None)
</span><span class="cx"> 
</span><del>-            # The new calendar data is what is currently stored - other modifications may have causes coalescing.
-            # Old calendar data is what was stored int he work item
-            self.calendar = (yield self.resource.componentForUser())
-            self.oldcalendar = calendar
</del><ins>+            # 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
</ins><span class="cx"> 
</span><span class="cx">         elif action == &quot;remove&quot;:
</span><del>-            # Check whether the resource still exists - it cannot be in existence as once it is deleted, its resource-id
-            # should never be used again.
-            if self.resource is not None:
-                log.debug(&quot;ImplicitScheduler - queuedOrganizerProcessing 'remove' found an organizer resource for UID: {uid}&quot;, uid=calendar.resourceUID())
-                raise ImplicitSchedulingWorkError(&quot;Resource exists for queued 'remove' scheduling work&quot;)
</del><ins>+            # 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.
</ins><span class="cx"> 
</span><del>-            # The &quot;new&quot; calendar data is in fact the calendar data at the time of the remove - which is the data stored
</del><ins>+            # The &quot;new&quot; calendar_old data is in fact the calendar_old data at the time of the remove - which is the data stored
</ins><span class="cx">             # in the work item.
</span><del>-            self.calendar = calendar
</del><ins>+            self.calendar = calendar_old
</ins><span class="cx"> 
</span><span class="cx">         yield self.extractCalendarData()
</span><span class="cx">         self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
</span><span class="lines">@@ -615,11 +613,10 @@
</span><span class="cx">             self.cancelledAttendees = [(attendee, None) for attendee in self.attendees]
</span><span class="cx"> 
</span><span class="cx">             # CANCEL always bumps sequence
</span><del>-            if not queued or not config.Scheduling.Options.WorkQueues.Enabled:
-                self.needs_sequence_change = True
</del><ins>+            self.needs_sequence_change = True
</ins><span class="cx"> 
</span><span class="cx">         # Check for a new resource or an update
</span><del>-        elif self.action == &quot;modify&quot;:
</del><ins>+        elif self.action in (&quot;modify&quot;, &quot;modify-cancelled&quot;):
</ins><span class="cx"> 
</span><span class="cx">             # Read in existing data
</span><span class="cx">             if not queued or not config.Scheduling.Options.WorkQueues.Enabled:
</span><span class="lines">@@ -678,8 +675,7 @@
</span><span class="cx"> 
</span><span class="cx">                 # For now we always bump the sequence number on modifications because we cannot track DTSTAMP on
</span><span class="cx">                 # the Attendee side. But we check the old and the new and only bump if the client did not already do it.
</span><del>-                if not queued or not config.Scheduling.Options.WorkQueues.Enabled:
-                    self.needs_sequence_change = self.calendar.needsiTIPSequenceChange(self.oldcalendar)
</del><ins>+                self.needs_sequence_change = self.calendar.needsiTIPSequenceChange(self.oldcalendar)
</ins><span class="cx"> 
</span><span class="cx">         elif self.action == &quot;create&quot;:
</span><span class="cx">             if self.split_details is None:
</span><span class="lines">@@ -694,12 +690,12 @@
</span><span class="cx">             if attendee.parameterValue(&quot;PARTSTAT&quot;, &quot;NEEDS-ACTION&quot;).upper() == &quot;NEEDS-ACTION&quot;:
</span><span class="cx">                 attendee.setParameter(&quot;RSVP&quot;, &quot;TRUE&quot;)
</span><span class="cx"> 
</span><del>-        if self.needs_sequence_change:
-            self.calendar.bumpiTIPInfo(oldcalendar=self.oldcalendar, doSequence=True)
-
</del><span class="cx">         # If processing a queue item, actually execute the scheduling operations, else queue it.
</span><span class="cx">         # Note a split is always queued, so we do not need to re-queue
</span><span class="cx">         if queued or not config.Scheduling.Options.WorkQueues.Enabled or self.split_details is not None:
</span><ins>+            if self.needs_sequence_change:
+                self.calendar.bumpiTIPInfo(oldcalendar=self.oldcalendar, doSequence=True)
+
</ins><span class="cx">             yield self.scheduleWithAttendees()
</span><span class="cx">         else:
</span><span class="cx">             yield self.queuedScheduleWithAttendees()
</span><span class="lines">@@ -1040,10 +1036,18 @@
</span><span class="cx">             self.calendar_home,
</span><span class="cx">             self.resource,
</span><span class="cx">             self.oldcalendar,
</span><ins>+            self.calendar,
</ins><span class="cx">             self.organizerPrincipal.canonicalCalendarUserAddress(),
</span><ins>+            len(self.calendar.getAllUniqueAttendees()) - 1,
</ins><span class="cx">             self.do_smart_merge,
</span><span class="cx">         )
</span><span class="cx"> 
</span><ins>+        # 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)
+
</ins><span class="cx">         # First process cancelled attendees
</span><span class="cx">         total = (yield self.processQueuedCancels())
</span><span class="cx"> 
</span><span class="lines">@@ -1287,20 +1291,24 @@
</span><span class="cx"> 
</span><span class="cx">     def handleSchedulingResponse(self, response, is_organizer):
</span><span class="cx"> 
</span><del>-        # Map each recipient in the response to a status code
-        responses = {}
-        propname = self.calendar.mainComponent().recipientPropertyName() if is_organizer else &quot;ORGANIZER&quot;
-        for item in response.responses:
-            recipient = str(item.recipient.children[0])
-            status = str(item.reqstatus)
-            responses[recipient] = status
</del><ins>+        # For a queued operation we stash the response away for the work item to deal with
+        if hasattr(self, &quot;queuedResponses&quot;):
+            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 &quot;ORGANIZER&quot;
+            for item in response.responses:
+                recipient = str(item.recipient.children[0])
+                status = str(item.reqstatus)
+                responses[recipient] = status
</ins><span class="cx"> 
</span><del>-            # Now apply to each ATTENDEE/ORGANIZER in the original data
-            self.calendar.setParameterToValueForPropertyWithValue(
-                &quot;SCHEDULE-STATUS&quot;,
-                status.split(&quot;;&quot;)[0],
-                propname,
-                recipient)
</del><ins>+                # Now apply to each ATTENDEE/ORGANIZER in the original data
+                self.calendar.setParameterToValueForPropertyWithValue(
+                    &quot;SCHEDULE-STATUS&quot;,
+                    status.split(&quot;;&quot;)[0],
+                    propname,
+                    recipient)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcaldavdatastoreschedulingworkpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/scheduling/work.py (12548 => 12549)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/scheduling/work.py        2014-02-04 01:25:14 UTC (rev 12548)
+++ CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/caldav/datastore/scheduling/work.py        2014-02-04 01:26:39 UTC (rev 12549)
</span><span class="lines">@@ -33,6 +33,7 @@
</span><span class="cx"> import datetime
</span><span class="cx"> import hashlib
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><ins>+import traceback
</ins><span class="cx"> 
</span><span class="cx"> __all__ = [
</span><span class="cx">     &quot;ScheduleOrganizerWork&quot;,
</span><span class="lines">@@ -87,8 +88,7 @@
</span><span class="cx">                 self.transaction.postCommit(_post)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def handleSchedulingResponse(self, response, calendar, resource, is_organizer):
</del><ins>+    def handleSchedulingResponse(self, response, calendar, is_organizer):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Update a user's calendar object resource based on the results of a queued scheduling
</span><span class="cx">         message response. Note we only need to update in the case where there is an error response
</span><span class="lines">@@ -99,8 +99,6 @@
</span><span class="cx">         @type response: L{caldavxml.ScheduleResponse}
</span><span class="cx">         @param calendar: original calendar component
</span><span class="cx">         @type calendar: L{Component}
</span><del>-        @param resource: calendar object resource to update
-        @type resource: L{CalendarObject}
</del><span class="cx">         @param is_organizer: whether or not iTIP message was sent by the organizer
</span><span class="cx">         @type is_organizer: C{bool}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -123,8 +121,7 @@
</span><span class="cx">                 )
</span><span class="cx">                 changed = True
</span><span class="cx"> 
</span><del>-        if changed:
-            yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTENDEE_ITIP_UPDATE)
</del><ins>+        return changed
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -138,16 +135,23 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def schedule(cls, txn, uid, action, home, resource, calendar, organizer, smart_merge):
</del><ins>+    def schedule(cls, txn, uid, action, home, resource, calendar_old, calendar_new, organizer, attendee_count, smart_merge):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         The actual arguments depend on the action:
</span><span class="cx"> 
</span><del>-        1) If action is &quot;create&quot;, resource is None, calendar is None
-        2) If action is &quot;modify&quot;, resource is existing resource, calendar is the old calendar data
-        3) If action is &quot;remove&quot;, resource is the existing resource, calendar is the old calendar data
</del><ins>+        1) If action is &quot;create&quot;, resource is None, calendar_old is None, calendar_new is the new data
+        2) If action is &quot;modify&quot;, resource is existing resource, calendar_old is the old calendar_old data, and
+            calendar_new is the new data
+        3) If action is &quot;remove&quot;, resource is the existing resource, calendar_old is the old calendar_old data,
+            and calendar_new is None
</ins><span class="cx"> 
</span><del>-        Note that for (1), when the work executes the resource will be in existence so we need to load it.
-        Note that for (3), when work executes the resource will have been removed.
</del><ins>+        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).
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         # Always queue up new work - coalescing happens when work is executed
</span><span class="cx">         notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.RequestDelaySeconds)
</span><span class="lines">@@ -158,7 +162,9 @@
</span><span class="cx">             scheduleAction=scheduleActionToSQL[action],
</span><span class="cx">             homeResourceID=home.id(),
</span><span class="cx">             resourceID=resource.id() if resource else None,
</span><del>-            icalendarText=calendar.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference) if calendar else None,
</del><ins>+            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,
</ins><span class="cx">             smartMerge=smart_merge
</span><span class="cx">         ))
</span><span class="cx">         cls._enqueued()
</span><span class="lines">@@ -185,7 +191,8 @@
</span><span class="cx">             resource = (yield home.objectResourceWithID(self.resourceID))
</span><span class="cx">             organizerPrincipal = home.directoryService().recordWithUID(home.uid())
</span><span class="cx">             organizer = organizerPrincipal.canonicalCalendarUserAddress()
</span><del>-            calendar = Component.fromString(self.icalendarText) if self.icalendarText else None
</del><ins>+            calendar_old = Component.fromString(self.icalendarTextOld) if self.icalendarTextOld else None
+            calendar_new = Component.fromString(self.icalendarTextNew) if self.icalendarTextNew else None
</ins><span class="cx"> 
</span><span class="cx">             log.debug(&quot;ScheduleOrganizerWork - running for ID: {id}, UID: {uid}, organizer: {org}&quot;, id=self.workID, uid=self.icalendarUid, org=organizer)
</span><span class="cx"> 
</span><span class="lines">@@ -194,15 +201,39 @@
</span><span class="cx"> 
</span><span class="cx">             from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
</span><span class="cx">             scheduler = ImplicitScheduler()
</span><del>-            yield scheduler.queuedOrganizerProcessing(self.transaction, scheduleActionFromSQL[self.scheduleAction], home, resource, self.icalendarUid, calendar, self.smartMerge)
</del><ins>+            yield scheduler.queuedOrganizerProcessing(
+                self.transaction,
+                scheduleActionFromSQL[self.scheduleAction],
+                home,
+                resource,
+                self.icalendarUid,
+                calendar_old,
+                calendar_new,
+                self.smartMerge
+            )
</ins><span class="cx"> 
</span><ins>+            # 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)
+
</ins><span class="cx">             self._dequeued()
</span><span class="cx"> 
</span><span class="cx">         except Exception, e:
</span><span class="cx">             log.debug(&quot;ScheduleOrganizerWork - exception ID: {id}, UID: '{uid}', {err}&quot;, id=self.workID, uid=self.icalendarUid, err=str(e))
</span><ins>+            log.debug(traceback.format_exc())
</ins><span class="cx">             raise
</span><span class="cx">         except:
</span><span class="cx">             log.debug(&quot;ScheduleOrganizerWork - bare exception ID: {id}, UID: '{uid}'&quot;, id=self.workID, uid=self.icalendarUid)
</span><ins>+            log.debug(traceback.format_exc())
</ins><span class="cx">             raise
</span><span class="cx"> 
</span><span class="cx">         log.debug(&quot;ScheduleOrganizerWork - done for ID: {id}, UID: {uid}, organizer: {org}&quot;, id=self.workID, uid=self.icalendarUid, org=organizer)
</span><span class="lines">@@ -297,8 +328,11 @@
</span><span class="cx"> 
</span><span class="cx">             # Send scheduling message and process response
</span><span class="cx">             response = (yield self.sendToOrganizer(home, &quot;REPLY&quot;, itipmsg, attendee, organizer))
</span><del>-            yield self.handleSchedulingResponse(response, calendar, resource, False)
</del><ins>+            changed = yield self.handleSchedulingResponse(response, calendar, False)
</ins><span class="cx"> 
</span><ins>+            if changed:
+                yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTENDEE_ITIP_UPDATE)
+
</ins><span class="cx">             self._dequeued()
</span><span class="cx"> 
</span><span class="cx">         except Exception, e:
</span><span class="lines">@@ -441,7 +475,8 @@
</span><span class="cx">             icalendarUid=organizer_resource.uid(),
</span><span class="cx">             homeResourceID=organizer_resource._home.id(),
</span><span class="cx">             resourceID=organizer_resource.id(),
</span><del>-            notBefore=notBefore
</del><ins>+            attendeeCount=len(attendees),
+            notBefore=notBefore,
</ins><span class="cx">         ))
</span><span class="cx">         cls._enqueued()
</span><span class="cx">         yield proposal.whenProposed()
</span><span class="lines">@@ -494,8 +529,10 @@
</span><span class="cx">             notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=config.Scheduling.Options.WorkQueues.AttendeeRefreshBatchIntervalSeconds)
</span><span class="cx">             yield self.transaction.enqueue(
</span><span class="cx">                 self.__class__,
</span><ins>+                icalendarUid=self.icalendarUid,
</ins><span class="cx">                 homeResourceID=self.homeResourceID,
</span><span class="cx">                 resourceID=self.resourceID,
</span><ins>+                attendeeCount=len(pendingAttendees),
</ins><span class="cx">                 notBefore=notBefore
</span><span class="cx">             )
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcommondatastoresql_schemacurrentoracledialectsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/current-oracle-dialect.sql (12548 => 12549)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/current-oracle-dialect.sql        2014-02-04 01:25:14 UTC (rev 12548)
+++ CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/current-oracle-dialect.sql        2014-02-04 01:26:39 UTC (rev 12549)
</span><span class="lines">@@ -400,7 +400,8 @@
</span><span class="cx">     &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
</span><span class="cx">     &quot;ICALENDAR_UID&quot; nvarchar2(255),
</span><span class="cx">     &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
</span><del>-    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade
</del><ins>+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;ATTENDEE_COUNT&quot; integer
</ins><span class="cx"> );
</span><span class="cx"> 
</span><span class="cx"> create table SCHEDULE_REFRESH_ATTENDEES (
</span><span class="lines">@@ -424,7 +425,9 @@
</span><span class="cx">     &quot;SCHEDULE_ACTION&quot; integer not null,
</span><span class="cx">     &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
</span><span class="cx">     &quot;RESOURCE_ID&quot; integer,
</span><del>-    &quot;ICALENDAR_TEXT&quot; nclob,
</del><ins>+    &quot;ICALENDAR_TEXT_OLD&quot; nclob,
+    &quot;ICALENDAR_TEXT_NEW&quot; nclob,
+    &quot;ATTENDEE_COUNT&quot; integer,
</ins><span class="cx">     &quot;SMART_MERGE&quot; integer
</span><span class="cx"> );
</span><span class="cx"> 
</span><span class="lines">@@ -435,7 +438,8 @@
</span><span class="cx"> 
</span><span class="cx"> insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('create', 0);
</span><span class="cx"> insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify', 1);
</span><del>-insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('remove', 2);
</del><ins>+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify-cancelled', 2);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('remove', 3);
</ins><span class="cx"> create table SCHEDULE_REPLY_WORK (
</span><span class="cx">     &quot;WORK_ID&quot; integer primary key not null,
</span><span class="cx">     &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcommondatastoresql_schemacurrentsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/current.sql (12548 => 12549)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/current.sql        2014-02-04 01:25:14 UTC (rev 12548)
+++ CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/current.sql        2014-02-04 01:26:39 UTC (rev 12549)
</span><span class="lines">@@ -751,7 +751,8 @@
</span><span class="cx">   NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
</span><span class="cx">   ICALENDAR_UID                                varchar(255) not null,
</span><span class="cx">   HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
</span><del>-  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade
</del><ins>+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE_COUNT                                integer
</ins><span class="cx"> );
</span><span class="cx"> 
</span><span class="cx"> create index SCHEDULE_REFRESH_WORK_HOME_RESOURCE_ID on
</span><span class="lines">@@ -796,7 +797,9 @@
</span><span class="cx">   SCHEDULE_ACTION                                integer                 not null, -- Enum SCHEDULE_ACTION
</span><span class="cx">   HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
</span><span class="cx">   RESOURCE_ID                   integer,         -- this references a possibly non-existent CALENDR_OBJECT
</span><del>-  ICALENDAR_TEXT                                text,
</del><ins>+  ICALENDAR_TEXT_OLD                        text,
+  ICALENDAR_TEXT_NEW                        text,
+  ATTENDEE_COUNT                                integer,
</ins><span class="cx">   SMART_MERGE                                        boolean
</span><span class="cx"> );
</span><span class="cx"> 
</span><span class="lines">@@ -814,7 +817,8 @@
</span><span class="cx"> 
</span><span class="cx"> insert into SCHEDULE_ACTION values (0, 'create');
</span><span class="cx"> insert into SCHEDULE_ACTION values (1, 'modify');
</span><del>-insert into SCHEDULE_ACTION values (2, 'remove');
</del><ins>+insert into SCHEDULE_ACTION values (2, 'modify-cancelled');
+insert into SCHEDULE_ACTION values (3, 'remove');
</ins><span class="cx"> 
</span><span class="cx"> -------------------------
</span><span class="cx"> -- Schedule Reply Work --
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_33_to_34sql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_33_to_34.sql (12548 => 12549)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_33_to_34.sql        2014-02-04 01:25:14 UTC (rev 12548)
+++ CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_33_to_34.sql        2014-02-04 01:26:39 UTC (rev 12549)
</span><span class="lines">@@ -25,7 +25,8 @@
</span><span class="cx">     &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
</span><span class="cx">     &quot;ICALENDAR_UID&quot; nvarchar2(255),
</span><span class="cx">     &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
</span><del>-    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade
</del><ins>+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;ATTENDEE_COUNT&quot; integer
</ins><span class="cx"> );
</span><span class="cx"> 
</span><span class="cx"> create index SCHEDULE_REFRESH_WORK_26084c7b on SCHEDULE_REFRESH_WORK (
</span><span class="lines">@@ -72,7 +73,9 @@
</span><span class="cx">     &quot;SCHEDULE_ACTION&quot; integer not null,
</span><span class="cx">     &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
</span><span class="cx">     &quot;RESOURCE_ID&quot; integer,
</span><del>-    &quot;ICALENDAR_TEXT&quot; nclob,
</del><ins>+    &quot;ICALENDAR_TEXT_OLD&quot; nclob,
+    &quot;ICALENDAR_TEXT_NEW&quot; nclob,
+    &quot;ATTENDEE_COUNT&quot; integer,
</ins><span class="cx">     &quot;SMART_MERGE&quot; integer
</span><span class="cx"> );
</span><span class="cx"> 
</span><span class="lines">@@ -91,7 +94,8 @@
</span><span class="cx"> 
</span><span class="cx"> insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('create', 0);
</span><span class="cx"> insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify', 1);
</span><del>-insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('remove', 2);
</del><ins>+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify-cancelled', 2);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('remove', 3);
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> create table SCHEDULE_REPLY_WORK (
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdabooschedulingqueuerefreshtxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_33_to_34sql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_33_to_34.sql (12548 => 12549)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_33_to_34.sql        2014-02-04 01:25:14 UTC (rev 12548)
+++ CalendarServer/branches/users/cdaboo/scheduling-queue-refresh/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_33_to_34.sql        2014-02-04 01:26:39 UTC (rev 12549)
</span><span class="lines">@@ -29,7 +29,8 @@
</span><span class="cx">   NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
</span><span class="cx">   ICALENDAR_UID                                varchar(255) not null,
</span><span class="cx">   HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
</span><del>-  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade
</del><ins>+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE_COUNT                                integer
</ins><span class="cx"> );
</span><span class="cx"> 
</span><span class="cx"> create index SCHEDULE_REFRESH_WORK_HOME_RESOURCE_ID on
</span><span class="lines">@@ -74,7 +75,9 @@
</span><span class="cx">   SCHEDULE_ACTION                                integer                 not null, -- Enum SCHEDULE_ACTION
</span><span class="cx">   HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
</span><span class="cx">   RESOURCE_ID                   integer,         -- this references a possibly non-existent CALENDR_OBJECT
</span><del>-  ICALENDAR_TEXT                                text,
</del><ins>+  ICALENDAR_TEXT_OLD                        text,
+  ICALENDAR_TEXT_NEW                        text,
+  ATTENDEE_COUNT                                integer,
</ins><span class="cx">   SMART_MERGE                                        boolean
</span><span class="cx"> );
</span><span class="cx"> 
</span><span class="lines">@@ -92,7 +95,8 @@
</span><span class="cx"> 
</span><span class="cx"> insert into SCHEDULE_ACTION values (0, 'create');
</span><span class="cx"> insert into SCHEDULE_ACTION values (1, 'modify');
</span><del>-insert into SCHEDULE_ACTION values (2, 'remove');
</del><ins>+insert into SCHEDULE_ACTION values (2, 'modify-cancelled');
+insert into SCHEDULE_ACTION values (3, 'remove');
</ins><span class="cx"> 
</span><span class="cx"> -------------------------
</span><span class="cx"> -- Schedule Reply Work --
</span></span></pre>
</div>
</div>

</body>
</html>