Store entity identity info, use it in conversation list tooltips
This commit is contained in:
parent
853dfa2d6f
commit
12cd56612d
|
@ -34,6 +34,7 @@ SOURCES
|
||||||
src/service/counterpart_interaction_manager.vala
|
src/service/counterpart_interaction_manager.vala
|
||||||
src/service/database.vala
|
src/service/database.vala
|
||||||
src/service/entity_capabilities_storage.vala
|
src/service/entity_capabilities_storage.vala
|
||||||
|
src/service/entity_info.vala
|
||||||
src/service/file_manager.vala
|
src/service/file_manager.vala
|
||||||
src/service/jingle_file_transfers.vala
|
src/service/jingle_file_transfers.vala
|
||||||
src/service/message_processor.vala
|
src/service/message_processor.vala
|
||||||
|
|
|
@ -44,6 +44,7 @@ public interface Application : GLib.Application {
|
||||||
NotificationEvents.start(stream_interactor);
|
NotificationEvents.start(stream_interactor);
|
||||||
SearchProcessor.start(stream_interactor, db);
|
SearchProcessor.start(stream_interactor, db);
|
||||||
Register.start(stream_interactor, db);
|
Register.start(stream_interactor, db);
|
||||||
|
EntityInfo.start(stream_interactor, db);
|
||||||
|
|
||||||
create_actions();
|
create_actions();
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ using Dino.Entities;
|
||||||
namespace Dino {
|
namespace Dino {
|
||||||
|
|
||||||
public class Database : Qlite.Database {
|
public class Database : Qlite.Database {
|
||||||
private const int VERSION = 12;
|
private const int VERSION = 13;
|
||||||
|
|
||||||
public class AccountTable : Table {
|
public class AccountTable : Table {
|
||||||
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
public Column<int> 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<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
||||||
|
public Column<int> account_id = new Column.Integer("account_id");
|
||||||
|
public Column<int> jid_id = new Column.Integer("jid_id");
|
||||||
|
public Column<string> resource = new Column.Text("resource");
|
||||||
|
public Column<string> caps_hash = new Column.Text("caps_hash");
|
||||||
|
public Column<long> 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 class ContentItemTable : Table {
|
||||||
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
|
||||||
public Column<int> conversation_id = new Column.Integer("conversation_id") { not_null = true };
|
public Column<int> 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<string> entity = new Column.Text("entity");
|
||||||
|
public Column<string> category = new Column.Text("category");
|
||||||
|
public Column<string> type = new Column.Text("type");
|
||||||
|
public Column<string> 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 class EntityFeatureTable : Table {
|
||||||
public Column<string> entity = new Column.Text("entity");
|
public Column<string> entity = new Column.Text("entity");
|
||||||
public Column<string> feature = new Column.Text("feature");
|
public Column<string> feature = new Column.Text("feature");
|
||||||
|
@ -215,12 +244,14 @@ public class Database : Qlite.Database {
|
||||||
|
|
||||||
public AccountTable account { get; private set; }
|
public AccountTable account { get; private set; }
|
||||||
public JidTable jid { get; private set; }
|
public JidTable jid { get; private set; }
|
||||||
|
public EntityTable entity { get; private set; }
|
||||||
public ContentItemTable content_item { get; private set; }
|
public ContentItemTable content_item { get; private set; }
|
||||||
public MessageTable message { get; private set; }
|
public MessageTable message { get; private set; }
|
||||||
public RealJidTable real_jid { get; private set; }
|
public RealJidTable real_jid { get; private set; }
|
||||||
public FileTransferTable file_transfer { get; private set; }
|
public FileTransferTable file_transfer { get; private set; }
|
||||||
public ConversationTable conversation { get; private set; }
|
public ConversationTable conversation { get; private set; }
|
||||||
public AvatarTable avatar { get; private set; }
|
public AvatarTable avatar { get; private set; }
|
||||||
|
public EntityIdentityTable entity_identity { get; private set; }
|
||||||
public EntityFeatureTable entity_feature { get; private set; }
|
public EntityFeatureTable entity_feature { get; private set; }
|
||||||
public RosterTable roster { get; private set; }
|
public RosterTable roster { get; private set; }
|
||||||
public MamCatchupTable mam_catchup { get; private set; }
|
public MamCatchupTable mam_catchup { get; private set; }
|
||||||
|
@ -234,17 +265,19 @@ public class Database : Qlite.Database {
|
||||||
base(fileName, VERSION);
|
base(fileName, VERSION);
|
||||||
account = new AccountTable(this);
|
account = new AccountTable(this);
|
||||||
jid = new JidTable(this);
|
jid = new JidTable(this);
|
||||||
|
entity = new EntityTable(this);
|
||||||
content_item = new ContentItemTable(this);
|
content_item = new ContentItemTable(this);
|
||||||
message = new MessageTable(this);
|
message = new MessageTable(this);
|
||||||
real_jid = new RealJidTable(this);
|
real_jid = new RealJidTable(this);
|
||||||
file_transfer = new FileTransferTable(this);
|
file_transfer = new FileTransferTable(this);
|
||||||
conversation = new ConversationTable(this);
|
conversation = new ConversationTable(this);
|
||||||
avatar = new AvatarTable(this);
|
avatar = new AvatarTable(this);
|
||||||
|
entity_identity = new EntityIdentityTable(this);
|
||||||
entity_feature = new EntityFeatureTable(this);
|
entity_feature = new EntityFeatureTable(this);
|
||||||
roster = new RosterTable(this);
|
roster = new RosterTable(this);
|
||||||
mam_catchup = new MamCatchupTable(this);
|
mam_catchup = new MamCatchupTable(this);
|
||||||
settings = new SettingsTable(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 {
|
try {
|
||||||
exec("PRAGMA synchronous=0");
|
exec("PRAGMA synchronous=0");
|
||||||
} catch (Error e) { }
|
} catch (Error e) { }
|
||||||
|
@ -433,24 +466,6 @@ public class Database : Qlite.Database {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add_entity_features(string entity, Gee.List<string> features) {
|
|
||||||
foreach (string feature in features) {
|
|
||||||
entity_feature.insert()
|
|
||||||
.value(entity_feature.entity, entity)
|
|
||||||
.value(entity_feature.feature, feature)
|
|
||||||
.perform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Gee.List<string> get_entity_features(string entity) {
|
|
||||||
ArrayList<string> ret = new ArrayList<string>();
|
|
||||||
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) {
|
public int get_jid_id(Jid jid_obj) {
|
||||||
var bare_jid = jid_obj.bare_jid;
|
var bare_jid = jid_obj.bare_jid;
|
||||||
if (jid_table_reverse.has_key(bare_jid)) {
|
if (jid_table_reverse.has_key(bare_jid)) {
|
||||||
|
|
|
@ -1,23 +1,69 @@
|
||||||
using Gee;
|
using Gee;
|
||||||
|
using Qlite;
|
||||||
using Xmpp;
|
using Xmpp;
|
||||||
|
using Xmpp.Xep.ServiceDiscovery;
|
||||||
|
|
||||||
namespace Dino {
|
namespace Dino {
|
||||||
|
|
||||||
public class EntityCapabilitiesStorage : Xep.EntityCapabilities.Storage, Object {
|
public class EntityCapabilitiesStorage : Xep.EntityCapabilities.Storage, Object {
|
||||||
|
|
||||||
private Database db;
|
private Database db;
|
||||||
|
private HashMap<string, Gee.List<string>> features_cache = new HashMap<string, Gee.List<string>>();
|
||||||
|
private HashMap<string, Identity> identity_cache = new HashMap<string, Identity>();
|
||||||
|
|
||||||
public EntityCapabilitiesStorage(Database db) {
|
public EntityCapabilitiesStorage(Database db) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void store_features(string entity, Gee.List<string> features) {
|
public void store_features(string entity, Gee.List<string> 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<Identity> 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<string> get_features(string entity) {
|
public Gee.List<string> get_features(string entity) {
|
||||||
return db.get_entity_features(entity);
|
Gee.List<string>? features = features_cache[entity];
|
||||||
|
if (features != null) {
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
features = new ArrayList<string>();
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
67
libdino/src/service/entity_info.vala
Normal file
67
libdino/src/service/entity_info.vala
Normal file
|
@ -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<EntityInfo> IDENTITY = new ModuleIdentity<EntityInfo>("entity_info");
|
||||||
|
public string id { get { return IDENTITY.id; } }
|
||||||
|
|
||||||
|
private StreamInteractor stream_interactor;
|
||||||
|
private Database db;
|
||||||
|
private EntityCapabilitiesStorage entity_capabilities_storage;
|
||||||
|
|
||||||
|
|
||||||
|
private HashMap<Jid, string> entity_caps_hashes = new HashMap<Jid, string>(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<XmppStreamModule> modules) {
|
||||||
|
modules.add(new Xep.EntityCapabilities.Module(entity_capabilities_storage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,14 +8,8 @@ namespace Dino {
|
||||||
public class ModuleManager {
|
public class ModuleManager {
|
||||||
private HashMap<Account, ArrayList<XmppStreamModule>> module_map = new HashMap<Account, ArrayList<XmppStreamModule>>(Account.hash_func, Account.equals_func);
|
private HashMap<Account, ArrayList<XmppStreamModule>> module_map = new HashMap<Account, ArrayList<XmppStreamModule>>(Account.hash_func, Account.equals_func);
|
||||||
|
|
||||||
private EntityCapabilitiesStorage entity_capabilities_storage;
|
|
||||||
|
|
||||||
public signal void initialize_account_modules(Account account, ArrayList<XmppStreamModule> modules);
|
public signal void initialize_account_modules(Account account, ArrayList<XmppStreamModule> modules);
|
||||||
|
|
||||||
public ModuleManager(Database db) {
|
|
||||||
entity_capabilities_storage = new EntityCapabilitiesStorage(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
public T? get_module<T>(Account account, Xmpp.ModuleIdentity<T> identity) {
|
public T? get_module<T>(Account account, Xmpp.ModuleIdentity<T> identity) {
|
||||||
if (identity == null) return null;
|
if (identity == null) return null;
|
||||||
lock (module_map) {
|
lock (module_map) {
|
||||||
|
@ -59,7 +53,7 @@ public class ModuleManager {
|
||||||
module_map[account].add(new Bind.Module(account.resourcepart));
|
module_map[account].add(new Bind.Module(account.resourcepart));
|
||||||
module_map[account].add(new Session.Module());
|
module_map[account].add(new Session.Module());
|
||||||
module_map[account].add(new Roster.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.PrivateXmlStorage.Module());
|
||||||
module_map[account].add(new Xep.Bookmarks.Module());
|
module_map[account].add(new Xep.Bookmarks.Module());
|
||||||
module_map[account].add(new Xep.Bookmarks2.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.MessageCarbons.Module());
|
||||||
module_map[account].add(new Xep.Muc.Module());
|
module_map[account].add(new Xep.Muc.Module());
|
||||||
module_map[account].add(new Xep.Pubsub.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.MessageDeliveryReceipts.Module());
|
||||||
module_map[account].add(new Xep.BlockingCommand.Module());
|
module_map[account].add(new Xep.BlockingCommand.Module());
|
||||||
module_map[account].add(new Xep.ChatStateNotifications.Module());
|
module_map[account].add(new Xep.ChatStateNotifications.Module());
|
||||||
|
|
|
@ -17,7 +17,7 @@ public class StreamInteractor : Object {
|
||||||
private ArrayList<StreamInteractionModule> modules = new ArrayList<StreamInteractionModule>();
|
private ArrayList<StreamInteractionModule> modules = new ArrayList<StreamInteractionModule>();
|
||||||
|
|
||||||
public StreamInteractor(Database db) {
|
public StreamInteractor(Database db) {
|
||||||
module_manager = new ModuleManager(db);
|
module_manager = new ModuleManager();
|
||||||
connection_manager = new ConnectionManager(module_manager);
|
connection_manager = new ConnectionManager(module_manager);
|
||||||
|
|
||||||
connection_manager.stream_opened.connect(on_stream_opened);
|
connection_manager.stream_opened.connect(on_stream_opened);
|
||||||
|
|
|
@ -28,6 +28,9 @@ set(RESOURCE_LIST
|
||||||
icons/im.dino.Dino-symbolic.svg
|
icons/im.dino.Dino-symbolic.svg
|
||||||
icons/dino-tick-symbolic.svg
|
icons/dino-tick-symbolic.svg
|
||||||
|
|
||||||
|
icons/dino-device-desktop-symbolic.svg
|
||||||
|
icons/dino-device-phone-symbolic.svg
|
||||||
|
|
||||||
icons/dino-file-document-symbolic.svg
|
icons/dino-file-document-symbolic.svg
|
||||||
icons/dino-file-download-symbolic.svg
|
icons/dino-file-download-symbolic.svg
|
||||||
icons/dino-file-image-symbolic.svg
|
icons/dino-file-image-symbolic.svg
|
||||||
|
@ -45,11 +48,10 @@ set(RESOURCE_LIST
|
||||||
contact_details_dialog.ui
|
contact_details_dialog.ui
|
||||||
conversation_list_titlebar.ui
|
conversation_list_titlebar.ui
|
||||||
conversation_list_titlebar_csd.ui
|
conversation_list_titlebar_csd.ui
|
||||||
|
conversation_row.ui
|
||||||
conversation_view.ui
|
conversation_view.ui
|
||||||
emojichooser.ui
|
emojichooser.ui
|
||||||
global_search.ui
|
global_search.ui
|
||||||
conversation_selector/chat_row_tooltip.ui
|
|
||||||
conversation_selector/conversation_row.ui
|
|
||||||
conversation_content_view/image_toolbar.ui
|
conversation_content_view/image_toolbar.ui
|
||||||
conversation_content_view/item_metadata_header.ui
|
conversation_content_view/item_metadata_header.ui
|
||||||
conversation_content_view/view.ui
|
conversation_content_view/view.ui
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<interface>
|
|
||||||
<object class="GtkBox" id="main_box">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="jid_label">
|
|
||||||
<property name="xalign">0</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
|
|
||||||
</attributes>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox" id="inner_box">
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</interface>
|
|
5
main/data/icons/dino-device-desktop-symbolic.svg
Normal file
5
main/data/icons/dino-device-desktop-symbolic.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m3 4h18a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1h1l2 3v1h-24v-1l2-3h1a1 1 0 0 1-1-1v-11a1 1 0 0 1 1-1m1 2v9h16v-9h-16z"/>
|
||||||
|
<rect x="4" y="6" width="16" height="9" opacity=".3" stroke-linecap="round" stroke-width="1.8898"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 346 B |
6
main/data/icons/dino-device-phone-symbolic.svg
Normal file
6
main/data/icons/dino-device-phone-symbolic.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17.25,18H6.75V4H17.25M14,21H10V20H14M16,1H8A3,3 0 0,0 5,4V20A3,3 0 0,0 8,23H16A3,3 0 0,0 19,20V4A3,3 0 0,0 16,1Z"/>
|
||||||
|
<rect x="6.75" y="4" width="10.5" height="14" fill="#030202" opacity=".3" stroke-linecap="round" stroke-width="1.8898"/>
|
||||||
|
<rect x="10" y="20" width="4" height="1" fill="#070504" opacity=".3" stroke-linecap="round" stroke-width="1.8898"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 490 B |
|
@ -9,7 +9,7 @@ using Xmpp;
|
||||||
|
|
||||||
namespace Dino.Ui {
|
namespace Dino.Ui {
|
||||||
|
|
||||||
[GtkTemplate (ui = "/im/dino/Dino/conversation_selector/conversation_row.ui")]
|
[GtkTemplate (ui = "/im/dino/Dino/conversation_row.ui")]
|
||||||
public class ConversationSelectorRow : ListBoxRow {
|
public class ConversationSelectorRow : ListBoxRow {
|
||||||
|
|
||||||
[GtkChild] protected AvatarImage image;
|
[GtkChild] protected AvatarImage image;
|
||||||
|
@ -217,29 +217,6 @@ public class ConversationSelectorRow : ListBoxRow {
|
||||||
message_label.label = message_label.label;
|
message_label.label = message_label.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Box get_fulljid_box(Jid full_jid) {
|
|
||||||
Box box = new Box(Orientation.HORIZONTAL, 5) { visible=true };
|
|
||||||
|
|
||||||
Show show = stream_interactor.get_module(PresenceManager.IDENTITY).get_last_show(full_jid, conversation.account);
|
|
||||||
Image image = new Image() { visible=true };
|
|
||||||
if (show.as == Show.AWAY) {
|
|
||||||
image.set_from_icon_name("dino-status-away", IconSize.SMALL_TOOLBAR);
|
|
||||||
} else if (show.as == Show.XA || show.as == Show.DND) {
|
|
||||||
image.set_from_icon_name("dino-status-dnd", IconSize.SMALL_TOOLBAR);
|
|
||||||
} else if (show.as == Show.CHAT) {
|
|
||||||
image.set_from_icon_name("dino-status-chat", IconSize.SMALL_TOOLBAR);
|
|
||||||
} else {
|
|
||||||
image.set_from_icon_name("dino-status-online", IconSize.SMALL_TOOLBAR);
|
|
||||||
}
|
|
||||||
box.add(image);
|
|
||||||
|
|
||||||
Label resource = new Label(full_jid.resourcepart) { visible=true };
|
|
||||||
resource.xalign = 0;
|
|
||||||
box.add(resource);
|
|
||||||
box.show_all();
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void state_flags_changed(StateFlags flags) {
|
public override void state_flags_changed(StateFlags flags) {
|
||||||
StateFlags curr_flags = get_state_flags();
|
StateFlags curr_flags = get_state_flags();
|
||||||
if ((curr_flags & StateFlags.PRELIGHT) != 0) {
|
if ((curr_flags & StateFlags.PRELIGHT) != 0) {
|
||||||
|
@ -251,21 +228,68 @@ public class ConversationSelectorRow : ListBoxRow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Widget generate_tooltip() {
|
private static Regex dino_resource_regex = /^dino\.[a-f0-9]{8}$/;
|
||||||
Builder builder = new Builder.from_resource("/im/dino/Dino/conversation_selector/chat_row_tooltip.ui");
|
|
||||||
Box main_box = builder.get_object("main_box") as Box;
|
|
||||||
Box inner_box = builder.get_object("inner_box") as Box;
|
|
||||||
Label jid_label = builder.get_object("jid_label") as Label;
|
|
||||||
|
|
||||||
jid_label.label = conversation.counterpart.to_string();
|
private Widget generate_tooltip() {
|
||||||
|
Grid grid = new Grid() { row_spacing=5, column_homogeneous=false, column_spacing=2, margin_start=5, margin_end=5, margin_top=2, margin_bottom=2, visible=true };
|
||||||
|
|
||||||
|
Label label = new Label(conversation.counterpart.to_string()) { valign=Align.START, xalign=0, visible=true };
|
||||||
|
label.attributes = new AttrList();
|
||||||
|
label.attributes.insert(attr_weight_new(Weight.BOLD));
|
||||||
|
|
||||||
|
grid.attach(label, 0, 0, 2, 1);
|
||||||
|
|
||||||
Gee.List<Jid>? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(conversation.counterpart, conversation.account);
|
Gee.List<Jid>? full_jids = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(conversation.counterpart, conversation.account);
|
||||||
if (full_jids != null) {
|
if (full_jids == null) return grid;
|
||||||
|
|
||||||
for (int i = 0; i < full_jids.size; i++) {
|
for (int i = 0; i < full_jids.size; i++) {
|
||||||
inner_box.add(get_fulljid_box(full_jids[i]));
|
Jid full_jid = full_jids[i];
|
||||||
|
Show show = stream_interactor.get_module(PresenceManager.IDENTITY).get_last_show(full_jid, conversation.account);
|
||||||
|
Xep.ServiceDiscovery.Identity? identity = stream_interactor.get_module(EntityInfo.IDENTITY).get_identity(conversation.account, full_jid);
|
||||||
|
|
||||||
|
Image image = new Image() { hexpand=false, valign=Align.START, visible=true };
|
||||||
|
grid.attach(image, 0, i + 1, 1, 1);
|
||||||
|
if (identity != null && identity.type_ == Xep.ServiceDiscovery.Identity.TYPE_PHONE || identity.type_ == Xep.ServiceDiscovery.Identity.TYPE_TABLET) {
|
||||||
|
image.set_from_icon_name("dino-device-phone-symbolic", IconSize.SMALL_TOOLBAR);
|
||||||
|
} else {
|
||||||
|
image.set_from_icon_name("dino-device-desktop-symbolic", IconSize.SMALL_TOOLBAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (show.as == Show.AWAY) {
|
||||||
|
Util.force_color(image, "#FF9800");
|
||||||
|
} else if (show.as == Show.XA || show.as == Show.DND) {
|
||||||
|
Util.force_color(image, "#FF5722");
|
||||||
|
} else {
|
||||||
|
Util.force_color(image, "#4CAF50");
|
||||||
}
|
}
|
||||||
return main_box;
|
|
||||||
|
string? status = null;
|
||||||
|
if (show.as == Show.AWAY) {
|
||||||
|
status = "away";
|
||||||
|
} else if (show.as == Show.XA) {
|
||||||
|
status = "not available";
|
||||||
|
} else if (show.as == Show.DND) {
|
||||||
|
status = "do not disturb";
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
if (identity.name != null) {
|
||||||
|
sb.append(identity.name);
|
||||||
|
} else if (dino_resource_regex.match(full_jid.resourcepart)) {
|
||||||
|
sb.append("Dino");
|
||||||
|
} else {
|
||||||
|
sb.append(full_jid.resourcepart);
|
||||||
|
}
|
||||||
|
if (status != null) {
|
||||||
|
sb.append(" <i>(");
|
||||||
|
sb.append(status);
|
||||||
|
sb.append(")</i>");
|
||||||
|
}
|
||||||
|
|
||||||
|
Label resource = new Label(sb.str) { use_markup=true, hexpand=true, xalign=0, visible=true };
|
||||||
|
grid.attach(resource, 1, i + 1, 1, 1);
|
||||||
|
}
|
||||||
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string get_relative_time(DateTime datetime) {
|
private static string get_relative_time(DateTime datetime) {
|
||||||
|
|
|
@ -4,6 +4,10 @@ public class Identity {
|
||||||
public const string CATEGORY_CLIENT = "client";
|
public const string CATEGORY_CLIENT = "client";
|
||||||
public const string CATEGORY_CONFERENCE = "conference";
|
public const string CATEGORY_CONFERENCE = "conference";
|
||||||
|
|
||||||
|
public const string TYPE_PC = "pc";
|
||||||
|
public const string TYPE_PHONE = "phone";
|
||||||
|
public const string TYPE_TABLET = "tablet";
|
||||||
|
|
||||||
public string category { get; set; }
|
public string category { get; set; }
|
||||||
public string type_ { get; set; }
|
public string type_ { get; set; }
|
||||||
public string? name { get; set; }
|
public string? name { get; set; }
|
||||||
|
|
|
@ -3,12 +3,24 @@ using Gee;
|
||||||
namespace Xmpp.Xep.EntityCapabilities {
|
namespace Xmpp.Xep.EntityCapabilities {
|
||||||
private const string NS_URI = "http://jabber.org/protocol/caps";
|
private const string NS_URI = "http://jabber.org/protocol/caps";
|
||||||
|
|
||||||
|
private Regex? sha1_base64_regex = null;
|
||||||
|
|
||||||
|
public string? get_caps_hash(Presence.Stanza presence) {
|
||||||
|
if (sha1_base64_regex == null) {
|
||||||
|
sha1_base64_regex = /^[A-Za-z0-9+\/]{27}=$/;
|
||||||
|
}
|
||||||
|
StanzaNode? c_node = presence.stanza.get_subnode("c", NS_URI);
|
||||||
|
if (c_node == null) return null;
|
||||||
|
string? ver_attribute = c_node.get_attribute("ver", NS_URI);
|
||||||
|
if (ver_attribute == null || !sha1_base64_regex.match(ver_attribute)) return null;
|
||||||
|
return ver_attribute;
|
||||||
|
}
|
||||||
|
|
||||||
public class Module : XmppStreamModule {
|
public class Module : XmppStreamModule {
|
||||||
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0115_entity_capabilities");
|
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0115_entity_capabilities");
|
||||||
|
|
||||||
private string own_ver_hash;
|
private string own_ver_hash;
|
||||||
private Storage storage;
|
private Storage storage;
|
||||||
private Regex sha1_base64_regex = /^[A-Za-z0-9+\/]{27}=$/;
|
|
||||||
|
|
||||||
public Module(Storage storage) {
|
public Module(Storage storage) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
@ -45,20 +57,19 @@ namespace Xmpp.Xep.EntityCapabilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_received_presence(XmppStream stream, Presence.Stanza presence) {
|
private void on_received_presence(XmppStream stream, Presence.Stanza presence) {
|
||||||
StanzaNode? c_node = presence.stanza.get_subnode("c", NS_URI);
|
string? caps_hash = get_caps_hash(presence);
|
||||||
if (c_node != null) {
|
if (caps_hash == null) return;
|
||||||
string? ver_attribute = c_node.get_attribute("ver", NS_URI);
|
|
||||||
if (ver_attribute == null || !sha1_base64_regex.match(ver_attribute)) return;
|
Gee.List<string> capabilities = storage.get_features(caps_hash);
|
||||||
Gee.List<string> capabilities = storage.get_features(ver_attribute);
|
ServiceDiscovery.Identity identity = storage.get_identities(caps_hash);
|
||||||
if (capabilities.size == 0) {
|
if (identity == null) {
|
||||||
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, presence.from, (stream, query_result) => {
|
stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, presence.from, (stream, query_result) => {
|
||||||
store_entity_result(stream, ver_attribute, query_result);
|
store_entity_result(stream, caps_hash, query_result);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
stream.get_flag(ServiceDiscovery.Flag.IDENTITY).set_entity_features(presence.from, capabilities);
|
stream.get_flag(ServiceDiscovery.Flag.IDENTITY).set_entity_features(presence.from, capabilities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void store_entity_result(XmppStream stream, string entity, ServiceDiscovery.InfoResult? query_result) {
|
private void store_entity_result(XmppStream stream, string entity, ServiceDiscovery.InfoResult? query_result) {
|
||||||
if (query_result == null) return;
|
if (query_result == null) return;
|
||||||
|
@ -69,6 +80,7 @@ namespace Xmpp.Xep.EntityCapabilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compute_hash(query_result.identities, query_result.features, data_forms) == entity) {
|
if (compute_hash(query_result.identities, query_result.features, data_forms) == entity) {
|
||||||
|
storage.store_identities(entity, query_result.identities);
|
||||||
storage.store_features(entity, query_result.features);
|
storage.store_features(entity, query_result.features);
|
||||||
stream.get_flag(ServiceDiscovery.Flag.IDENTITY).set_entity_features(query_result.iq.from, query_result.features);
|
stream.get_flag(ServiceDiscovery.Flag.IDENTITY).set_entity_features(query_result.iq.from, query_result.features);
|
||||||
}
|
}
|
||||||
|
@ -142,7 +154,9 @@ namespace Xmpp.Xep.EntityCapabilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Storage : Object {
|
public interface Storage : Object {
|
||||||
|
public abstract void store_identities(string entity, Gee.List<ServiceDiscovery.Identity> identities);
|
||||||
public abstract void store_features(string entity, Gee.List<string> capabilities);
|
public abstract void store_features(string entity, Gee.List<string> capabilities);
|
||||||
|
public abstract ServiceDiscovery.Identity? get_identities(string entity);
|
||||||
public abstract Gee.List<string> get_features(string entity);
|
public abstract Gee.List<string> get_features(string entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue