[CalendarServer-changes] [6588] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Nov 10 12:01:32 PST 2010


Revision: 6588
          http://trac.macosforge.org/projects/calendarserver/changeset/6588
Author:   cdaboo at apple.com
Date:     2010-11-10 12:01:28 -0800 (Wed, 10 Nov 2010)
Log Message:
-----------
Put schedule-tag property into the calendar object table rather than dead property table.

Modified Paths:
--------------
    CalendarServer/trunk/contrib/tools/LO_DB_upgrade.py
    CalendarServer/trunk/twistedcaldav/method/get.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql
    CalendarServer/trunk/txdav/common/datastore/sql_tables.py

Modified: CalendarServer/trunk/contrib/tools/LO_DB_upgrade.py
===================================================================
--- CalendarServer/trunk/contrib/tools/LO_DB_upgrade.py	2010-11-10 19:37:33 UTC (rev 6587)
+++ CalendarServer/trunk/contrib/tools/LO_DB_upgrade.py	2010-11-10 20:01:28 UTC (rev 6588)
@@ -14,14 +14,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-from txdav.base.propertystore.base import PropertyName
+
+from twext.web2.dav.element.parser import WebDAVDocument
+from twext.web2.dav.resource import TwistedQuotaUsedProperty
+from twistedcaldav.caldavxml import ScheduleTag
 from twistedcaldav.customxml import TwistedCalendarHasPrivateCommentsProperty,\
     TwistedCalendarAccessProperty, TwistedSchedulingObjectResource,\
     TwistedScheduleMatchETags
+from twistedcaldav.ical import Component
+from txdav.base.propertystore.base import PropertyName
 import sys
-from twext.web2.dav.element.parser import WebDAVDocument
-from twistedcaldav.ical import Component
-from twext.web2.dav.resource import TwistedQuotaUsedProperty
 
 """
 Tool to manage schema upgrade of SQL database during internal development phase as we don't have
@@ -71,6 +73,8 @@
             add column
               SCHEDULE_OBJECT  boolean default false not null,
             add column
+              SCHEDULE_TAG     varchar(36)  default null,
+            add column
               SCHEDULE_ETAGS   text    default null,
             add column
               PRIVATE_COMMENTS boolean default false not null;
@@ -132,6 +136,20 @@
     removeProperty(TwistedSchedulingObjectResource)
     db.commit()
     
+    # ScheduleTag - copy string value into column.
+    print "Move ScheduleTag"
+    for row in rowsForProperty(ScheduleTag):
+        resource_id, value = row
+        prop = WebDAVDocument.fromString(value).root_element
+        query(db, """
+            update CALENDAR_OBJECT
+            set SCHEDULE_TAG = %s
+            where RESOURCE_ID = %s
+        """, (str(prop), resource_id,)
+        )
+    removeProperty(ScheduleTag)
+    db.commit()
+
     # TwistedScheduleMatchETags - copy string-list value into column.
     print "Move TwistedScheduleMatchETags"
     for row in rowsForProperty(TwistedScheduleMatchETags):

Modified: CalendarServer/trunk/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/get.py	2010-11-10 19:37:33 UTC (rev 6587)
+++ CalendarServer/trunk/twistedcaldav/method/get.py	2010-11-10 20:01:28 UTC (rev 6588)
@@ -30,7 +30,6 @@
 from twext.web2.http_headers import MimeType
 from twext.web2.stream import MemoryStream
 
-from twistedcaldav.caldavxml import ScheduleTag
 from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.datafilters.privateevents import PrivateEventFilter
 from twistedcaldav.resource import isPseudoCalendarCollectionResource,\
@@ -84,10 +83,8 @@
                 response.headers.setHeader("content-type", MimeType.fromString("text/calendar; charset=utf-8"))
         
                 # Add Schedule-Tag header if property is present
-                if self.hasDeadProperty(ScheduleTag):
-                    scheduletag = self.readDeadProperty(ScheduleTag)
-                    if scheduletag:
-                        response.headers.setHeader("Schedule-Tag", str(scheduletag))
+                if self.scheduleTag:
+                    response.headers.setHeader("Schedule-Tag", self.scheduleTag)
             
                 returnValue(response)
 

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-11-10 19:37:33 UTC (rev 6587)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2010-11-10 20:01:28 UTC (rev 6588)
@@ -48,7 +48,7 @@
 from txdav.common.icommondatastore import ReservationError
 
 from twistedcaldav.config import config
-from twistedcaldav.caldavxml import ScheduleTag, NoUIDConflict
+from twistedcaldav.caldavxml import NoUIDConflict
 from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
 from twistedcaldav.caldavxml import caldav_namespace, MaxAttendeesPerInstance
 from twistedcaldav.customxml import calendarserver_namespace
@@ -585,14 +585,6 @@
     @inlineCallbacks
     def doImplicitScheduling(self):
 
-        # Get any existing schedule-tag property on the resource
-        if self.destination.exists() and self.destination.hasDeadProperty(ScheduleTag):
-            self.scheduletag = self.destination.readDeadProperty(ScheduleTag)
-            if self.scheduletag:
-                self.scheduletag = str(self.scheduletag)
-        else:
-            self.scheduletag = None
-
         data_changed = False
         did_implicit_action = False
 
@@ -763,8 +755,8 @@
                     # to another Attendee's REPLY should leave the tag unchanged
                     change_scheduletag = not hasattr(self.request, "doing_attendee_refresh")
 
-            if change_scheduletag or self.scheduletag is None:
-                self.scheduletag = str(uuid.uuid4())
+            if change_scheduletag or not self.destination.scheduleTag:
+                self.destination.scheduleTag = str(uuid.uuid4())
 
 
             # Handle weak etag compatibility
@@ -781,6 +773,7 @@
             else:
                 self.destination.scheduleEtags = ()                
         else:
+            self.destination.scheduleTag = ""
             self.destination.scheduleEtags = ()                
 
 
@@ -790,12 +783,8 @@
 
         if self.isScheduleResource:
             # Add a response header
-            response.headers.setHeader("Schedule-Tag", self.scheduletag)                
+            response.headers.setHeader("Schedule-Tag", self.destination.scheduleTag)                
 
-            self.destination.writeDeadProperty(ScheduleTag.fromString(self.scheduletag))
-        else:
-            self.destination.removeDeadProperty(ScheduleTag)                
-
         returnValue(response)
 
     @inlineCallbacks

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-11-10 19:37:33 UTC (rev 6587)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-11-10 20:01:28 UTC (rev 6588)
@@ -358,6 +358,14 @@
                 caldavxml.SupportedCalendarData.qname(),
                 customxml.GETCTag.qname(),
             )
+            if config.MaximumAttachmentSize:
+                baseProperties += (
+                    caldavxml.MaxResourceSize.qname(),
+                )
+            if config.MaxAttendeesPerInstance:
+                baseProperties += (
+                    caldavxml.MaxAttendeesPerInstance.qname(),
+                )
 
         if self.isCalendarCollection():
             baseProperties += (
@@ -372,7 +380,16 @@
                 customxml.GETCTag.qname(),
                 customxml.PubSubXMPPPushKeyProperty.qname(),
             )
+            if config.MaximumAttachmentSize:
+                baseProperties += (
+                    carddavxml.MaxResourceSize.qname(),
+                )
 
+        if hasattr(self, "scheduleTag") and self.scheduleTag:
+            baseProperties += (
+                caldavxml.ScheduleTag.qname(),
+            )
+            
         if config.EnableSyncReport and (davxml.Report(SyncCollection(),) in self.supportedReports()):
             baseProperties += (davxml.SyncToken.qname(),)
             
@@ -422,12 +439,6 @@
                 return False
             else:
                 return True
-        elif qname in (
-            customxml.GETCTag.qname(),
-            caldavxml.MaxResourceSize.qname(),
-            caldavxml.MaxAttendeesPerInstance.qname(),
-        ):
-            return True
         else:
             return False
 
@@ -580,6 +591,13 @@
                     str(config.MaxAttendeesPerInstance)
                 ))
 
+        elif qname == caldavxml.ScheduleTag.qname():
+            # CalDAV-scheduling
+            if hasattr(self, "scheduleTag") and self.scheduleTag:
+                returnValue(caldavxml.ScheduleTag.fromString(
+                    self.scheduleTag
+                ))
+
         elif qname == caldavxml.ScheduleCalendarTransp.qname():
             # For backwards compatibility, if the property does not exist we need to create
             # it and default to the old free-busy-set value.
@@ -600,6 +618,13 @@
                 }),
             ))
 
+        elif qname == carddavxml.MaxResourceSize.qname():
+            # CardDAV, section 6.2.3
+            if config.MaximumAttachmentSize:
+                returnValue(carddavxml.MaxResourceSize.fromString(
+                    str(config.MaximumAttachmentSize)
+                ))
+
         elif qname == customxml.Invite.qname():
             if config.Sharing.Enabled and (
                 config.Sharing.Calendars.Enabled and self.isCalendarCollection() or 

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2010-11-10 19:37:33 UTC (rev 6587)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2010-11-10 20:01:28 UTC (rev 6588)
@@ -46,7 +46,7 @@
 )
 from twext.web2.stream import ProducerStream, readStream, MemoryStream
 
-from twistedcaldav.caldavxml import ScheduleTag, caldav_namespace
+from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
 from twistedcaldav.notifications import NotificationCollectionResource, \
     NotificationResource
@@ -1130,6 +1130,17 @@
 
     isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
 
+    def _get_scheduleTag(self):
+        return self._newStoreObject.scheduleTag if self._newStoreObject else self._metadata.get("scheduleTag", None)
+
+    def _set_scheduleTag(self, value):
+        if self._newStoreObject:
+            self._newStoreObject.scheduleTag = value
+        else:
+            self._metadata["scheduleTag"] = value
+
+    scheduleTag = property(_get_scheduleTag, _set_scheduleTag)
+
     def _get_scheduleEtags(self):
         return self._newStoreObject.scheduleEtags if self._newStoreObject else self._metadata.get("scheduleEtags", None)
 
@@ -1206,14 +1217,10 @@
         header = request.headers.getHeader("If-Schedule-Tag-Match")
         if header:
             # Do "precondition" test
-            matched = False
-            if self.hasDeadProperty(ScheduleTag):
-                scheduletag = self.readDeadProperty(ScheduleTag)
-                matched = (scheduletag == header)
-            if not matched:
+            if (self.scheduleTag != header):
                 log.debug(
                     "If-Schedule-Tag-Match: header value '%s' does not match resource value '%s'" %
-                    (header, scheduletag,))
+                    (header, self.scheduleTag,))
                 raise HTTPError(PRECONDITION_FAILED)
 
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-11-10 19:37:33 UTC (rev 6587)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2010-11-10 20:01:28 UTC (rev 6588)
@@ -46,9 +46,6 @@
 
 from twistedcaldav import caldavxml, customxml
 from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
-from twistedcaldav.customxml import TwistedCalendarAccessProperty,\
-    TwistedCalendarHasPrivateCommentsProperty, TwistedSchedulingObjectResource,\
-    TwistedScheduleMatchETags
 from twistedcaldav.sharing import InvitesDatabase
 
 from txdav.caldav.icalendarstore import IAttachment
@@ -237,6 +234,7 @@
             metadata = {}
         self.accessMode = metadata.get("accessMode", "")
         self.isScheduleObject = metadata.get("isScheduleObject", False)
+        self.scheduleTag = metadata.get("scheduleTag", "")
         self.scheduleEtags = metadata.get("scheduleEtags", "")
         self.hasPrivateComment = metadata.get("hasPrivateComment", False)
 
@@ -370,51 +368,63 @@
         return self.component().getOrganizer()
 
     def _get_accessMode(self):
-        return str(self.properties().get(PropertyName.fromElement(TwistedCalendarAccessProperty), ""))
+        return str(self.properties().get(PropertyName.fromElement(customxml.TwistedCalendarAccessProperty), ""))
 
     def _set_accessMode(self, value):
-        pname = PropertyName.fromElement(TwistedCalendarAccessProperty)
+        pname = PropertyName.fromElement(customxml.TwistedCalendarAccessProperty)
         if value:
-            self.properties()[pname] = TwistedCalendarAccessProperty(value)
+            self.properties()[pname] = customxml.TwistedCalendarAccessProperty(value)
         elif pname in self.properties():
             del self.properties()[pname]
 
     accessMode = property(_get_accessMode, _set_accessMode)
 
     def _get_isScheduleObject(self):
-        return str(self.properties().get(PropertyName.fromElement(TwistedSchedulingObjectResource), "false")) == "true"
+        return str(self.properties().get(PropertyName.fromElement(customxml.TwistedSchedulingObjectResource), "false")) == "true"
 
     def _set_isScheduleObject(self, value):
-        pname = PropertyName.fromElement(TwistedSchedulingObjectResource)
+        pname = PropertyName.fromElement(customxml.TwistedSchedulingObjectResource)
         if value:
-            self.properties()[pname] = TwistedSchedulingObjectResource.fromString("true" if value else "false")
+            self.properties()[pname] = customxml.TwistedSchedulingObjectResource.fromString("true" if value else "false")
         elif pname in self.properties():
             del self.properties()[pname]
 
     isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
 
+    def _get_scheduleTag(self):
+        return str(self.properties().get(PropertyName.fromElement(caldavxml.ScheduleTag), ""))
+
+    def _set_scheduleTag(self, value):
+        pname = PropertyName.fromElement(caldavxml.ScheduleTag)
+        if value:
+            self.properties()[pname] = caldavxml.ScheduleTag.fromString(value)
+        elif pname in self.properties():
+            del self.properties()[pname]
+
+    scheduleTag = property(_get_scheduleTag, _set_scheduleTag)
+
     def _get_scheduleEtags(self):
-        return tuple([str(etag) for etag in self.properties().get(PropertyName.fromElement(TwistedScheduleMatchETags), TwistedScheduleMatchETags()).children])
+        return tuple([str(etag) for etag in self.properties().get(PropertyName.fromElement(customxml.TwistedScheduleMatchETags), customxml.TwistedScheduleMatchETags()).children])
 
     def _set_scheduleEtags(self, value):
         if value:
             etags = [davxml.GETETag.fromString(etag) for etag in value]
-            self.properties()[PropertyName.fromElement(TwistedScheduleMatchETags)] = TwistedScheduleMatchETags(*etags)
+            self.properties()[PropertyName.fromElement(customxml.TwistedScheduleMatchETags)] = customxml.TwistedScheduleMatchETags(*etags)
         else:
             try:
-                del self.properties()[PropertyName.fromElement(TwistedScheduleMatchETags)]
+                del self.properties()[PropertyName.fromElement(customxml.TwistedScheduleMatchETags)]
             except KeyError:
                 pass
 
     scheduleEtags = property(_get_scheduleEtags, _set_scheduleEtags)
 
     def _get_hasPrivateComment(self):
-        return PropertyName.fromElement(TwistedCalendarHasPrivateCommentsProperty) in self.properties()
+        return PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty) in self.properties()
 
     def _set_hasPrivateComment(self, value):
-        pname = PropertyName.fromElement(TwistedCalendarHasPrivateCommentsProperty)
+        pname = PropertyName.fromElement(customxml.TwistedCalendarHasPrivateCommentsProperty)
         if value:
-            self.properties()[pname] = TwistedCalendarHasPrivateCommentsProperty()
+            self.properties()[pname] = customxml.TwistedCalendarHasPrivateCommentsProperty()
         elif pname in self.properties():
             del self.properties()[pname]
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-11-10 19:37:33 UTC (rev 6587)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2010-11-10 20:01:28 UTC (rev 6588)
@@ -248,6 +248,7 @@
             metadata = {}
         self.accessMode = metadata.get("accessMode", "")
         self.isScheduleObject = metadata.get("isScheduleObject", False)
+        self.scheduleTag = metadata.get("scheduleTag", "")
         self.scheduleEtags = metadata.get("scheduleEtags", "")
         self.hasPrivateComment = metadata.get("hasPrivateComment", False)
 
@@ -272,6 +273,7 @@
                   character_length(%(column_TEXT)s),
                   %(column_ACCESS)s,
                   %(column_SCHEDULE_OBJECT)s,
+                  %(column_SCHEDULE_TAG)s,
                   %(column_SCHEDULE_ETAGS)s,
                   %(column_PRIVATE_COMMENTS)s,
                   %(column_CREATED)s,
@@ -291,6 +293,7 @@
                   character_length(%(column_TEXT)s),
                   %(column_ACCESS)s,
                   %(column_SCHEDULE_OBJECT)s,
+                  %(column_SCHEDULE_TAG)s,
                   %(column_SCHEDULE_ETAGS)s,
                   %(column_PRIVATE_COMMENTS)s,
                   %(column_CREATED)s,
@@ -308,6 +311,7 @@
              self._size,
              self._access,
              self._schedule_object,
+             self._schedule_tag,
              self._schedule_etags,
              self._private_comments,
              self._created,
@@ -396,10 +400,10 @@
                 """
                 insert into CALENDAR_OBJECT
                 (CALENDAR_RESOURCE_ID, RESOURCE_NAME, ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE,
-                 ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, ACCESS, SCHEDULE_OBJECT, SCHEDULE_ETAGS,
-                 PRIVATE_COMMENTS, MD5)
+                 ATTACHMENTS_MODE, ORGANIZER, RECURRANCE_MAX, ACCESS, SCHEDULE_OBJECT, SCHEDULE_TAG,
+                 SCHEDULE_ETAGS, PRIVATE_COMMENTS, MD5)
                  values
-                (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
+                (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                 returning
                  RESOURCE_ID,
                  CREATED,
@@ -418,6 +422,7 @@
                     normalizeForIndex(instances.limit) if instances.limit else None,
                     self._access,
                     self._schedule_object,
+                    self._schedule_tag,
                     self._schedule_etags,
                     self._private_comments,
                     self._md5,
@@ -428,10 +433,10 @@
                 """
                 update CALENDAR_OBJECT set
                 (ICALENDAR_TEXT, ICALENDAR_UID, ICALENDAR_TYPE, ATTACHMENTS_MODE,
-                 ORGANIZER, RECURRANCE_MAX, ACCESS, SCHEDULE_OBJECT, SCHEDULE_ETAGS,
-                 PRIVATE_COMMENTS, MD5, MODIFIED)
+                 ORGANIZER, RECURRANCE_MAX, ACCESS, SCHEDULE_OBJECT, SCHEDULE_TAG,
+                 SCHEDULE_ETAGS, PRIVATE_COMMENTS, MD5, MODIFIED)
                  =
-                (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
+                (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, timezone('UTC', CURRENT_TIMESTAMP))
                 where RESOURCE_ID = %s
                 returning MODIFIED
                 """,
@@ -447,6 +452,7 @@
                     normalizeForIndex(instances.limit) if instances.limit else None,
                     self._access,
                     self._schedule_object,
+                    self._schedule_tag,
                     self._schedule_etags,
                     self._private_comments,
                     self._md5,
@@ -579,6 +585,14 @@
 
     isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
 
+    def _get_scheduleTag(self):
+        return self._schedule_tag
+
+    def _set_scheduleTag(self, value):
+        self._schedule_tag = value
+
+    scheduleTag = property(_get_scheduleTag, _set_scheduleTag)
+
     def _get_scheduleEtags(self):
         return tuple(self._schedule_etags.split(",")) if self._schedule_etags else ()
 
@@ -675,8 +689,6 @@
             (
             ),
             (
-                PropertyName.fromElement(caldavxml.ScheduleTag),
-                PropertyName.fromElement(customxml.TwistedScheduleMatchETags),
                 PropertyName.fromElement(caldavxml.Originator),
                 PropertyName.fromElement(caldavxml.Recipient),
                 PropertyName.fromElement(customxml.ScheduleChanges),

Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql	2010-11-10 19:37:33 UTC (rev 6587)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql	2010-11-10 20:01:28 UTC (rev 6588)
@@ -65,7 +65,7 @@
   NOTIFICATION_UID              varchar(255) not null,
   XML_TYPE                      varchar      not null,
   XML_DATA                      varchar      not null,
-  MD5                           char(32)      not null,
+  MD5                           char(32)     not null,
   CREATED                       timestamp default timezone('UTC', CURRENT_TIMESTAMP),
   MODIFIED                      timestamp default timezone('UTC', CURRENT_TIMESTAMP),
 
@@ -139,6 +139,7 @@
   RECURRANCE_MAX       date,        -- maximum date that recurrences have been expanded to.
   ACCESS               integer      default 0 not null,
   SCHEDULE_OBJECT      boolean      default false not null,
+  SCHEDULE_TAG         varchar(36)  default null,
   SCHEDULE_ETAGS       text         default null,
   PRIVATE_COMMENTS     boolean      default false not null,
   MD5                  char(32)     not null,

Modified: CalendarServer/trunk/txdav/common/datastore/sql_tables.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_tables.py	2010-11-10 19:37:33 UTC (rev 6587)
+++ CalendarServer/trunk/txdav/common/datastore/sql_tables.py	2010-11-10 20:01:28 UTC (rev 6588)
@@ -127,6 +127,7 @@
     "column_UID"                : "ICALENDAR_UID",
     "column_ACCESS"             : "ACCESS",
     "column_SCHEDULE_OBJECT"    : "SCHEDULE_OBJECT",
+    "column_SCHEDULE_TAG"       : "SCHEDULE_TAG",
     "column_SCHEDULE_ETAGS"     : "SCHEDULE_ETAGS",
     "column_PRIVATE_COMMENTS"   : "PRIVATE_COMMENTS",
     "column_MD5"                : "MD5",
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20101110/a4533687/attachment-0001.html>


More information about the calendarserver-changes mailing list