changeset 2851:7764383a968c

quick_frontend (widget, chat): implementation of new sync mechanism, first draft: QuickWidget implement base of sync mechanism with a sync property which can be overriden, and resync method which is called when resynchronisation is needed. When resync is required, chat widget get missing occupant and history elements A new callable argument can be used with historyPrint, when set it is called when history has been fully printed.
author Goffi <goffi@goffi.org>
date Sun, 10 Mar 2019 18:03:11 +0100
parents 272dc905ff20
children e2595c81eb6d
files sat_frontends/quick_frontend/quick_chat.py sat_frontends/quick_frontend/quick_widgets.py
diffstat 2 files changed, 80 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/sat_frontends/quick_frontend/quick_chat.py	Sun Mar 10 18:03:02 2019 +0100
+++ b/sat_frontends/quick_frontend/quick_chat.py	Sun Mar 10 18:03:11 2019 +0100
@@ -281,6 +281,9 @@
         self.type = type_
         self.encrypted = False  # True if this session is currently encrypted
         self._locked = False
+        # True when resync is in progress, avoid resynchronising twice when resync is called
+        # and history is still being updated. For internal use only
+        self._resync_lock = False
         self.setLocked()
         if type_ == C.CHAT_GROUP:
             if target.resource:
@@ -363,6 +366,43 @@
     def contact_list(self):
         return self.host.contact_lists[self.profile]
 
+    ## synchornisation handling ##
+
+    @quick_widgets.QuickWidget.sync.setter
+    def sync(self, state):
+        quick_widgets.QuickWidget.sync.fset(self, state)
+        if not state:
+            self.setLocked()
+
+    def _resyncComplete(self):
+        self.sync = True
+        self._resync_lock = False
+
+    def resync(self):
+        if self._resync_lock:
+            return
+        self._resync_lock = True
+        log.debug(u"resynchronising {self}".format(self=self))
+        for mess in reversed(self.messages.values()):
+            if mess.type == C.MESS_TYPE_INFO:
+                continue
+            last_message = mess
+            break
+        else:
+            # we have no message yet, we can get normal history
+            self.historyPrint(callback=self._resyncComplete, profile=self.profile)
+            return
+        if self.type == C.CHAT_GROUP:
+            self.occupants.clear()
+            self.host.bridge.mucOccupantsGet(
+                unicode(self.target), self.profile, callback=self.updateOccupants,
+                errback=log.error)
+        self.historyPrint(
+            size=C.HISTORY_LIMIT_NONE,
+            filters={'timestamp_start': last_message.timestamp},
+            callback=self._resyncComplete,
+            profile=self.profile)
+
     ## Widget management ##
 
     def __unicode__(self):
@@ -428,9 +468,11 @@
     ## occupants ##
 
     def setOccupants(self, occupants):
-        """set the whole list of occupants"""
+        """Set the whole list of occupants"""
         assert len(self.occupants) == 0
         for nick, data in occupants.iteritems():
+            log.debug(u"adding occupant {nick} to {room}".format(
+                nick=nick, room=self.target))
             self.occupants[nick] = Occupant(self, data, self.profile)
 
     def updateOccupants(self, occupants):
@@ -523,12 +565,14 @@
         """
         self.setUnlocked()
 
-    def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile="@NONE@"):
+    def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, callback=None,
+                     profile="@NONE@"):
         """Print the current history
 
         Note: self.setUnlocked will be called once history is printed
         @param size (int): number of messages
         @param search (str): pattern to filter the history results
+        @param callback(callable, None): method to call when history has been printed
         @param profile (str): %(doc_profile)s
         """
         if filters is None:
@@ -589,10 +633,14 @@
                     profile,
                 )
             self._onHistoryPrinted()
+            if callback is not None:
+                callback()
 
         def _historyGetEb(err):
             log.error(_(u"Can't get history: {}").format(err))
             self._onHistoryPrinted()
+            if callback is not None:
+                callback()
 
         self.host.bridge.historyGet(
             unicode(self.host.profiles[profile].whoami.bare),
--- a/sat_frontends/quick_frontend/quick_widgets.py	Sun Mar 10 18:03:02 2019 +0100
+++ b/sat_frontends/quick_frontend/quick_widgets.py	Sun Mar 10 18:03:11 2019 +0100
@@ -334,6 +334,7 @@
         self.targets = set()
         self.addTarget(target)
         self.profiles = set()
+        self._sync = True
         if isinstance(profiles, basestring):
             self.addProfile(profiles)
         elif profiles is None:
@@ -354,6 +355,31 @@
         )
         return list(self.profiles)[0]
 
+    # synchronisation state
+
+    @property
+    def sync(self):
+        return self._sync
+
+    @sync.setter
+    def sync(self, state):
+        """state of synchronisation with backend
+
+        @param state(bool): True when backend is synchronised
+            False is set by core
+            True must be set by the widget when resynchronisation is finished
+        """
+        self._sync = state
+
+    def resync(self):
+        """Method called when backend can be resynchronized
+
+        The widget has to set self.sync itself when the synchronisation if finished
+        """
+        pass
+
+    # target/profile
+
     def addTarget(self, target):
         """Add a target if it doesn't already exists
 
@@ -370,6 +396,8 @@
             raise ValueError("multiple profiles are not allowed")
         self.profiles.add(profile)
 
+    # widget identitication
+
     @staticmethod
     def getWidgetHash(target, profiles):
         """Return the hash associated with this target for this widget class
@@ -386,6 +414,8 @@
         """
         return unicode(target)  # by defaut, there is one hash for one target
 
+    # widget life events
+
     def onDelete(self, *args, **kwargs):
         """Called when a widget is being deleted