[CalendarServer-changes] [10308] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu Jan 17 14:19:50 PST 2013


Revision: 10308
          http://trac.calendarserver.org//changeset/10308
Author:   cdaboo at apple.com
Date:     2013-01-17 14:19:49 -0800 (Thu, 17 Jan 2013)
Log Message:
-----------
Dropbox -> managed attachments migration.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/upgrade/sql/__init__.py
    CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrade.py
    CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/__init__.py

Added Paths:
-----------
    CalendarServer/trunk/txdav/common/datastore/upgrade/sql/others/
    CalendarServer/trunk/txdav/common/datastore/upgrade/sql/others/__init__.py
    CalendarServer/trunk/txdav/common/datastore/upgrade/sql/others/attachment_migration.py

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2013-01-16 00:03:16 UTC (rev 10307)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -64,7 +64,7 @@
 
 from txdav.common.datastore.sql_tables import schema
 from txdav.common.datastore.upgrade.sql.upgrade import (
-    UpgradeDatabaseSchemaService, UpgradeDatabaseDataService,
+    UpgradeDatabaseSchemaService, UpgradeDatabaseDataService, UpgradeDatabaseOtherService,
 )
 from txdav.common.datastore.upgrade.migrate import UpgradeToDatabaseService
 
@@ -1123,7 +1123,10 @@
                         UpgradeDatabaseDataService.wrapService(
                             UpgradeToDatabaseService.wrapService(
                                 CachingFilePath(config.DocumentRoot),
-                                postImport,
+                                UpgradeDatabaseOtherService.wrapService(
+                                    postImport,
+                                    store, uid=overrideUID, gid=overrideGID,
+                                ),
                                 store, uid=overrideUID, gid=overrideGID,
                                 spawner=spawner, merge=config.MergeUpgrades,
                                 parallel=parallel
@@ -2051,6 +2054,8 @@
 
     return None
 
+
+
 def getSystemIDs(userName, groupName):
     """
     Return the system ID numbers corresponding to either:
@@ -2069,7 +2074,7 @@
         try:
             uid = getpwnam(userName).pw_uid
         except KeyError:
-           raise ConfigurationError("Invalid user name: %s" % (userName,))
+            raise ConfigurationError("Invalid user name: %s" % (userName,))
     else:
         uid = getuid()
 

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2013-01-16 00:03:16 UTC (rev 10307)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -954,18 +954,21 @@
         self._markAsDirty()
 
 
-    def removePropertiesWithName(self, pname):
+    def removeAllPropertiesWithName(self, pname):
         """
-        Remove all properties with the given name from this component.
+        Remove all properties with the given name from all components.
 
-        @param pname: the property name to remove from this component.
+        @param pname: the property name to remove from all components.
         @type pname: C{str}
         """
 
         for property in self.properties(pname):
             self.removeProperty(property)
 
+        for component in self.subcomponents():
+            component.removeAllPropertiesWithName(pname)
 
+
     def replaceProperty(self, property):
         """
         Add or replace a property in this component.

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2013-01-16 00:03:16 UTC (rev 10307)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -37,6 +37,7 @@
 
 from twext.python.clsprop import classproperty
 from twext.python.filepath import CachingFilePath
+from twext.python.log import Logger
 from twext.python.vcomponent import VComponent
 from twext.web2.http_headers import MimeType, generateContentType
 from twext.web2.stream import readStream
@@ -91,6 +92,8 @@
 import tempfile
 import uuid
 
+log = Logger()
+
 class CalendarStoreFeatures(object):
     """
     Manages store-wide operations specific to calendars.
@@ -105,10 +108,30 @@
 
 
     @inlineCallbacks
-    def upgradeToManagedAttachments(self, txn, batchSize=10):
+    def hasDropboxAttachments(self, txn):
         """
+        Determine whether any dropbox attachments are present.
+
+        @param txn: the transaction to run under
+        @type txn: L{txdav.common.datastore.sql.CommonStoreTransaction}
+        """
+
+        at = schema.ATTACHMENT
+        rows = (yield Select(
+            (at.DROPBOX_ID,),
+            From=at,
+            Where=at.DROPBOX_ID != ".",
+            Limit=1,
+        ).on(txn))
+        returnValue(len(rows) != 0)
+
+
+    @inlineCallbacks
+    def upgradeToManagedAttachments(self, batchSize=10):
+        """
         Upgrade the calendar server from old-style dropbox attachments to the new
-        managed attachments. This is a one-way, one-time migration step.
+        managed attachments. This is a one-way, one-time migration step. This method
+        creates its own transactions as needed (possibly multiple when batching).
 
         Things to do:
 
@@ -122,32 +145,63 @@
         TODO: parallelize this as much as possible as it will have to touch a lot of data.
         """
 
-        # Clear out unused CALENDAR_OBJECT.DROPBOX_IDs
-        co = schema.CALENDAR_OBJECT
-        at = schema.ATTACHMENT
-        yield Update(
-            {co.DROPBOX_ID: None},
-            Where=co.RESOURCE_ID.In(Select(
-                (co.RESOURCE_ID,),
-                From=co.join(at, co.DROPBOX_ID == at.DROPBOX_ID, "left outer"),
-                Where=(co.DROPBOX_ID != None).And(at.DROPBOX_ID == None),
-            )),
-        ).on(txn)
+        txn = self._store.newTransaction("CalendarStoreFeatures.upgradeToManagedAttachments - preliminary work")
+        try:
+            # Clear out unused CALENDAR_OBJECT.DROPBOX_IDs
+            co = schema.CALENDAR_OBJECT
+            at = schema.ATTACHMENT
+            yield Update(
+                {co.DROPBOX_ID: None},
+                Where=co.RESOURCE_ID.In(Select(
+                    (co.RESOURCE_ID,),
+                    From=co.join(at, co.DROPBOX_ID == at.DROPBOX_ID, "left outer"),
+                    Where=(co.DROPBOX_ID != None).And(at.DROPBOX_ID == None),
+                )),
+            ).on(txn)
 
-        # For each remaining attachment
-        while True:
+            # Count number to process so we can display progress
             rows = (yield Select(
-                (at.DROPBOX_ID,),
+                (Count(at.DROPBOX_ID),),
                 From=at,
                 Where=at.DROPBOX_ID != ".",
+                GroupBy=at.DROPBOX_ID,
                 Limit=batchSize,
             ).on(txn))
-            if len(rows) == 0:
-                break
-            for dropbox_id in rows:
-                (yield self._upgradeDropbox(txn, dropbox_id))
+            total = rows[0][0]
+            count = 0
+            log.warn("%d dropbox ids to migrate" % (total,))
+        except RuntimeError:
+            yield txn.abort()
+            raise
+        else:
+            yield txn.commit()
 
+        # For each remaining attachment
+        rows = -1
+        while rows:
+            txn = self._store.newTransaction("CalendarStoreFeatures.upgradeToManagedAttachments - attachment loop count: %d" % (count,))
+            try:
+                dropbox_id = "Batched select"
+                rows = (yield Select(
+                    (at.DROPBOX_ID,),
+                    From=at,
+                    Where=at.DROPBOX_ID != ".",
+                    Limit=batchSize,
+                    Distinct=True,
+                ).on(txn))
+                if len(rows) > 0:
+                    for dropbox_id in rows:
+                        (yield self._upgradeDropbox(txn, dropbox_id))
+                    count += len(rows)
+                    log.warn("%d of %d dropbox ids migrated" % (count, total,))
+            except RuntimeError, e:
+                log.error("Dropbox migration failed for '%s': %s" % (dropbox_id, e,))
+                yield txn.abort()
+                raise
+            else:
+                yield txn.commit()
 
+
     @inlineCallbacks
     def _upgradeDropbox(self, txn, dropbox_id):
         """
@@ -161,8 +215,11 @@
         @type dropbox_id: C{str}
         """
 
+        log.debug("Processing dropbox id: %s" % (dropbox_id,))
+
         # Get all affected calendar objects
         cobjs = (yield self._loadCalendarObjectsForDropboxID(txn, dropbox_id))
+        log.debug("  %d affected calendar objects" % (len(cobjs),))
 
         # Get names of each matching attachment
         at = schema.ATTACHMENT
@@ -171,9 +228,12 @@
             From=at,
             Where=at.DROPBOX_ID == dropbox_id,
         ).on(txn))
+        log.debug("  %d associated attachment objects" % (len(names),))
 
         # For each attachment, update each calendar object
         for name in names:
+            name = name[0]
+            log.debug("  processing attachment object: %s" % (name,))
             attachment = (yield DropBoxAttachment.load(txn, dropbox_id, name))
 
             # Find owner objects and group all by UID
@@ -183,26 +243,34 @@
                 if cobj._parentCollection.ownerHome()._resourceID == attachment._ownerHomeID:
                     owners.append(cobj)
                 cobj_by_UID[cobj.uid()].append(cobj)
+            log.debug("    %d owner calendar objects" % (len(owners),))
+            log.debug("    %d UIDs" % (len(cobj_by_UID),))
+            log.debug("    %d total calendar objects" % (sum([len(items) for items in cobj_by_UID.values()]),))
 
             if owners:
                 # Create the managed attachment without references to calendar objects.
                 managed = (yield attachment.convertToManaged())
+                log.debug("    converted attachment: %r" % (attachment,))
 
                 # Do conversion for each owner object
                 for owner_obj in owners:
 
                     # Add a reference to the managed attachment
                     mattachment = (yield managed.newReference(owner_obj._resourceID))
+                    log.debug("    added reference for: %r" % (owner_obj,))
 
                     # Rewrite calendar data
                     for cobj in cobj_by_UID[owner_obj.uid()]:
                         (yield cobj.convertAttachments(attachment, mattachment))
+                        log.debug("    re-wrote calendar object: %r" % (cobj,))
             else:
                 # TODO: look for cobjs that were not changed and remove their ATTACH properties.
                 # These could happen if the owner object no longer exists.
                 pass
 
+        log.debug("  finished dropbox id: %s" % (dropbox_id,))
 
+
     @inlineCallbacks
     def _loadCalendarObjectsForDropboxID(self, txn, dropbox_id):
         """
@@ -1745,18 +1813,14 @@
         for component in cal.subcomponents():
             attachments = component.properties("ATTACH")
             removed = False
-            still_contains_dropbox = False
             for attachment in tuple(attachments):
                 if attachment.value().endswith("/dropbox/%s/%s" % (oldattachment.dropboxID(), oldattachment.name(),)):
                     component.removeProperty(attachment)
                     removed = True
-                elif attachment.value().find("/dropbox/") != -1:
-                    still_contains_dropbox = True
             if removed:
                 attach, _ignore_location = (yield newattachment.attachProperty())
                 component.addProperty(attach)
-            if not still_contains_dropbox:
-                component.removePropertiesWithName("X-APPLE-DROPBOX")
+            component.removeAllPropertiesWithName("X-APPLE-DROPBOX")
 
         # Write the component back (and no need to re-index as we have not
         # changed any timing properties in the calendar data).
@@ -2029,6 +2093,10 @@
         self._justCreated = justCreated
 
 
+    def __repr__(self):
+        return "<%s: %s>" % (self.__class__.__name__, self._attachmentID)
+
+
     def _attachmentPathRoot(self):
         return self._txn._store.attachmentsPath
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py	2013-01-16 00:03:16 UTC (rev 10307)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_attachments.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -471,11 +471,7 @@
         home = (yield txn.calendarHomeWithUID("home1"))
         calendar = (yield home.calendarWithName("calendar1"))
         event = (yield calendar.calendarObjectWithName("1.2.ics"))
-        component = (yield event.component()).mainComponent()
 
-        # Still has X-APPLE-DROPBOX
-        self.assertTrue(component.hasProperty("X-APPLE-DROPBOX"))
-
         # Check that one managed-id and one dropbox ATTACH exist
         attachments = (yield event.component()).mainComponent().properties("ATTACH")
         dropbox_count = 0
@@ -611,10 +607,8 @@
 
         yield self._addAllAttachments()
 
-        txn = self._sqlCalendarStore.newTransaction()
         calstore = CalendarStoreFeatures(self._sqlCalendarStore)
-        yield calstore.upgradeToManagedAttachments(txn, 2)
-        yield txn.commit()
+        yield calstore.upgradeToManagedAttachments(2)
 
         yield self._verifyConversion("home1", "calendar1", "1.2.ics", ("attach_1_2_1.txt", "attach_1_2_2.txt",))
         yield self._verifyConversion("home1", "calendar1", "1.3.ics", ("attach_1_3.txt",))

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2013-01-16 00:03:16 UTC (rev 10307)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -181,7 +181,12 @@
         else:
             self.queryCacher = None
 
+        # Always import these here to trigger proper "registration" of the calendar and address book
+        # home classes
+        __import__("txdav.caldav.datastore.sql")
+        __import__("txdav.carddav.datastore.sql")
 
+
     @inlineCallbacks
     def _withEachHomeDo(self, homeTable, homeFromTxn, action, batchSize):
         """
@@ -427,7 +432,6 @@
                 self._primaryHomeType = EADDRESSBOOKTYPE
         directlyProvides(self, *extraInterfaces)
 
-        self._circularImportHack()
         self._sqlTxn = sqlTxn
         self.paramstyle = sqlTxn.paramstyle
         self.dialect = sqlTxn.dialect
@@ -441,20 +445,6 @@
         self.currentStatement = None
 
 
-    @classmethod
-    def _circularImportHack(cls):
-        """
-        This method is run when the first L{CommonStoreTransaction} is
-        instantiated, to populate class-scope (in other words, global) state
-        that requires importing modules which depend on this one.
-
-        @see: L{CommonHome._register}
-        """
-        if not cls._homeClass:
-            __import__("txdav.caldav.datastore.sql")
-            __import__("txdav.carddav.datastore.sql")
-
-
     def enqueue(self, workItem, **kw):
         """
         Enqueue a L{twext.enterprise.queue.WorkItem} for later execution.

Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/__init__.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/__init__.py	2013-01-16 00:03:16 UTC (rev 10307)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/__init__.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -13,4 +13,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-

Added: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/others/__init__.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/others/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/others/__init__.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2013 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.
+##

Added: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/others/attachment_migration.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/others/attachment_migration.py	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/others/attachment_migration.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -0,0 +1,44 @@
+##
+# Copyright (c) 2013 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.
+##
+
+from twisted.internet.defer import inlineCallbacks
+from txdav.caldav.datastore.sql import CalendarStoreFeatures
+
+"""
+Upgrader that checks for any dropbox attachments, and upgrades them all to managed attachments.
+"""
+
+ at inlineCallbacks
+def doUpgrade(upgrader):
+    """
+    Do the required upgrade steps.
+    """
+
+    storeWrapper = CalendarStoreFeatures(upgrader.sqlStore)
+    txn = upgrader.sqlStore.newTransaction("attachment_migration.doUpgrade")
+    try:
+        needUpgrade = (yield storeWrapper.hasDropboxAttachments(txn))
+        if needUpgrade:
+            upgrader.log_warn("Starting dropbox migration")
+            yield storeWrapper.upgradeToManagedAttachments(batchSize=10)
+            upgrader.log_warn("Finished dropbox migration")
+        else:
+            upgrader.log_warn("No dropbox migration needed")
+    except RuntimeError:
+        yield txn.abort()
+        raise
+    else:
+        yield txn.commit()

Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrade.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrade.py	2013-01-16 00:03:16 UTC (rev 10307)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrade.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -30,6 +30,8 @@
 from twisted.python.modules import getModule
 from twisted.python.reflect import namedObject
 
+from txdav.common.datastore.upgrade.sql.others import attachment_migration
+
 class UpgradeDatabaseCoreService(Service, LoggingMixIn, object):
     """
     Base class for either schema or data upgrades on the database.
@@ -73,6 +75,7 @@
         """
         return cls(store, service, uid=uid, gid=gid,)
 
+
     def __init__(self, sqlStore, service, uid=None, gid=None):
         """
         Initialize the service.
@@ -83,25 +86,27 @@
         self.gid = gid
         self.schemaLocation = getModule(__name__).filePath.parent().parent().sibling("sql_schema")
         self.pyLocation = getModule(__name__).filePath.parent()
-        
+
         self.versionKey = None
         self.versionDescriptor = ""
         self.upgradeFileSuffix = ""
         self.defaultKeyValue = None
-        
+
+
     def startService(self):
         """
         Start the service.
         """
         self.databaseUpgrade()
 
+
     @inlineCallbacks
     def databaseUpgrade(self):
         """
         Do a database schema upgrade.
         """
         self.log_warn("Beginning database %s check." % (self.versionDescriptor,))
-        
+
         # Retrieve information from schema and database
         dialect, required_version, actual_version = yield self.getVersions()
 
@@ -117,13 +122,14 @@
             self.sqlStore.setUpgrading(True)
             yield self.upgradeVersion(actual_version, required_version, dialect)
             self.sqlStore.setUpgrading(False)
-            
+
         self.log_warn("Database %s check complete." % (self.versionDescriptor,))
 
         # see http://twistedmatrix.com/trac/ticket/4649
         if self.wrappedService is not None:
             reactor.callLater(0, self.wrappedService.setServiceParent, self.parent)
-    
+
+
     @inlineCallbacks
     def getVersions(self):
         """
@@ -141,7 +147,7 @@
         else:
             required_version = int(found.group(1))
             self.log_warn("Required database key %s: %s." % (self.versionKey, required_version,))
-        
+
         # Get the schema version in the current database
         sqlTxn = self.sqlStore.newTransaction()
         dialect = sqlTxn.dialect
@@ -161,6 +167,7 @@
 
         returnValue((dialect, required_version, actual_version,))
 
+
     @inlineCallbacks
     def upgradeVersion(self, fromVersion, toVersion, dialect):
         """
@@ -169,10 +176,10 @@
         """
 
         self.log_warn("Starting %s upgrade from version %d to %d." % (self.versionDescriptor, fromVersion, toVersion,))
-        
+
         # Scan for all possible upgrade files - returned sorted
         files = self.scanForUpgradeFiles(dialect)
-        
+
         # Determine upgrade sequence and run each upgrade
         upgrades = self.determineUpgradeSequence(fromVersion, toVersion, files, dialect)
 
@@ -186,17 +193,19 @@
 
         self.log_warn("%s upgraded from version %d to %d." % (self.versionDescriptor.capitalize(), fromVersion, toVersion,))
 
+
     def getPathToUpgrades(self, dialect):
         """
-        Return the path where appropriate upgrade files can be found. 
+        Return the path where appropriate upgrade files can be found.
         """
         raise NotImplementedError
 
+
     def scanForUpgradeFiles(self, dialect):
         """
         Scan for upgrade files with the require name.
         """
-        
+
         fp = self.getPathToUpgrades(dialect)
         upgrades = []
         regex = re.compile("upgrade_from_(\d+)_to_(\d+)%s" % (self.upgradeFileSuffix,))
@@ -206,10 +215,11 @@
                 fromV = int(matched.group(1))
                 toV = int(matched.group(2))
                 upgrades.append((fromV, toV, child))
-        
-        upgrades.sort(key=lambda x:(x[0], x[1]))
+
+        upgrades.sort(key=lambda x: (x[0], x[1]))
         return upgrades
 
+
     def determineUpgradeSequence(self, fromVersion, toVersion, files, dialect):
         """
         Determine the upgrade_from_X_to_Y(.sql|.py) files that cover the full range of upgrades.
@@ -224,7 +234,7 @@
         for fromV, toV, fp in files:
             if fromV not in filesByFromVersion or filesByFromVersion[fromV][1] < toV:
                 filesByFromVersion[fromV] = fromV, toV, fp
-        
+
         upgrades = []
         nextVersion = fromVersion
         while nextVersion != toVersion:
@@ -235,15 +245,18 @@
             else:
                 upgrades.append(filesByFromVersion[nextVersion][2])
                 nextVersion = filesByFromVersion[nextVersion][1]
-        
+
         return upgrades
 
+
     def applyUpgrade(self, fp):
         """
         Apply the supplied upgrade to the database. Always return an L{Deferred"
         """
         raise NotImplementedError
-        
+
+
+
 class UpgradeDatabaseSchemaService(UpgradeDatabaseCoreService):
     """
     Checks and upgrades the database schema. This assumes there are a bunch of
@@ -265,19 +278,21 @@
     def __init__(self, sqlStore, service, uid=None, gid=None):
         """
         Initialize the service.
-        
+
         @param sqlStore: The store to operate on. Can be C{None} when doing unit tests.
         @param service:  Wrapped service. Can be C{None} when doing unit tests.
         """
         super(UpgradeDatabaseSchemaService, self).__init__(sqlStore, service, uid, gid)
-        
+
         self.versionKey = "VERSION"
         self.versionDescriptor = "schema"
         self.upgradeFileSuffix = ".sql"
 
+
     def getPathToUpgrades(self, dialect):
         return self.schemaLocation.child("upgrades").child(dialect)
 
+
     @inlineCallbacks
     def applyUpgrade(self, fp):
         """
@@ -293,6 +308,8 @@
             yield sqlTxn.abort()
             raise
 
+
+
 class UpgradeDatabaseDataService(UpgradeDatabaseCoreService):
     """
     Checks and upgrades the database data. This assumes there are a bunch of
@@ -314,25 +331,27 @@
     def __init__(self, sqlStore, service, uid=None, gid=None):
         """
         Initialize the service.
-        
+
         @param sqlStore: The store to operate on. Can be C{None} when doing unit tests.
         @param service:  Wrapped service. Can be C{None} when doing unit tests.
         """
         super(UpgradeDatabaseDataService, self).__init__(sqlStore, service, uid, gid)
-        
+
         self.versionKey = "CALENDAR-DATAVERSION"
         self.versionDescriptor = "data"
         self.upgradeFileSuffix = ".py"
 
+
     def getPathToUpgrades(self, dialect):
         return self.pyLocation.child("upgrades")
 
+
     @inlineCallbacks
     def applyUpgrade(self, fp):
         """
         Apply the data upgrade .py files to the database.
         """
-        
+
         # Find the module function we need to execute
         try:
             module = getModule(__name__)
@@ -345,3 +364,51 @@
 
         self.log_warn("Applying data upgrade: %s" % (module,))
         yield doUpgrade(self.sqlStore)
+
+
+
+class UpgradeDatabaseOtherService(UpgradeDatabaseCoreService):
+    """
+    Do any other upgrade behaviors once all the schema, data, file migration upgraders
+    are done.
+
+    @ivar sqlStore: The store to operate on.
+    @type sqlStore: L{txdav.idav.IDataStore}
+
+    @ivar wrappedService: Wrapped L{IService} that will be started after this
+        L{UpgradeDatabaseOtherService}'s work is done
+    @type wrappedService: L{IService} or C{NoneType}
+    """
+
+    def __init__(self, sqlStore, service, uid=None, gid=None):
+        """
+        Initialize the service.
+
+        @param sqlStore: The store to operate on. Can be C{None} when doing unit tests.
+        @param service:  Wrapped service. Can be C{None} when doing unit tests.
+        """
+        super(UpgradeDatabaseOtherService, self).__init__(sqlStore, service, uid, gid)
+
+        self.versionDescriptor = "other upgrades"
+
+
+    @inlineCallbacks
+    def databaseUpgrade(self):
+        """
+        Do upgrades.
+        """
+        self.log_warn("Beginning database %s check." % (self.versionDescriptor,))
+
+        # Do each upgrade in our own predefined order
+        self.sqlStore.setUpgrading(True)
+
+        # Migration from dropbox to managed attachments
+        yield attachment_migration.doUpgrade(self)
+
+        self.sqlStore.setUpgrading(False)
+
+        self.log_warn("Database %s check complete." % (self.versionDescriptor,))
+
+        # see http://twistedmatrix.com/trac/ticket/4649
+        if self.wrappedService is not None:
+            reactor.callLater(0, self.wrappedService.setServiceParent, self.parent)

Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/__init__.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/__init__.py	2013-01-16 00:03:16 UTC (rev 10307)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/__init__.py	2013-01-17 22:19:49 UTC (rev 10308)
@@ -13,4 +13,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130117/5c84b66a/attachment-0001.html>


More information about the calendarserver-changes mailing list