[CalendarServer-changes] [14490] CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common /datastore

source_changes at macosforge.org source_changes at macosforge.org
Sat Feb 28 14:26:20 PST 2015


Revision: 14490
          http://trac.calendarserver.org//changeset/14490
Author:   cdaboo at apple.com
Date:     2015-02-28 14:26:20 -0800 (Sat, 28 Feb 2015)
Log Message:
-----------
Checkpoint: final sync steps.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py
    CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py
    CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py
    CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py
    CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py
    CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py
    CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py

Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py	2015-02-28 16:40:03 UTC (rev 14489)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py	2015-02-28 22:26:20 UTC (rev 14490)
@@ -25,8 +25,8 @@
 from txdav.caldav.datastore.sql import ManagedAttachment, CalendarBindRecord
 from txdav.common.datastore.sql_external import NotificationCollectionExternal
 from txdav.common.datastore.sql_notification import NotificationCollection
-from txdav.common.datastore.sql_tables import _HOME_STATUS_EXTERNAL, \
-    _HOME_STATUS_MIGRATING
+from txdav.common.datastore.sql_tables import _HOME_STATUS_MIGRATING, _HOME_STATUS_DISABLED, \
+    _HOME_STATUS_EXTERNAL, _HOME_STATUS_NORMAL
 from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
 
 from uuid import uuid4
@@ -95,6 +95,7 @@
 
         self.store = store
         self.diruid = diruid
+        self.disabledRemote = False
 
 
     def label(self, detail):
@@ -190,25 +191,48 @@
         pass
 
 
+    @inTransactionWrapper
     @inlineCallbacks
-    def disableRemoteHome(self):
+    def disableRemoteHome(self, txn):
         """
         Mark the remote home as disabled.
         """
 
-        # TODO: implement API on CommonHome to rename the ownerUID column and
-        # change the status column.
-        pass
+        # Calendar home
+        remote_home = yield self._remoteHome(txn)
+        yield remote_home.setStatus(_HOME_STATUS_DISABLED)
 
+        # Notification home
+        notifications = yield self._remoteNotificationsHome(txn)
+        yield notifications.setStatus(_HOME_STATUS_DISABLED)
 
+        self.disabledRemote = True
+
+
+    @inTransactionWrapper
     @inlineCallbacks
-    def enableLocalHome(self):
+    def enableLocalHome(self, txn):
         """
-        Mark the local home as enabled.
+        Mark the local home as enabled and remove any previously existing external home.
         """
 
-        # TODO: implement API on CommonHome to rename the ownerUID column and
-        # change the status column. Also adjust NotificationCollection.
+        # Disable any local external homes
+        oldhome = yield txn.calendarHomeWithUID(self.diruid, status=_HOME_STATUS_EXTERNAL)
+        if oldhome is not None:
+            yield oldhome.setStatus(_HOME_STATUS_DISABLED)
+        oldnotifications = yield txn.notificationsWithUID(self.diruid, status=_HOME_STATUS_EXTERNAL)
+        if oldnotifications:
+            yield oldnotifications.setStatus(_HOME_STATUS_DISABLED)
+
+        # Enable the migrating ones
+        newhome = yield txn.calendarHomeWithUID(self.diruid, status=_HOME_STATUS_MIGRATING)
+        if newhome is not None:
+            yield newhome.setStatus(_HOME_STATUS_NORMAL)
+        newnotifications = yield txn.notificationsWithUID(self.diruid, status=_HOME_STATUS_MIGRATING)
+        if newnotifications:
+            yield newnotifications.setStatus(_HOME_STATUS_NORMAL)
+
+        # TODO: purge the old ones
         pass
 
 
@@ -256,7 +280,7 @@
         Make sure the home meta-data (alarms, default calendars) is properly sync'd
         """
 
-        remote_home = yield self._remoteHome(txn=txn)
+        remote_home = yield self._remoteHome(txn)
         yield remote_home.readMetaData()
 
         calendars = yield CalendarMigrationRecord.querysimple(txn, calendarHomeResourceID=self.homeId)
@@ -273,11 +297,25 @@
         """
 
         from txdav.caldav.datastore.sql_external import CalendarHomeExternal
-        resourceID = yield txn.store().conduit.send_home_resource_id(txn, self.record)
+        resourceID = yield txn.store().conduit.send_home_resource_id(txn, self.record, migrating=True)
         home = CalendarHomeExternal.makeSyntheticExternalHome(txn, self.record.uid, resourceID) if resourceID is not None else None
+        if self.disabledRemote:
+            home._migratingHome = True
         returnValue(home)
 
 
+    @inlineCallbacks
+    def _remoteNotificationsHome(self, txn):
+        """
+        Create a synthetic external home object that maps to the actual remote home.
+        """
+
+        notifications = yield NotificationCollectionExternal.notificationsWithUID(txn, self.diruid, create=True)
+        if self.disabledRemote:
+            notifications._migratingHome = True
+        returnValue(notifications)
+
+
     def _localHome(self, txn):
         """
         Get the home on this pod that will have data migrated to it.
@@ -917,7 +955,7 @@
         Get all the existing L{NotificationObjectRecord}'s from the remote store.
         """
 
-        notifications = yield NotificationCollectionExternal.notificationsWithUID(txn, self.diruid, True)
+        notifications = yield self._remoteNotificationsHome(txn)
         records = yield notifications.notificationObjectRecords()
         for record in records:
             # This needs to be reset when added to the local store
@@ -936,7 +974,7 @@
         Create L{NotificationObjectRecord} records in the local store.
         """
 
-        notifications = yield NotificationCollection.notificationsWithUID(txn, self.diruid, True, _HOME_STATUS_EXTERNAL)
+        notifications = yield NotificationCollection.notificationsWithUID(txn, self.diruid, status=_HOME_STATUS_MIGRATING, create=True)
         for record in records:
             # Do this via the "write" API so that sync revisions are updated properly, rather than just
             # inserting the records directly.

Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py	2015-02-28 16:40:03 UTC (rev 14489)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py	2015-02-28 22:26:20 UTC (rev 14490)
@@ -806,7 +806,8 @@
         self.assertEqual(changed, set(((yield _mapLocalIDToRemote(id0_1)), (yield _mapLocalIDToRemote(id0_2)),)))
         self.assertEqual(removed, set())
 
-        # Link attachments
+        # Link attachments (after home is disabled)
+        yield syncer.disableRemoteHome()
         len_links = yield syncer.linkAttachments()
         self.assertEqual(len_links, 3)
 
@@ -891,6 +892,7 @@
         # Sync from remote side
         syncer = CrossPodHomeSync(self.theStoreUnderTest(1), "user01")
         yield syncer.loadRecord()
+        yield syncer.disableRemoteHome()
         yield syncer.delegateReconcile()
 
         # Now have local delegates
@@ -952,6 +954,7 @@
         syncer = CrossPodHomeSync(self.theStoreUnderTest(1), "user01")
         yield syncer.loadRecord()
         yield syncer.prepareCalendarHome()
+        yield syncer.disableRemoteHome()
         changes = yield syncer.notificationsReconcile()
         self.assertEqual(changes, 2)
 
@@ -959,8 +962,7 @@
         notifications = yield NotificationCollection.notificationsWithUID(
             self.theTransactionUnderTest(1),
             "user01",
-            True,
-            _HOME_STATUS_EXTERNAL
+            status=_HOME_STATUS_MIGRATING,
         )
         results = yield notifications.notificationObjects()
         self.assertEqual(len(results), 2)
@@ -1059,6 +1061,7 @@
         syncer = CrossPodHomeSync(self.theStoreUnderTest(1), "user01")
         yield syncer.loadRecord()
         yield syncer.sync()
+        yield syncer.disableRemoteHome()
         changes = yield syncer.sharedByCollectionsReconcile()
         self.assertEqual(changes, 2)
         changes = yield syncer.sharedToCollectionsReconcile()
@@ -1125,6 +1128,7 @@
         syncer = CrossPodHomeSync(self.theStoreUnderTest(1), "user01")
         yield syncer.loadRecord()
         yield syncer.sync()
+        yield syncer.disableRemoteHome()
         changes = yield syncer.sharedByCollectionsReconcile()
         self.assertEqual(changes, 3)
         changes = yield syncer.sharedToCollectionsReconcile()

Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py	2015-02-28 16:40:03 UTC (rev 14489)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py	2015-02-28 22:26:20 UTC (rev 14490)
@@ -18,6 +18,7 @@
 
 from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
 from txdav.common.datastore.podding.util import UtilityConduitMixin
+from txdav.common.datastore.sql_tables import _HOME_STATUS_DISABLED
 
 from twistedcaldav.caldavxml import TimeRange
 
@@ -28,17 +29,20 @@
     """
 
     @inlineCallbacks
-    def send_home_resource_id(self, txn, recipient):
+    def send_home_resource_id(self, txn, recipient, migrating=False):
         """
         Lookup the remote resourceID matching the specified directory uid.
 
         @param ownerUID: directory record for user whose home is needed
         @type ownerUID: L{DirectroryRecord}
+        @param migrating: if L{True} then also return a disbaled home
+        @type migrating: L{bool}
         """
 
         request = {
             "action": "home-resource_id",
             "ownerUID": recipient.uid,
+            "migrating": migrating,
         }
 
         response = yield self.sendRequest(txn, recipient, request)
@@ -55,6 +59,8 @@
         """
 
         home = yield txn.calendarHomeWithUID(request["ownerUID"])
+        if home is None and request["migrating"]:
+            home = yield txn.calendarHomeWithUID(request["ownerUID"], status=_HOME_STATUS_DISABLED)
         returnValue(home.id() if home is not None else None)
 
 
@@ -154,8 +160,9 @@
 # These are the actions on store objects we need to expose via the conduit api
 
 # Calls on L{CommonHome} objects
-UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_metadata", "serialize", classMethod=False)
-UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_get_all_group_attendees", "getAllGroupAttendees", classMethod=False, transform_recv_result=StoreAPIConduitMixin._to_serialize_pair_list)
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_metadata", "serialize")
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_set_status", "setStatus")
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_get_all_group_attendees", "getAllGroupAttendees", transform_recv_result=StoreAPIConduitMixin._to_serialize_pair_list)
 UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_shared_to_records", "sharedToBindRecords", transform_recv_result=StoreAPIConduitMixin._to_serialize_dict_list_serialized_value)
 
 # Calls on L{CommonHomeChild} objects
@@ -185,4 +192,5 @@
 UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "objectresource_remove", "remove")
 
 # Calls on L{NotificationCollection} objects
-UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "notification_all_records", "notificationObjectRecords", classMethod=False, transform_recv_result=UtilityConduitMixin._to_serialize_list)
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "notification_set_status", "setStatus")
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "notification_all_records", "notificationObjectRecords", transform_recv_result=UtilityConduitMixin._to_serialize_list)

Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py	2015-02-28 16:40:03 UTC (rev 14489)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/util.py	2015-02-28 22:26:20 UTC (rev 14490)
@@ -83,6 +83,8 @@
         if viewer_home:
             result["homeType"] = viewer_home._homeType
             result["homeUID"] = viewer_home.uid()
+            if getattr(viewer_home, "_migratingHome", False):
+                result["allowDisabledHome"] = True
             if home_child:
                 if home_child.owned():
                     result["homeChildID"] = home_child.id()
@@ -97,6 +99,8 @@
 
         elif notification:
             result["notificationUID"] = notification.uid()
+            if getattr(notification, "_migratingHome", False):
+                result["allowDisabledHome"] = True
             recipient = yield self.store.directoryService().recordWithUID(notification.uid())
 
         returnValue((txn, result, recipient.server(),))
@@ -111,6 +115,9 @@
         returnObject = txn
         classObject = None
 
+        if "allowDisabledHome" in request:
+            txn._allowDisabled = True
+
         if "homeUID" in request:
             home = yield txn.homeWithUID(request["homeType"], request["homeUID"])
             if home is None:

Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py	2015-02-28 16:40:03 UTC (rev 14489)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py	2015-02-28 22:26:20 UTC (rev 14490)
@@ -64,7 +64,8 @@
 from txdav.common.datastore.sql_notification import NotificationCollection
 from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, _BIND_STATUS_ACCEPTED, \
     _HOME_STATUS_EXTERNAL, _HOME_STATUS_NORMAL, \
-    _HOME_STATUS_PURGING, schema, splitSQLString, _HOME_STATUS_MIGRATING
+    _HOME_STATUS_PURGING, schema, splitSQLString, _HOME_STATUS_MIGRATING, \
+    _HOME_STATUS_DISABLED
 from txdav.common.datastore.sql_util import _SharedSyncLogic
 from txdav.common.datastore.sql_sharing import SharingHomeMixIn, SharingMixIn
 from txdav.common.icommondatastore import ConcurrentModification, \
@@ -584,6 +585,7 @@
         self._bumpedRevisionAlready = set()
         self._label = label
         self._migrating = migrating
+        self._allowDisabled = False
         self._primaryHomeType = None
         self._disableCache = disableCache or not store.queryCachingEnabled()
         if disableCache:
@@ -764,11 +766,11 @@
 
 
     @memoizedKey("uid", "_notificationHomes")
-    def notificationsWithUID(self, uid, create=True):
+    def notificationsWithUID(self, uid, status=None, create=True):
         """
         Implement notificationsWithUID.
         """
-        return NotificationCollection.notificationsWithUID(self, uid, create)
+        return NotificationCollection.notificationsWithUID(self, uid, create=create)
 
 
     @memoizedKey("rid", "_notificationHomes")
@@ -1811,6 +1813,8 @@
                     cacheKeys.append(queryCacher.keyForHomeWithUID(cls._homeType, uid, status))
             else:
                 statusSet = (_HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL, _HOME_STATUS_PURGING)
+                if txn._allowDisabled:
+                    statusSet += (_HOME_STATUS_DISABLED,)
                 query = query.And(cls._homeSchema.STATUS.In(statusSet))
                 if queryCacher:
                     for item in statusSet:
@@ -1826,7 +1830,7 @@
         else:
             result = None
 
-        # If nothing in thr cache, do the SQL query and cache the result
+        # If nothing in the cache, do the SQL query and cache the result
         if result is None:
             results = yield Select(
                 cls.homeColumns(),
@@ -1834,12 +1838,14 @@
                 Where=query,
             ).on(txn)
 
-            # Pick the internal one
             if len(results) > 1:
-                if results[0][cls.homeColumns().index(cls._homeSchema.STATUS)] == _HOME_STATUS_NORMAL:
-                    result = results[0]
-                else:
-                    result = results[1]
+                # Pick the best one in order: normal, disabled and external
+                byStatus = dict([(result[cls.homeColumns().index(cls._homeSchema.STATUS)], result) for result in results])
+                result = byStatus.get(_HOME_STATUS_NORMAL)
+                if result is None:
+                    result = byStatus.get(_HOME_STATUS_DISABLED)
+                if result is None:
+                    result = byStatus.get(_HOME_STATUS_EXTERNAL)
             elif results:
                 result = results[0]
             else:

Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py	2015-02-28 16:40:03 UTC (rev 14489)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py	2015-02-28 22:26:20 UTC (rev 14490)
@@ -85,6 +85,10 @@
         self.deserialize(mapping)
 
 
+    def setStatus(self, newStatus):
+        return self._txn.store().conduit.send_home_set_status(self, newStatus)
+
+
     def external(self):
         """
         Is this an external home.
@@ -493,11 +497,24 @@
     """
 
     @classmethod
-    def notificationsWithUID(cls, txn, uid, create):
-        return super(NotificationCollectionExternal, cls).notificationsWithUID(txn, uid, create, expected_status=_HOME_STATUS_EXTERNAL)
+    def notificationsWithUID(cls, txn, uid, create=False):
+        return super(NotificationCollectionExternal, cls).notificationsWithUID(txn, uid, status=_HOME_STATUS_EXTERNAL, create=create)
 
 
+    def initFromStore(self):
+        """
+        NoOp for an external share as there are no properties.
+        """
+        return succeed(self)
+
+
     @inlineCallbacks
     def notificationObjectRecords(self):
         results = yield self._txn.store().conduit.send_notification_all_records(self)
         returnValue(map(NotificationObjectRecord.deserialize, results))
+
+
+    def setStatus(self, newStatus):
+        return self._txn.store().conduit.send_notification_set_status(self, newStatus)
+
+NotificationCollection._externalClass = NotificationCollectionExternal

Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py	2015-02-28 16:40:03 UTC (rev 14489)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py	2015-02-28 22:26:20 UTC (rev 14490)
@@ -27,7 +27,7 @@
 from twistedcaldav.dateops import datetimeMktime
 from txdav.base.propertystore.sql import PropertyStore
 from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_NORMAL, \
-    _HOME_STATUS_EXTERNAL
+    _HOME_STATUS_EXTERNAL, _HOME_STATUS_DISABLED, _HOME_STATUS_MIGRATING
 from txdav.common.datastore.sql_util import _SharedSyncLogic
 from txdav.common.icommondatastore import RecordNotAllowedError
 from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
@@ -49,20 +49,74 @@
     implements(INotificationCollection)
 
     compareAttributes = (
-        "_uid",
+        "_ownerUID",
         "_resourceID",
     )
 
     _revisionsSchema = schema.NOTIFICATION_OBJECT_REVISIONS
     _homeSchema = schema.NOTIFICATION_HOME
 
+    _externalClass = None
 
-    def __init__(self, txn, uid, resourceID, status):
 
+    @classmethod
+    def makeClass(cls, transaction, homeData):
+        """
+        Build the actual home class taking into account the possibility that we might need to
+        switch in the external version of the class.
+
+        @param transaction: transaction
+        @type transaction: L{CommonStoreTransaction}
+        @param homeData: home table column data
+        @type homeData: C{list}
+        """
+
+        status = homeData[cls.homeColumns().index(cls._homeSchema.STATUS)]
+        if status == _HOME_STATUS_EXTERNAL:
+            home = cls._externalClass(transaction, homeData)
+        else:
+            home = cls(transaction, homeData)
+        return home.initFromStore()
+
+
+    @classmethod
+    def homeColumns(cls):
+        """
+        Return a list of column names to retrieve when doing an ownerUID->home lookup.
+        """
+
+        # Common behavior is to have created and modified
+
+        return (
+            cls._homeSchema.RESOURCE_ID,
+            cls._homeSchema.OWNER_UID,
+            cls._homeSchema.STATUS,
+        )
+
+
+    @classmethod
+    def homeAttributes(cls):
+        """
+        Return a list of attributes names to map L{homeColumns} to.
+        """
+
+        # Common behavior is to have created and modified
+
+        return (
+            "_resourceID",
+            "_ownerUID",
+            "_status",
+        )
+
+
+    def __init__(self, txn, homeData):
+
         self._txn = txn
-        self._uid = uid
-        self._resourceID = resourceID
-        self._status = status
+
+        for attr, value in zip(self.homeAttributes(), homeData):
+            setattr(self, attr, value)
+
+        self._txn = txn
         self._dataVersion = None
         self._notifications = {}
         self._notificationNames = None
@@ -72,25 +126,15 @@
         # as well as the home it is in
         self._notifiers = dict([(factory_name, factory.newNotifier(self),) for factory_name, factory in txn._notifierFactories.items()])
 
-    _resourceIDFromUIDQuery = Select(
-        [_homeSchema.RESOURCE_ID, _homeSchema.STATUS],
-        From=_homeSchema,
-        Where=_homeSchema.OWNER_UID == Parameter("uid")
-    )
 
-    _UIDFromResourceIDQuery = Select(
-        [_homeSchema.OWNER_UID],
-        From=_homeSchema,
-        Where=_homeSchema.RESOURCE_ID == Parameter("rid")
-    )
+    @inlineCallbacks
+    def initFromStore(self):
+        """
+        Initialize this object from the store.
+        """
 
-    _provisionNewNotificationsQuery = Insert(
-        {
-            _homeSchema.OWNER_UID: Parameter("uid"),
-            _homeSchema.STATUS: Parameter("status"),
-        },
-        Return=_homeSchema.RESOURCE_ID
-    )
+        yield self._loadPropertyStore()
+        returnValue(self)
 
 
     @property
@@ -103,28 +147,78 @@
 
 
     @classmethod
+    def notificationsWithUID(cls, txn, uid, status=None, create=True):
+        return cls.notificationsWith(txn, None, uid, status, create=create)
+
+
+    @classmethod
+    def notificationsWithResourceID(cls, txn, rid):
+        return cls.notificationsWith(txn, rid, None)
+
+
+    @classmethod
     @inlineCallbacks
-    def notificationsWithUID(cls, txn, uid, create, expected_status=_HOME_STATUS_NORMAL):
+    def notificationsWith(cls, txn, rid, uid, status=None, create=True):
         """
         @param uid: I'm going to assume uid is utf-8 encoded bytes
         """
-        rows = yield cls._resourceIDFromUIDQuery.on(txn, uid=uid)
+        if rid is not None:
+            query = cls._homeSchema.RESOURCE_ID == rid
+        elif uid is not None:
+            query = cls._homeSchema.OWNER_UID == uid
+            if status is not None:
+                query = query.And(cls._homeSchema.STATUS == status)
+            else:
+                statusSet = (_HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL,)
+                if txn._allowDisabled:
+                    statusSet += (_HOME_STATUS_DISABLED,)
+                query = query.And(cls._homeSchema.STATUS.In(statusSet))
+        else:
+            raise AssertionError("One of rid or uid must be set")
 
-        if rows:
-            resourceID = rows[0][0]
-            status = rows[0][1]
-            if status != expected_status:
-                raise RecordNotAllowedError("Notifications status mismatch: {} != {}".format(status, expected_status))
-            created = False
-        elif create:
+        results = yield Select(
+            cls.homeColumns(),
+            From=cls._homeSchema,
+            Where=query,
+        ).on(txn)
+
+        if len(results) > 1:
+            # Pick the best one in order: normal, disabled and external
+            byStatus = dict([(result[cls.homeColumns().index(cls._homeSchema.STATUS)], result) for result in results])
+            result = byStatus.get(_HOME_STATUS_NORMAL)
+            if result is None:
+                result = byStatus.get(_HOME_STATUS_DISABLED)
+            if result is None:
+                result = byStatus.get(_HOME_STATUS_EXTERNAL)
+        elif results:
+            result = results[0]
+        else:
+            result = None
+
+        if result:
+            # Return object that already exists in the store
+            homeObject = yield cls.makeClass(txn, result)
+            returnValue(homeObject)
+        else:
+            # Can only create when uid is specified
+            if not create or uid is None:
+                returnValue(None)
+
             # Determine if the user is local or external
             record = yield txn.directoryService().recordWithUID(uid.decode("utf-8"))
             if record is None:
                 raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".format(uid))
 
-            status = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
-            if status != expected_status:
-                raise RecordNotAllowedError("Notifications status mismatch: {} != {}".format(status, expected_status))
+            if status is None:
+                createStatus = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
+            elif status == _HOME_STATUS_MIGRATING:
+                if record.thisServer():
+                    raise RecordNotAllowedError("Cannot migrate a user data for a user already hosted on this server")
+                createStatus = status
+            elif status in (_HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL,):
+                createStatus = status
+            else:
+                raise RecordNotAllowedError("Cannot create home with status {}: {}".format(status, uid))
 
             # Use savepoint so we can do a partial rollback if there is a race
             # condition where this row has already been inserted
@@ -132,55 +226,52 @@
             yield savepoint.acquire(txn)
 
             try:
-                resourceID = str((
-                    yield cls._provisionNewNotificationsQuery.on(txn, uid=uid, status=status)
-                )[0][0])
+                resourceid = (yield Insert(
+                    {
+                        cls._homeSchema.OWNER_UID: uid,
+                        cls._homeSchema.STATUS: createStatus,
+                    },
+                    Return=cls._homeSchema.RESOURCE_ID
+                ).on(txn))[0][0]
             except Exception:
                 # FIXME: Really want to trap the pg.DatabaseError but in a non-
                 # DB specific manner
                 yield savepoint.rollback(txn)
 
                 # Retry the query - row may exist now, if not re-raise
-                rows = yield cls._resourceIDFromUIDQuery.on(txn, uid=uid)
-                if rows:
-                    resourceID = rows[0][0]
-                    status = rows[0][1]
-                    if status != expected_status:
-                        raise RecordNotAllowedError("Notifications status mismatch: {} != {}".format(status, expected_status))
-                    created = False
+                results = yield Select(
+                    cls.homeColumns(),
+                    From=cls._homeSchema,
+                    Where=query,
+                ).on(txn)
+                if results:
+                    homeObject = yield cls.makeClass(txn, results[0])
+                    returnValue(homeObject)
                 else:
                     raise
             else:
-                created = True
                 yield savepoint.release(txn)
-        else:
-            returnValue(None)
-        collection = cls(txn, uid, resourceID, status)
-        yield collection._loadPropertyStore()
-        if created:
-            yield collection._initSyncToken()
-            yield collection.notifyChanged()
-        returnValue(collection)
 
+                # Note that we must not cache the owner_uid->resource_id
+                # mapping in the query cacher when creating as we don't want that to appear
+                # until AFTER the commit
+                results = yield Select(
+                    cls.homeColumns(),
+                    From=cls._homeSchema,
+                    Where=cls._homeSchema.RESOURCE_ID == resourceid,
+                ).on(txn)
+                homeObject = yield cls.makeClass(txn, results[0])
+                if homeObject.normal():
+                    yield homeObject._initSyncToken()
+                    yield homeObject.notifyChanged()
+                returnValue(homeObject)
 
-    @classmethod
-    @inlineCallbacks
-    def notificationsWithResourceID(cls, txn, rid):
-        rows = yield cls._UIDFromResourceIDQuery.on(txn, rid=rid)
 
-        if rows:
-            uid = rows[0][0]
-            result = (yield cls.notificationsWithUID(txn, uid, create=False))
-            returnValue(result)
-        else:
-            returnValue(None)
-
-
     @inlineCallbacks
     def _loadPropertyStore(self):
         self._propertyStore = yield PropertyStore.load(
-            self._uid,
-            self._uid,
+            self._ownerUID,
+            self._ownerUID,
             None,
             self._txn,
             self._resourceID,
@@ -224,9 +315,41 @@
 
 
     def uid(self):
-        return self._uid
+        return self._ownerUID
 
 
+    @inlineCallbacks
+    def setStatus(self, newStatus):
+        """
+        Mark this home as being purged.
+        """
+        # Only if different
+        if self._status != newStatus:
+            yield Update(
+                {self._homeSchema.STATUS: newStatus},
+                Where=(self._homeSchema.RESOURCE_ID == self._resourceID),
+            ).on(self._txn)
+            self._status = newStatus
+
+
+    def normal(self):
+        """
+        Is this an normal (internal) home.
+
+        @return: a L{bool}.
+        """
+        return self._status == _HOME_STATUS_NORMAL
+
+
+    def external(self):
+        """
+        Is this an external home.
+
+        @return: a L{bool}.
+        """
+        return self._status == _HOME_STATUS_EXTERNAL
+
+
     def owned(self):
         return True
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150228/35be6c0d/attachment-0001.html>


More information about the calendarserver-changes mailing list