<!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>[13247] CalendarServer/branches/release/CalendarServer-5.3-dev</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/13247">13247</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-04-10 13:09:41 -0700 (Thu, 10 Apr 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Add extra logging/tracking of auto-scheduling.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devconfcaldavdtestplist">CalendarServer/branches/release/CalendarServer-5.3-dev/conf/caldavd-test.plist</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devsupportbuildsh">CalendarServer/branches/release/CalendarServer-5.3-dev/support/build.sh</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavaccountingpy">CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/accounting.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavstdconfigpy">CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavtesttest_accountingpy">CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_accounting.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingicaldiffpy">CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/icaldiff.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingprocessingpy">CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingschedulerpy">CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/scheduler.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesreleaseCalendarServer53devconfcaldavdtestplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/conf/caldavd-test.plist (13246 => 13247)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/conf/caldavd-test.plist        2014-04-10 20:08:50 UTC (rev 13246)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/conf/caldavd-test.plist        2014-04-10 20:09:41 UTC (rev 13247)
</span><span class="lines">@@ -597,16 +597,24 @@
</span><span class="cx">     &lt;!-- Enable accounting for certain operations --&gt;
</span><span class="cx">     &lt;key&gt;AccountingCategories&lt;/key&gt;
</span><span class="cx">     &lt;dict&gt;
</span><ins>+      &lt;key&gt;HTTP&lt;/key&gt;
+      &lt;false/&gt;
</ins><span class="cx">       &lt;key&gt;iTIP&lt;/key&gt;
</span><span class="cx">       &lt;false/&gt;
</span><del>-      &lt;key&gt;HTTP&lt;/key&gt;
</del><ins>+      &lt;key&gt;iTIP-VFREEBUSY&lt;/key&gt;
</ins><span class="cx">       &lt;false/&gt;
</span><ins>+      &lt;key&gt;Implicit Errors&lt;/key&gt;
+      &lt;false/&gt;
+      &lt;key&gt;AutoScheduling&lt;/key&gt;
+      &lt;false/&gt;
+      &lt;key&gt;iSchedule&lt;/key&gt;
+      &lt;false/&gt;
</ins><span class="cx">     &lt;/dict&gt;
</span><span class="cx"> 
</span><del>-    &lt;!-- Enable accounting for specific principals --&gt;
</del><ins>+    &lt;!-- Enable accounting for specific principal GUIDs or use &quot;*&quot; for all --&gt;
</ins><span class="cx">     &lt;key&gt;AccountingPrincipals&lt;/key&gt;
</span><span class="cx">     &lt;array&gt;
</span><del>-      &lt;!-- &lt;string&gt;/principals/__uids__/454D85C0-09F0-4DC6-A3C6-97DFEB4622CD/&lt;/string&gt; --&gt;
</del><ins>+      &lt;!-- &lt;string&gt;454D85C0-09F0-4DC6-A3C6-97DFEB4622CD&lt;/string&gt; --&gt;
</ins><span class="cx">     &lt;/array&gt;
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devsupportbuildsh"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/support/build.sh (13246 => 13247)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/support/build.sh        2014-04-10 20:08:50 UTC (rev 13246)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/support/build.sh        2014-04-10 20:09:41 UTC (rev 13247)
</span><span class="lines">@@ -809,7 +809,7 @@
</span><span class="cx"> 
</span><span class="cx">   # XXX actually PyCalendar should be imported in-place.
</span><span class="cx">   py_dependency -fe -i &quot;src&quot; -r HEAD \
</span><del>-    &quot;PyCalendar&quot; &quot;pycalendar&quot; &quot;pycalendar&quot; \
</del><ins>+    &quot;PyCalendar&quot; &quot;pycalendar&quot; &quot;pycalendar-2&quot; \
</ins><span class="cx">     &quot;${svn_uri_base}/PyCalendar/branches/CalendarServer-5.2&quot;;
</span><span class="cx"> 
</span><span class="cx">   #
</span><span class="lines">@@ -882,7 +882,7 @@
</span><span class="cx">     &quot;${pypi}/p/${n}/${p}.tar.gz&quot;;
</span><span class="cx"> 
</span><span class="cx">   svn_get &quot;CalDAVTester&quot; &quot;${top}/CalDAVTester&quot; \
</span><del>-      &quot;${svn_uri_base}/CalDAVTester/branches/release/CalDAVTester-5.2-dev&quot; HEAD;
</del><ins>+      &quot;${svn_uri_base}/CalDAVTester/branches/release/CalDAVTester-5.3-dev&quot; HEAD;
</ins><span class="cx"> 
</span><span class="cx">   local v=&quot;3.0.1&quot;;
</span><span class="cx">   local n=&quot;epydoc&quot;;
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavaccountingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/accounting.py (13246 => 13247)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/accounting.py        2014-04-10 20:08:50 UTC (rev 13246)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/accounting.py        2014-04-10 20:09:41 UTC (rev 13247)
</span><span class="lines">@@ -33,15 +33,17 @@
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><del>-def accountingEnabled(category, principal):
</del><ins>+def accountingEnabled(category, record):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    Determine if accounting is enabled for the given category and principal.
</del><ins>+    Determine if accounting is enabled for the given category and directory record.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     return (
</span><span class="cx">         accountingEnabledForCategory(category) and
</span><del>-        accountingEnabledForPrincipal(principal)
</del><ins>+        accountingEnabledForRecord(record)
</ins><span class="cx">     )
</span><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> def accountingEnabledForCategory(category):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Determine if accounting is enabled for the given category.
</span><span class="lines">@@ -51,43 +53,40 @@
</span><span class="cx">         return False
</span><span class="cx">     return AccountingCategories.get(category, False)
</span><span class="cx"> 
</span><del>-def accountingEnabledForPrincipal(principal):
</del><ins>+
+
+def accountingEnabledForRecord(record):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    Determine if accounting is enabled for the given principal.
</del><ins>+    Determine if accounting is enabled for the given record.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     enabledPrincipalURIs = config.AccountingPrincipals
</span><span class="cx"> 
</span><span class="cx">     if &quot;*&quot; in enabledPrincipalURIs:
</span><span class="cx">         return True
</span><span class="cx"> 
</span><del>-    if principal.principalURL() in enabledPrincipalURIs:
-        return True
</del><ins>+    return record.guid in enabledPrincipalURIs
</ins><span class="cx"> 
</span><del>-    for principal in principal.alternateURIs():
-        if principal in enabledPrincipalURIs:
-            return True
</del><span class="cx"> 
</span><del>-    return False
</del><span class="cx"> 
</span><del>-def emitAccounting(category, principal, data, tag=None):
</del><ins>+def emitAccounting(category, record, data, tag=None, filename=None):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Write the supplied data to the appropriate location for the given
</span><del>-    category and principal.
</del><ins>+    category and record.
</ins><span class="cx"> 
</span><del>-    @param principal: the principal for whom a log entry is to be created.
-    @type principal: L{DirectoryPrincipalResource}
</del><ins>+    @param record: the record for whom a log entry is to be created.
+    @type record: L{DirectoryRecord}
</ins><span class="cx">     @param category: accounting category
</span><span class="cx">     @type category: C{tuple}
</span><span class="cx">     @param data: data to write.
</span><span class="cx">     @type data: C{str}
</span><del>-    &quot;&quot;&quot;    
-    if isinstance(principal, str):
-        principalLogPath = principal
-    elif accountingEnabled(category, principal):
</del><ins>+    &quot;&quot;&quot;
+    if isinstance(record, str):
+        principalLogPath = record
+    elif accountingEnabled(category, record):
</ins><span class="cx">         principalLogPath = os.path.join(
</span><del>-            principal.record.guid[0:2],
-            principal.record.guid[2:4],
-            principal.record.guid
</del><ins>+            record.guid[0:2],
+            record.guid[2:4],
+            record.guid
</ins><span class="cx">         )
</span><span class="cx">     else:
</span><span class="cx">         return None
</span><span class="lines">@@ -105,30 +104,32 @@
</span><span class="cx">             )
</span><span class="cx">         logFilename = os.path.join(
</span><span class="cx">             logDirectory,
</span><del>-            datetime.datetime.now().isoformat()
</del><ins>+            datetime.datetime.now().isoformat() if filename is None else filename
</ins><span class="cx">         )
</span><del>-    
</del><ins>+
</ins><span class="cx">         if not os.path.isdir(os.path.join(logRoot, logDirectory)):
</span><span class="cx">             os.makedirs(os.path.join(logRoot, logDirectory))
</span><del>-            logFilename = &quot;%s-01&quot; % (logFilename,)
-            if tag:
-                logFilename += &quot; (%s)&quot; % (tag,)
-            logFilename += &quot;.txt&quot;
</del><ins>+            if filename is None:
+                logFilename = &quot;%s-01&quot; % (logFilename,)
+                if tag:
+                    logFilename += &quot; (%s)&quot; % (tag,)
+                logFilename += &quot;.txt&quot;
</ins><span class="cx">         else:
</span><del>-            index = 1
-            while True:
-                path = &quot;%s-%02d&quot; % (logFilename, index)
-                if tag:
-                    path += &quot; (%s)&quot; % (tag,)
-                path += &quot;.txt&quot;
-                if not os.path.isfile(os.path.join(logRoot, path)):
-                    logFilename = path
-                    break
-                if index == 1000:
-                    log.error(&quot;Too many %s accounting files for %s&quot; % (category, principal))
-                    return None
-                index += 1
-    
</del><ins>+            if filename is None:
+                index = 1
+                while True:
+                    path = &quot;%s-%02d&quot; % (logFilename, index)
+                    if tag:
+                        path += &quot; (%s)&quot; % (tag,)
+                    path += &quot;.txt&quot;
+                    if not os.path.isfile(os.path.join(logRoot, path)):
+                        logFilename = path
+                        break
+                    if index == 1000:
+                        log.error(&quot;Too many %s accounting files for %s&quot; % (category, record))
+                        return None
+                    index += 1
+
</ins><span class="cx">         #
</span><span class="cx">         # Now write out the data to the log file
</span><span class="cx">         #
</span><span class="lines">@@ -137,7 +138,7 @@
</span><span class="cx">             logFile.write(data)
</span><span class="cx">         finally:
</span><span class="cx">             logFile.close()
</span><del>-            
</del><ins>+
</ins><span class="cx">         return logFilename
</span><span class="cx"> 
</span><span class="cx">     except OSError, e:
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/stdconfig.py (13246 => 13247)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/stdconfig.py        2014-04-10 20:08:50 UTC (rev 13246)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/stdconfig.py        2014-04-10 20:09:41 UTC (rev 13247)
</span><span class="lines">@@ -476,7 +476,12 @@
</span><span class="cx">     &quot;LogID&quot;             : &quot;&quot;,
</span><span class="cx"> 
</span><span class="cx">     &quot;AccountingCategories&quot;: {
</span><ins>+        &quot;HTTP&quot;: False,
</ins><span class="cx">         &quot;iTIP&quot;: False,
</span><ins>+        &quot;iTIP-VFREEBUSY&quot;: False,
+        &quot;Implicit Errors&quot;: False,
+        &quot;AutoScheduling&quot;: False,
+        &quot;iSchedule&quot;: False,
</ins><span class="cx">     },
</span><span class="cx">     &quot;AccountingPrincipals&quot;: [],
</span><span class="cx">     &quot;AccountingLogRoot&quot;   : &quot;accounting&quot;,
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtwistedcaldavtesttest_accountingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_accounting.py (13246 => 13247)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_accounting.py        2014-04-10 20:08:50 UTC (rev 13246)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/test/test_accounting.py        2014-04-10 20:09:41 UTC (rev 13247)
</span><span class="lines">@@ -33,18 +33,12 @@
</span><span class="cx">         os.mkdir(config.AccountingLogRoot)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    class _Principal(object):
</del><ins>+    class _Record(object):
</ins><span class="cx"> 
</span><del>-        class _Record(object):
-
-            def __init__(self, guid):
-                self.guid = guid
-
</del><span class="cx">         def __init__(self, guid):
</span><ins>+            self.guid = guid
</ins><span class="cx"> 
</span><del>-            self.record = self._Record(guid)
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def test_permissions_makedirs(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test permissions when creating accounting
</span><span class="lines">@@ -53,7 +47,7 @@
</span><span class="cx">         # Make log root non-writeable
</span><span class="cx">         os.chmod(config.AccountingLogRoot, stat.S_IRUSR)
</span><span class="cx"> 
</span><del>-        emitAccounting(&quot;iTIP&quot;, self._Principal(&quot;1234-5678&quot;), &quot;bogus&quot;)
</del><ins>+        emitAccounting(&quot;iTIP&quot;, self._Record(&quot;1234-5678&quot;), &quot;bogus&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_file_instead_of_directory(self):
</span><span class="lines">@@ -64,7 +58,7 @@
</span><span class="cx">         # Make log root a file
</span><span class="cx">         config.AccountingLogRoot = &quot;other&quot;
</span><span class="cx">         open(config.AccountingLogRoot, &quot;w&quot;).close()
</span><del>-        emitAccounting(&quot;iTIP&quot;, self._Principal(&quot;1234-5678&quot;), &quot;bogus&quot;)
</del><ins>+        emitAccounting(&quot;iTIP&quot;, self._Record(&quot;1234-5678&quot;), &quot;bogus&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingicaldiffpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/icaldiff.py (13246 => 13247)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/icaldiff.py        2014-04-10 20:08:50 UTC (rev 13246)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/icaldiff.py        2014-04-10 20:09:41 UTC (rev 13247)
</span><span class="lines">@@ -497,6 +497,7 @@
</span><span class="cx">             # If PARTSTAT was changed by the attendee, add a timestamp if needed
</span><span class="cx">             if config.Scheduling.Options.TimestampAttendeePartStatChanges:
</span><span class="cx">                 serverAttendee.setParameter(&quot;X-CALENDARSERVER-DTSTAMP&quot;, PyCalendarDateTime.getNowUTC().getText())
</span><ins>+            serverAttendee.removeParameter(&quot;X-CALENDARSERVER-AUTO&quot;)
</ins><span class="cx"> 
</span><span class="cx">             replyNeeded = True
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingprocessingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py (13246 => 13247)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py        2014-04-10 20:08:50 UTC (rev 13246)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py        2014-04-10 20:09:41 UTC (rev 13247)
</span><span class="lines">@@ -19,7 +19,6 @@
</span><span class="cx"> from pycalendar.timezone import PyCalendarTimezone
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><del>-from twext.web2.dav.method.report import NumberOfMatchesWithinLimits
</del><span class="cx"> from twext.web2.http import HTTPError
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet import reactor
</span><span class="lines">@@ -40,9 +39,11 @@
</span><span class="cx"> import hashlib
</span><span class="cx"> import uuid
</span><span class="cx"> from txdav.caldav.icalendarstore import ComponentUpdateState, \
</span><del>-    ComponentRemoveState
</del><ins>+    ComponentRemoveState, QueryMaxResources
</ins><span class="cx"> from twext.enterprise.locking import NamedLock
</span><span class="cx"> from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
</span><ins>+from twistedcaldav.accounting import emitAccounting, accountingEnabled
+import json
</ins><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> CalDAV implicit processing.
</span><span class="lines">@@ -543,7 +544,15 @@
</span><span class="cx">             if self.recipient.principal.canAutoSchedule(organizer=organizer):
</span><span class="cx">                 # auto schedule mode can depend on who the organizer is
</span><span class="cx">                 mode = self.recipient.principal.getAutoScheduleMode(organizer=organizer)
</span><del>-                send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar, mode))
</del><ins>+                send_reply, store_inbox, partstat, accounting = (yield self.checkAttendeeAutoReply(new_calendar, mode))
+                if accounting is not None:
+                    accounting[&quot;action&quot;] = &quot;create&quot;
+                    emitAccounting(
+                        &quot;AutoScheduling&quot;,
+                        self.recipient.principal,
+                        json.dumps(accounting) + &quot;\r\n&quot;,
+                        filename=self.uid + &quot;.txt&quot;
+                )
</ins><span class="cx"> 
</span><span class="cx">                 # Only store inbox item when reply is not sent or always for users
</span><span class="cx">                 store_inbox = store_inbox or self.recipient.principal.getCUType() == &quot;INDIVIDUAL&quot;
</span><span class="lines">@@ -577,7 +586,15 @@
</span><span class="cx">                 if self.recipient.principal.canAutoSchedule(organizer=organizer) and not hasattr(self.txn, &quot;doing_attendee_refresh&quot;):
</span><span class="cx">                     # auto schedule mode can depend on who the organizer is
</span><span class="cx">                     mode = self.recipient.principal.getAutoScheduleMode(organizer=organizer)
</span><del>-                    send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar, mode))
</del><ins>+                    send_reply, store_inbox, partstat, accounting = (yield self.checkAttendeeAutoReply(new_calendar, mode))
+                    if accounting is not None:
+                        accounting[&quot;action&quot;] = &quot;modify&quot;
+                        emitAccounting(
+                            &quot;AutoScheduling&quot;,
+                            self.recipient.principal,
+                            json.dumps(accounting) + &quot;\r\n&quot;,
+                            filename=self.uid + &quot;.txt&quot;
+                        )
</ins><span class="cx"> 
</span><span class="cx">                     # Only store inbox item when reply is not sent or always for users
</span><span class="cx">                     store_inbox = store_inbox or self.recipient.principal.getCUType() == &quot;INDIVIDUAL&quot;
</span><span class="lines">@@ -655,6 +672,18 @@
</span><span class="cx">             # Check to see if this is a cancel of the entire event
</span><span class="cx">             processed_message, delete_original, rids = iTipProcessing.processCancel(self.message, self.recipient_calendar, autoprocessing=autoprocessed)
</span><span class="cx">             if processed_message:
</span><ins>+                if autoprocessed and accountingEnabled(&quot;AutoScheduling&quot;, self.recipient.principal):
+                    accounting = {
+                        &quot;action&quot;: &quot;cancel&quot;,
+                        &quot;when&quot;: PyCalendarDateTime.getNowUTC().getText(),
+                        &quot;deleting&quot;: delete_original,
+                    }
+                    emitAccounting(
+                        &quot;AutoScheduling&quot;,
+                        self.recipient.principal,
+                        json.dumps(accounting) + &quot;\r\n&quot;,
+                        filename=self.uid + &quot;.txt&quot;
+                    )
</ins><span class="cx">                 if delete_original:
</span><span class="cx"> 
</span><span class="cx">                     # Delete the attendee's copy of the event
</span><span class="lines">@@ -774,9 +803,18 @@
</span><span class="cx">             should be added, and the new PARTSTAT.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        if accountingEnabled(&quot;AutoScheduling&quot;, self.recipient.principal):
+            accounting = {
+                &quot;when&quot;: PyCalendarDateTime.getNowUTC().getText(),
+                &quot;automode&quot;: automode,
+                &quot;changed&quot;: False,
+            }
+        else:
+            accounting = None
+
</ins><span class="cx">         # First ignore the none mode
</span><span class="cx">         if automode == &quot;none&quot;:
</span><del>-            returnValue((False, True, &quot;&quot;,))
</del><ins>+            returnValue((False, True, &quot;&quot;, accounting,))
</ins><span class="cx">         elif not automode or automode == &quot;default&quot;:
</span><span class="cx">             automode = config.Scheduling.Options.AutoSchedule.DefaultMode
</span><span class="cx"> 
</span><span class="lines">@@ -789,6 +827,10 @@
</span><span class="cx">         expand_max = PyCalendarDateTime.getToday() + default_future_expansion_duration
</span><span class="cx">         instances = calendar.expandTimeRanges(expand_max, ignoreInvalidInstances=True)
</span><span class="cx"> 
</span><ins>+        if accounting is not None:
+            accounting[&quot;expand-max&quot;] = expand_max.getText()
+            accounting[&quot;instances&quot;] = len(instances.instances)
+
</ins><span class="cx">         # We are going to ignore auto-accept processing for anything more than a day old (actually use -2 days
</span><span class="cx">         # to add some slop to account for possible timezone offsets)
</span><span class="cx">         min_date = PyCalendarDateTime.getToday()
</span><span class="lines">@@ -805,11 +847,15 @@
</span><span class="cx">             if instance.active:
</span><span class="cx">                 allOld = False
</span><span class="cx"> 
</span><ins>+        instances = sorted(instances.instances.values(), key=lambda x: x.rid)
+
</ins><span class="cx">         # If every instance is in the past we punt right here so we don't waste time on freebusy lookups etc.
</span><span class="cx">         # There will be no auto-accept and no inbox item stored (so as not to waste storage on items that will
</span><span class="cx">         # never be processed).
</span><span class="cx">         if allOld:
</span><del>-            returnValue((False, False, &quot;&quot;,))
</del><ins>+            if accounting is not None:
+                accounting[&quot;status&quot;] = &quot;all instances are old&quot;
+            returnValue((False, False, &quot;&quot;, accounting))
</ins><span class="cx"> 
</span><span class="cx">         # Extract UID from primary component as we want to ignore this one if we match it
</span><span class="cx">         # in any calendars.
</span><span class="lines">@@ -818,6 +864,9 @@
</span><span class="cx">         # Now compare each instance time-range with the index and see if there is an overlap
</span><span class="cx">         fbset = (yield self.recipient.inbox.ownerHome().loadCalendars())
</span><span class="cx">         fbset = [fbcalendar for fbcalendar in fbset if fbcalendar.isUsedForFreeBusy()]
</span><ins>+        if accounting is not None:
+            accounting[&quot;fbset&quot;] = [testcal.name() for testcal in fbset]
+            accounting[&quot;tr&quot;] = []
</ins><span class="cx"> 
</span><span class="cx">         for testcal in fbset:
</span><span class="cx"> 
</span><span class="lines">@@ -827,8 +876,11 @@
</span><span class="cx">             tzinfo = tz.gettimezone() if tz is not None else PyCalendarTimezone(utc=True)
</span><span class="cx"> 
</span><span class="cx">             # Now do search for overlapping time-range and set instance.free based
</span><del>-            # on whether there is an overlap or not
-            for instance in instances.instances.itervalues():
</del><ins>+            # on whether there is an overlap or not.
+            # NB Do this in reverse order so that the date farthest in the future is tested first - that will
+            # ensure that freebusy that far into the future is determined and will trigger time-range caching
+            # and indexing out that far - and that will happen only once through this loop.
+            for instance in reversed(instances):
</ins><span class="cx">                 if instance.partstat == &quot;NEEDS-ACTION&quot; and instance.free and instance.active:
</span><span class="cx">                     try:
</span><span class="cx">                         # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
</span><span class="lines">@@ -854,19 +906,26 @@
</span><span class="cx">                         # If any fbinfo entries exist we have an overlap
</span><span class="cx">                         if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):
</span><span class="cx">                             instance.free = False
</span><del>-                    except NumberOfMatchesWithinLimits:
-                        instance.free[instance] = False
</del><ins>+                        if accounting is not None:
+                            accounting[&quot;tr&quot;].insert(0, (tr.attributes[&quot;start&quot;], tr.attributes[&quot;end&quot;], instance.free,))
+                    except QueryMaxResources:
+                        instance.free = False
</ins><span class="cx">                         log.info(&quot;Exceeded number of matches whilst trying to find free-time.&quot;)
</span><ins>+                        if accounting is not None:
+                            accounting[&quot;problem&quot;] = &quot;Exceeded number of matches&quot;
</ins><span class="cx"> 
</span><span class="cx">             # If everything is declined we can exit now
</span><del>-            if not any([instance.free for instance in instances.instances.itervalues()]):
</del><ins>+            if not any([instance.free for instance in instances]):
</ins><span class="cx">                 break
</span><span class="cx"> 
</span><ins>+        if accounting is not None:
+            accounting[&quot;tr&quot;] = accounting[&quot;tr&quot;][:30]
+
</ins><span class="cx">         # Now adjust the instance.partstat currently set to &quot;NEEDS-ACTION&quot; to the
</span><span class="cx">         # value determined by auto-accept logic based on instance.free state. However,
</span><span class="cx">         # ignore any instance in the past - leave them as NEEDS-ACTION.
</span><span class="cx">         partstat_counts = collections.defaultdict(int)
</span><del>-        for instance in instances.instances.itervalues():
</del><ins>+        for instance in instances:
</ins><span class="cx">             if instance.partstat == &quot;NEEDS-ACTION&quot; and instance.active:
</span><span class="cx">                 if automode == &quot;accept-always&quot;:
</span><span class="cx">                     freePartstat = busyPartstat = &quot;ACCEPTED&quot;
</span><span class="lines">@@ -880,14 +939,18 @@
</span><span class="cx"> 
</span><span class="cx">         if len(partstat_counts) == 0:
</span><span class="cx">             # Nothing to do
</span><del>-            returnValue((False, False, &quot;&quot;,))
</del><ins>+            if accounting is not None:
+                accounting[&quot;status&quot;] = &quot;no partstat changes&quot;
+            returnValue((False, False, &quot;&quot;, accounting,))
</ins><span class="cx"> 
</span><span class="cx">         elif len(partstat_counts) == 1:
</span><span class="cx">             # Do the simple case of all PARTSTATs the same separately
</span><span class="cx">             # Extract the ATTENDEE property matching current recipient from the calendar data
</span><span class="cx">             attendeeProps = calendar.getAttendeeProperties(cuas)
</span><span class="cx">             if not attendeeProps:
</span><del>-                returnValue((False, False, &quot;&quot;,))
</del><ins>+                if accounting is not None:
+                    accounting[&quot;status&quot;] = &quot;no attendee to change&quot;
+                returnValue((False, False, &quot;&quot;, accounting,))
</ins><span class="cx"> 
</span><span class="cx">             made_changes = False
</span><span class="cx">             partstat = partstat_counts.keys()[0]
</span><span class="lines">@@ -895,6 +958,10 @@
</span><span class="cx">                 made_changes |= self.resetAttendeePartstat(component, cuas, partstat)
</span><span class="cx">             store_inbox = partstat == &quot;NEEDS-ACTION&quot;
</span><span class="cx"> 
</span><ins>+            if accounting is not None:
+                accounting[&quot;status&quot;] = &quot;setting all partstats to {}&quot;.format(partstat) if made_changes else &quot;all partstats correct&quot;
+                accounting[&quot;changed&quot;] = made_changes
+
</ins><span class="cx">         else:
</span><span class="cx">             # Hard case: some accepted, some declined, some needs-action
</span><span class="cx">             # What we will do is mark any master instance as accepted, then mark each existing
</span><span class="lines">@@ -920,7 +987,7 @@
</span><span class="cx">                     made_changes |= self.resetAttendeePartstat(master, cuas, defaultPartStat)
</span><span class="cx"> 
</span><span class="cx">             # Look at expanded instances and change partstat accordingly
</span><del>-            for instance in sorted(instances.instances.values(), key=lambda x: x.rid):
</del><ins>+            for instance in instances:
</ins><span class="cx"> 
</span><span class="cx">                 overridden = calendar.overriddenComponent(instance.rid)
</span><span class="cx">                 if not overridden and instance.partstat == defaultPartStat:
</span><span class="lines">@@ -945,11 +1012,15 @@
</span><span class="cx">                             made_changes = True
</span><span class="cx">                             calendar.addComponent(derived)
</span><span class="cx"> 
</span><ins>+            if accounting is not None:
+                accounting[&quot;status&quot;] = &quot;mixed partstat changes&quot; if made_changes else &quot;mixed partstats correct&quot;
+                accounting[&quot;changed&quot;] = made_changes
+
</ins><span class="cx">         # Fake a SCHEDULE-STATUS on the ORGANIZER property
</span><span class="cx">         if made_changes:
</span><span class="cx">             calendar.setParameterToValueForPropertyWithValue(&quot;SCHEDULE-STATUS&quot;, iTIPRequestStatus.MESSAGE_DELIVERED_CODE, &quot;ORGANIZER&quot;, None)
</span><span class="cx"> 
</span><del>-        returnValue((made_changes, store_inbox, partstat,))
</del><ins>+        returnValue((made_changes, store_inbox, partstat, accounting,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1035,6 +1106,10 @@
</span><span class="cx">             # Adjust TRANSP to OPAQUE if PARTSTAT is ACCEPTED, otherwise TRANSPARENT
</span><span class="cx">             component.replaceProperty(Property(&quot;TRANSP&quot;, &quot;OPAQUE&quot; if partstat == &quot;ACCEPTED&quot; else &quot;TRANSPARENT&quot;))
</span><span class="cx"> 
</span><ins>+            if madeChanges:
+                attendee.setParameter(&quot;X-CALENDARSERVER-AUTO&quot;, PyCalendarDateTime.getNowUTC().getText())
+                attendee.removeParameter(&quot;X-CALENDARSERVER-DTSTAMP&quot;)
+
</ins><span class="cx">         return madeChanges
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer53devtxdavcaldavdatastoreschedulingschedulerpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/scheduler.py (13246 => 13247)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/scheduler.py        2014-04-10 20:08:50 UTC (rev 13246)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/scheduler.py        2014-04-10 20:09:41 UTC (rev 13247)
</span><span class="lines">@@ -403,7 +403,8 @@
</span><span class="cx">             accountingType = &quot;iTIP-VFREEBUSY&quot; if self.calendar.mainType() == &quot;VFREEBUSY&quot; else &quot;iTIP&quot;
</span><span class="cx">             if accountingEnabled(accountingType, self.organizer.principal):
</span><span class="cx">                 emitAccounting(
</span><del>-                    accountingType, self.organizer.principal,
</del><ins>+                    accountingType,
+                    self.organizer.principal,
</ins><span class="cx">                     &quot;Originator: %s\nRecipients:\n%sMethod:%s\n\n%s&quot;
</span><span class="cx">                     % (
</span><span class="cx">                         str(self.originator),
</span></span></pre>
</div>
</div>

</body>
</html>