[polari/wip/raresv/nick-popover: 6/16] userTracker: Add global tracking



commit 1d7ef34f5b9b1f1a1e6df236b6a8acda5e490b11
Author: raresv <rares visalom gmail com>
Date:   Tue Aug 2 00:46:26 2016 +0300

    userTracker: Add global tracking
    
    The first userTracker patch adds only the local tracking
    functionality of the userTracker, but we still need the
    global one too. This patch adds the global tracking
    functionality. First of all we create the UserStatusMonitor,
    a singleton that handles all other instances of UserTrackers.
    UserTrackers are now account-specific, meaning that they
    track rooms that are on the same account (network).
    Obviously, each room is tracked both individually (local
    tracking) and globally (global tracking). Global tracking
    is achieved through emitting the status-changed::basenick
    detailed signal while the local one uses custom callbacks.
    The reason we use custom callbacks is that it avoids
    filtering of the parameters sent by a signal. Local status
    is in connection with both the basenick and the room, hence
    double filtering would be required in the case of using
    a detailed signal for the local tracking part.
    Also, there is a new contacts-changed::basenick signal that
    is emitted whenever the contacts change for a specific
    basenick (useful when there are more users with the same
    basenick).

 src/chatView.js    |   19 ++++-
 src/userTracker.js |  214 +++++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 200 insertions(+), 33 deletions(-)
---
diff --git a/src/chatView.js b/src/chatView.js
index ba21975..0b744bc 100644
--- a/src/chatView.js
+++ b/src/chatView.js
@@ -297,8 +297,8 @@ const ChatView = new Lang.Class({
         this._pendingLogs = [];
         this._statusCount = { left: 0, joined: 0, total: 0 };
 
-        this._userTracker = new UserTracker.UserTracker(this._room);
-        this._userTracker.connect('status-changed', Lang.bind(this, this._onNickStatusChanged));
+        let statusMonitor = UserTracker.getUserStatusMonitor();
+        this._userTracker = statusMonitor.getUserTrackerForAccount(room.account);
 
         this._room.account.connect('notify::nickname', Lang.bind(this,
             function() {
@@ -351,6 +351,15 @@ const ChatView = new Lang.Class({
             this._roomSignals.push(room.connect(signal.name, signal.handler));
         }));
         this._onChannelChanged();
+
+        this._nickStatusChangedId =
+            this._userTracker.watchRoomStatus(this._room, null,
+                                        Lang.bind(this, this._onNickStatusChanged));
+
+        this.connect('destroy', () => {
+            this._userTracker.unwatchRoomStatus(this._room, this._nickStatusChangedId);
+            this._userTracker = null;
+        });
     },
 
     _createTags: function() {
@@ -1168,6 +1177,7 @@ const ChatView = new Lang.Class({
                     this._view.get_buffer().get_tag_table().add(nickTag);
                 }
                 tags.push(nickTag);
+
                 if (needsGap)
                     tags.push(this._lookupTag('gap'));
                 this._insertWithTags(iter, message.nick + '\t', tags);
@@ -1202,8 +1212,9 @@ const ChatView = new Lang.Class({
         this._insertWithTags(iter, text.substr(pos), tags);
     },
 
-    _onNickStatusChanged: function(tracker, nickName, status) {
-        let nickTag = this._lookupTag(this._getNickTagName(nickName));
+    _onNickStatusChanged: function(baseNick, status) {
+        let nickTagName = this._getNickTagName(baseNick);
+        let nickTag = this._lookupTag(nickTagName);
 
         if (!nickTag)
             return;
diff --git a/src/userTracker.js b/src/userTracker.js
index 5f9ca55..a9d0db2 100644
--- a/src/userTracker.js
+++ b/src/userTracker.js
@@ -7,6 +7,52 @@ const Utils = imports.utils;
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
 
+const AccountsMonitor = imports.accountsMonitor;
+const ChatroomManager = imports.chatroomManager;
+
+let _singleton = null;
+
+function getUserStatusMonitor() {
+    if (_singleton == null)
+        _singleton = new UserStatusMonitor();
+    return _singleton;
+}
+
+const UserStatusMonitor = new Lang.Class({
+    Name: 'UserStatusMonitor',
+
+    _init: function() {
+        this._userTrackers = new Map();
+        this._accountsMonitor = AccountsMonitor.getDefault();
+
+        this._accountsMonitor.connect('account-added', Lang.bind(this, this._onAccountAdded));
+        this._accountsMonitor.connect('account-removed', Lang.bind(this, this._onAccountRemoved));
+
+        this._accountsMonitor.dupAccounts().forEach(a => { this._onAccountAdded(this._accountsMonitor, a); 
});
+    },
+
+    _onAccountAdded: function(accountsMonitor, account) {
+        if (this._userTrackers.has(account))
+            return;
+
+        this._userTrackers.set(account, new UserTracker(account));
+    },
+
+    _onAccountRemoved: function(accountsMonitor, account) {
+        if (!this._userTrackers.has(account))
+            return;
+
+        this._userTrackers.delete(account);
+    },
+
+    getUserTrackerForAccount: function(account) {
+        if (this._userTrackers.has(account))
+            return this._userTrackers.get(account);
+        return null;
+    }
+});
+
+
 const UserTracker = new Lang.Class({
     Name: 'UserTracker',
     Extends: GObject.Object,
@@ -15,21 +61,45 @@ const UserTracker = new Lang.Class({
         'status-changed': {
             flags: GObject.SignalFlags.DETAILED,
             param_types: [GObject.TYPE_STRING, GObject.TYPE_INT]
+        },
+        'contacts-changed': {
+            flags: GObject.SignalFlags.DETAILED,
+            param_types: [GObject.TYPE_STRING]
         }
     },
 
-    _init: function(room) {
+    _init: function(account) {
         this.parent();
 
+        this._account = account;
+
         this._baseNickContacts = new Map();
+        this._roomData = new Map();
+        this._handlerCounter = 0;
 
-        this._room = room;
+        this._chatroomManager = ChatroomManager.getDefault();
+        this._chatroomManager.connect('room-added', Lang.bind(this, this._onRoomAdded));
+        this._chatroomManager.connect('room-removed', Lang.bind(this, this._onRoomRemoved));
+    },
+
+    _getRoomContacts: function(room) {
+        return this._roomData.get(room).contactMapping;
+    },
+
+    _getRoomHandlers: function(room) {
+        return this._roomData.get(room).handlerMapping;
+    },
 
-        this._onRoomAdded(this._room);
-        this._onChannelChanged(this._room);
+    _getRoomSignals: function(room) {
+        return this._roomData.get(room).roomSignals;
     },
 
-    _onRoomAdded: function(room) {
+    _onRoomAdded: function(roomManager, room) {
+        if (room.account != this._account)
+            return;
+
+        this._ensureRoomMappingForRoom(room);
+
         let roomSignals = [
             { name: 'notify::channel',
               handler: Lang.bind(this, this._onChannelChanged) },
@@ -47,14 +117,24 @@ const UserTracker = new Lang.Class({
               handler: Lang.bind(this, this._onMemberLeft) }
         ];
 
-        roomSignals.forEach(Lang.bind(this, function(signal) {
-            room.connect(signal.name, signal.handler);
-        }));
-    }
+        let signalIds = this._getRoomSignals(room);
+        roomSignals.forEach(signal => {
+            signalIds.push(room.connect(signal.name, signal.handler));
+        });
+    },
+
+    _onRoomRemoved: function(roomManager, room) {
+        if (!this._roomData.has(room))
+            return;
+
+        this._getRoomSignals(room).forEach(id => { room.disconnect(id); });
+        this._clearUsersFromRoom(room);
+        this._roomData.delete(room);
+    },
 
     _onChannelChanged: function(room) {
         if (!room.channel) {
-            this._clearUsers();
+            this._clearUsersFromRoom(room);
             return;
         }
 
@@ -64,44 +144,70 @@ const UserTracker = new Lang.Class({
         else
             members = [room.channel.connection.self_contact, room.channel.target_contact];
 
-        members.forEach(m => { this._trackMember(m); });
+        /*keep track of initial members in the room, both locally and
+        globally*/
+        members.forEach(m => { this._trackMember(m, room); });
     },
 
-    _clearUsers: function() {
-        for ([baseNick, contacts] of this._baseNickContacts)
-            contacts.slice().forEach((m) => { this._untrackMember(m); });
+    _clearUsersFromRoom: function(room) {
+        let map = this._getRoomContacts(room);
+        for ([baseNick, contacts] of map)
+            contacts.slice().forEach((m) => { this._untrackMember(m, room); });
+    },
+
+    _ensureRoomMappingForRoom: function(room) {
+        if (this._roomData.has(room))
+            return;
+        this._roomData.set(room, { contactMapping: new Map(),
+                                   handlerMapping: new Map(),
+                                   roomSignals: [] });
     },
 
     _onMemberRenamed: function(room, oldMember, newMember) {
-        this._untrackMember(oldMember);
-        this._trackMember(newMember);
+        this._untrackMember(oldMember, room);
+        this._trackMember(newMember, room);
     },
 
     _onMemberJoined: function(room, member) {
-        this._trackMember(member);
+        this._trackMember(member, room);
     },
 
     _onMemberLeft: function(room, member) {
-        this._untrackMember(member);
+        this._untrackMember(member, room);
     },
 
-    _pushMember: function(baseNick, member) {
-        if (!this._baseNickContacts.has(baseNick))
-            this._baseNickContacts.set(baseNick, []);
-        let contacts = this._baseNickContacts.get(baseNick);
+    _runHandlers: function(room, member, status) {
+        let baseNick = Polari.util_get_basenick(member.alias);
+        let roomHandlers = this._getRoomHandlers(room);
+        for ([id, info] of roomHandlers)
+            if (!info.nickName || info.nickName == baseNick)
+                info.handler(baseNick, status);
+    },
+
+    _pushMember: function(map, baseNick, member) {
+        if (!map.has(baseNick))
+            map.set(baseNick, []);
+        let contacts = map.get(baseNick);
         return contacts.push(member);
     },
 
-    _trackMember: function(member) {
+    _trackMember: function(member, room) {
         let baseNick = Polari.util_get_basenick(member.alias);
         let status = Tp.ConnectionPresenceType.AVAILABLE;
 
-        if (this._pushMember(baseNick, member) == 1)
+        let map = this._baseNickContacts;
+        if (this._pushMember(map, baseNick, member) == 1)
             this.emit("status-changed::" + baseNick, baseNick, status);
+
+        let roomMap = this._getRoomContacts(room);
+        if (this._pushMember(roomMap, baseNick, member) == 1)
+            this._runHandlers(room, member, status);
+
+        this.emit("contacts-changed::" + baseNick, member.alias);
     },
 
-    _popMember: function(baseNick, member) {
-        let contacts = this._baseNickContacts.get(baseNick) || [];
+    _popMember: function(map, baseNick, member) {
+        let contacts = map.get(baseNick) || [];
         let index = contacts.map(c => c.alias).indexOf(member.alias);
         if (index < 0)
             return [false, contacts.length];
@@ -109,14 +215,22 @@ const UserTracker = new Lang.Class({
         return [true, contacts.length];
     },
 
-    _untrackMember: function(member) {
+    _untrackMember: function(member, room) {
         let baseNick = Polari.util_get_basenick(member.alias);
         let status = Tp.ConnectionPresenceType.OFFLINE;
 
-        let [found, nContacts] = this._popMember(baseNick, member);
-        if (found)
+        let map = this._baseNickContacts;
+        let [found, nContacts] = this._popMember(map, baseNick, member);
+        if (found) {
             if (nContacts == 0)
                 this.emit("status-changed::" + baseNick, member.alias, status);
+            this.emit("contacts-changed::" + baseNick, member.alias);
+        }
+
+        let roomMap = this._getRoomContacts(room);
+        [found, nContacts] = this._popMember(roomMap, baseNick, member);
+        if (found && nContacts == 0)
+            this._runHandlers(room, member, status);
     },
 
     getNickStatus: function(nickName) {
@@ -125,5 +239,47 @@ const UserTracker = new Lang.Class({
         let contacts = this._baseNickContacts.get(baseNick) || [];
         return contacts.length == 0 ? Tp.ConnectionPresenceType.OFFLINE
                                     : Tp.ConnectionPresenceType.AVAILABLE;
+    },
+
+    getNickRoomStatus: function(nickName, room) {
+        let baseNick = Polari.util_get_basenick(nickName);
+
+        this._ensureRoomMappingForRoom(room);
+
+        let contacts = this._getRoomContacts(room).get(baseNick) || [];
+        return contacts.length == 0 ? Tp.ConnectionPresenceType.OFFLINE
+                                    : Tp.ConnectionPresenceType.AVAILABLE;
+    },
+
+    lookupContact: function(nickName) {
+        let baseNick = Polari.util_get_basenick(nickName);
+
+        let contacts = this._baseNickContacts.get(baseNick) || [];
+
+        if (contacts.length == 0)
+            return null;
+
+        for (let i = 0; i < contacts.length; i++)
+            if (contacts[i].alias == nickName)
+                return contacts[i];
+
+        return contacts[0];
+    },
+
+    watchRoomStatus: function(room, baseNick, callback) {
+        this._ensureRoomMappingForRoom(room);
+
+        this._getRoomHandlers(room).set(++this._handlerCounter, {
+            nickName: baseNick,
+            handler: callback
+        });
+
+        return this._handlerCounter;
+    },
+
+    unwatchRoomStatus: function(room, handlerID) {
+        if (!this._roomData.has(room))
+            return;
+        this._getRoomHandlers(room).delete(handlerID);
     }
 });


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]