[polari] userTracker: Allow tracking users across rooms
- From: Rares Visalom <raresvisalom src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [polari] userTracker: Allow tracking users across rooms
- Date: Mon, 10 Oct 2016 22:08:32 +0000 (UTC)
commit 38088cedd47bc0b118a17817fc4cf4773cafc006
Author: raresv <rares visalom gmail com>
Date: Tue Aug 2 00:46:26 2016 +0300
userTracker: Allow tracking users across rooms
The UserTracker can be used to track the status of users
in a particular room, however sometimes it is more
interesting to know whether a user is online at all (for
example to determine if it is possible to start a private
conversation with them). While we don't have a way to get
that information, we can approximate it by tracking users
across all rooms we have joined.
https://bugzilla.gnome.org/show_bug.cgi?id=760853
src/chatView.js | 24 +++++--
src/userTracker.js | 217 +++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 206 insertions(+), 35 deletions(-)
---
diff --git a/src/chatView.js b/src/chatView.js
index 14211f4..123b640 100644
--- a/src/chatView.js
+++ b/src/chatView.js
@@ -295,8 +295,8 @@ const ChatView = new Lang.Class({
this._initialPending = [];
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() {
@@ -352,6 +352,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() {
@@ -451,7 +460,8 @@ const ChatView = new Lang.Class({
if (!nickname)
return;
- this._updateNickTag(tag, this._userTracker.getNickStatus(nickname));
+ let status = this._userTracker.getNickRoomStatus(nickname, this._room);
+ this._updateNickTag(tag, status);
});
},
@@ -1181,7 +1191,8 @@ const ChatView = new Lang.Class({
if (!nickTag) {
nickTag = new Gtk.TextTag({ name: nickTagName });
- this._updateNickTag(nickTag, this._userTracker.getNickStatus(message.nick));
+ let status = this._userTracker.getNickRoomStatus(message.nick, this._room);
+ this._updateNickTag(nickTag, status);
this._view.get_buffer().get_tag_table().add(nickTag);
}
@@ -1234,8 +1245,9 @@ const ChatView = new Lang.Class({
this._view.buffer.create_mark(null, iter, true));
},
- _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 57e2937..5d8faef 100644
--- a/src/userTracker.js
+++ b/src/userTracker.js
@@ -7,6 +7,50 @@ const Utils = imports.utils;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
+const AccountsMonitor = imports.accountsMonitor;
+const RoomManager = imports.roomManager;
+
+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.accounts.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) {
+ this._userTrackers.delete(account);
+ },
+
+ getUserTrackerForAccount: function(account) {
+ return this._userTrackers.get(account);
+ }
+});
+
+
const UserTracker = new Lang.Class({
Name: 'UserTracker',
Extends: GObject.Object,
@@ -15,21 +59,53 @@ 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._app = Gio.Application.get_default();
- this._room = room;
+ this._app.connect('prepare-shutdown', Lang.bind(this, this._onShutdown));
- this._onRoomAdded(this._room);
- this._onChannelChanged(this._room);
+ this._roomManager = RoomManager.getDefault();
+ this._roomManager.connect('room-added', Lang.bind(this, this._onRoomAdded));
+ this._roomManager.connect('room-removed', Lang.bind(this, this._onRoomRemoved));
},
- _onRoomAdded: function(room) {
+ _onShutdown: function() {
+ for (let room of this._roomData.keys())
+ this._onRoomRemoved(this._roomManager, room);
+ },
+
+ _getRoomContacts: function(room) {
+ return this._roomData.get(room).contactMapping;
+ },
+
+ _getRoomHandlers: function(room) {
+ return this._roomData.get(room).handlerMapping;
+ },
+
+ _getRoomSignals: function(room) {
+ return this._roomData.get(room).roomSignals;
+ },
+
+ _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 +123,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 +150,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 (let [baseNick, contacts] of this._baseNickContacts)
- contacts.slice().forEach((m) => { this._untrackMember(m); });
+ _clearUsersFromRoom: function(room) {
+ let map = this._getRoomContacts(room);
+ for (let [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 (let [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 roomMap = this._getRoomContacts(room);
+ if (this._pushMember(roomMap, baseNick, member) == 1)
+ this._runHandlers(room, member, status);
+
+ let map = this._baseNickContacts;
+ if (this._pushMember(map, baseNick, member) == 1)
this.emit("status-changed::" + baseNick, baseNick, 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 +221,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 roomMap = this._getRoomContacts(room);
+ [found, nContacts] = this._popMember(roomMap, baseNick, member);
+ if (found && nContacts == 0)
+ this._runHandlers(room, member, status);
+
+ 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);
+ }
},
getNickStatus: function(nickName) {
@@ -125,5 +245,44 @@ 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) || [];
+
+ 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]