[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