[CalendarServer-changes] [6671] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Dec 3 14:06:06 PST 2010


Revision: 6671
          http://trac.macosforge.org/projects/calendarserver/changeset/6671
Author:   sagen at apple.com
Date:     2010-12-03 14:06:02 -0800 (Fri, 03 Dec 2010)
Log Message:
-----------
Updates purge-principal tool to use new storage APIs

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/calendarserver/tap/util.py
    CalendarServer/trunk/calendarserver/tools/purge.py
    CalendarServer/trunk/calendarserver/tools/test/test_purge.py
    CalendarServer/trunk/setup.py
    CalendarServer/trunk/support/Makefile.Apple
    CalendarServer/trunk/twistedcaldav/stdconfig.py

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2010-12-03 21:49:42 UTC (rev 6670)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2010-12-03 22:06:02 UTC (rev 6671)
@@ -847,6 +847,19 @@
         return self.storageService(slaveSvcCreator)
 
 
+    def makeService_Utility(self, options):
+        """
+        Create a service to be used in a command-line utility
+
+        Specify the actual utility service class in config.UtilityServiceClass.
+        When created, that service will have access to the storage facilities.
+        """
+
+        def toolServiceCreator(pool, store):
+            return config.UtilityServiceClass(store)
+
+        return self.storageService(toolServiceCreator)
+
     def storageService(self, createMainService, uid=None, gid=None):
         """
         If necessary, create a service to be started used for storage; for

Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py	2010-12-03 21:49:42 UTC (rev 6670)
+++ CalendarServer/trunk/calendarserver/tap/util.py	2010-12-03 22:06:02 UTC (rev 6671)
@@ -43,7 +43,6 @@
 from twisted.internet.tcp import Connection
 from twisted.python.reflect import namedClass
 
-from twistedcaldav import memcachepool
 from twistedcaldav.bind import doBind
 from twistedcaldav.directory import augment, calendaruserproxy
 from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
@@ -80,6 +79,7 @@
 from txdav.common.datastore.sql import v1_schema
 from txdav.base.datastore.subpostgres import PostgresService
 from twext.python.filepath import CachingFilePath
+from urllib import quote
 
 
 log = Logger()
@@ -625,11 +625,31 @@
             self._rememberResource(resource, url)
         returnValue(resource)
 
+    @inlineCallbacks
+    def locateChildResource(self, parent, childName):
+        if parent is None or childName is None:
+            returnValue(None)
+        parentURL = self.urlForResource(parent)
+        if not parentURL.endswith("/"):
+            parentURL += "/"
+        url = parentURL + quote(childName)
+        segment = childName
+        resource = (yield self._getChild(parent, [segment]))
+        if resource:
+            self._rememberResource(resource, url)
+        returnValue(resource)
+
     def _rememberResource(self, resource, url):
         self._resourcesByURL[url] = resource
         self._urlsByResource[resource] = url
         return resource
 
+    def _forgetResource(self, resource, url):
+        if self._resourcesByURL.has_key(url):
+            del self._resourcesByURL[url]
+        if self._urlsByResource.has_key(resource):
+            del self._urlsByResource[resource]
+
     def urlForResource(self, resource):
         url = self._urlsByResource.get(resource, None)
         if url is None:

Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py	2010-12-03 21:49:42 UTC (rev 6670)
+++ CalendarServer/trunk/calendarserver/tools/purge.py	2010-12-03 22:06:02 UTC (rev 6671)
@@ -16,25 +16,27 @@
 # limitations under the License.
 ##
 
+from calendarserver.tap.caldav import CalDAVServiceMaker, CalDAVOptions
+from twisted.application.service import Service
 from calendarserver.tap.util import FakeRequest
 from calendarserver.tap.util import getRootResource
 from calendarserver.tools.principals import removeProxy
-from calendarserver.tools.util import loadConfig, setupMemcached
+from calendarserver.tools.util import loadConfig
 from datetime import date, timedelta, datetime
 from getopt import getopt, GetoptError
-from grp import getgrnam
-from pwd import getpwnam
 from twext.python.log import Logger
 from twext.web2.dav import davxml
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.python.util import switchUID
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import TimeRange
 from twistedcaldav.config import config, ConfigurationError
-from twistedcaldav.directory.directory import DirectoryError, DirectoryRecord
+from twistedcaldav.directory.directory import DirectoryRecord
 from twistedcaldav.method.delete_common import DeleteResource
+from twistedcaldav.method.put_common import StoreCalendarObjectResource
 from twistedcaldav.query import calendarqueryfilter
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
+from vobject.icalendar import utc
 import os
 import sys
 
@@ -82,39 +84,67 @@
         sys.exit(0)
 
 
+class PurgeOldEventsService(Service):
 
-def shared_main(configFileName, method, *args, **kwds):
+    def __init__(self, store):
+        self._store = store
 
+    def startService(self):
+        try:
+            rootResource = getRootResource(config, self._store)
+            directory = rootResource.getDirectory()
+        finally:
+            reactor.stop()
+
+
+class PurgePrincipalService(Service):
+
+    guids = None
+    dryrun = False
+    verbose = False
+
+    def __init__(self, store):
+        self._store = store
+
+    @inlineCallbacks
+    def startService(self):
+        try:
+            rootResource = getRootResource(config, self._store)
+            directory = rootResource.getDirectory()
+            total = (yield purgeGUIDs(directory, rootResource, self.guids,
+                verbose=self.verbose, dryrun=self.dryrun))
+            if self.verbose:
+                amount = "%d event%s" % (total, "s" if total > 1 else "")
+                if self.dryrun:
+                    print "Would have modified or deleted %s" % (amount,)
+                else:
+                    print "Modified or deleted %s" % (amount,)
+        except Exception, e:
+            print "Error:", e
+            raise
+        finally:
+            reactor.stop()
+
+
+def shared_main(configFileName, serviceClass):
+
     try:
         loadConfig(configFileName)
 
-        # Shed privileges
-        if config.UserName and config.GroupName and os.getuid() == 0:
-            uid = getpwnam(config.UserName).pw_uid
-            gid = getgrnam(config.GroupName).gr_gid
-            switchUID(uid, uid, gid)
+        config.ProcessType = "Utility"
+        config.UtilityServiceClass = serviceClass
 
-        os.umask(config.umask)
+        maker = CalDAVServiceMaker()
+        options = CalDAVOptions
+        service = maker.makeService(options)
 
-        try:
-            # TODO: getRootResource needs a parent service now.
-            rootResource = getRootResource(config)
-            directory = rootResource.getDirectory()
-        except DirectoryError, e:
-            print "Error: %s" % (e,)
-            return
-        setupMemcached(config)
+        reactor.addSystemEventTrigger("during", "startup", service.startService)
+        reactor.addSystemEventTrigger("before", "shutdown", service.stopService)
+
     except ConfigurationError, e:
         print "Error: %s" % (e,)
         return
 
-
-    #
-    # Start the reactor
-    #
-    reactor.callLater(0.1, callThenStop, method, directory,
-        rootResource, *args, **kwds)
-
     reactor.run()
 
 def main_purge_events():
@@ -170,7 +200,7 @@
 
     shared_main(
         configFileName,
-        purgeOldEvents,
+        PurgeOldEventsService,
         cutoff,
         verbose=verbose,
         dryrun=dryrun,
@@ -215,13 +245,14 @@
             raise NotImplementedError(opt)
 
     # args is a list of guids
+    PurgePrincipalService.guids = args
+    PurgePrincipalService.dryrun = dryrun
+    PurgePrincipalService.verbose = verbose
 
+
     shared_main(
         configFileName,
-        purgeGUIDs,
-        args,
-        verbose=verbose,
-        dryrun=dryrun,
+        PurgePrincipalService
     )
 
 
@@ -341,6 +372,7 @@
     # TODO: this seems hacky, even for a stub request:
     request._rememberResource(resource, uri)
 
+    # Cyrus says to use resource.storeRemove( ) -- see twistedcaldav/method/delete_common.py
     deleter = DeleteResource(request, resource, uri,
         collection, "infinity", allowImplicitSchedule=implicit)
     return deleter.run()
@@ -357,7 +389,6 @@
             verbose=verbose, dryrun=dryrun))
         total += count
 
-
     # TODO: figure out what to do with the purged proxy assignments...
     # ...print to stdout?
     # ...save in a file?
@@ -365,9 +396,129 @@
     returnValue(total)
 
 
+CANCELEVENT_SKIPPED = 1
+CANCELEVENT_MODIFIED = 2
+CANCELEVENT_NOT_MODIFIED = 3
+CANCELEVENT_SHOULD_DELETE = 4
+
+def cancelEvent(event, when, cua):
+    """
+    Modify a VEVENT such that all future occurrences are removed
+
+    @param event: the event to modify
+    @type event: L{twistedcaldav.ical.Component}
+
+    @param when: the cutoff date (anything after which is removed)
+    @type when: datetime with tzinfo
+
+    @param cua: Calendar User Address of principal being purged, to compare
+        to see if it's the organizer of the event or just an attendee
+    @type cua: string
+
+    Assumes that event does not occur entirely in the past.
+
+    @return: one of the 4 constants above to indicate what action to take
+    """
+
+    whenDate = when.date()
+
+    master = event.masterComponent()
+
+    # Only process VEVENT
+    if master.name() != "VEVENT":
+        return CANCELEVENT_SKIPPED
+
+    # Anything completely in the future is deleted
+    dtstart = master.getStartDateUTC()
+    if isinstance(dtstart, datetime):
+        isDateTime = True
+        if dtstart > when:
+            return CANCELEVENT_SHOULD_DELETE
+    else:
+        isDateTime = False
+        if dtstart > whenDate:
+            return CANCELEVENT_SHOULD_DELETE
+
+    organizer = master.getOrganizer()
+
+    # Non-meetings are deleted
+    if organizer is None:
+        return CANCELEVENT_SHOULD_DELETE
+
+    # Meetings which cua is merely an attendee are deleted (thus implicitly
+    # declined)
+    # FIXME: I think we want to decline anything after the cut-off, not delete
+    # the whole event.
+    if organizer != cua:
+        return CANCELEVENT_SHOULD_DELETE
+
+    dirty = False
+
+    # Set the UNTIL on RRULE to cease at the cutoff
+    if master.hasProperty("RRULE"):
+        for rrule in master.properties("RRULE"):
+            tokens = {}
+            tokens.update([valuePart.split("=") for valuePart in rrule.value().split(";")])
+            if tokens.has_key("COUNT"):
+                dirty = True
+                del tokens["COUNT"]
+
+            if isDateTime:
+                tokens["UNTIL"] = when.strftime("%Y%m%dT%H%M%SZ")
+            else:
+                tokens["UNTIL"] = when.strftime("%Y%m%d")
+
+            newValue = ";".join(["%s=%s" % (key, value,) for key, value in tokens.iteritems()])
+            rrule.setValue(newValue)
+            dirty = True
+
+    # Remove any EXDATEs and RDATEs beyond the cutoff
+    for dateType in ("EXDATE", "RDATE"):
+        if master.hasProperty(dateType):
+            for exdate_rdate in master.properties(dateType):
+                newValues = []
+                for value in exdate_rdate.value():
+                    if isinstance(value, datetime):
+                        if value < when:
+                            newValues.append(value)
+                    else:
+                        if value < whenDate:
+                            newValues.append(value)
+                if not newValues:
+                    master.removeProperty(exdate_rdate)
+                    dirty = True
+                else:
+                    exdate_rdate.setValue(newValues)
+                    dirty = True
+
+
+    # Remove any overridden components beyond the cutoff
+    for component in tuple(event.subcomponents()):
+        if component.name() == "VEVENT":
+            dtstart = component.getStartDateUTC()
+            remove = False
+            if isinstance(dtstart, datetime):
+                if dtstart > when:
+                    remove = True
+            else:
+                if dtstart > whenDate:
+                    remove = True
+            if remove:
+                event.removeComponent(component)
+                dirty = True
+
+    if dirty:
+        return CANCELEVENT_MODIFIED
+    else:
+        return CANCELEVENT_NOT_MODIFIED
+
+
 @inlineCallbacks
 def purgeGUID(guid, directory, root, verbose=False, dryrun=False):
 
+    when = datetime.now(tz=utc)
+    # when = datetime(2010, 12, 6, 12, 0, 0, 0, utc)
+
     # Does the record exist?
     record = directory.recordWithGUID(guid)
     if record is None:
@@ -382,17 +533,26 @@
         directory._tmpRecords["shortNames"][guid] = record
         directory._tmpRecords["guids"][guid] = record
 
+    cua = "urn:uuid:%s" % (guid,)
+
     principalCollection = directory.principalCollection
     principal = principalCollection.principalForRecord(record)
-    calendarHome = yield principal.calendarHome()
 
+    request = FakeRequest(root, None, None)
+    request.checkedSACL = True
+    request.authnUser = request.authzUser = davxml.Principal(
+        davxml.HRef.fromString("/principals/__uids__/%s/" % (guid,))
+    )
+
+    calendarHome = yield principal.calendarHome(request)
+
     # Anything in the past is left alone
-    now = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
+    whenString = when.strftime("%Y%m%dT%H%M%SZ")
     filter =  caldavxml.Filter(
           caldavxml.ComponentFilter(
               caldavxml.ComponentFilter(
-                  TimeRange(start=now,),
-                  name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
+                  TimeRange(start=whenString,),
+                  name=("VEVENT",),
               ),
               name="VCALENDAR",
            )
@@ -400,26 +560,61 @@
     filter = calendarqueryfilter.Filter(filter)
 
     count = 0
+    assignments = []
 
-    for collName in calendarHome.listChildren():
-        collection = calendarHome.getChild(collName)
+    perUserFilter = PerUserDataFilter(guid)
+
+    for collName in (yield calendarHome.listChildren()):
+        collection = (yield calendarHome.getChild(collName))
         if collection.isCalendarCollection():
 
-            for name, uid, type in collection.index().indexedSearch(filter):
-                if isinstance(name, unicode):
-                    name = name.encode("utf-8")
-                resource = collection.getChild(name)
-                uri = "/calendars/__uids__/%s/%s/%s" % (
-                    record.uid,
-                    collName,
-                    name
-                )
-                if not dryrun:
-                    (yield deleteResource(root, collection, resource,
-                        uri, guid, implicit=True))
-                count += 1
+            for childName, childUid, childType in (yield collection.index().indexedSearch(filter)):
+                childResource = (yield collection.getChild(childName))
+                event = (yield childResource.iCalendar())
+                event = perUserFilter.filter(event)
+                # print "BEFORE CANCEL", event
+                action = cancelEvent(event, when, cua)
+                # print "AFTER CANCEL", action, event
 
+                uri = "/calendars/__uids__/%s/%s/%s" % (guid, collName, childName)
+                request.path = uri
+                if action == CANCELEVENT_MODIFIED:
+                    count += 1
+                    request._rememberResource(childResource, uri)
+                    storer = StoreCalendarObjectResource(
+                        request=request,
+                        destination=childResource,
+                        destination_uri=uri,
+                        destinationcal=True,
+                        destinationparent=collection,
+                        calendar=str(event),
+                    )
+                    if verbose:
+                        if dryrun:
+                            print "Would modify: %s" % (uri,)
+                        else:
+                            print "Modifying: %s" % (uri,)
+                    if not dryrun:
+                        result = (yield storer.run())
+
+                elif action == CANCELEVENT_SHOULD_DELETE:
+                    count += 1
+                    request._rememberResource(childResource, uri)
+                    if verbose:
+                        if dryrun:
+                            print "Would delete: %s" % (uri,)
+                        else:
+                            print "Deleting: %s" % (uri,)
+                    if not dryrun:
+                        result = (yield childResource.storeRemove(request, True, uri))
+
+    # Commit
+    txn = request._newStoreTransaction
+    (yield txn.commit())
+
     if not dryrun:
+        if verbose:
+            print "Deleting any proxy assignments"
         assignments = (yield purgeProxyAssignments(principal))
 
     returnValue((count, assignments))

Modified: CalendarServer/trunk/calendarserver/tools/test/test_purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_purge.py	2010-12-03 21:49:42 UTC (rev 6670)
+++ CalendarServer/trunk/calendarserver/tools/test/test_purge.py	2010-12-03 22:06:02 UTC (rev 6671)
@@ -14,6 +14,13 @@
 # limitations under the License.
 ##
 
+
+from twistedcaldav.ical import Component
+from calendarserver.tools.purge import cancelEvent
+from calendarserver.tools.purge import CANCELEVENT_MODIFIED, CANCELEVENT_SHOULD_DELETE
+from vobject.icalendar import utc
+from datetime import datetime
+
 from calendarserver.tap.util import getRootResource
 from calendarserver.tools.principals import addProxy
 from calendarserver.tools.purge import purgeOldEvents, purgeGUID, purgeProxyAssignments
@@ -1130,3 +1137,393 @@
 END:VCALENDAR
 """.replace("\n", "\r\n") % (future,)
 
+
+
+class CancelEventTestCase(TestCase):
+
+    def test_cancelRepeating(self):
+        # A repeating event where purged CUA is organizer
+        event = Component.fromString(REPEATING_1_ICS_BEFORE)
+        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+            "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
+        self.assertEquals(action, CANCELEVENT_MODIFIED)
+        self.assertEquals(str(event), REPEATING_1_ICS_AFTER)
+
+    def test_cancelAllDayRepeating(self):
+        # A repeating All Day event where purged CUA is organizer
+        event = Component.fromString(REPEATING_2_ICS_BEFORE)
+        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+            "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
+        self.assertEquals(action, CANCELEVENT_MODIFIED)
+        self.assertEquals(str(event), REPEATING_2_ICS_AFTER)
+
+    def test_cancelFutureEvent(self):
+        # A future event
+        event = Component.fromString(FUTURE_EVENT_ICS)
+        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+            "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
+        self.assertEquals(action, CANCELEVENT_SHOULD_DELETE)
+
+    def test_cancelNonMeeting(self):
+        # A repeating non-meeting event
+        event = Component.fromString(REPEATING_NON_MEETING_ICS)
+        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+            "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
+        self.assertEquals(action, CANCELEVENT_SHOULD_DELETE)
+
+    def test_cancelAsAttendee(self):
+        # A repeating meeting event where purged CUA is an attendee
+        event = Component.fromString(REPEATING_ATTENDEE_MEETING_ICS)
+        action = cancelEvent(event, datetime(2010, 12, 6, 12, 0, 0, 0, utc),
+            "urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1")
+        self.assertEquals(action, CANCELEVENT_SHOULD_DELETE)
+
+
+# This event begins on Nov 30, 2010, has two EXDATES (Dec 3 and 9), and has two
+# overridden instances (Dec 4 and 11).  The Dec 11 one will be removed since
+# the cutoff date for this test is Dec 6.
+
+REPEATING_1_ICS_BEFORE = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:59E260E3-1644-4BDF-BBC6-6130B0C3A520
+DTSTART;TZID=US/Pacific:20101130T100000
+DTEND;TZID=US/Pacific:20101130T110000
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=1.2:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T184815Z
+DTSTAMP:20101203T185019Z
+EXDATE;TZID=US/Pacific:20101203T100000
+EXDATE;TZID=US/Pacific:20101209T100000
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+RRULE:FREQ=DAILY;COUNT=400
+SEQUENCE:4
+SUMMARY:Repeating 1
+TRANSP:OPAQUE
+END:VEVENT
+BEGIN:VEVENT
+UID:59E260E3-1644-4BDF-BBC6-6130B0C3A520
+RECURRENCE-ID;TZID=US/Pacific:20101204T100000
+DTSTART;TZID=US/Pacific:20101204T120000
+DTEND;TZID=US/Pacific:20101204T130000
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=2.0:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T184815Z
+DTSTAMP:20101203T185027Z
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+SEQUENCE:6
+SUMMARY:Repeating 1
+TRANSP:OPAQUE
+END:VEVENT
+BEGIN:VEVENT
+UID:59E260E3-1644-4BDF-BBC6-6130B0C3A520
+RECURRENCE-ID;TZID=US/Pacific:20101211T100000
+DTSTART;TZID=US/Pacific:20101211T120000
+DTEND;TZID=US/Pacific:20101211T130000
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=2.0:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T184815Z
+DTSTAMP:20101203T185038Z
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+SEQUENCE:6
+SUMMARY:Repeating 1
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+REPEATING_1_ICS_AFTER = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:59E260E3-1644-4BDF-BBC6-6130B0C3A520
+DTSTART;TZID=US/Pacific:20101130T100000
+DTEND;TZID=US/Pacific:20101130T110000
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=1.2:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T184815Z
+DTSTAMP:20101203T185019Z
+EXDATE;TZID=US/Pacific:20101203T100000
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+RRULE:FREQ=DAILY;UNTIL=20101206T120000Z
+SEQUENCE:4
+SUMMARY:Repeating 1
+TRANSP:OPAQUE
+END:VEVENT
+BEGIN:VEVENT
+UID:59E260E3-1644-4BDF-BBC6-6130B0C3A520
+RECURRENCE-ID;TZID=US/Pacific:20101204T100000
+DTSTART;TZID=US/Pacific:20101204T120000
+DTEND;TZID=US/Pacific:20101204T130000
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=2.0:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T184815Z
+DTSTAMP:20101203T185027Z
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+SEQUENCE:6
+SUMMARY:Repeating 1
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# This event is similar to the "Repeating 1" event above except this one is an
+# all-day event.
+
+REPEATING_2_ICS_BEFORE = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+BEGIN:VEVENT
+UID:53BA0EA4-05B1-4E89-BD1E-8397F071FD6A
+DTSTART;VALUE=DATE:20101130
+DTEND;VALUE=DATE:20101201
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=1.2:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T203510Z
+DTSTAMP:20101203T203603Z
+EXDATE;VALUE=DATE:20101203
+EXDATE;VALUE=DATE:20101209
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+RRULE:FREQ=DAILY;COUNT=400
+SEQUENCE:5
+SUMMARY:All Day
+TRANSP:TRANSPARENT
+END:VEVENT
+BEGIN:VEVENT
+UID:53BA0EA4-05B1-4E89-BD1E-8397F071FD6A
+RECURRENCE-ID;VALUE=DATE:20101211
+DTSTART;VALUE=DATE:20101211
+DTEND;VALUE=DATE:20101212
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=1.2:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+CREATED:20101203T203510Z
+DTSTAMP:20101203T203631Z
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+SEQUENCE:6
+SUMMARY:Modified Title
+TRANSP:TRANSPARENT
+END:VEVENT
+BEGIN:VEVENT
+UID:53BA0EA4-05B1-4E89-BD1E-8397F071FD6A
+RECURRENCE-ID;VALUE=DATE:20101204
+DTSTART;VALUE=DATE:20101204
+DTEND;VALUE=DATE:20101205
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=1.2:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T203510Z
+DTSTAMP:20101203T203618Z
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+SEQUENCE:7
+SUMMARY:Modified Title
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+REPEATING_2_ICS_AFTER = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+BEGIN:VEVENT
+UID:53BA0EA4-05B1-4E89-BD1E-8397F071FD6A
+DTSTART;VALUE=DATE:20101130
+DTEND;VALUE=DATE:20101201
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=1.2:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T203510Z
+DTSTAMP:20101203T203603Z
+EXDATE;VALUE=DATE:20101203
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+RRULE:FREQ=DAILY;UNTIL=20101206
+SEQUENCE:5
+SUMMARY:All Day
+TRANSP:TRANSPARENT
+END:VEVENT
+BEGIN:VEVENT
+UID:53BA0EA4-05B1-4E89-BD1E-8397F071FD6A
+RECURRENCE-ID;VALUE=DATE:20101204
+DTSTART;VALUE=DATE:20101204
+DTEND;VALUE=DATE:20101205
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICI
+ PANT;SCHEDULE-STATUS=1.2:urn:uuid:3FF02D2B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:0F1684
+ 77-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T203510Z
+DTSTAMP:20101203T203618Z
+ORGANIZER;CN=Purge Test:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+SEQUENCE:7
+SUMMARY:Modified Title
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
+# This event is on Dec 8 (in the future compared to Dec 6) and should be flagged
+# as needing to be deleted
+
+FUTURE_EVENT_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:97B243D3-D252-4034-AA6D-9AE34E063991
+DTSTART;TZID=US/Pacific:20101208T091500
+DTEND;TZID=US/Pacific:20101208T101500
+CREATED:20101203T172929Z
+DTSTAMP:20101203T172932Z
+SEQUENCE:2
+SUMMARY:Future event single
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+REPEATING_NON_MEETING_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:4E4D0C8C-6546-4777-9BF5-AD629C05E7D5
+DTSTART;TZID=US/Pacific:20101130T110000
+DTEND;TZID=US/Pacific:20101130T120000
+CREATED:20101203T204353Z
+DTSTAMP:20101203T204409Z
+RRULE:FREQ=DAILY;COUNT=400
+SEQUENCE:3
+SUMMARY:Repeating non meeting
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+REPEATING_ATTENDEE_MEETING_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:111A679F-EF8E-4CA5-9262-7C805E2C184D
+DTSTART;TZID=US/Pacific:20101130T120000
+DTEND;TZID=US/Pacific:20101130T130000
+ATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:3FF02D2
+ B-07A3-4420-8570-7B7C7D07F08A
+ATTENDEE;CN=Purge Test;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=REQ-PARTIC
+ IPANT:urn:uuid:0F168477-CF3D-45D3-AE60-9875EA02C4D1
+CREATED:20101203T204908Z
+DTSTAMP:20101203T204927Z
+ORGANIZER;CN=Test User;SCHEDULE-STATUS=1.2:urn:uuid:3FF02D2B-07A3-4420-857
+ 0-7B7C7D07F08A
+RRULE:FREQ=DAILY;COUNT=400
+SEQUENCE:4
+SUMMARY:As an attendee
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")

Modified: CalendarServer/trunk/setup.py
===================================================================
--- CalendarServer/trunk/setup.py	2010-12-03 21:49:42 UTC (rev 6670)
+++ CalendarServer/trunk/setup.py	2010-12-03 22:06:02 UTC (rev 6671)
@@ -121,6 +121,7 @@
                              "bin/calendarserver_manage_principals",
                              "bin/calendarserver_command_gateway",
                              "bin/calendarserver_purge_events",
+                             "bin/calendarserver_purge_principals",
                              "bin/calendarserver_migrate_resources",
                            ],
         data_files       = [ ("caldavd", ["conf/caldavd.plist"]), ],

Modified: CalendarServer/trunk/support/Makefile.Apple
===================================================================
--- CalendarServer/trunk/support/Makefile.Apple	2010-12-03 21:49:42 UTC (rev 6670)
+++ CalendarServer/trunk/support/Makefile.Apple	2010-12-03 22:06:02 UTC (rev 6671)
@@ -86,6 +86,7 @@
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_migrate_resources.8" "$(DSTROOT)$(MANDIR)/man8"
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_command_gateway.8"   "$(DSTROOT)$(MANDIR)/man8"
 	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_purge_events.8"   "$(DSTROOT)$(MANDIR)/man8"
+	$(_v) $(INSTALL_FILE) "$(Sources)/doc/calendarserver_purge_principals.8"   "$(DSTROOT)$(MANDIR)/man8"
 	$(_v) gzip -9 -f "$(DSTROOT)$(MANDIR)/man8/"*.[0-9]
 	@echo "Installing launchd config..."
 	$(_v) $(INSTALL_DIRECTORY) "$(DSTROOT)$(NSLOCALDIR)/$(NSLIBRARYSUBDIR)/Server/Calendar and Contacts"

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-12-03 21:49:42 UTC (rev 6670)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-12-03 22:06:02 UTC (rev 6671)
@@ -617,6 +617,10 @@
     # OpenDirectory.framework
     "OpenDirectoryModule": "calendarserver.platform.darwin.od.opendirectory",
 
+    # Used in the command line utilities to specify which service class to
+    # use to carry out work.
+    "UtilityServiceClass": "",
+
     "Includes": [],     # Other plists to parse after this one
 }
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101203/a7b9d951/attachment-0001.html>


More information about the calendarserver-changes mailing list