From 12cd56612dd6edd056e2cd8aae59ea3ae8f05d1e Mon Sep 17 00:00:00 2001 From: fiaxh Date: Sun, 29 Mar 2020 20:23:47 +0200 Subject: Store entity identity info, use it in conversation list tooltips --- libdino/src/application.vala | 1 + libdino/src/service/database.vala | 55 +++++++++++------- .../src/service/entity_capabilities_storage.vala | 52 ++++++++++++++++- libdino/src/service/entity_info.vala | 67 ++++++++++++++++++++++ libdino/src/service/module_manager.vala | 9 +-- libdino/src/service/stream_interactor.vala | 2 +- 6 files changed, 154 insertions(+), 32 deletions(-) create mode 100644 libdino/src/service/entity_info.vala (limited to 'libdino/src') diff --git a/libdino/src/application.vala b/libdino/src/application.vala index e7e02be7..ac9a4e4b 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -44,6 +44,7 @@ public interface Application : GLib.Application { NotificationEvents.start(stream_interactor); SearchProcessor.start(stream_interactor, db); Register.start(stream_interactor, db); + EntityInfo.start(stream_interactor, db); create_actions(); diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 34bbea7a..ebf05637 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -7,7 +7,7 @@ using Dino.Entities; namespace Dino { public class Database : Qlite.Database { - private const int VERSION = 12; + private const int VERSION = 13; public class AccountTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; @@ -35,6 +35,21 @@ public class Database : Qlite.Database { } } + public class EntityTable : Table { + public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; + public Column account_id = new Column.Integer("account_id"); + public Column jid_id = new Column.Integer("jid_id"); + public Column resource = new Column.Text("resource"); + public Column caps_hash = new Column.Text("caps_hash"); + public Column last_seen = new Column.Long("last_seen"); + + internal EntityTable(Database db) { + base(db, "entity"); + init({id, account_id, jid_id, resource, caps_hash, last_seen}); + unique({account_id, jid_id, resource}, "IGNORE"); + } + } + public class ContentItemTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; public Column conversation_id = new Column.Integer("conversation_id") { not_null = true }; @@ -162,6 +177,20 @@ public class Database : Qlite.Database { } } + public class EntityIdentityTable : Table { + public Column entity = new Column.Text("entity"); + public Column category = new Column.Text("category"); + public Column type = new Column.Text("type"); + public Column name = new Column.Text("name"); + + internal EntityIdentityTable(Database db) { + base(db, "entity_identity"); + init({entity, category, name, type}); + unique({entity, category, type}, "IGNORE"); + index("entity_identity_idx", {entity}); + } + } + public class EntityFeatureTable : Table { public Column entity = new Column.Text("entity"); public Column feature = new Column.Text("feature"); @@ -215,12 +244,14 @@ public class Database : Qlite.Database { public AccountTable account { get; private set; } public JidTable jid { get; private set; } + public EntityTable entity { get; private set; } public ContentItemTable content_item { get; private set; } public MessageTable message { get; private set; } public RealJidTable real_jid { get; private set; } public FileTransferTable file_transfer { get; private set; } public ConversationTable conversation { get; private set; } public AvatarTable avatar { get; private set; } + public EntityIdentityTable entity_identity { get; private set; } public EntityFeatureTable entity_feature { get; private set; } public RosterTable roster { get; private set; } public MamCatchupTable mam_catchup { get; private set; } @@ -234,17 +265,19 @@ public class Database : Qlite.Database { base(fileName, VERSION); account = new AccountTable(this); jid = new JidTable(this); + entity = new EntityTable(this); content_item = new ContentItemTable(this); message = new MessageTable(this); real_jid = new RealJidTable(this); file_transfer = new FileTransferTable(this); conversation = new ConversationTable(this); avatar = new AvatarTable(this); + entity_identity = new EntityIdentityTable(this); entity_feature = new EntityFeatureTable(this); roster = new RosterTable(this); mam_catchup = new MamCatchupTable(this); settings = new SettingsTable(this); - init({ account, jid, content_item, message, real_jid, file_transfer, conversation, avatar, entity_feature, roster, mam_catchup, settings }); + init({ account, jid, entity, content_item, message, real_jid, file_transfer, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, settings }); try { exec("PRAGMA synchronous=0"); } catch (Error e) { } @@ -433,24 +466,6 @@ public class Database : Qlite.Database { return ret; } - public void add_entity_features(string entity, Gee.List features) { - foreach (string feature in features) { - entity_feature.insert() - .value(entity_feature.entity, entity) - .value(entity_feature.feature, feature) - .perform(); - } - } - - public Gee.List get_entity_features(string entity) { - ArrayList ret = new ArrayList(); - foreach (Row row in entity_feature.select({entity_feature.feature}).with(entity_feature.entity, "=", entity)) { - ret.add(row[entity_feature.feature]); - } - return ret; - } - - public int get_jid_id(Jid jid_obj) { var bare_jid = jid_obj.bare_jid; if (jid_table_reverse.has_key(bare_jid)) { diff --git a/libdino/src/service/entity_capabilities_storage.vala b/libdino/src/service/entity_capabilities_storage.vala index 94d9d88e..d9f66913 100644 --- a/libdino/src/service/entity_capabilities_storage.vala +++ b/libdino/src/service/entity_capabilities_storage.vala @@ -1,23 +1,69 @@ using Gee; - +using Qlite; using Xmpp; +using Xmpp.Xep.ServiceDiscovery; namespace Dino { public class EntityCapabilitiesStorage : Xep.EntityCapabilities.Storage, Object { private Database db; + private HashMap> features_cache = new HashMap>(); + private HashMap identity_cache = new HashMap(); public EntityCapabilitiesStorage(Database db) { this.db = db; } public void store_features(string entity, Gee.List features) { - db.add_entity_features(entity, features); + foreach (string feature in features) { + db.entity_feature.insert() + .value(db.entity_feature.entity, entity) + .value(db.entity_feature.feature, feature) + .perform(); + } + } + + public void store_identities(string entity, Gee.List identities) { + foreach (Identity identity in identities) { + if (identity.category == Identity.CATEGORY_CLIENT) { + db.entity_identity.insert() + .value(db.entity_identity.entity, entity) + .value(db.entity_identity.category, identity.category) + .value(db.entity_identity.type, identity.type_) + .value(db.entity_identity.name, identity.name) + .perform(); + return; + } + } } public Gee.List get_features(string entity) { - return db.get_entity_features(entity); + Gee.List? features = features_cache[entity]; + if (features != null) { + return features; + } + + features = new ArrayList(); + foreach (Row row in db.entity_feature.select({db.entity_feature.feature}).with(db.entity_feature.entity, "=", entity)) { + features.add(row[db.entity_feature.feature]); + } + features_cache[entity] = features; + return features; + } + + public Identity? get_identities(string entity) { + Identity? identity = identity_cache[entity]; + if (identity != null) { + return identity; + } + + RowOption row = db.entity_identity.select().with(db.entity_identity.entity, "=", entity).single().row(); + if (row.is_present()) { + identity = new Identity(row[db.entity_identity.category], row[db.entity_identity.type], row[db.entity_identity.name]); + } + identity_cache[entity] = identity; + return identity; } } } diff --git a/libdino/src/service/entity_info.vala b/libdino/src/service/entity_info.vala new file mode 100644 index 00000000..8efea7e5 --- /dev/null +++ b/libdino/src/service/entity_info.vala @@ -0,0 +1,67 @@ +using Gee; +using Dino.Entities; +using Xmpp; +using Xmpp.Xep; +using Xmpp.Xep.ServiceDiscovery; + +namespace Dino { +public class EntityInfo : StreamInteractionModule, Object { + public static ModuleIdentity IDENTITY = new ModuleIdentity("entity_info"); + public string id { get { return IDENTITY.id; } } + + private StreamInteractor stream_interactor; + private Database db; + private EntityCapabilitiesStorage entity_capabilities_storage; + + + private HashMap entity_caps_hashes = new HashMap(Jid.hash_func, Jid.equals_func); + + public static void start(StreamInteractor stream_interactor, Database db) { + EntityInfo m = new EntityInfo(stream_interactor, db); + stream_interactor.add_module(m); + } + + private EntityInfo(StreamInteractor stream_interactor, Database db) { + this.stream_interactor = stream_interactor; + this.db = db; + this.entity_capabilities_storage = new EntityCapabilitiesStorage(db); + + stream_interactor.account_added.connect(on_account_added); + stream_interactor.module_manager.initialize_account_modules.connect(initialize_modules); + } + + public Identity? get_identity(Account account, Jid jid) { + string? caps_hash = entity_caps_hashes[jid]; + if (caps_hash == null) return null; + return entity_capabilities_storage.get_identities(caps_hash); + } + + private void on_received_available_presence(Account account, Presence.Stanza presence) { + bool is_gc = stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(presence.from.bare_jid, account); + if (is_gc) return; + + string? caps_hash = EntityCapabilities.get_caps_hash(presence); + if (caps_hash == null) return; + + db.entity.upsert() + .value(db.entity.account_id, account.id, true) + .value(db.entity.jid_id, db.get_jid_id(presence.from), true) + .value(db.entity.resource, presence.from.resourcepart, true) + .value(db.entity.last_seen, (long)(new DateTime.now_local()).to_unix()) + .value(db.entity.caps_hash, caps_hash) + .perform(); + + if (caps_hash != null) { + entity_caps_hashes[presence.from] = caps_hash; + } + } + + private void on_account_added(Account account) { + stream_interactor.module_manager.get_module(account, Presence.Module.IDENTITY).received_available.connect((stream, presence) => on_received_available_presence(account, presence)); + } + + private void initialize_modules(Account account, ArrayList modules) { + modules.add(new Xep.EntityCapabilities.Module(entity_capabilities_storage)); + } +} +} diff --git a/libdino/src/service/module_manager.vala b/libdino/src/service/module_manager.vala index a3f56652..0cd76a14 100644 --- a/libdino/src/service/module_manager.vala +++ b/libdino/src/service/module_manager.vala @@ -8,14 +8,8 @@ namespace Dino { public class ModuleManager { private HashMap> module_map = new HashMap>(Account.hash_func, Account.equals_func); - private EntityCapabilitiesStorage entity_capabilities_storage; - public signal void initialize_account_modules(Account account, ArrayList modules); - public ModuleManager(Database db) { - entity_capabilities_storage = new EntityCapabilitiesStorage(db); - } - public T? get_module(Account account, Xmpp.ModuleIdentity identity) { if (identity == null) return null; lock (module_map) { @@ -59,7 +53,7 @@ public class ModuleManager { module_map[account].add(new Bind.Module(account.resourcepart)); module_map[account].add(new Session.Module()); module_map[account].add(new Roster.Module()); - module_map[account].add(new Xep.ServiceDiscovery.Module.with_identity("client", "pc")); + module_map[account].add(new Xep.ServiceDiscovery.Module.with_identity("client", "pc", "Dino")); module_map[account].add(new Xep.PrivateXmlStorage.Module()); module_map[account].add(new Xep.Bookmarks.Module()); module_map[account].add(new Xep.Bookmarks2.Module()); @@ -69,7 +63,6 @@ public class ModuleManager { module_map[account].add(new Xep.MessageCarbons.Module()); module_map[account].add(new Xep.Muc.Module()); module_map[account].add(new Xep.Pubsub.Module()); - module_map[account].add(new Xep.EntityCapabilities.Module(entity_capabilities_storage)); module_map[account].add(new Xep.MessageDeliveryReceipts.Module()); module_map[account].add(new Xep.BlockingCommand.Module()); module_map[account].add(new Xep.ChatStateNotifications.Module()); diff --git a/libdino/src/service/stream_interactor.vala b/libdino/src/service/stream_interactor.vala index a1770bb8..1ace195d 100644 --- a/libdino/src/service/stream_interactor.vala +++ b/libdino/src/service/stream_interactor.vala @@ -17,7 +17,7 @@ public class StreamInteractor : Object { private ArrayList modules = new ArrayList(); public StreamInteractor(Database db) { - module_manager = new ModuleManager(db); + module_manager = new ModuleManager(); connection_manager = new ConnectionManager(module_manager); connection_manager.stream_opened.connect(on_stream_opened); -- cgit v1.2.3-70-g09d2