[CalendarServer-changes] [3711] CalendarServer/branches/users/sagen/migration-3709

source_changes at macosforge.org source_changes at macosforge.org
Mon Feb 23 14:57:06 PST 2009


Revision: 3711
          http://trac.macosforge.org/projects/calendarserver/changeset/3711
Author:   sagen at apple.com
Date:     2009-02-23 14:57:05 -0800 (Mon, 23 Feb 2009)
Log Message:
-----------
Bringing the migration branch up to trunk rev 3709

Revision Links:
--------------
    http://trac.macosforge.org/projects/calendarserver/changeset/3709

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/migration-3709/calendarserver/tap/caldav.py
    CalendarServer/branches/users/sagen/migration-3709/calendarserver/tools/util.py
    CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/ical.py
    CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/resource.py
    CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/upgrade.py

Added Paths:
-----------
    CalendarServer/branches/users/sagen/migration-3709/calendarserver/tools/migrate.py

Modified: CalendarServer/branches/users/sagen/migration-3709/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/sagen/migration-3709/calendarserver/tap/caldav.py	2009-02-23 22:55:40 UTC (rev 3710)
+++ CalendarServer/branches/users/sagen/migration-3709/calendarserver/tap/caldav.py	2009-02-23 22:57:05 UTC (rev 3711)
@@ -73,7 +73,7 @@
 from twistedcaldav.static import TimezoneServiceFile
 from twistedcaldav.mail import IMIPInboxResource
 from twistedcaldav.timezones import TimezoneCache
-from twistedcaldav.upgrade import UpgradeTheServer
+from twistedcaldav.upgrade import upgradeData
 from twistedcaldav.pdmonster import PDClientAddressWrapper
 from twistedcaldav import memcachepool
 from twistedcaldav.notify import installNotificationClient
@@ -327,8 +327,6 @@
 
     def makeService(self, options):
 
-        # Now do any on disk upgrades we might need.
-        UpgradeTheServer.doUpgrade()
 
         serviceMethod = getattr(self, "makeService_%s" % (config.ProcessType,), None)
 
@@ -339,6 +337,16 @@
                 % (config.ProcessType,)
             )
         else:
+
+            if config.ProcessType in ('Combined', 'Single'):
+
+                # Process localization string files
+                processLocalizationFiles(config.Localization)
+
+                # Now do any on disk upgrades we might need.
+                upgradeData(config)
+
+
             service = serviceMethod(options)
 
             #

Copied: CalendarServer/branches/users/sagen/migration-3709/calendarserver/tools/migrate.py (from rev 3710, CalendarServer/branches/users/sagen/migration-3686/calendarserver/tools/migrate.py)
===================================================================
--- CalendarServer/branches/users/sagen/migration-3709/calendarserver/tools/migrate.py	                        (rev 0)
+++ CalendarServer/branches/users/sagen/migration-3709/calendarserver/tools/migrate.py	2009-02-23 22:57:05 UTC (rev 3711)
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+This tool migrates existing calendar data from any previous calendar server
+version to the current version.
+
+This tool requires access to the calendar server's configuration and
+data storage; it does not operate by talking to the server via the
+network.
+"""
+
+import os
+import sys
+from getopt import getopt, GetoptError
+from os.path import dirname, abspath
+
+from twistedcaldav.upgrade import upgradeData
+from calendarserver.tools.util import loadConfig
+
+def usage(e=None):
+    if e:
+        print e
+        print ""
+
+    name = os.path.basename(sys.argv[0])
+    print "usage: %s [options]" % (name,)
+    print ""
+    print "Migrate calendar data to current version"
+    print __doc__
+    print "options:"
+    print "  -h --help: print this help and exit"
+    print "  -f --config: Specify caldavd.plist configuration path"
+
+    if e:
+        sys.exit(64)
+    else:
+        sys.exit(0)
+
+def main():
+    try:
+        (optargs, args) = getopt(
+            sys.argv[1:], "hf:", [
+                "config=",
+                "help",
+            ],
+        )
+    except GetoptError, e:
+        usage(e)
+
+    configFileName = None
+
+
+    for opt, arg in optargs:
+        if opt in ("-h", "--help"):
+            usage()
+
+        elif opt in ("-f", "--config"):
+            configFileName = arg
+
+    if args:
+        usage("Too many arguments: %s" % (" ".join(args),))
+
+    config = loadConfig(configFileName)
+    upgradeData(config)
+
+if __name__ == "__main__":
+    main()

Modified: CalendarServer/branches/users/sagen/migration-3709/calendarserver/tools/util.py
===================================================================
--- CalendarServer/branches/users/sagen/migration-3709/calendarserver/tools/util.py	2009-02-23 22:55:40 UTC (rev 3710)
+++ CalendarServer/branches/users/sagen/migration-3709/calendarserver/tools/util.py	2009-02-23 22:57:05 UTC (rev 3711)
@@ -44,7 +44,7 @@
     BaseDirectoryService = namedClass(config.DirectoryService.type)
 
     class MyDirectoryService (BaseDirectoryService):
-        def principalCollection(self):
+        def getPrincipalCollection(self):
             if not hasattr(self, "_principalCollection"):
                 #
                 # Instantiating a CalendarHomeProvisioningResource with a directory
@@ -60,12 +60,26 @@
 
             return self._principalCollection
 
+        def setPrincipalCollection(self, coll):
+            # See principal.py line 237:  self.directory.principalCollection = self
+            pass
+
+        principalCollection = property(getPrincipalCollection, setPrincipalCollection)
+
         def calendarHomeForShortName(self, recordType, shortName):
-            principal = self.principalCollection().principalForShortName(recordType, shortName)
+            principal = self.principalCollection.principalForShortName(recordType, shortName)
             if principal:
                 return principal.calendarHome()
             return None
 
+        def principalForCalendarUserAddress(self, cua):
+            record = self.recordWithCalendarUserAddress(cua)
+            if record is not None:
+                return self.principalCollection.principalForUID(record.uid)
+            else:
+                return None
+
+
     return MyDirectoryService(**config.DirectoryService.params)
 
 class DummyDirectoryService (DirectoryService):

Modified: CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/ical.py	2009-02-23 22:55:40 UTC (rev 3710)
+++ CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/ical.py	2009-02-23 22:57:05 UTC (rev 3711)
@@ -45,6 +45,7 @@
 import cStringIO as StringIO
 import datetime
 import heapq
+import itertools
 
 log = Logger()
 
@@ -1808,6 +1809,66 @@
                     if dataValue.find(dropboxPrefix) != -1:
                         component.removeProperty(attachment)
 
+    def normalizeCalendarUserAddresses(self, lookupFunction):
+        """
+        Do the ORGANIZER/ATTENDEE property normalization.
+
+        @param lookupFunction: function returning full name, guid, CUAs for a given CUA
+        @type lookupFunction: L{Function}
+        """
+        for component in self.subcomponents():
+            if component.name() == "VTIMEZONE":
+                continue
+            for prop in itertools.chain(
+                component.properties("ORGANIZER"),
+                component.properties("ATTENDEE")
+            ):
+
+                # Check that we can lookup this calendar user address - if not
+                # we cannot do anything with it
+                cuaddr = normalizeCUAddr(prop.value())
+                name, guid, cuaddrs = lookupFunction(cuaddr)
+                if guid is None:
+                    continue
+
+                # Always re-write value to urn:uuid
+                prop.setValue("urn:uuid:%s" % (guid,))
+
+                # Always re-write the CN parameter
+                if name:
+                    prop.params()["CN"] = [name,]
+                else:
+                    try:
+                        del prop.params()["CN"]
+                    except KeyError:
+                        pass
+
+                # Re-write the X-CALENDARSERVER-EMAIL if its value no longer
+                # matches
+                oldemail = prop.params().get("X-CALENDARSERVER-EMAIL",
+                    (None,))[0]
+                if oldemail:
+                    oldemail = "mailto:%s" % (oldemail,)
+                if oldemail is None or oldemail not in cuaddrs:
+                    if cuaddr.startswith("mailto:") and cuaddr in cuaddrs:
+                        email = cuaddr[7:]
+                    else:
+                        for addr in cuaddrs:
+                            if addr.startswith("mailto:"):
+                                email = addr[7:]
+                                break
+                        else:
+                            email = None
+
+                    if email:
+                        prop.params()["X-CALENDARSERVER-EMAIL"] = [email,]
+                    else:
+                        try:
+                            del prop.params()["X-CALENDARSERVER-EMAIL"]
+                        except KeyError:
+                            pass
+
+        
 ##
 # Dates and date-times
 ##

Modified: CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/resource.py	2009-02-23 22:55:40 UTC (rev 3710)
+++ CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/resource.py	2009-02-23 22:57:05 UTC (rev 3711)
@@ -652,64 +652,17 @@
         @param ical: calendar object to normalize.
         @type ical: L{Component}
         """
-        
-        def normalizeCalendarUserAddress(prop):
-            """
-            Do the ORGANIZER/ATTENDEE property normalization.
 
-            @param prop: organizer/attendee property
-            @type prop: L{Property}
-            """
-            
-            # Check that we have a principal for this calendar user address - if not we
-            # cannot do anything with it
-            cuaddr = normalizeCUAddr(prop.value())
+        def lookupFunction(cuaddr):
             principal = self.principalForCalendarUserAddress(cuaddr)
             if principal is None:
-                return
-
-            # Always re-write value to urn:uuid
-            prop.setValue("urn:uuid:%s" % (principal.record.guid,))
-
-            # Always re-write the CN parameter
-            if principal.record.fullName:
-                prop.params()["CN"] = [principal.record.fullName,]
+                return (None, None, None)
             else:
-                try:
-                    del prop.params()["CN"]
-                except KeyError:
-                    pass
+                return (principal.record.fullName, principal.record.guid,
+                    principal.record.calendarUserAddresses)
 
-            # Re-write the X-CALENDARSERVER-EMAIL if its value no longer matches
-            oldemail = prop.params().get("X-CALENDARSERVER-EMAIL", (None,))[0]
-            if oldemail:
-                oldemail = "mailto:%s" % (oldemail,)
-            if oldemail is None or oldemail not in principal.record.calendarUserAddresses:
-                if cuaddr.startswith("mailto:") and cuaddr in principal.record.calendarUserAddresses:
-                    email = cuaddr[7:]
-                else:
-                    for addr in principal.record.calendarUserAddresses:
-                        if addr.startswith("mailto:"):
-                            email = addr[7:]
-                            break
-                    else:
-                        email = None
-                        
-                if email:
-                    prop.params()["X-CALENDARSERVER-EMAIL"] = [email,]
-                else:
-                    try:
-                        del prop.params()["X-CALENDARSERVER-EMAIL"]
-                    except KeyError:
-                        pass
+        ical.normalizeCalendarUserAddresses(lookupFunction)
 
-        for component in ical.subcomponents():
-            if component.name() != "VTIMEZONE":
-                for prop in itertools.chain(
-                    component.properties("ORGANIZER"),
-                    component.properties("ATTENDEE")
-                ):
-                    normalizeCalendarUserAddress(prop)
 
     def principalForCalendarUserAddress(self, address):
         for principalCollection in self.principalCollections():

Modified: CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/upgrade.py	2009-02-23 22:55:40 UTC (rev 3710)
+++ CalendarServer/branches/users/sagen/migration-3709/twistedcaldav/upgrade.py	2009-02-23 22:57:05 UTC (rev 3711)
@@ -14,72 +14,366 @@
 # limitations under the License.
 ##
 
+from __future__ import with_statement
+
 from twisted.web2.dav.fileop import rmdir
-from twistedcaldav.config import config
+from twisted.web2.dav import davxml
+from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyDatabase
 from twistedcaldav.log import Logger
-import os
+from twistedcaldav.ical import Component
+from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
+from twistedcaldav import caldavxml
+from calendarserver.tools.util import getDirectory, dummyDirectoryRecord
+import xattr, itertools, os, zlib
+from zlib import compress, decompress
+from cPickle import loads as unpickle, PicklingError, UnpicklingError
 
+
 log = Logger()
 
-class UpgradeTheServer(object):
-    
-    @staticmethod
-    def doUpgrade():
-        
-        UpgradeTheServer._doPrincipalCollectionInMemoryUpgrade()
-    
-    @staticmethod
-    def _doPrincipalCollectionInMemoryUpgrade():
-        
-        # Look for the /principals/ directory on disk
-        old_principals = os.path.join(config.DocumentRoot, "principals")
-        if os.path.exists(old_principals):
-            # First move the proxy database and rename it
-            UpgradeTheServer._doProxyDatabaseMoveUpgrade()
-        
-            # Now delete the on disk representation of principals
-            rmdir(old_principals)
-            log.info(
-                "Removed the old principal directory at '%s'."
-                % (old_principals,)
-            )
 
-    @staticmethod
-    def _doProxyDatabaseMoveUpgrade():
-        
+
+#
+# upgrade_to_1
+#
+# Upconverts data from any calendar server version prior to data format 1
+#
+
+def upgrade_to_1(config):
+
+
+    def fixBadQuotes(data):
+        if (
+            data.find('\\"') != -1 or
+            data.find('\\\r\n "') != -1 or
+            data.find('\r\n \r\n "') != -1
+        ):
+            # Fix by continuously replacing \" with " until no more
+            # replacements occur
+            while True:
+                newData = data.replace('\\"', '"').replace('\\\r\n "', '\r\n "').replace('\r\n \r\n "', '\r\n "')
+                if newData == data:
+                    break
+                else:
+                    data = newData
+
+            return data, True
+        else:
+            return data, False
+
+
+
+    def normalizeCUAddrs(data, directory):
+        cal = Component.fromString(data)
+
+        def lookupFunction(cuaddr):
+            try:
+                principal = directory.principalForCalendarUserAddress(cuaddr)
+            except Exception, e:
+                log.debug("Lookup of %s failed: %s" % (cuaddr, e))
+                principal = None
+
+            if principal is None:
+                return (None, None, None)
+            else:
+                return (principal.record.fullName, principal.record.guid,
+                    principal.record.calendarUserAddresses)
+
+        cal.normalizeCalendarUserAddresses(lookupFunction)
+
+        newData = str(cal)
+        return newData, not newData == data
+
+
+    def upgradeCalendarCollection(calPath, directory):
+
+        for resource in os.listdir(calPath):
+
+            if resource.startswith("."):
+                continue
+
+            resPath = os.path.join(calPath, resource)
+            resPathTmp = "%s.tmp" % resPath
+
+            log.info("Processing: %s" % (resPath,))
+            with open(resPath) as res:
+                data = res.read()
+
+                try:
+                    data, fixed = fixBadQuotes(data)
+                    if fixed:
+                        log.info("Fixing bad quotes in %s" % (oldRes,))
+                except Exception, e:
+                    log.error("Error while fixing bad quotes in %s: %s" %
+                        (oldRes, e))
+                    raise
+
+                try:
+                    data, fixed = normalizeCUAddrs(data, directory)
+                    if fixed:
+                        log.info("Normalized CUAddrs in %s" % (oldRes,))
+                except Exception, e:
+                    log.error("Error while normalizing %s: %s" %
+                        (oldRes, e))
+                    raise
+
+            # Write to a new file, then rename it over the old one
+            with open(resPathTmp, "w") as res:
+                res.write(data)
+            os.rename(resPathTmp, resPath)
+
+
+        # Remove the ctag xattr from the calendar collection
+        for attr, value in xattr.xattr(calPath).iteritems():
+            if attr == "WebDAV:{http:%2F%2Fcalendarserver.org%2Fns%2F}getctag":
+                xattr.removexattr(calPath, attr, value)
+
+
+    def upgradeCalendarHome(homePath, directory):
+
+        log.info("Upgrading calendar home: %s" % (homePath,))
+
+        for cal in os.listdir(homePath):
+            calPath = os.path.join(homePath, cal)
+            log.info("Upgrading calendar: %s" % (calPath,))
+            upgradeCalendarCollection(calPath, directory)
+
+            # Change the calendar-free-busy-set xattrs of the inbox to the
+            # __uids__/<guid> form
+            if cal == "inbox":
+                for attr, value in xattr.xattr(calPath).iteritems():
+                    if attr == "WebDAV:{urn:ietf:params:xml:ns:caldav}calendar-free-busy-set":
+                        value = updateFreeBusySet(value, directory)
+                        if value is not None:
+                            # Need to write the xattr back to disk
+                            xattr.setxattr(calPath, attr, value)
+
+
+
+
+    def doProxyDatabaseMoveUpgrade(config):
+
         # See if the old DB is present
-        old_db_path = os.path.join(config.DocumentRoot, "principals", CalendarUserProxyDatabase.dbOldFilename)
-        if not os.path.exists(old_db_path):
+        oldDbPath = os.path.join(config.DocumentRoot, "principals",
+            CalendarUserProxyDatabase.dbOldFilename)
+        if not os.path.exists(oldDbPath):
             # Nothing to be done
             return
-        
+
         # See if the new one is already present
-        new_db_path = os.path.join(config.DataRoot, CalendarUserProxyDatabase.dbFilename)
-        if os.path.exists(new_db_path):
-            # We have a problem - both the old and new ones exist. Stop the server from starting
-            # up and alert the admin to this condition
+        newDbPath = os.path.join(config.DataRoot,
+            CalendarUserProxyDatabase.dbFilename)
+        if os.path.exists(newDbPath):
+            # We have a problem - both the old and new ones exist. Stop the server
+            # from starting up and alert the admin to this condition
             raise UpgradeError(
                 "Upgrade Error: unable to move the old calendar user proxy database at '%s' to '%s' because the new database already exists."
-                % (old_db_path, new_db_path,)
+                % (oldDbPath, newDbPath,)
             )
-        
+
         # Now move the old one to the new location
         try:
-            os.rename(old_db_path, new_db_path)
+            if not os.path.exists(config.DataRoot):
+                os.makedirs(config.DataRoot)
+            os.rename(oldDbPath, newDbPath)
         except Exception, e:
             raise UpgradeError(
                 "Upgrade Error: unable to move the old calendar user proxy database at '%s' to '%s' due to %s."
-                % (old_db_path, new_db_path, str(e))
+                % (oldDbPath, newDbPath, str(e))
             )
-            
+
         log.info(
             "Moved the calendar user proxy database from '%s' to '%s'."
-            % (old_db_path, new_db_path,)
+            % (oldDbPath, newDbPath,)
         )
 
+
+    def moveCalendarHome(oldHome, newHome):
+        if os.path.exists(newHome):
+            # Both old and new homes exist; stop immediately to let the
+            # administrator fix it
+            raise UpgradeError(
+                "Upgrade Error: calendar home is in two places: %s and %s.  Please remove one of them and restart calendar server."
+                % (oldHome, newHome)
+            )
+
+
+
+    directory = getDirectory()
+    docRoot = config.DocumentRoot
+
+    if os.path.exists(docRoot):
+
+        # Look for the /principals/ directory on disk
+        oldPrincipals = os.path.join(docRoot, "principals")
+        if os.path.exists(oldPrincipals):
+            # First move the proxy database and rename it
+            doProxyDatabaseMoveUpgrade(config)
+
+            # Now delete the on disk representation of principals
+            rmdir(oldPrincipals)
+            log.info(
+                "Removed the old principal directory at '%s'."
+                % (oldPrincipals,)
+            )
+
+        calRoot = os.path.join(docRoot, "calendars")
+        if os.path.exists(calRoot):
+
+            uidHomes = os.path.join(calRoot, "__uids__")
+
+            # Move calendar homes to new location:
+
+            if os.path.exists(uidHomes):
+                for home in os.listdir(uidHomes):
+
+                    # MOR: This assumes no UID is going to be 2 chars or less
+                    if len(home) <= 2:
+                        continue
+
+                    oldHome = os.path.join(uidHomes, home)
+                    newHome = os.path.join(uidHomes, home[0:2], home[2:4], home)
+                    moveCalendarHome(oldHome, newHome)
+
+            else:
+                os.mkdir(uidHomes)
+
+            for recordType, dirName in (
+                (DirectoryService.recordType_users, "users"),
+                (DirectoryService.recordType_groups, "groups"),
+                (DirectoryService.recordType_locations, "locations"),
+                (DirectoryService.recordType_resources, "resources"),
+            ):
+                dirPath = os.path.join(calRoot, dirName)
+                if os.path.exists(dirPath):
+                    for shortName in os.listdir(dirPath):
+                        record = directory.recordWithShortName(recordType,
+                            shortName)
+                        if record is not None:
+                            uid = record.uid
+                            oldHome = os.path.join(dirPath, shortName)
+                            newHome = os.path.join(uidHomes, uid[0:2], uid[2:4],
+                                uid)
+                            moveCalendarHome(oldHome, newHome)
+
+            # Upgrade calendar homes in the new location:
+            for first in os.listdir(uidHomes):
+                if len(first) == 2:
+                    firstPath = os.path.join(uidHomes, first)
+                    for second in os.listdir(firstPath):
+                        if len(second) == 2:
+                            secondPath = os.path.join(firstPath, second)
+                            for home in os.listdir(secondPath):
+                                homePath = os.path.join(secondPath, home)
+                                upgradeCalendarHome(homePath, directory)
+
+
+
+# Each method in this array will upgrade from one version to the next;
+# the index of each method within the array corresponds to the on-disk
+# version number that it upgrades from.  For example, if the on-disk
+# .version file contains a "3", but there are 6 methods in this array,
+# methods 3 through 5 (using 0-based array indexing) will be executed in
+# order.
+upgradeMethods = [
+    upgrade_to_1,
+]
+# MOR: Change the above to an array of tuples
+
+def upgradeData(config):
+
+    # MOR: Temporary:
+    # config.DocumentRoot = "/Users/morgen/Migration/CalendarServer/Documents"
+    # config.DataRoot = "/Users/morgen/Migration/CalendarServer/Data"
+    docRoot = config.DocumentRoot
+
+    versionFilePath = os.path.join(docRoot, ".calendarserver_version")
+
+    newestVersion = len(upgradeMethods)
+
+    onDiskVersion = 0
+    if os.path.exists(versionFilePath):
+        try:
+            with open(versionFilePath) as versionFile:
+                onDiskVersion = int(versionFile.read().strip())
+        except IOError, e:
+            log.error("Cannot open %s; skipping migration" %
+                (versionFilePath,))
+        except ValueError, e:
+            log.error("Invalid version number in %s; skipping migration" %
+                (versionFilePath,))
+
+    for upgradeVersion in range(onDiskVersion, newestVersion):
+        log.info("Upgrading to version %d" % (upgradeVersion+1,))
+        upgradeMethods[upgradeVersion](config)
+        with open(versionFilePath, "w") as verFile:
+            verFile.write(str(upgradeVersion+1))
+
+
 class UpgradeError(RuntimeError):
     """
     Generic upgrade error.
     """
     pass
+
+
+#
+# Utility functions
+#
+def updateFreeBusyHref(href, directory):
+    pieces = href.split("/")
+    if pieces[2] == "__uids__":
+        # Already updated
+        return None
+
+    recordType = pieces[2]
+    shortName = pieces[3]
+    record = directory.recordWithShortName(recordType, shortName)
+    if record is None:
+        msg = "Can't update free-busy href; %s is not in the directory" % shortName
+        log.error(msg)
+        raise UpgradeError(msg)
+
+    uid = record.uid
+    newHref = "/calendars/__uids__/%s/%s/" % (uid, pieces[4])
+    return newHref
+
+
+def updateFreeBusySet(value, directory):
+
+    try:
+        value = zlib.decompress(value)
+    except zlib.error:
+        # Legacy data - not zlib compressed
+        pass
+
+    try:
+        doc = davxml.WebDAVDocument.fromString(value)
+        freeBusySet = doc.root_element
+    except ValueError:
+        try:
+            freeBusySet = unpickle(value)
+        except UnpicklingError:
+            log.err("Invalid xattr property value for: %s" % attr)
+            # MOR: continue on?
+            return
+
+    fbset = set()
+    didUpdate = False
+    for href in freeBusySet.children:
+        href = str(href)
+        newHref = updateFreeBusyHref(href, directory)
+        if newHref is None:
+            fbset.add(href)
+        else:
+            didUpdate = True
+            fbset.add(newHref)
+
+    if didUpdate:
+        property = caldavxml.CalendarFreeBusySet(*[davxml.HRef(href)
+            for href in fbset])
+        value = compress(property.toxml())
+        return value
+
+    return None # no update required
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090223/7e59c81c/attachment-0001.html>


More information about the calendarserver-changes mailing list