[CalendarServer-changes] [10188] CalendarServer/branches/release/CalendarServer-4.3-dev/ calendarserver/tools

source_changes at macosforge.org source_changes at macosforge.org
Tue Dec 18 08:40:51 PST 2012


Revision: 10188
          http://trac.calendarserver.org//changeset/10188
Author:   cdaboo at apple.com
Date:     2012-12-18 08:40:51 -0800 (Tue, 18 Dec 2012)
Log Message:
-----------
Check and fix cuaddrs using mailto when they should use urn:uuid.

Modified Paths:
--------------
    CalendarServer/branches/release/CalendarServer-4.3-dev/calendarserver/tools/calverify.py
    CalendarServer/branches/release/CalendarServer-4.3-dev/calendarserver/tools/test/test_calverify.py

Modified: CalendarServer/branches/release/CalendarServer-4.3-dev/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-4.3-dev/calendarserver/tools/calverify.py	2012-12-17 23:47:46 UTC (rev 10187)
+++ CalendarServer/branches/release/CalendarServer-4.3-dev/calendarserver/tools/calverify.py	2012-12-18 16:40:51 UTC (rev 10188)
@@ -34,7 +34,7 @@
 organizer event resource is the only one we store (with attendee views
 derived from that), in a situation where we have server-to-server scheduling
 it is possible for mismatches to creep in. In that case having a way to analyze
-multiple DBs for inconsistency would be good too. 
+multiple DBs for inconsistency would be good too.
 
 """
 
@@ -54,7 +54,7 @@
 from twisted.python.usage import Options
 from twistedcaldav import caldavxml
 from twistedcaldav.dateops import pyCalendarTodatetime
-from twistedcaldav.ical import Component, ignoredComponents,\
+from twistedcaldav.ical import Component, ignoredComponents, \
     InvalidICalendarDataError, Property
 from twistedcaldav.scheduling.itip import iTipGenerator
 from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
@@ -85,10 +85,10 @@
 
         # If the master has no recurrence properties treat any other components as invalid
         if master.isRecurring():
-            
+
             # Remove all EXDATEs with a matching RECURRENCE-ID. Do this before we start
             # processing of valid instances just in case the matching R-ID is also not valid and
-            # thus will need RDATE added. 
+            # thus will need RDATE added.
             exdates = {}
             for property in list(master.properties("EXDATE")):
                 for exdate in property.value():
@@ -108,7 +108,7 @@
                         fixed.append("Removed EXDATE for valid override: %s" % (rid,))
                     else:
                         unfixed.append("EXDATE for valid override: %s" % (rid,))
-            
+
             # Get the set of all valid recurrence IDs
             valid_rids = self.validInstances(all_rids, ignoreInvalidInstances=True)
 
@@ -118,7 +118,6 @@
                 rdates.extend([_rdate.getValue() for _rdate in property.value()])
             valid_rids.update(set(rdates))
 
-
             # Remove EXDATEs predating master
             dtstart = master.propertyValue("DTSTART")
             if dtstart is not None:
@@ -144,7 +143,6 @@
                             property.setValue(newValues)
                             master.addProperty(property)
 
-
         else:
             valid_rids = set()
 
@@ -156,7 +154,7 @@
             brokenComponent = self.overriddenComponent(invalid_rid)
             brokenRID = brokenComponent.propertyValue("RECURRENCE-ID")
             if doFix:
-                master.addProperty(Property("RDATE", [brokenRID,]))
+                master.addProperty(Property("RDATE", [brokenRID, ]))
                 fixed.append("Added RDATE for invalid occurrence: %s" %
                     (brokenRID,))
             else:
@@ -164,6 +162,8 @@
 
     return fixed, unfixed
 
+
+
 def new_hasDuplicateAlarms(self, doFix=False):
     """
     test and optionally remove alarms that have the same ACTION and TRIGGER values in the same component.
@@ -191,7 +191,7 @@
 if not hasattr(Component, "maxAlarmCounts"):
     Component.hasDuplicateAlarms = new_hasDuplicateAlarms
 
-VERSION = "7"
+VERSION = "8"
 
 def printusage(e=None):
     if e:
@@ -231,13 +231,13 @@
 
 Options for all modes:
 
---fix      : changes are only made when this is present.        
+--fix      : changes are only made when this is present.
 --config   : caldavd.plist file for the server.
 -v         : verbose logging
 
 Options for --ical:
 
---badcua   : only look for with bad CALENDARSERVER-OLD-CUA.
+--badcua   : only look for bad calendar user addresses.
 --nobase64 : do not apply base64 encoding to CALENDARSERVER-OLD-CUA.
 --uuid     : only scan specified calendar homes. Can be a partial GUID
              to scan all GUIDs with that as a prefix.
@@ -249,6 +249,11 @@
 --details  : log extended details on each mismatch.
 --tzid     : timezone to adjust details to.
 
+CHANGES
+v8: Detects ORGANIZER or ATTENDEE properties with mailto: calendar user
+    addresses for users that have valid directory records. Fix is to
+    replace the value with a urn:uuid: form.
+
 """ % (VERSION,)
 
 
@@ -256,6 +261,7 @@
     return ((multiplier * x) / y) if y else 0
 
 
+
 class CalVerifyOptions(Options):
     """
     Command-line options for 'calendarserver_verify_data'
@@ -287,9 +293,11 @@
         super(CalVerifyOptions, self).__init__()
         self.outputName = '-'
 
+
     def getUsage(self, width=None):
         return ""
 
+
     def opt_output(self, filename):
         """
         Specify output file path (default: '-', meaning stdout).
@@ -309,6 +317,7 @@
             return open(self.outputName, 'wb')
 
 
+
 class CalVerifyService(Service, object):
     """
     Service which runs, exports the appropriate records, then stops the reactor.
@@ -332,16 +341,16 @@
 
     def __init__(self, store, options, output, reactor, config):
         super(CalVerifyService, self).__init__()
-        self.store   = store
+        self.store = store
         self.options = options
-        self.output  = output
+        self.output = output
         self.reactor = reactor
         self.config = config
         self._directory = None
-        
+
         self.cuaCache = {}
         self.validForCalendaringUUIDs = {}
-        
+
         self.results = {}
         self.summary = []
         self.fixAttendeesForOrganizerMissing = 0
@@ -349,7 +358,7 @@
         self.fixOrganizersForAttendeeMissing = 0
         self.fixOrganizersForAttendeeMismatch = 0
         self.fixFailed = 0
-        self.fixedAutoAccepts = [] 
+        self.fixedAutoAccepts = []
         self.total = 0
         self.totalErrors = None
         self.totalExceptions = None
@@ -376,10 +385,10 @@
             else:
                 if self.options["missing"]:
                     yield self.doOrphans()
-                    
+
                 if self.options["mismatch"] or self.options["ical"] or self.options["badcua"]:
                     yield self.doScan(self.options["ical"] or self.options["badcua"], self.options["mismatch"], self.options["fix"])
-    
+
                 self.printSummary()
 
             self.output.close()
@@ -407,7 +416,7 @@
             homeName = pathbits[3]
             calendarName = pathbits[4]
             resourceName = pathbits[5]
-            
+
             rid = yield self.getResourceID(homeName, calendarName, resourceName)
             if rid is None:
                 yield self.txn.commit()
@@ -421,7 +430,7 @@
                 rid = int(nuke)
             except ValueError:
                 printusage("nuke argument must be a calendar object path or an SQL resource-id")
-        
+
         if self.options["fix"]:
             result = yield self.fixByRemovingEvent(rid)
             if result:
@@ -432,13 +441,13 @@
             self.output.write("Resource: %s.\n" % (rid,))
         yield self.txn.commit()
         self.txn = None
-            
-        
+
+
     @inlineCallbacks
     def doOrphans(self):
         """
         Report on home collections for which there are no directory records, or record is for user on
-        a different pod, or a user not enabled for calendaring. 
+        a different pod, or a user not enabled for calendaring.
         """
         self.output.write("\n---- Finding calendar homes with missing or disabled directory records ----\n")
         self.txn = self.store.newTransaction()
@@ -458,9 +467,9 @@
         for ctr, uid in enumerate(uids):
             if self.options["verbose"] and divmod(ctr, uids_div)[1] == 0:
                 self.output.write(("\r%d of %d (%d%%)" % (
-                    ctr+1,
+                    ctr + 1,
                     uids_len,
-                    ((ctr+1) * 100 / uids_len),
+                    ((ctr + 1) * 100 / uids_len),
                 )).ljust(80))
                 self.output.flush()
 
@@ -474,7 +483,7 @@
             elif not record.enabledForCalendaring:
                 contents = yield self.countHomeContents(uid)
                 disabled.append((uid, contents,))
-            
+
             # To avoid holding locks on all the rows scanned, commit every 100 resources
             if divmod(ctr, 100)[1] == 0:
                 yield self.txn.commit()
@@ -488,53 +497,53 @@
         # Print table of results
         table = tables.Table()
         table.addHeader(("Owner UID", "Calendar Objects"))
-        for uid, count in sorted(missing, key=lambda x:x[0]):
+        for uid, count in sorted(missing, key=lambda x: x[0]):
             table.addRow((
                 uid,
                 count,
             ))
-        
+
         self.output.write("\n")
         self.output.write("Homes without a matching directory record (total=%d):\n" % (len(missing),))
         table.printTable(os=self.output)
         self.addToSummary("Homes without a matching directory record", len(missing), uids_len)
-        
+
         # Print table of results
         table = tables.Table()
         table.addHeader(("Owner UID", "Calendar Objects"))
-        for uid, count in sorted(wrong_server, key=lambda x:x[0]):
+        for uid, count in sorted(wrong_server, key=lambda x: x[0]):
             record = self.directoryService().recordWithGUID(uid)
             table.addRow((
                 "%s/%s (%s)" % (record.recordType if record else "-", record.shortNames[0] if record else "-", uid,),
                 count,
             ))
-        
+
         self.output.write("\n")
         self.output.write("Homes not hosted on this server (total=%d):\n" % (len(wrong_server),))
         table.printTable(os=self.output)
         self.addToSummary("Homes not hosted on this server", len(wrong_server), uids_len)
-        
+
         # Print table of results
         table = tables.Table()
         table.addHeader(("Owner UID", "Calendar Objects"))
-        for uid, count in sorted(disabled, key=lambda x:x[0]):
+        for uid, count in sorted(disabled, key=lambda x: x[0]):
             record = self.directoryService().recordWithGUID(uid)
             table.addRow((
                 "%s/%s (%s)" % (record.recordType if record else "-", record.shortNames[0] if record else "-", uid,),
                 count,
             ))
-        
+
         self.output.write("\n")
         self.output.write("Homes without an enabled directory record (total=%d):\n" % (len(disabled),))
         table.printTable(os=self.output)
         self.addToSummary("Homes without an enabled directory record", len(disabled), uids_len)
-        
 
+
     @inlineCallbacks
     def getAllHomeUIDs(self):
         ch = schema.CALENDAR_HOME
         rows = (yield Select(
-            [ch.OWNER_UID,],
+            [ch.OWNER_UID, ],
             From=ch,
         ).on(self.txn))
         returnValue(tuple([uid[0] for uid in rows]))
@@ -545,9 +554,9 @@
         ch = schema.CALENDAR_HOME
         cb = schema.CALENDAR_BIND
         co = schema.CALENDAR_OBJECT
-        kwds = { "UID" : uid }
+        kwds = {"UID" : uid}
         rows = (yield Select(
-            [Count(co.RESOURCE_ID),],
+            [Count(co.RESOURCE_ID), ],
             From=ch.join(
                 cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID).And(
                     cb.BIND_MODE == _BIND_MODE_OWN)).join(
@@ -559,7 +568,7 @@
 
     @inlineCallbacks
     def doScan(self, ical, mismatch, fix, start=None):
-        
+
         self.output.write("\n---- Scanning calendar data ----\n")
 
         self.now = PyCalendarDateTime.getNowUTC()
@@ -568,7 +577,7 @@
         self.end = self.start.duplicate()
         self.end.offsetYear(1)
         self.fix = fix
-        
+
         self.tzid = PyCalendarTimezone(tzid=self.options["tzid"] if self.options["tzid"] else "America/Los_Angeles")
 
         self.txn = self.store.newTransaction()
@@ -599,12 +608,12 @@
 
         if self.options["verbose"]:
             self.output.write("%s time: %.1fs\n" % (descriptor, time.time() - t,))
-        
+
         self.total = len(rows)
         self.output.write("Number of events to process: %s\n" % (len(rows,)))
         self.results["Number of events to process"] = len(rows)
         self.addToSummary("Number of events to process", self.total)
-        
+
         # Split into organizer events and attendee events
         self.organized = []
         self.organized_byuid = {}
@@ -612,7 +621,7 @@
         self.attended_byuid = collections.defaultdict(list)
         self.matched_attendee_to_organizer = collections.defaultdict(set)
         skipped, inboxes = self.buildResourceInfo(rows)
-                
+
         self.output.write("Number of organizer events to process: %s\n" % (len(self.organized),))
         self.output.write("Number of attendee events to process: %s\n" % (len(self.attended,)))
         self.results["Number of organizer events to process"] = len(self.organized)
@@ -632,7 +641,7 @@
             self.totalErrors = 0
             yield self.verifyAllAttendeesForOrganizer()
             yield self.verifyAllOrganizersForAttendee()
-            
+
             # Need to add fix summary information
             if fix:
                 self.addSummaryBreak()
@@ -647,9 +656,9 @@
                 self.addToSummary("Fixed missing organizer events", self.fixOrganizersForAttendeeMissing)
                 self.addToSummary("Fixed mismatched organizer events", self.fixOrganizersForAttendeeMismatch)
                 self.addToSummary("Fix failures", self.fixFailed)
-                
+
                 self.printAutoAccepts()
-        
+
         yield succeed(None)
 
 
@@ -658,7 +667,7 @@
         co = schema.CALENDAR_OBJECT
         cb = schema.CALENDAR_BIND
         ch = schema.CALENDAR_HOME
-        
+
         if inbox:
             cojoin = (cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
                     cb.BIND_MODE == _BIND_MODE_OWN)
@@ -768,7 +777,7 @@
         ch = schema.CALENDAR_HOME
         kwds = {"resid": resid}
         rows = (yield Select(
-            [ch.RESOURCE_ID, cb.CALENDAR_RESOURCE_ID,],
+            [ch.RESOURCE_ID, cb.CALENDAR_RESOURCE_ID, ],
             From=ch.join(
                 cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID)).join(
                 co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
@@ -783,11 +792,11 @@
         co = schema.CALENDAR_OBJECT
         cb = schema.CALENDAR_BIND
         ch = schema.CALENDAR_HOME
-        
+
         kwds = {
-            "home":home,
-            "calendar":calendar,
-            "resource":resource,
+            "home": home,
+            "calendar": calendar,
+            "resource": resource,
         }
         rows = (yield Select(
             [co.RESOURCE_ID],
@@ -801,12 +810,12 @@
         ).on(self.txn, **kwds))
         returnValue(rows[0][0] if rows else None)
 
-    
+
     def buildResourceInfo(self, rows, onlyOrganizer=False, onlyAttendee=False):
         skipped = 0
         inboxes = 0
         for owner, resid, uid, calname, md5, organizer, created, modified in rows:
-            
+
             # Skip owners not enabled for calendaring
             if not self.testForCalendaringUUID(owner):
                 skipped += 1
@@ -821,7 +830,7 @@
             if self.options["uuid"]:
                 if not organizer.startswith("urn:uuid:") or self.options["uuid"] != organizer[9:]:
                     continue
-                
+
             # Cache organizer/attendee states
             if organizer.startswith("urn:uuid:") and owner == organizer[9:]:
                 if not onlyAttendee:
@@ -831,9 +840,10 @@
                 if not onlyOrganizer:
                     self.attended.append((owner, resid, uid, md5, organizer, created, modified,))
                     self.attended_byuid[uid].append((owner, resid, uid, md5, organizer, created, modified,))
-        
+
         return skipped, inboxes
 
+
     def testForCalendaringUUID(self, uuid):
         """
         Determine if the specified directory UUID is valid for calendaring. Keep a cache of
@@ -841,7 +851,7 @@
 
         @param uuid: the directory UUID to test
         @type uuid: C{str}
-        
+
         @return: C{True} if valid, C{False} if not
         """
 
@@ -871,9 +881,11 @@
         for owner, resid, uid, calname, _ignore_md5, _ignore_organizer, _ignore_created, _ignore_modified in rows:
             try:
                 result, message = yield self.validCalendarData(resid, calname == "inbox")
-            except Exception:
+            except Exception, e:
                 result = False
                 message = "Exception for validCalendarData"
+                if self.options["verbose"]:
+                    print e
             if not result:
                 results_bad.append((owner, uid, resid, message))
                 badlen += 1
@@ -883,14 +895,14 @@
                     self.output.write("Bad".rjust(rjust) + "Current".rjust(rjust) + "Total".rjust(rjust) + "Complete".rjust(rjust) + "\n")
                 if divmod(count, 100)[1] == 0:
                     self.output.write((
-                        "\r" + 
+                        "\r" +
                         ("%s" % badlen).rjust(rjust) +
                         ("%s" % count).rjust(rjust) +
                         ("%s" % total).rjust(rjust) +
                         ("%d%%" % safePercent(count, total)).rjust(rjust)
                     ).ljust(80))
                     self.output.flush()
-            
+
             # To avoid holding locks on all the rows scanned, commit every 100 resources
             if divmod(count, 100)[1] == 0:
                 yield self.txn.commit()
@@ -900,17 +912,17 @@
         self.txn = None
         if self.options["verbose"]:
             self.output.write((
-                "\r" + 
+                "\r" +
                 ("%s" % badlen).rjust(rjust) +
                 ("%s" % count).rjust(rjust) +
                 ("%s" % total).rjust(rjust) +
                 ("%d%%" % safePercent(count, total)).rjust(rjust)
             ).ljust(80) + "\n")
-        
+
         # Print table of results
         table = tables.Table()
         table.addHeader(("Owner", "Event UID", "RID", "Problem",))
-        for item in sorted(results_bad, key=lambda x:(x[0],x[1])):
+        for item in sorted(results_bad, key=lambda x: (x[0], x[1])):
             owner, uid, resid, message = item
             owner_record = self.directoryService().recordWithGUID(owner)
             table.addRow((
@@ -919,14 +931,14 @@
                 resid,
                 message,
             ))
-        
+
         self.output.write("\n")
         self.output.write("Bad iCalendar data (total=%d):\n" % (len(results_bad),))
         table.printTable(os=self.output)
-        
+
         self.results["Bad iCalendar data"] = results_bad
         self.addToSummary("Bad iCalendar data", len(results_bad), total)
-         
+
         if self.options["verbose"]:
             diff_time = time.time() - t
             self.output.write("Time: %.2f s  Average: %.1f ms/resource\n" % (
@@ -980,21 +992,20 @@
 
 
     def noPrincipalPathCUAddresses(self, component, doFix):
-        
-        def lookupFunction(cuaddr, principalFunction, config):
-    
+
+        def lookupFunction(cuaddr, principalFunction, conf):
+
             # Return cached results, if any.
-            if self.cuaCache.has_key(cuaddr):
+            if cuaddr in self.cuaCache:
                 return self.cuaCache[cuaddr]
-    
-            result = normalizationLookup(cuaddr, principalFunction, config)
+
+            result = normalizationLookup(cuaddr, principalFunction, conf)
             _ignore_name, guid, _ignore_cuaddrs = result
             if guid is None:
                 if cuaddr.find("__uids__") != -1:
-                    guid = cuaddr[cuaddr.find("__uids__/")+9:][:36]
+                    guid = cuaddr[cuaddr.find("__uids__/") + 9:][:36]
                     result = "", guid, set()
-                    
-    
+
             # Cache the result
             self.cuaCache[cuaddr] = result
             return result
@@ -1005,20 +1016,26 @@
             organizer = subcomponent.getProperty("ORGANIZER")
             if organizer:
                 cuaddr = organizer.value()
-                
+
                 # http(s) principals need to be converted to urn:uuid
                 if cuaddr.startswith("http"):
                     if doFix:
                         component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
                     else:
                         raise InvalidICalendarDataError("iCalendar ORGANIZER starts with 'http(s)'")
+                elif cuaddr.startswith("mailto:"):
+                    if lookupFunction(cuaddr, self.directoryService().principalForCalendarUserAddress, self.config)[1] is not None:
+                        if doFix:
+                            component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
+                        else:
+                            raise InvalidICalendarDataError("iCalendar ORGANIZER starts with 'mailto:' and record exists")
                 else:
                     if ("@" in cuaddr) and (":" not in cuaddr) and ("/" not in cuaddr):
                         if doFix:
                             # Add back in mailto: then re-normalize to urn:uuid if possible
                             organizer.setValue("mailto:%s" % (cuaddr,))
                             component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
-                            
+
                             # Remove any SCHEDULE-AGENT=NONE
                             if organizer.parameterValue("SCHEDULE-AGENT", "SERVER") == "NONE":
                                 organizer.removeParameter("SCHEDULE-AGENT")
@@ -1043,6 +1060,12 @@
                         component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
                     else:
                         raise InvalidICalendarDataError("iCalendar ATTENDEE starts with 'http(s)'")
+                elif cuaddr.startswith("mailto:"):
+                    if lookupFunction(cuaddr, self.directoryService().principalForCalendarUserAddress, self.config)[1] is not None:
+                        if doFix:
+                            component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
+                        else:
+                            raise InvalidICalendarDataError("iCalendar ATTENDEE starts with 'mailto:' and record exists")
                 else:
                     if ("@" in cuaddr) and (":" not in cuaddr) and ("/" not in cuaddr):
                         if doFix:
@@ -1072,7 +1095,7 @@
         home = yield self.txn.calendarHomeWithResourceID(homeID)
         calendar = yield home.childWithID(calendarID)
         calendarObj = yield calendar.objectResourceWithID(resid)
-        
+
         try:
             component = yield calendarObj.component()
         except InternalDataStoreError:
@@ -1090,7 +1113,7 @@
         except ValueError:
             result = False
             message = "Failed fix: "
-        
+
         if result:
             # Write out fix, commit and get a new transaction
             try:
@@ -1119,10 +1142,10 @@
         home = yield self.txn.calendarHomeWithResourceID(homeID)
         calendar = yield home.childWithID(calendarID)
         calendarObj = yield calendar.objectResourceWithID(resid)
-        
+
         # Do raw data fix one line at a time
         caltxt = self.fixBadOldCuaLines(caltxt)
-        
+
         # Re-parse
         try:
             component = Component.fromString(caltxt)
@@ -1152,7 +1175,7 @@
                 endpos = line.find("urn:uuid:")
                 if endpos != -1:
                     endpos += len("urn:uuid:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\"")
-                    badparam = line[startpos+len(";CALENDARSERVER-OLD-CUA=\""):endpos]
+                    badparam = line[startpos + len(";CALENDARSERVER-OLD-CUA=\""):endpos]
                     endbadparam = badparam.find(";")
                     if endbadparam != -1:
                         badparam = badparam[:endbadparam].replace("\\", "")
@@ -1169,6 +1192,7 @@
         caltxt = "\r\n".join(lines) + "\r\n"
         return caltxt
 
+
     @inlineCallbacks
     def verifyAllAttendeesForOrganizer(self):
         """
@@ -1176,7 +1200,7 @@
         We will look for events that an organizer has and are missing for the attendee, and events that an organizer's
         view of attendee status does not match the attendee's view of their own status.
         """
-        
+
         self.output.write("\n---- Verifying Organizer events against Attendee copies ----\n")
         self.txn = self.store.newTransaction()
 
@@ -1189,12 +1213,12 @@
         # Test organized events
         t = time.time()
         for ctr, organizerEvent in enumerate(self.organized):
-            
+
             if self.options["verbose"] and divmod(ctr, organizer_div)[1] == 0:
                 self.output.write(("\r%d of %d (%d%%) Missing: %d  Mismatched: %s" % (
-                    ctr+1,
+                    ctr + 1,
                     organized_len,
-                    ((ctr+1) * 100 / organized_len),
+                    ((ctr + 1) * 100 / organized_len),
                     len(results_missing),
                     len(results_mismatch),
                 )).ljust(80))
@@ -1206,7 +1230,7 @@
                 self.txn = self.store.newTransaction()
                 t = time.time()
 
-            # Get the organizer's view of attendee states            
+            # Get the organizer's view of attendee states
             organizer, resid, uid, _ignore_md5, _ignore_organizer, org_created, org_modified = organizerEvent
             calendar = yield self.getCalendar(resid)
             if calendar is None:
@@ -1221,7 +1245,7 @@
                 pass
             if len(organizerViewOfAttendees) == 0:
                 continue
-            
+
             # Get attendee states for matching UID
             eachAttendeesOwnStatus = {}
             attendeeCreatedModified = {}
@@ -1233,13 +1257,13 @@
                     continue
                 eachAttendeesOwnStatus[owner] = self.buildAttendeeStates(calendar, self.start, self.end, attendee_only=owner)
                 attendeeResIDs[(owner, attuid)] = attresid
-            
+
             # Look at each attendee in the organizer's meeting
             for organizerAttendee, organizerViewOfStatus in organizerViewOfAttendees.iteritems():
                 broken = False
 
                 self.matched_attendee_to_organizer[uid].add(organizerAttendee)
-                
+
                 # Skip attendees not enabled for calendaring
                 if not self.testForCalendaringUUID(organizerAttendee):
                     continue
@@ -1253,7 +1277,7 @@
                         attendeeResIDs[(organizerAttendee, uid)] = attresid
                         attendeeCreatedModified[organizerAttendee] = (att_created, att_modified,)
                         #print "Reloaded missing attendee data"
-                     
+
                 # If an entry for the attendee exists, then check whether attendee status matches
                 if organizerAttendee in eachAttendeesOwnStatus:
                     attendeeOwnStatus = eachAttendeesOwnStatus[organizerAttendee].get(organizerAttendee, set())
@@ -1295,7 +1319,7 @@
                             self.results.setdefault("Missing Attendee", set()).add((uid, organizer, organizerAttendee,))
                             broken = True
                             break
-                
+
                 # If there was a problem we can fix it
                 if broken and self.fix:
                     yield self.fixByReinvitingAttendee(resid, attendeeResIDs.get((organizerAttendee, uid)), organizerAttendee)
@@ -1321,7 +1345,7 @@
                 created,
                 "" if modified == created else modified,
             ))
-        
+
         self.output.write("\n")
         self.output.write("Events missing from Attendee's calendars (total=%d):\n" % (len(results_missing),))
         table.printTable(os=self.output)
@@ -1347,7 +1371,7 @@
                 att_created,
                 "" if att_modified == att_created else att_modified,
             ))
-        
+
         self.output.write("\n")
         self.output.write("Events mismatched between Organizer's and Attendee's calendars (total=%d):\n" % (len(results_mismatch),))
         table.printTable(os=self.output)
@@ -1372,12 +1396,12 @@
 
         t = time.time()
         for ctr, attendeeEvent in enumerate(tuple(self.attended)): # self.attended might mutate during the loop
-            
+
             if self.options["verbose"] and divmod(ctr, attended_div)[1] == 0:
                 self.output.write(("\r%d of %d (%d%%) Missing: %d  Mismatched: %s" % (
-                    ctr+1,
+                    ctr + 1,
                     attended_len,
-                    ((ctr+1) * 100 / attended_len),
+                    ((ctr + 1) * 100 / attended_len),
                     len(missing),
                     len(mismatched),
                 )).ljust(80))
@@ -1397,7 +1421,7 @@
             if attendee not in eachAttendeesOwnStatus:
                 continue
 
-            # Only care about data for hosted organizers                
+            # Only care about data for hosted organizers
             if not organizer.startswith("urn:uuid:"):
                 continue
             organizer = organizer[9:]
@@ -1411,19 +1435,19 @@
                 # Try to reload the organizer info data
                 rows = yield self.getAllResourceInfoWithUID(uid)
                 self.buildResourceInfo(rows, onlyOrganizer=True)
-                
+
                 #if uid in self.organized_byuid:
                 #    print "Reloaded missing organizer data: %s" % (uid,)
-                 
+
             if uid not in self.organized_byuid:
 
                 # Check whether attendee has all instances cancelled
                 if self.allCancelled(eachAttendeesOwnStatus):
                     continue
-                
+
                 missing.append((uid, attendee, organizer, resid, att_created, att_modified,))
                 self.results.setdefault("Missing Organizer", set()).add((uid, attendee, organizer,))
-                
+
                 # If there is a miss we fix by removing the attendee data
                 if self.fix:
                     # This is where we attempt a fix
@@ -1431,7 +1455,7 @@
                     if fix_result:
                         self.fixOrganizersForAttendeeMissing += 1
                     else:
-                        self.fixFailed += 1 
+                        self.fixFailed += 1
 
             elif attendee not in self.matched_attendee_to_organizer[uid]:
                 # Check whether attendee has all instances cancelled
@@ -1440,7 +1464,7 @@
 
                 mismatched.append((uid, attendee, organizer, resid, att_created, att_modified,))
                 self.results.setdefault("Mismatch Organizer", set()).add((uid, attendee, organizer,))
-                
+
                 # If there is a mismatch we fix by re-inviting the attendee
                 if self.fix:
                     yield self.fixByReinvitingAttendee(self.organized_byuid[uid][1], resid, attendee)
@@ -1470,7 +1494,7 @@
                 created,
                 "" if modified == created else modified,
             ))
-        
+
         self.output.write("\n")
         self.output.write("Attendee events missing in Organizer's calendar (total=%d, unique=%d):\n" % (len(missing), len(unique_set),))
         table.printTable(os=self.output)
@@ -1498,7 +1522,7 @@
                 att_created,
                 "" if att_modified == att_created else att_modified,
             ))
-        
+
         self.output.write("\n")
         self.output.write("Attendee events mismatched in Organizer's calendar (total=%d):\n" % (len(mismatched),))
         table.printTable(os=self.output)
@@ -1511,23 +1535,23 @@
         """
         Fix a mismatch/missing error by having the organizer send a REQUEST for the entire event to the attendee
         to trigger implicit scheduling to resync the attendee event.
-        
+
         We do not have implicit apis in the store, but really want to use store-only apis here to avoid having to create
         "fake" HTTP requests and manipulate HTTP resources. So what we will do is emulate implicit behavior by copying the
         organizer resource to the attendee (filtering it for the attendee's view of the event) and deposit an inbox item
         for the same event. Right now that will wipe out any per-attendee data - notably alarms.
         """
-        
+
         try:
             cuaddr = "urn:uuid:%s" % attendee
-    
+
             # Get the organizer's calendar data
             calendar = (yield self.getCalendar(orgresid))
             calendar = Component(None, pycalendar=calendar)
-            
+
             # Generate an iTip message for the entire event filtered for the attendee's view
             itipmsg = iTipGenerator.generateAttendeeRequest(calendar, (cuaddr,), None)
-            
+
             # Handle the case where the attendee is not actually in the organizer event at all by
             # removing the attendee event instead of re-inviting
             if itipmsg.resourceUID() is None:
@@ -1537,10 +1561,10 @@
             # Convert iTip message into actual calendar data - just remove METHOD
             attendee_calendar = itipmsg.duplicate()
             attendee_calendar.removeProperty(attendee_calendar.getProperty("METHOD"))
-            
+
             # Adjust TRANSP to match PARTSTAT
             self.setTransparencyForAttendee(attendee_calendar, cuaddr)
-    
+
             # Get attendee home store object
             home = (yield self.txn.calendarHomeWithUID(attendee))
             if home is None:
@@ -1548,7 +1572,7 @@
             inbox = (yield home.calendarWithName("inbox"))
             if inbox is None:
                 raise ValueError("Cannot find inbox")
-    
+
             details = {}
             # Replace existing resource data, or create a new one
             if attresid:
@@ -1559,7 +1583,7 @@
                 calendarObj.scheduleTag = str(uuid.uuid4())
                 yield calendarObj.setComponent(attendee_calendar)
                 self.results.setdefault("Fix change event", set()).add((home.name(), calendar.name(), attendee_calendar.resourceUID(),))
-                
+
                 details["path"] = "/calendars/__uids__/%s/%s/%s" % (home.name(), calendar.name(), calendarObj.name(),)
                 details["rid"] = attresid
             else:
@@ -1582,14 +1606,14 @@
                     break
             details["start"] = instance.start.adjustTimezone(self.tzid)
             details["title"] = instance.component.propertyValue("SUMMARY")
-            
+
             # Write new itip message to attendee inbox
             yield inbox.createCalendarObjectWithName(str(uuid.uuid4()) + ".ics", itipmsg, self.metadata_inbox)
             self.results.setdefault("Fix add inbox", set()).add((home.name(), itipmsg.resourceUID(),))
-     
+
             yield self.txn.commit()
             self.txn = self.store.newTransaction()
-    
+
             # Need to know whether the attendee is a location or resource with auto-accept set
             record = self.directoryService().recordWithGUID(attendee)
             if record.autoSchedule:
@@ -1601,11 +1625,11 @@
         except Exception, e:
             print "Failed to fix resource: %d for attendee: %s\n%s" % (orgresid, attendee, e,)
             returnValue(False)
-        
 
+
     @inlineCallbacks
     def defaultCalendarForAttendee(self, home, inbox):
-        
+
         # Check for property
         default = inbox.properties().get(PropertyName.fromElement(caldavxml.ScheduleDefaultCalendarURL))
         if default:
@@ -1638,15 +1662,15 @@
             yield calendar._removeObjectResource(calendarObj)
             yield self.txn.commit()
             self.txn = self.store.newTransaction()
-            
+
             self.results.setdefault("Fix remove", set()).add((home.name(), calendar.name(), objname,))
 
             returnValue(True)
         except Exception, e:
             print "Failed to remove resource whilst fixing: %d\n%s" % (resid, e,)
             returnValue(False)
-        
 
+
     def addToSummary(self, title, count, total=None):
         if total is not None:
             percent = safePercent(count, total),
@@ -1665,7 +1689,7 @@
         table.addHeader(("Item", "Count", "%"))
         table.setDefaultColumnFormats(
             (
-                tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.LEFT_JUSTIFY), 
+                tables.Table.ColumnFormat("%s", tables.Table.ColumnFormat.LEFT_JUSTIFY),
                 tables.Table.ColumnFormat("%d", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
                 tables.Table.ColumnFormat("%.1f%%", tables.Table.ColumnFormat.RIGHT_JUSTIFY),
             )
@@ -1676,7 +1700,7 @@
         if self.totalErrors is not None:
             table.addRow(None)
             table.addRow(("Total Errors", self.totalErrors, safePercent(self.totalErrors, self.total),))
-        
+
         self.output.write("\n")
         self.output.write("Overall Summary:\n")
         table.printTable(os=self.output)
@@ -1686,7 +1710,7 @@
         # Print summary of results
         table = tables.Table()
         table.addHeader(("Path", "RID", "UID", "Start Time", "Title"))
-        for item in sorted(self.fixedAutoAccepts, key=lambda x:x["path"]):
+        for item in sorted(self.fixedAutoAccepts, key=lambda x: x["path"]):
             table.addRow((
                 item["path"],
                 item["rid"],
@@ -1694,7 +1718,7 @@
                 item["start"],
                 item["title"],
             ))
-        
+
         self.output.write("\n")
         self.output.write("Auto-Accept Fixes:\n")
         table.printTable(os=self.output)
@@ -1703,7 +1727,7 @@
     @inlineCallbacks
     def getCalendar(self, resid, doFix=False):
         co = schema.CALENDAR_OBJECT
-        kwds = { "ResourceID" : resid }
+        kwds = {"ResourceID" : resid}
         rows = (yield Select(
             [co.ICALENDAR_TEXT],
             From=co,
@@ -1728,7 +1752,7 @@
                     else:
                         self.parseError = "Bad CALENDARSERVER-OLD-CUA"
                         returnValue(None)
-            
+
             self.parseError = "Failed to parse"
             returnValue(None)
 
@@ -1741,10 +1765,10 @@
         co = schema.CALENDAR_OBJECT
         cb = schema.CALENDAR_BIND
         ch = schema.CALENDAR_HOME
-        
-        kwds = { "OWNER" : owner, "UID": uid }
+
+        kwds = {"OWNER": owner, "UID": uid}
         rows = (yield Select(
-            [co.ICALENDAR_TEXT, co.RESOURCE_ID, co.CREATED, co.MODIFIED,],
+            [co.ICALENDAR_TEXT, co.RESOURCE_ID, co.CREATED, co.MODIFIED, ],
             From=ch.join(
                 cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID)).join(
                 co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
@@ -1770,9 +1794,10 @@
         for component in calendar.getComponents(definitions.cICalComponent_VEVENT):
             if not component.hasProperty("RECURRENCE-ID"):
                 return component
-        
+
         return None
 
+
     def buildAttendeeStates(self, calendar, start, end, attendee_only=None):
         # Expand events into instances in the start/end range
         results = []
@@ -1783,28 +1808,28 @@
             ),
             results
         )
-    
+
         # Need to do iCal fake master fixup
         overrides = len(calendar.getComponents(definitions.cICalComponent_VEVENT)) > 1
-    
+
         # Create map of each attendee's instances with the instance id (start time) and attendee part-stat
         attendees = {}
         for item in results:
-            
+
             # Fake master fixup
             if overrides:
                 if not item.getOwner().isRecurrenceInstance():
                     if item.getOwner().getRecurrenceSet() is None or not item.getOwner().getRecurrenceSet().hasRecurrence():
                         continue
-    
+
             # Get Status - ignore cancelled events
             status = item.getOwner().loadValueString(definitions.cICalProperty_STATUS)
             cancelled = status == definitions.cICalProperty_STATUS_CANCELLED
-    
+
             # Get instance start
             item.getInstanceStart().adjustToUTC()
             instance_id = item.getInstanceStart().getText()
-            
+
             props = item.getOwner().getProperties().get(definitions.cICalProperty_ATTENDEE, [])
             for prop in props:
                 caladdr = prop.getCalAddressValue().getValue()
@@ -1821,12 +1846,12 @@
                         partstat = definitions.cICalAttribute_PARTSTAT_NEEDSACTION
                     else:
                         partstat = prop.getAttributeValue(definitions.cICalAttribute_PARTSTAT)
-    
+
                 attendees.setdefault(caladdr, set()).add((instance_id, partstat))
-                    
+
         return attendees
-    
-    
+
+
     def allCancelled(self, attendeesStatus):
         # Check whether attendees have all instances cancelled
         all_cancelled = True
@@ -1838,8 +1863,8 @@
             if not all_cancelled:
                 break
         return all_cancelled
-       
 
+
     def setTransparencyForAttendee(self, calendar, attendee):
         """
         Set the TRANSP property based on the PARTSTAT value on matching ATTENDEE properties
@@ -1855,6 +1880,7 @@
                 addTransp = partstat in ("NEEDS-ACTION", "DECLINED",)
             component.replaceProperty(Property("TRANSP", "TRANSPARENT" if addTransp else "OPAQUE"))
 
+
     def directoryService(self):
         """
         Get an appropriate directory service for this L{CalVerifyService}'s
@@ -1895,6 +1921,7 @@
         stderr.write("Unable to open output file for writing: %s\n" % (e))
         sys.exit(1)
 
+
     def makeService(store):
         from twistedcaldav.config import config
         config.TransactionTimeoutSeconds = 0

Modified: CalendarServer/branches/release/CalendarServer-4.3-dev/calendarserver/tools/test/test_calverify.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-4.3-dev/calendarserver/tools/test/test_calverify.py	2012-12-17 23:47:46 UTC (rev 10187)
+++ CalendarServer/branches/release/CalendarServer-4.3-dev/calendarserver/tools/test/test_calverify.py	2012-12-18 16:40:51 UTC (rev 10188)
@@ -154,9 +154,9 @@
 DTSTART:20100307T111500Z
 DTSTAMP:20100303T181220Z
 SEQUENCE:2
-ORGANIZER:mailto:example2 at example.com
-ATTENDEE:mailto:example1 at example.com
-ATTENDEE:mailto:example2 at example.com
+ORGANIZER:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
+ATTENDEE:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
@@ -175,8 +175,8 @@
 DTSTART:20100307T111500Z
 DTSTAMP:20100303T181220Z
 ORGANIZER:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
-ATTENDEE:mailto:example1 at example.com
-ATTENDEE:mailto:example2 at example.com
+ATTENDEE:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 SEQUENCE:2
 END:VEVENT
 END:VCALENDAR
@@ -196,9 +196,9 @@
 SUMMARY:Ancient event
 DTSTART:20100307T111500Z
 DTSTAMP:20100303T181220Z
-ORGANIZER:mailto:example1 at example.com
+ORGANIZER:urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
 ATTENDEE:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
-ATTENDEE:mailto:example2 at example.com
+ATTENDEE:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 SEQUENCE:2
 END:VEVENT
 END:VCALENDAR
@@ -220,7 +220,7 @@
 DTSTAMP:20100303T181220Z
 ORGANIZER:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
 ATTENDEE:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
-ATTENDEE:mailto:example2 at example.com
+ATTENDEE:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 SEQUENCE:2
 END:VEVENT
 END:VCALENDAR
@@ -246,7 +246,7 @@
 ATTENDEE;CALENDARSERVER-OLD-CUA="http://demo.com:8008/principals/__uids__/D
  46F3D71-04B7-43C2-A7B6-6F92F92E61D0":urn:uuid:D46F3D71-04B7-43C2-A7B6-6F92
  F92E61D0
-ATTENDEE:mailto:example2 at example.com
+ATTENDEE:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 SEQUENCE:2
 END:VEVENT
 END:VCALENDAR
@@ -272,13 +272,13 @@
 ATTENDEE;CALENDARSERVER-OLD-CUA="base64-aHR0cDovL2RlbW8uY29tOjgwMDgvcHJpbmN
  pcGFscy9fX3VpZHNfXy9ENDZGM0Q3MS0wNEI3LTQzQzItQTdCNi02RjkyRjkyRTYxRDA=":u
  rn:uuid:D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
-ATTENDEE:mailto:example2 at example.com
+ATTENDEE:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 SEQUENCE:2
 END:VEVENT
 END:VCALENDAR
 """.replace("\n", "\r\n")
 
-BAD9_ICS =                 """BEGIN:VCALENDAR
+BAD9_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
 CALSCALE:GREGORIAN
 PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
@@ -390,8 +390,28 @@
 END:VCALENDAR
 """.replace("\n", "\r\n")
 
+BAD12_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD12
+DTEND:20100307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20100307T111500Z
+DTSTAMP:20100303T181220Z
+ORGANIZER:mailto:example2 at example.com
+ATTENDEE:mailto:example1 at example.com
+ATTENDEE:mailto:example2 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
 
 
+
 class CalVerifyDataTests(CommonCommonTests, unittest.TestCase):
     """
     Tests calverify for iCalendar data problems.
@@ -420,6 +440,7 @@
                 "bad9.ics" : (BAD9_ICS, metadata,),
                 "bad10.ics" : (BAD10_ICS, metadata,),
                 "bad11.ics" : (BAD11_ICS, metadata,),
+                "bad12.ics" : (BAD12_ICS, metadata,),
             }
         },
     }
@@ -447,7 +468,7 @@
 
     @inlineCallbacks
     def populate(self):
-        
+
         # Need to bypass normal validation inside the store
         util.validationBypass = True
         yield populateCalendarsFrom(self.requirements, self.storeUnderTest(), migrating=True)
@@ -508,19 +529,19 @@
         self.commit()
 
         options = {
-            "ical":True,
-            "nobase64":False,
-            "verbose":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": True,
+            "nobase64": False,
+            "verbose": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         output = StringIO()
         calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
         calverify.emailDomain = "example.com"
         yield calverify.doScan(True, False, False)
 
-        self.assertEqual(calverify.results["Number of events to process"], 12)
+        self.assertEqual(calverify.results["Number of events to process"], 13)
         self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
             ("home1", "BAD1",),
             ("home1", "BAD2",),
@@ -532,6 +553,7 @@
             ("home1", "BAD9",),
             ("home1", "BAD10",),
             ("home1", "BAD11",),
+            ("home1", "BAD12",),
         )))
 
         sync_token_new = (yield (yield self.calendarUnderTest()).syncToken())
@@ -549,15 +571,15 @@
         self.commit()
 
         options = {
-            "ical":True,
-            "nobase64":False,
-            "verbose":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": True,
+            "nobase64": False,
+            "verbose": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         output = StringIO()
-        
+
         # Do fix
         self.patch(config.Scheduling.Options, "PrincipalHostAliases", "demo.com")
         self.patch(config, "HTTPPort", 8008)
@@ -565,7 +587,7 @@
         calverify.emailDomain = "example.com"
         yield calverify.doScan(True, False, True)
 
-        self.assertEqual(calverify.results["Number of events to process"], 12)
+        self.assertEqual(calverify.results["Number of events to process"], 13)
         self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
             ("home1", "BAD1",),
             ("home1", "BAD2",),
@@ -577,6 +599,7 @@
             ("home1", "BAD9",),
             ("home1", "BAD10",),
             ("home1", "BAD11",),
+            ("home1", "BAD12",),
         )))
 
         # Do scan
@@ -584,14 +607,14 @@
         calverify.emailDomain = "example.com"
         yield calverify.doScan(True, False, False)
 
-        self.assertEqual(calverify.results["Number of events to process"], 12)
+        self.assertEqual(calverify.results["Number of events to process"], 13)
         self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
             ("home1", "BAD1",),
         )))
 
         sync_token_new = (yield (yield self.calendarUnderTest()).syncToken())
         self.assertNotEqual(sync_token_old, sync_token_new)
-        
+
         # Make sure mailto: fix results in urn:uuid value without SCHEDULE-AGENT
         obj = yield self.calendarObjectUnderTest("bad10.ics")
         ical = yield obj.component()
@@ -603,8 +626,8 @@
                 attendee.value().startswith("urn:uuid:") or
                 attendee.value().startswith("/principals")
             )
-            
 
+
     @inlineCallbacks
     def test_scanBadCuaOnly(self):
         """
@@ -616,20 +639,20 @@
         self.commit()
 
         options = {
-            "ical":False,
-            "badcua":True,
-            "nobase64":False,
-            "verbose":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": False,
+            "badcua": True,
+            "nobase64": False,
+            "verbose": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         output = StringIO()
         calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
         calverify.emailDomain = "example.com"
         yield calverify.doScan(True, False, False)
 
-        self.assertEqual(calverify.results["Number of events to process"], 12)
+        self.assertEqual(calverify.results["Number of events to process"], 13)
         self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
             ("home1", "BAD4",),
             ("home1", "BAD5",),
@@ -637,11 +660,13 @@
             ("home1", "BAD7",),
             ("home1", "BAD9",),
             ("home1", "BAD10",),
+            ("home1", "BAD12",),
         )))
 
         sync_token_new = (yield (yield self.calendarUnderTest()).syncToken())
         self.assertEqual(sync_token_old, sync_token_new)
 
+
     @inlineCallbacks
     def test_fixBadCuaOnly(self):
         """
@@ -653,16 +678,16 @@
         self.commit()
 
         options = {
-            "ical":False,
-            "badcua":True,
-            "nobase64":False,
-            "verbose":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": False,
+            "badcua": True,
+            "nobase64": False,
+            "verbose": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         output = StringIO()
-        
+
         # Do fix
         self.patch(config.Scheduling.Options, "PrincipalHostAliases", "demo.com")
         self.patch(config, "HTTPPort", 8008)
@@ -670,7 +695,7 @@
         calverify.emailDomain = "example.com"
         yield calverify.doScan(True, False, True)
 
-        self.assertEqual(calverify.results["Number of events to process"], 12)
+        self.assertEqual(calverify.results["Number of events to process"], 13)
         self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
             ("home1", "BAD4",),
             ("home1", "BAD5",),
@@ -678,6 +703,7 @@
             ("home1", "BAD7",),
             ("home1", "BAD9",),
             ("home1", "BAD10",),
+            ("home1", "BAD12",),
         )))
 
         # Do scan
@@ -685,13 +711,14 @@
         calverify.emailDomain = "example.com"
         yield calverify.doScan(True, False, False)
 
-        self.assertEqual(calverify.results["Number of events to process"], 12)
+        self.assertEqual(calverify.results["Number of events to process"], 13)
         self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
         )))
 
         sync_token_new = (yield (yield self.calendarUnderTest()).syncToken())
         self.assertNotEqual(sync_token_old, sync_token_new)
 
+
     def test_fixBadCuaLines(self):
         """
         CalVerifyService.fixBadOldCuaLines. Make sure it applies correct fix.
@@ -871,25 +898,25 @@
 """.replace("\n", "\r\n"),
             ),
         )
-        
+
         optionsNo64 = {
-            "ical":True,
-            "nobase64":True,
-            "verbose":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": True,
+            "nobase64": True,
+            "verbose": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         calverifyNo64 = CalVerifyService(self._sqlCalendarStore, optionsNo64, StringIO(), reactor, config)
         calverifyNo64.emailDomain = "example.com"
 
         options64 = {
-            "ical":True,
-            "nobase64":False,
-            "verbose":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": True,
+            "nobase64": False,
+            "verbose": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         calverify64 = CalVerifyService(self._sqlCalendarStore, options64, StringIO(), reactor, config)
         calverify64.emailDomain = "example.com"
@@ -902,6 +929,7 @@
             self.assertEqual(calverify64.fixBadOldCuaLines(bad), okbase64)
 
 
+
 class CalVerifyMismatchTestsBase(CommonCommonTests, unittest.TestCase):
     """
     Tests calverify for iCalendar mismatch problems.
@@ -915,9 +943,9 @@
         "hasPrivateComment": False,
     }
 
-    uuid1  = "D46F3D71-04B7-43C2-A7B6-6F92F92E61D0"
-    uuid2  = "47B16BB4-DB5F-4BF6-85FE-A7DA54230F92"
-    uuid3  = "AC478592-7783-44D1-B2AE-52359B4E8415"
+    uuid1 = "D46F3D71-04B7-43C2-A7B6-6F92F92E61D0"
+    uuid2 = "47B16BB4-DB5F-4BF6-85FE-A7DA54230F92"
+    uuid3 = "AC478592-7783-44D1-B2AE-52359B4E8415"
     uuidl1 = "75EA36BE-F71B-40F9-81F9-CF59BF40CA8F"
 
     @inlineCallbacks
@@ -945,7 +973,7 @@
         self.patch(config.AugmentService.params, "xmlFiles",
             [os.path.join(
                 os.path.dirname(__file__), "calverify", "augments.xml"
-            ),]
+            ), ]
         )
         self.rootResource = getRootResource(config, self._sqlCalendarStore)
         self.directory = self.rootResource.getDirectory()
@@ -953,7 +981,7 @@
 
     @inlineCallbacks
     def populate(self):
-        
+
         # Need to bypass normal validation inside the store
         util.validationBypass = True
         yield populateCalendarsFrom(self.requirements, self.storeUnderTest(), migrating=True)
@@ -1024,7 +1052,7 @@
 ATTENDEE:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     # Attendees have event, organizer does not
     MISSING_ORGANIZER_2_ICS = """BEGIN:VCALENDAR
@@ -1046,7 +1074,7 @@
 ATTENDEE:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISSING_ORGANIZER_3_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1067,7 +1095,7 @@
 ATTENDEE:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     # Attendee partstat mismatch
     MISMATCH_ATTENDEE_1_ICS = """BEGIN:VCALENDAR
@@ -1089,7 +1117,7 @@
 ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH_ATTENDEE_2_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1110,7 +1138,7 @@
 ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH_ATTENDEE_3_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1131,7 +1159,7 @@
 ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     # Attendee events outside time range
     MISMATCH2_ATTENDEE_1_ICS = """BEGIN:VCALENDAR
@@ -1153,7 +1181,7 @@
 ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH2_ATTENDEE_2_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1174,7 +1202,7 @@
 ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH2_ATTENDEE_3_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1195,7 +1223,7 @@
 ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     # Organizer event outside time range
     MISMATCH_ORGANIZER_1_ICS = """BEGIN:VCALENDAR
@@ -1217,7 +1245,7 @@
 ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now-2}
+""".replace("\n", "\r\n") % {"year": now - 2}
 
     MISMATCH_ORGANIZER_2_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1238,7 +1266,7 @@
 ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     # Attendee uuid3 has event with different organizer
     MISMATCH3_ATTENDEE_1_ICS = """BEGIN:VCALENDAR
@@ -1260,7 +1288,7 @@
 ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH3_ATTENDEE_2_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1280,7 +1308,7 @@
 ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH3_ATTENDEE_3_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1299,7 +1327,7 @@
 ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH_ORGANIZER_3_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1320,7 +1348,7 @@
 ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     # Attendee uuid3 has event they are not invited to
     MISMATCH2_ORGANIZER_1_ICS = """BEGIN:VCALENDAR
@@ -1341,7 +1369,7 @@
 ATTENDEE;PARTSTAT=DECLINED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH2_ORGANIZER_2_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1361,7 +1389,7 @@
 ATTENDEE;PARTSTAT=DECLINED:urn:uuid:47B16BB4-DB5F-4BF6-85FE-A7DA54230F92
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH2_ORGANIZER_3_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1382,9 +1410,8 @@
 ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:AC478592-7783-44D1-B2AE-52359B4E8415
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
-
     requirements = {
         CalVerifyMismatchTestsBase.uuid1 : {
             "calendar" : {
@@ -1436,15 +1463,15 @@
         self.commit()
 
         options = {
-            "ical":False,
-            "badcua":False,
-            "mismatch":True,
-            "nobase64":False,
-            "verbose":False,
-            "details":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": False,
+            "badcua": False,
+            "mismatch": True,
+            "nobase64": False,
+            "verbose": False,
+            "details": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         output = StringIO()
         calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
@@ -1501,15 +1528,15 @@
         self.commit()
 
         options = {
-            "ical":False,
-            "badcua":False,
-            "mismatch":True,
-            "nobase64":False,
-            "verbose":False,
-            "details":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": False,
+            "badcua": False,
+            "mismatch": True,
+            "nobase64": False,
+            "verbose": False,
+            "details": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         output = StringIO()
         calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
@@ -1536,7 +1563,7 @@
             ("MISMATCH_ORGANIZER_ICS", self.uuid3, self.uuid1,),
             ("MISMATCH2_ORGANIZER_ICS", self.uuid3, self.uuid1,),
         )))
-        
+
         self.assertEqual(calverify.results["Fix change event"], set((
             (self.uuid2, "calendar", "MISMATCH_ATTENDEE_ICS",),
             (self.uuid3, "calendar", "MISMATCH_ATTENDEE_ICS",),
@@ -1546,12 +1573,12 @@
             (self.uuid2, "calendar", "MISMATCH_ORGANIZER_ICS",),
             (self.uuid3, "calendar2", "MISMATCH_ORGANIZER_ICS",),
         )))
-        
+
         self.assertEqual(calverify.results["Fix add event"], set((
             (self.uuid2, "calendar", "MISSING_ATTENDEE_ICS",),
             (self.uuid3, "calendar2", "MISSING_ATTENDEE_ICS",),
         )))
-        
+
         self.assertEqual(calverify.results["Fix add inbox"], set((
             (self.uuid2, "MISSING_ATTENDEE_ICS",),
             (self.uuid3, "MISSING_ATTENDEE_ICS",),
@@ -1563,7 +1590,7 @@
             (self.uuid2, "MISMATCH_ORGANIZER_ICS",),
             (self.uuid3, "MISMATCH_ORGANIZER_ICS",),
         )))
-        
+
         self.assertEqual(calverify.results["Fix remove"], set((
             (self.uuid2, "calendar", "missing_organizer.ics",),
             (self.uuid3, "calendar", "missing_organizer.ics",),
@@ -1602,6 +1629,8 @@
         self.assertTrue("Fix failures" not in calverify.results)
         self.assertTrue("Fixed Auto-Accepts" not in calverify.results)
 
+
+
 class CalVerifyMismatchTestsAutoAccept(CalVerifyMismatchTestsBase):
     """
     Tests calverify for iCalendar mismatch problems for auto-accept attendees.
@@ -1626,7 +1655,7 @@
 ATTENDEE:urn:uuid:75EA36BE-F71B-40F9-81F9-CF59BF40CA8F
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     # Attendee partstat mismatch
     MISMATCH_ATTENDEE_1_ICS = """BEGIN:VCALENDAR
@@ -1647,7 +1676,7 @@
 ATTENDEE;PARTSTAT=NEEDS-ACTION:urn:uuid:75EA36BE-F71B-40F9-81F9-CF59BF40CA8F
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
     MISMATCH_ATTENDEE_L1_ICS = """BEGIN:VCALENDAR
 VERSION:2.0
@@ -1667,9 +1696,8 @@
 ATTENDEE;PARTSTAT=ACCEPTED:urn:uuid:75EA36BE-F71B-40F9-81F9-CF59BF40CA8F
 END:VEVENT
 END:VCALENDAR
-""".replace("\n", "\r\n") % {"year":now}
+""".replace("\n", "\r\n") % {"year": now}
 
-
     requirements = {
         CalVerifyMismatchTestsBase.uuid1 : {
             "calendar" : {
@@ -1706,15 +1734,15 @@
         self.commit()
 
         options = {
-            "ical":False,
-            "badcua":False,
-            "mismatch":True,
-            "nobase64":False,
-            "verbose":False,
-            "details":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": False,
+            "badcua": False,
+            "mismatch": True,
+            "nobase64": False,
+            "verbose": False,
+            "details": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         output = StringIO()
         calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
@@ -1755,15 +1783,15 @@
         self.commit()
 
         options = {
-            "ical":False,
-            "badcua":False,
-            "mismatch":True,
-            "nobase64":False,
-            "verbose":False,
-            "details":False,
-            "uid":"",
-            "uuid":"",
-            "tzid":"",
+            "ical": False,
+            "badcua": False,
+            "mismatch": True,
+            "nobase64": False,
+            "verbose": False,
+            "details": False,
+            "uid": "",
+            "uuid": "",
+            "tzid": "",
         }
         output = StringIO()
         calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
@@ -1778,24 +1806,24 @@
         )))
         self.assertTrue("Missing Organizer" not in calverify.results)
         self.assertTrue("Mismatch Organizer" not in calverify.results)
-        
+
         self.assertEqual(calverify.results["Fix change event"], set((
             (self.uuidl1, "calendar", "MISMATCH_ATTENDEE_ICS",),
         )))
-        
+
         self.assertEqual(calverify.results["Fix add event"], set((
             (self.uuidl1, "calendar", "MISSING_ATTENDEE_ICS",),
         )))
-        
+
         self.assertEqual(calverify.results["Fix add inbox"], set((
             (self.uuidl1, "MISSING_ATTENDEE_ICS",),
             (self.uuidl1, "MISMATCH_ATTENDEE_ICS",),
         )))
-        
+
         self.assertTrue("Fix remove" not in calverify.results)
 
         self.assertEqual(calverify.results["Fix failures"], 0)
-        testResults = sorted(calverify.results["Fixed Auto-Accepts"], key=lambda x:x["uid"])
+        testResults = sorted(calverify.results["Fixed Auto-Accepts"], key=lambda x: x["uid"])
         self.assertEqual(testResults[0]["path"], "/calendars/__uids__/%s/calendar/mismatched_attendee.ics" % self.uuidl1)
         self.assertEqual(testResults[0]["uid"], "MISMATCH_ATTENDEE_ICS")
         self.assertEqual(testResults[0]["start"].getText(), "%s0307T031500" % (now,))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121218/48e3beb9/attachment-0001.html>


More information about the calendarserver-changes mailing list