[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