[CalendarServer-changes] [10767] CalendarServer/trunk/calendarserver/tools
source_changes at macosforge.org
source_changes at macosforge.org
Wed Feb 20 12:08:47 PST 2013
Revision: 10767
http://trac.calendarserver.org//changeset/10767
Author: cdaboo at apple.com
Date: 2013-02-20 12:08:47 -0800 (Wed, 20 Feb 2013)
Log Message:
-----------
Refactor calverify into multiple service classes to make adding new functionality easier and to potentially
allow the individual actions to be moved to store work queue items.
Modified Paths:
--------------
CalendarServer/trunk/calendarserver/tools/calverify.py
CalendarServer/trunk/calendarserver/tools/test/test_calverify.py
Modified: CalendarServer/trunk/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/calverify.py 2013-02-20 18:31:53 UTC (rev 10766)
+++ CalendarServer/trunk/calendarserver/tools/calverify.py 2013-02-20 20:08:47 UTC (rev 10767)
@@ -49,7 +49,7 @@
from pycalendar.timezone import PyCalendarTimezone
from twext.enterprise.dal.syntax import Select, Parameter, Count
from twisted.application.service import Service
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.python import log, usage
from twisted.python.usage import Options
from twistedcaldav import caldavxml
@@ -191,7 +191,7 @@
if not hasattr(Component, "maxAlarmCounts"):
Component.hasDuplicateAlarms = new_hasDuplicateAlarms
-VERSION = "8"
+VERSION = "9"
def printusage(e=None):
if e:
@@ -321,25 +321,9 @@
class CalVerifyService(Service, object):
"""
- Service which runs, exports the appropriate records, then stops the reactor.
+ Base class for common service behaviors.
"""
- metadata = {
- "accessMode": "PUBLIC",
- "isScheduleObject": True,
- "scheduleTag": "abc",
- "scheduleEtags": (),
- "hasPrivateComment": False,
- }
-
- metadata_inbox = {
- "accessMode": "PUBLIC",
- "isScheduleObject": False,
- "scheduleTag": "",
- "scheduleEtags": (),
- "hasPrivateComment": False,
- }
-
def __init__(self, store, options, output, reactor, config):
super(CalVerifyService, self).__init__()
self.store = store
@@ -350,16 +334,9 @@
self._directory = None
self.cuaCache = {}
- self.validForCalendaringUUIDs = {}
self.results = {}
self.summary = []
- self.fixAttendeesForOrganizerMissing = 0
- self.fixAttendeesForOrganizerMismatch = 0
- self.fixOrganizersForAttendeeMissing = 0
- self.fixOrganizersForAttendeeMismatch = 0
- self.fixFailed = 0
- self.fixedAutoAccepts = []
self.total = 0
self.totalErrors = None
self.totalExceptions = None
@@ -373,25 +350,31 @@
self.doCalVerify()
+ def stopService(self):
+ """
+ Stop the service. Nothing to do; everything should be finished by this
+ time.
+ """
+ # TODO: stopping this service mid-export should really stop the export
+ # loop, but this is not implemented because nothing will actually do it
+ # except hitting ^C (which also calls reactor.stop(), so that will exit
+ # anyway).
+ pass
+
+
+ def title(self):
+ return ""
+
+
@inlineCallbacks
def doCalVerify(self):
"""
- Do the export, stopping the reactor when done.
+ Do the operation stopping the reactor when done.
"""
- self.output.write("\n---- CalVerify version: %s ----\n" % (VERSION,))
+ self.output.write("\n---- CalVerify %s version: %s ----\n" % (self.title(), VERSION,))
try:
- if self.options["nuke"]:
- yield self.doNuke()
- 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()
-
+ yield self.doAction()
self.output.close()
except:
log.err()
@@ -399,148 +382,17 @@
self.reactor.stop()
- @inlineCallbacks
- def doNuke(self):
+ def directoryService(self):
"""
- Remove a resource using either its path or resource id. When doing this do not
- read the iCalendar data which may be corrupt.
+ Get an appropriate directory service for this L{CalVerifyService}'s
+ configuration, creating one first if necessary.
"""
+ if self._directory is None:
+ self._directory = getDirectory(self.config) #directoryFromConfig(self.config)
+ return self._directory
- self.output.write("\n---- Removing calendar resource ----\n")
- self.txn = self.store.newTransaction()
- nuke = self.options["nuke"]
- if nuke.startswith("/calendars/__uids__/"):
- pathbits = nuke.split("/")
- if len(pathbits) != 6:
- printusage("Not a valid calendar object resource path: %s" % (nuke,))
- homeName = pathbits[3]
- calendarName = pathbits[4]
- resourceName = pathbits[5]
-
- rid = yield self.getResourceID(homeName, calendarName, resourceName)
- if rid is None:
- yield self.txn.commit()
- self.txn = None
- self.output.write("\n")
- self.output.write("Path does not exist. Nothing nuked.\n")
- returnValue(None)
- rid = int(rid)
- else:
- try:
- 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:
- self.output.write("\n")
- self.output.write("Removed resource: %s.\n" % (rid,))
- else:
- self.output.write("\n")
- 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.
- """
- self.output.write("\n---- Finding calendar homes with missing or disabled directory records ----\n")
- self.txn = self.store.newTransaction()
-
- if self.options["verbose"]:
- t = time.time()
- uids = yield self.getAllHomeUIDs()
- if self.options["verbose"]:
- self.output.write("getAllHomeUIDs time: %.1fs\n" % (time.time() - t,))
- missing = []
- wrong_server = []
- disabled = []
- uids_len = len(uids)
- uids_div = 1 if uids_len < 100 else uids_len / 100
- self.addToSummary("Total Homes", uids_len)
-
- 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,
- uids_len,
- ((ctr + 1) * 100 / uids_len),
- )).ljust(80))
- self.output.flush()
-
- record = self.directoryService().recordWithGUID(uid)
- if record is None:
- contents = yield self.countHomeContents(uid)
- missing.append((uid, contents,))
- elif not record.thisServer():
- contents = yield self.countHomeContents(uid)
- wrong_server.append((uid, contents,))
- 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()
- self.txn = self.store.newTransaction()
-
- yield self.txn.commit()
- self.txn = None
- if self.options["verbose"]:
- self.output.write("\r".ljust(80) + "\n")
-
- # Print table of results
- table = tables.Table()
- table.addHeader(("Owner UID", "Calendar Objects"))
- 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]):
- 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]):
- 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(
@@ -568,102 +420,6 @@
@inlineCallbacks
- def doScan(self, ical, mismatch, fix, start=None):
-
- self.output.write("\n---- Scanning calendar data ----\n")
-
- self.now = PyCalendarDateTime.getNowUTC()
- self.start = start if start is not None else PyCalendarDateTime.getToday()
- self.start.setDateOnly(False)
- 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()
-
- if self.options["verbose"]:
- t = time.time()
- descriptor = None
- if ical:
- if self.options["uuid"]:
- rows = yield self.getAllResourceInfoWithUUID(self.options["uuid"], inbox=True)
- descriptor = "getAllResourceInfoWithUUID"
- elif self.options["uid"]:
- rows = yield self.getAllResourceInfoWithUID(self.options["uid"], inbox=True)
- descriptor = "getAllResourceInfoWithUID"
- else:
- rows = yield self.getAllResourceInfo(inbox=True)
- descriptor = "getAllResourceInfo"
- else:
- if self.options["uid"]:
- rows = yield self.getAllResourceInfoWithUID(self.options["uid"])
- descriptor = "getAllResourceInfoWithUID"
- else:
- rows = yield self.getAllResourceInfoTimeRange(self.start)
- descriptor = "getAllResourceInfoTimeRange"
-
- yield self.txn.commit()
- self.txn = None
-
- 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 = {}
- self.attended = []
- 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)
- self.results["Number of attendee events to process"] = len(self.attended)
- self.results["Number of skipped events"] = skipped
- self.results["Number of inbox events"] = inboxes
- self.addToSummary("Number of organizer events to process", len(self.organized), self.total)
- self.addToSummary("Number of attendee events to process", len(self.attended), self.total)
- self.addToSummary("Number of skipped events", skipped, self.total)
- if ical:
- self.addToSummary("Number of inbox events", inboxes, self.total)
- self.addSummaryBreak()
-
- if ical:
- yield self.calendarDataCheck(rows)
- elif mismatch:
- self.totalErrors = 0
- yield self.verifyAllAttendeesForOrganizer()
- yield self.verifyAllOrganizersForAttendee()
-
- # Need to add fix summary information
- if fix:
- self.addSummaryBreak()
- self.results["Fixed missing attendee events"] = self.fixAttendeesForOrganizerMissing
- self.results["Fixed mismatched attendee events"] = self.fixAttendeesForOrganizerMismatch
- self.results["Fixed missing organizer events"] = self.fixOrganizersForAttendeeMissing
- self.results["Fixed mismatched organizer events"] = self.fixOrganizersForAttendeeMismatch
- self.results["Fix failures"] = self.fixFailed
- self.results["Fixed Auto-Accepts"] = self.fixedAutoAccepts
- self.addToSummary("Fixed missing attendee events", self.fixAttendeesForOrganizerMissing)
- self.addToSummary("Fixed mismatched attendee events", self.fixAttendeesForOrganizerMismatch)
- 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)
-
-
- @inlineCallbacks
def getAllResourceInfo(self, inbox=False):
co = schema.CALENDAR_OBJECT
cb = schema.CALENDAR_BIND
@@ -737,7 +493,7 @@
cb.CALENDAR_RESOURCE_NAME != "inbox").And(
co.ORGANIZER != "")).join(
tr, type="left", on=(co.RESOURCE_ID == tr.CALENDAR_OBJECT_RESOURCE_ID)),
- Where=(tr.START_DATE >= Parameter("Start")).Or(co.RECURRANCE_MAX == Parameter("Max")),
+ Where=(tr.START_DATE >= Parameter("Start")).Or(co.RECURRANCE_MAX <= Parameter("Start")),
GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, cb.CALENDAR_RESOURCE_NAME, co.MD5, co.ORGANIZER, co.CREATED, co.MODIFIED,),
).on(self.txn, **kwds))
returnValue(tuple(rows))
@@ -812,57 +568,409 @@
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:
+ @inlineCallbacks
+ def getCalendar(self, resid, doFix=False):
+ co = schema.CALENDAR_OBJECT
+ kwds = {"ResourceID" : resid}
+ rows = (yield Select(
+ [co.ICALENDAR_TEXT],
+ From=co,
+ Where=(
+ co.RESOURCE_ID == Parameter("ResourceID")
+ ),
+ ).on(self.txn, **kwds))
+ try:
+ caldata = PyCalendar.parseText(rows[0][0]) if rows else None
+ except PyCalendarError:
+ caltxt = rows[0][0] if rows else None
+ if caltxt:
+ caltxt = caltxt.replace("\r\n ", "")
+ if caltxt.find("CALENDARSERVER-OLD-CUA=\"//") != -1:
+ if doFix:
+ caltxt = (yield self.fixBadOldCua(resid, caltxt))
+ try:
+ caldata = PyCalendar.parseText(caltxt) if rows else None
+ except PyCalendarError:
+ self.parseError = "No fix bad CALENDARSERVER-OLD-CUA"
+ returnValue(None)
+ else:
+ self.parseError = "Bad CALENDARSERVER-OLD-CUA"
+ returnValue(None)
- # Skip owners not enabled for calendaring
- if not self.testForCalendaringUUID(owner):
- skipped += 1
- continue
+ self.parseError = "Failed to parse"
+ returnValue(None)
- # Skip inboxes
- if calname == "inbox":
- inboxes += 1
- continue
+ self.parseError = None
+ returnValue(caldata)
- # If targeting a specific organizer, skip events belonging to others
- 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:
- self.organized.append((owner, resid, uid, md5, organizer, created, modified,))
- self.organized_byuid[uid] = (owner, resid, uid, md5, organizer, created, modified,)
- else:
- 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,))
+ @inlineCallbacks
+ def getCalendarForOwnerByUID(self, owner, uid):
+ co = schema.CALENDAR_OBJECT
+ cb = schema.CALENDAR_BIND
+ ch = schema.CALENDAR_HOME
- return skipped, inboxes
+ kwds = {"OWNER": owner, "UID": uid}
+ rows = (yield Select(
+ [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(
+ cb.BIND_MODE == _BIND_MODE_OWN).And(
+ cb.CALENDAR_RESOURCE_NAME != "inbox")),
+ Where=(ch.OWNER_UID == Parameter("OWNER")).And(co.ICALENDAR_UID == Parameter("UID")),
+ ).on(self.txn, **kwds))
+ try:
+ caldata = PyCalendar.parseText(rows[0][0]) if rows else None
+ except PyCalendarError:
+ returnValue((None, None, None, None,))
- def testForCalendaringUUID(self, uuid):
+ returnValue((caldata, rows[0][1], rows[0][2], rows[0][3],) if rows else (None, None, None, None,))
+
+
+ @inlineCallbacks
+ def fixBadOldCua(self, resid, caltxt):
"""
- Determine if the specified directory UUID is valid for calendaring. Keep a cache of
- valid and invalid so we can do this quickly.
+ Fix bad CALENDARSERVER-OLD-CUA lines and write fixed data to store. Assumes iCalendar data lines unfolded.
+ """
- @param uuid: the directory UUID to test
- @type uuid: C{str}
+ # Get store objects
+ homeID, calendarID = yield self.getAllResourceInfoForResourceID(resid)
+ home = yield self.txn.calendarHomeWithResourceID(homeID)
+ calendar = yield home.childWithID(calendarID)
+ calendarObj = yield calendar.objectResourceWithID(resid)
- @return: C{True} if valid, C{False} if not
+ # Do raw data fix one line at a time
+ caltxt = self.fixBadOldCuaLines(caltxt)
+
+ # Re-parse
+ try:
+ component = Component.fromString(caltxt)
+ except InvalidICalendarDataError:
+ returnValue(None)
+
+ # Write out fix, commit and get a new transaction
+ # Use _migrating to ignore possible overridden instance errors - we are either correcting or ignoring those
+ self.txn._migrating = True
+ component = yield calendarObj.setComponent(component)
+ yield self.txn.commit()
+ self.txn = self.store.newTransaction()
+
+ returnValue(caltxt)
+
+
+ def fixBadOldCuaLines(self, caltxt):
"""
+ Fix bad CALENDARSERVER-OLD-CUA lines. Assumes iCalendar data lines unfolded.
+ """
- if uuid not in self.validForCalendaringUUIDs:
- record = self.directoryService().recordWithGUID(uuid)
- self.validForCalendaringUUIDs[uuid] = record is not None and record.enabledForCalendaring and record.thisServer()
- return self.validForCalendaringUUIDs[uuid]
+ # Do raw data fix one line at a time
+ lines = caltxt.splitlines()
+ for ctr, line in enumerate(lines):
+ startpos = line.find(";CALENDARSERVER-OLD-CUA=\"//")
+ if startpos != -1:
+ 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]
+ endbadparam = badparam.find(";")
+ if endbadparam != -1:
+ badparam = badparam[:endbadparam].replace("\\", "")
+ if badparam.find("8443") != -1:
+ badparam = "https:" + badparam
+ else:
+ badparam = "http:" + badparam
+ if self.options["nobase64"]:
+ badparam = "\"" + badparam + "\""
+ else:
+ badparam = "base64-%s" % (base64.b64encode(badparam),)
+ badparam = ";CALENDARSERVER-OLD-CUA=" + badparam
+ lines[ctr] = line[:startpos] + badparam + line[endpos:]
+ caltxt = "\r\n".join(lines) + "\r\n"
+ return caltxt
@inlineCallbacks
+ def removeEvent(self, resid):
+ """
+ Remove the calendar resource specified by resid - this is a force remove - no implicit
+ scheduling is required so we use store apis directly.
+ """
+
+ try:
+ homeID, calendarID = yield self.getAllResourceInfoForResourceID(resid)
+ home = yield self.txn.calendarHomeWithResourceID(homeID)
+ calendar = yield home.childWithID(calendarID)
+ calendarObj = yield calendar.objectResourceWithID(resid)
+ objname = calendarObj.name()
+ 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),
+ else:
+ percent = ""
+ self.summary.append((title, count, percent))
+
+
+ def addSummaryBreak(self):
+ self.summary.append(None)
+
+
+ def printSummary(self):
+ # Print summary of results
+ table = tables.Table()
+ table.addHeader(("Item", "Count", "%"))
+ table.setDefaultColumnFormats(
+ (
+ 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),
+ )
+ )
+ for item in self.summary:
+ table.addRow(item)
+
+ 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)
+
+
+
+class NukeService(CalVerifyService):
+ """
+ Service which removes specific events.
+ """
+
+ def title(self):
+ return "Nuke Service"
+
+
+ @inlineCallbacks
+ def doAction(self):
+ """
+ Remove a resource using either its path or resource id. When doing this do not
+ read the iCalendar data which may be corrupt.
+ """
+
+ self.output.write("\n---- Removing calendar resource ----\n")
+ self.txn = self.store.newTransaction()
+
+ nuke = self.options["nuke"]
+ if nuke.startswith("/calendars/__uids__/"):
+ pathbits = nuke.split("/")
+ if len(pathbits) != 6:
+ printusage("Not a valid calendar object resource path: %s" % (nuke,))
+ homeName = pathbits[3]
+ calendarName = pathbits[4]
+ resourceName = pathbits[5]
+
+ rid = yield self.getResourceID(homeName, calendarName, resourceName)
+ if rid is None:
+ yield self.txn.commit()
+ self.txn = None
+ self.output.write("\n")
+ self.output.write("Path does not exist. Nothing nuked.\n")
+ returnValue(None)
+ rid = int(rid)
+ else:
+ try:
+ 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.removeEvent(rid)
+ if result:
+ self.output.write("\n")
+ self.output.write("Removed resource: %s.\n" % (rid,))
+ else:
+ self.output.write("\n")
+ self.output.write("Resource: %s.\n" % (rid,))
+ yield self.txn.commit()
+ self.txn = None
+
+
+
+class OrphansService(CalVerifyService):
+ """
+ Service which removes specific events.
+ """
+
+ def title(self):
+ return "Orphans Service"
+
+
+ @inlineCallbacks
+ def doAction(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.
+ """
+ self.output.write("\n---- Finding calendar homes with missing or disabled directory records ----\n")
+ self.txn = self.store.newTransaction()
+
+ if self.options["verbose"]:
+ t = time.time()
+ uids = yield self.getAllHomeUIDs()
+ if self.options["verbose"]:
+ self.output.write("getAllHomeUIDs time: %.1fs\n" % (time.time() - t,))
+ missing = []
+ wrong_server = []
+ disabled = []
+ uids_len = len(uids)
+ uids_div = 1 if uids_len < 100 else uids_len / 100
+ self.addToSummary("Total Homes", uids_len)
+
+ 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,
+ uids_len,
+ ((ctr + 1) * 100 / uids_len),
+ )).ljust(80))
+ self.output.flush()
+
+ record = self.directoryService().recordWithGUID(uid)
+ if record is None:
+ contents = yield self.countHomeContents(uid)
+ missing.append((uid, contents,))
+ elif not record.thisServer():
+ contents = yield self.countHomeContents(uid)
+ wrong_server.append((uid, contents,))
+ 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()
+ self.txn = self.store.newTransaction()
+
+ yield self.txn.commit()
+ self.txn = None
+ if self.options["verbose"]:
+ self.output.write("\r".ljust(80) + "\n")
+
+ # Print table of results
+ table = tables.Table()
+ table.addHeader(("Owner UID", "Calendar Objects"))
+ 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]):
+ 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]):
+ 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)
+
+ self.printSummary()
+
+
+
+class BadDataService(CalVerifyService):
+ """
+ Service which scans for bad calendar data.
+ """
+
+ def title(self):
+ return "Bad Data Service"
+
+
+ @inlineCallbacks
+ def doAction(self):
+
+ self.output.write("\n---- Scanning calendar data ----\n")
+
+ self.now = PyCalendarDateTime.getNowUTC()
+ self.start = PyCalendarDateTime.getToday()
+ self.start.setDateOnly(False)
+ self.end = self.start.duplicate()
+ self.end.offsetYear(1)
+ self.fix = self.options["fix"]
+
+ self.tzid = PyCalendarTimezone(tzid=self.options["tzid"] if self.options["tzid"] else "America/Los_Angeles")
+
+ self.txn = self.store.newTransaction()
+
+ if self.options["verbose"]:
+ t = time.time()
+ descriptor = None
+ if self.options["uuid"]:
+ rows = yield self.getAllResourceInfoWithUUID(self.options["uuid"], inbox=True)
+ descriptor = "getAllResourceInfoWithUUID"
+ elif self.options["uid"]:
+ rows = yield self.getAllResourceInfoWithUID(self.options["uid"], inbox=True)
+ descriptor = "getAllResourceInfoWithUID"
+ else:
+ rows = yield self.getAllResourceInfo(inbox=True)
+ descriptor = "getAllResourceInfo"
+
+ yield self.txn.commit()
+ self.txn = None
+
+ 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)
+ self.addSummaryBreak()
+
+ yield self.calendarDataCheck(rows)
+
+ self.printSummary()
+
+
+ @inlineCallbacks
def calendarDataCheck(self, rows):
"""
Check each calendar resource for valid iCalendar data.
@@ -1132,66 +1240,181 @@
returnValue((result, message,))
+
+class SchedulingMismatchService(CalVerifyService):
+ """
+ Service which runs, exports the appropriate records, then stops the reactor.
+ """
+
+ metadata = {
+ "accessMode": "PUBLIC",
+ "isScheduleObject": True,
+ "scheduleTag": "abc",
+ "scheduleEtags": (),
+ "hasPrivateComment": False,
+ }
+
+ metadata_inbox = {
+ "accessMode": "PUBLIC",
+ "isScheduleObject": False,
+ "scheduleTag": "",
+ "scheduleEtags": (),
+ "hasPrivateComment": False,
+ }
+
+ def __init__(self, store, options, output, reactor, config):
+ super(SchedulingMismatchService, self).__init__(store, options, output, reactor, config)
+
+ self.validForCalendaringUUIDs = {}
+
+ self.fixAttendeesForOrganizerMissing = 0
+ self.fixAttendeesForOrganizerMismatch = 0
+ self.fixOrganizersForAttendeeMissing = 0
+ self.fixOrganizersForAttendeeMismatch = 0
+ self.fixFailed = 0
+ self.fixedAutoAccepts = []
+
+
@inlineCallbacks
- def fixBadOldCua(self, resid, caltxt):
+ def doAction(self):
+
+ self.output.write("\n---- Scanning calendar data ----\n")
+
+ self.now = PyCalendarDateTime.getNowUTC()
+ self.start = self.options["start"] if "start" in self.options else PyCalendarDateTime.getToday()
+ self.start.setDateOnly(False)
+ self.end = self.start.duplicate()
+ self.end.offsetYear(1)
+ self.fix = self.options["fix"]
+
+ self.tzid = PyCalendarTimezone(tzid=self.options["tzid"] if self.options["tzid"] else "America/Los_Angeles")
+
+ self.txn = self.store.newTransaction()
+
+ if self.options["verbose"]:
+ t = time.time()
+ descriptor = None
+ if self.options["uid"]:
+ rows = yield self.getAllResourceInfoWithUID(self.options["uid"])
+ descriptor = "getAllResourceInfoWithUID"
+ else:
+ rows = yield self.getAllResourceInfoTimeRange(self.start)
+ descriptor = "getAllResourceInfoTimeRange"
+
+ yield self.txn.commit()
+ self.txn = None
+
+ 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 = {}
+ self.attended = []
+ 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)
+ self.results["Number of attendee events to process"] = len(self.attended)
+ self.results["Number of skipped events"] = skipped
+ self.results["Number of inbox events"] = inboxes
+ self.addToSummary("Number of organizer events to process", len(self.organized), self.total)
+ self.addToSummary("Number of attendee events to process", len(self.attended), self.total)
+ self.addToSummary("Number of skipped events", skipped, self.total)
+ self.addSummaryBreak()
+
+ self.totalErrors = 0
+ yield self.verifyAllAttendeesForOrganizer()
+ yield self.verifyAllOrganizersForAttendee()
+
+ # Need to add fix summary information
+ if self.fix:
+ self.addSummaryBreak()
+ self.results["Fixed missing attendee events"] = self.fixAttendeesForOrganizerMissing
+ self.results["Fixed mismatched attendee events"] = self.fixAttendeesForOrganizerMismatch
+ self.results["Fixed missing organizer events"] = self.fixOrganizersForAttendeeMissing
+ self.results["Fixed mismatched organizer events"] = self.fixOrganizersForAttendeeMismatch
+ self.results["Fix failures"] = self.fixFailed
+ self.results["Fixed Auto-Accepts"] = self.fixedAutoAccepts
+ self.addToSummary("Fixed missing attendee events", self.fixAttendeesForOrganizerMissing)
+ self.addToSummary("Fixed mismatched attendee events", self.fixAttendeesForOrganizerMismatch)
+ self.addToSummary("Fixed missing organizer events", self.fixOrganizersForAttendeeMissing)
+ self.addToSummary("Fixed mismatched organizer events", self.fixOrganizersForAttendeeMismatch)
+ self.addToSummary("Fix failures", self.fixFailed)
+
+ self.printAutoAccepts()
+
+ self.printSummary()
+
+
+ def buildResourceInfo(self, rows, onlyOrganizer=False, onlyAttendee=False):
"""
- Fix bad CALENDARSERVER-OLD-CUA lines and write fixed data to store. Assumes iCalendar data lines unfolded.
+ For each resource, determine whether it is an organizer or attendee event, and also
+ cache the attendee partstats.
+
+ @param rows: set of DB query rows
+ @type rows: C{list}
+ @param onlyOrganizer: whether organizer information only is required
+ @type onlyOrganizer: C{bool}
+ @param onlyAttendee: whether attendee information only is required
+ @type onlyAttendee: C{bool}
"""
- # Get store objects
- homeID, calendarID = yield self.getAllResourceInfoForResourceID(resid)
- home = yield self.txn.calendarHomeWithResourceID(homeID)
- calendar = yield home.childWithID(calendarID)
- calendarObj = yield calendar.objectResourceWithID(resid)
+ skipped = 0
+ inboxes = 0
+ for owner, resid, uid, calname, md5, organizer, created, modified in rows:
- # Do raw data fix one line at a time
- caltxt = self.fixBadOldCuaLines(caltxt)
+ # Skip owners not enabled for calendaring
+ if not self.testForCalendaringUUID(owner):
+ skipped += 1
+ continue
- # Re-parse
- try:
- component = Component.fromString(caltxt)
- except InvalidICalendarDataError:
- returnValue(None)
+ # Skip inboxes
+ if calname == "inbox":
+ inboxes += 1
+ continue
- # Write out fix, commit and get a new transaction
- # Use _migrating to ignore possible overridden instance errors - we are either correcting or ignoring those
- self.txn._migrating = True
- component = yield calendarObj.setComponent(component)
- yield self.txn.commit()
- self.txn = self.store.newTransaction()
+ # If targeting a specific organizer, skip events belonging to others
+ if self.options["uuid"]:
+ if not organizer.startswith("urn:uuid:") or self.options["uuid"] != organizer[9:]:
+ continue
- returnValue(caltxt)
+ # Cache organizer/attendee states
+ if organizer.startswith("urn:uuid:") and owner == organizer[9:]:
+ if not onlyAttendee:
+ self.organized.append((owner, resid, uid, md5, organizer, created, modified,))
+ self.organized_byuid[uid] = (owner, resid, uid, md5, organizer, created, modified,)
+ else:
+ 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 fixBadOldCuaLines(self, caltxt):
+
+ def testForCalendaringUUID(self, uuid):
"""
- Fix bad CALENDARSERVER-OLD-CUA lines. Assumes iCalendar data lines unfolded.
+ Determine if the specified directory UUID is valid for calendaring. Keep a cache of
+ valid and invalid so we can do this quickly.
+
+ @param uuid: the directory UUID to test
+ @type uuid: C{str}
+
+ @return: C{True} if valid, C{False} if not
"""
- # Do raw data fix one line at a time
- lines = caltxt.splitlines()
- for ctr, line in enumerate(lines):
- startpos = line.find(";CALENDARSERVER-OLD-CUA=\"//")
- if startpos != -1:
- 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]
- endbadparam = badparam.find(";")
- if endbadparam != -1:
- badparam = badparam[:endbadparam].replace("\\", "")
- if badparam.find("8443") != -1:
- badparam = "https:" + badparam
- else:
- badparam = "http:" + badparam
- if self.options["nobase64"]:
- badparam = "\"" + badparam + "\""
- else:
- badparam = "base64-%s" % (base64.b64encode(badparam),)
- badparam = ";CALENDARSERVER-OLD-CUA=" + badparam
- lines[ctr] = line[:startpos] + badparam + line[endpos:]
- caltxt = "\r\n".join(lines) + "\r\n"
- return caltxt
+ if uuid not in self.validForCalendaringUUIDs:
+ record = self.directoryService().recordWithGUID(uuid)
+ self.validForCalendaringUUIDs[uuid] = record is not None and record.enabledForCalendaring and record.thisServer()
+ return self.validForCalendaringUUIDs[uuid]
@inlineCallbacks
@@ -1452,7 +1675,7 @@
# If there is a miss we fix by removing the attendee data
if self.fix:
# This is where we attempt a fix
- fix_result = (yield self.fixByRemovingEvent(resid))
+ fix_result = (yield self.removeEvent(resid))
if fix_result:
self.fixOrganizersForAttendeeMissing += 1
else:
@@ -1556,7 +1779,7 @@
# 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:
- yield self.fixByRemovingEvent(attresid)
+ yield self.removeEvent(attresid)
returnValue(True)
# Convert iTip message into actual calendar data - just remove METHOD
@@ -1647,66 +1870,6 @@
returnValue(None)
- @inlineCallbacks
- def fixByRemovingEvent(self, resid):
- """
- Remove the calendar resource specified by resid - this is a force remove - no implicit
- scheduling is required so we use store apis directly.
- """
-
- try:
- homeID, calendarID = yield self.getAllResourceInfoForResourceID(resid)
- home = yield self.txn.calendarHomeWithResourceID(homeID)
- calendar = yield home.childWithID(calendarID)
- calendarObj = yield calendar.objectResourceWithID(resid)
- objname = calendarObj.name()
- 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),
- else:
- percent = ""
- self.summary.append((title, count, percent))
-
-
- def addSummaryBreak(self):
- self.summary.append(None)
-
-
- def printSummary(self):
- # Print summary of results
- table = tables.Table()
- table.addHeader(("Item", "Count", "%"))
- table.setDefaultColumnFormats(
- (
- 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),
- )
- )
- for item in self.summary:
- table.addRow(item)
-
- 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)
-
-
def printAutoAccepts(self):
# Print summary of results
table = tables.Table()
@@ -1725,71 +1888,11 @@
table.printTable(os=self.output)
- @inlineCallbacks
- def getCalendar(self, resid, doFix=False):
- co = schema.CALENDAR_OBJECT
- kwds = {"ResourceID" : resid}
- rows = (yield Select(
- [co.ICALENDAR_TEXT],
- From=co,
- Where=(
- co.RESOURCE_ID == Parameter("ResourceID")
- ),
- ).on(self.txn, **kwds))
- try:
- caldata = PyCalendar.parseText(rows[0][0]) if rows else None
- except PyCalendarError:
- caltxt = rows[0][0] if rows else None
- if caltxt:
- caltxt = caltxt.replace("\r\n ", "")
- if caltxt.find("CALENDARSERVER-OLD-CUA=\"//") != -1:
- if doFix:
- caltxt = (yield self.fixBadOldCua(resid, caltxt))
- try:
- caldata = PyCalendar.parseText(caltxt) if rows else None
- except PyCalendarError:
- self.parseError = "No fix bad CALENDARSERVER-OLD-CUA"
- returnValue(None)
- else:
- self.parseError = "Bad CALENDARSERVER-OLD-CUA"
- returnValue(None)
-
- self.parseError = "Failed to parse"
- returnValue(None)
-
- self.parseError = None
- returnValue(caldata)
-
-
- @inlineCallbacks
- def getCalendarForOwnerByUID(self, owner, uid):
- co = schema.CALENDAR_OBJECT
- cb = schema.CALENDAR_BIND
- ch = schema.CALENDAR_HOME
-
- kwds = {"OWNER": owner, "UID": uid}
- rows = (yield Select(
- [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(
- cb.BIND_MODE == _BIND_MODE_OWN).And(
- cb.CALENDAR_RESOURCE_NAME != "inbox")),
- Where=(ch.OWNER_UID == Parameter("OWNER")).And(co.ICALENDAR_UID == Parameter("UID")),
- ).on(self.txn, **kwds))
-
- try:
- caldata = PyCalendar.parseText(rows[0][0]) if rows else None
- except PyCalendarError:
- returnValue((None, None, None, None,))
-
- returnValue((caldata, rows[0][1], rows[0][2], rows[0][3],) if rows else (None, None, None, None,))
-
-
def masterComponent(self, calendar):
"""
Return the master iCal component in this calendar.
- @return: the L{Component} for the master component,
+
+ @return: the L{PyCalendarComponent} for the master component,
or C{None} if there isn't one.
"""
for component in calendar.getComponents(definitions.cICalComponent_VEVENT):
@@ -1882,32 +1985,9 @@
component.replaceProperty(Property("TRANSP", "TRANSPARENT" if addTransp else "OPAQUE"))
- def directoryService(self):
- """
- Get an appropriate directory service for this L{CalVerifyService}'s
- configuration, creating one first if necessary.
- """
- if self._directory is None:
- self._directory = getDirectory(self.config) #directoryFromConfig(self.config)
- return self._directory
+def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
- def stopService(self):
- """
- Stop the service. Nothing to do; everything should be finished by this
- time.
- """
- # TODO: stopping this service mid-export should really stop the export
- # loop, but this is not implemented because nothing will actually do it
- # except hitting ^C (which also calls reactor.stop(), so that will exit
- # anyway).
-
-
-
-def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
- """
- Do the export.
- """
if reactor is None:
from twisted.internet import reactor
options = CalVerifyOptions()
@@ -1926,9 +2006,16 @@
def makeService(store):
from twistedcaldav.config import config
config.TransactionTimeoutSeconds = 0
- return CalVerifyService(store, options, output, reactor, config)
+ if options["nuke"]:
+ return NukeService(store, options, output, reactor, config)
+ elif options["missing"]:
+ return OrphansService(store, options, output, reactor, config)
+ elif options["ical"] or options["badcua"]:
+ return BadDataService(store, options, output, reactor, config)
+ elif options["mismatch"]:
+ return SchedulingMismatchService(store, options, output, reactor, config)
- utilityMain(options['config'], makeService, reactor, verbose=options['debug'])
+ utilityMain(options['config'], makeService, reactor)
if __name__ == '__main__':
main()
Modified: CalendarServer/trunk/calendarserver/tools/test/test_calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_calverify.py 2013-02-20 18:31:53 UTC (rev 10766)
+++ CalendarServer/trunk/calendarserver/tools/test/test_calverify.py 2013-02-20 20:08:47 UTC (rev 10767)
@@ -20,7 +20,8 @@
from StringIO import StringIO
from calendarserver.tap.util import getRootResource
-from calendarserver.tools.calverify import CalVerifyService
+from calendarserver.tools.calverify import BadDataService, \
+ SchedulingMismatchService
from pycalendar.datetime import PyCalendarDateTime
from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks, returnValue
@@ -530,6 +531,7 @@
options = {
"ical": True,
+ "fix": False,
"nobase64": False,
"verbose": False,
"uid": "",
@@ -537,9 +539,9 @@
"tzid": "",
}
output = StringIO()
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+ calverify = BadDataService(self._sqlCalendarStore, options, output, reactor, config)
calverify.emailDomain = "example.com"
- yield calverify.doScan(True, False, False)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 13)
self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
@@ -572,6 +574,7 @@
options = {
"ical": True,
+ "fix": True,
"nobase64": False,
"verbose": False,
"uid": "",
@@ -583,9 +586,9 @@
# Do fix
self.patch(config.Scheduling.Options, "PrincipalHostAliases", "demo.com")
self.patch(config, "HTTPPort", 8008)
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+ calverify = BadDataService(self._sqlCalendarStore, options, output, reactor, config)
calverify.emailDomain = "example.com"
- yield calverify.doScan(True, False, True)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 13)
self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
@@ -603,9 +606,10 @@
)))
# Do scan
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+ options["fix"] = False
+ calverify = BadDataService(self._sqlCalendarStore, options, output, reactor, config)
calverify.emailDomain = "example.com"
- yield calverify.doScan(True, False, False)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 13)
self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
@@ -640,6 +644,7 @@
options = {
"ical": False,
+ "fix": False,
"badcua": True,
"nobase64": False,
"verbose": False,
@@ -648,9 +653,9 @@
"tzid": "",
}
output = StringIO()
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+ calverify = BadDataService(self._sqlCalendarStore, options, output, reactor, config)
calverify.emailDomain = "example.com"
- yield calverify.doScan(True, False, False)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 13)
self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
@@ -679,6 +684,7 @@
options = {
"ical": False,
+ "fix": True,
"badcua": True,
"nobase64": False,
"verbose": False,
@@ -691,9 +697,9 @@
# Do fix
self.patch(config.Scheduling.Options, "PrincipalHostAliases", "demo.com")
self.patch(config, "HTTPPort", 8008)
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+ calverify = BadDataService(self._sqlCalendarStore, options, output, reactor, config)
calverify.emailDomain = "example.com"
- yield calverify.doScan(True, False, True)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 13)
self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
@@ -707,9 +713,10 @@
)))
# Do scan
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+ options["fix"] = False
+ calverify = BadDataService(self._sqlCalendarStore, options, output, reactor, config)
calverify.emailDomain = "example.com"
- yield calverify.doScan(True, False, False)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 13)
self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
@@ -907,7 +914,7 @@
"uuid": "",
"tzid": "",
}
- calverifyNo64 = CalVerifyService(self._sqlCalendarStore, optionsNo64, StringIO(), reactor, config)
+ calverifyNo64 = BadDataService(self._sqlCalendarStore, optionsNo64, StringIO(), reactor, config)
calverifyNo64.emailDomain = "example.com"
options64 = {
@@ -918,7 +925,7 @@
"uuid": "",
"tzid": "",
}
- calverify64 = CalVerifyService(self._sqlCalendarStore, options64, StringIO(), reactor, config)
+ calverify64 = BadDataService(self._sqlCalendarStore, options64, StringIO(), reactor, config)
calverify64.emailDomain = "example.com"
for bad, oknobase64, okbase64 in data:
@@ -1467,15 +1474,17 @@
"badcua": False,
"mismatch": True,
"nobase64": False,
+ "fix": False,
"verbose": False,
"details": False,
"uid": "",
"uuid": "",
"tzid": "",
+ "start": PyCalendarDateTime(now, 1, 1, 0, 0, 0),
}
output = StringIO()
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
- yield calverify.doScan(False, True, False, start=PyCalendarDateTime(now, 1, 1, 0, 0, 0))
+ calverify = SchedulingMismatchService(self._sqlCalendarStore, options, output, reactor, config)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 17)
self.assertEqual(calverify.results["Missing Attendee"], set((
@@ -1532,15 +1541,17 @@
"badcua": False,
"mismatch": True,
"nobase64": False,
+ "fix": True,
"verbose": False,
"details": False,
"uid": "",
"uuid": "",
"tzid": "",
+ "start": PyCalendarDateTime(now, 1, 1, 0, 0, 0),
}
output = StringIO()
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
- yield calverify.doScan(False, True, True, start=PyCalendarDateTime(now, 1, 1, 0, 0, 0))
+ calverify = SchedulingMismatchService(self._sqlCalendarStore, options, output, reactor, config)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 17)
self.assertEqual(calverify.results["Missing Attendee"], set((
@@ -1615,8 +1626,9 @@
# Re-scan after changes to make sure there are no errors
self.commit()
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
- yield calverify.doScan(False, True, False, start=PyCalendarDateTime(now, 1, 1, 0, 0, 0))
+ options["fix"] = False
+ calverify = SchedulingMismatchService(self._sqlCalendarStore, options, output, reactor, config)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 14)
self.assertTrue("Missing Attendee" not in calverify.results)
@@ -1738,15 +1750,17 @@
"badcua": False,
"mismatch": True,
"nobase64": False,
+ "fix": False,
"verbose": False,
"details": False,
"uid": "",
"uuid": "",
"tzid": "",
+ "start": PyCalendarDateTime(now, 1, 1, 0, 0, 0),
}
output = StringIO()
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
- yield calverify.doScan(False, True, False, start=PyCalendarDateTime(now, 1, 1, 0, 0, 0))
+ calverify = SchedulingMismatchService(self._sqlCalendarStore, options, output, reactor, config)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 3)
self.assertEqual(calverify.results["Missing Attendee"], set((
@@ -1787,15 +1801,17 @@
"badcua": False,
"mismatch": True,
"nobase64": False,
+ "fix": True,
"verbose": False,
"details": False,
"uid": "",
"uuid": "",
"tzid": "",
+ "start": PyCalendarDateTime(now, 1, 1, 0, 0, 0),
}
output = StringIO()
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
- yield calverify.doScan(False, True, True, start=PyCalendarDateTime(now, 1, 1, 0, 0, 0))
+ calverify = SchedulingMismatchService(self._sqlCalendarStore, options, output, reactor, config)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 3)
self.assertEqual(calverify.results["Missing Attendee"], set((
@@ -1837,8 +1853,9 @@
# Re-scan after changes to make sure there are no errors
self.commit()
- calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
- yield calverify.doScan(False, True, False, start=PyCalendarDateTime(now, 1, 1, 0, 0, 0))
+ options["fix"] = False
+ calverify = SchedulingMismatchService(self._sqlCalendarStore, options, output, reactor, config)
+ yield calverify.doAction()
self.assertEqual(calverify.results["Number of events to process"], 4)
self.assertTrue("Missing Attendee" not in calverify.results)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130220/33d190db/attachment-0001.html>
More information about the calendarserver-changes
mailing list