aboutsummaryrefslogtreecommitdiff
path: root/libdino/src/service
diff options
context:
space:
mode:
authoreerielili <lionel@les-miquelots.net>2024-08-25 13:32:38 +0000
committerGitHub <noreply@github.com>2024-08-25 13:32:38 +0000
commit45755727db79a2935376d24e7bde7eadb0f2f7ca (patch)
tree73715da99c9d980079df6f2d561822364655e04d /libdino/src/service
parent62cdea3a5e701c04f3a7fd9d6b5f48e28fef1f72 (diff)
parent51252f74c94c17d56aa75534652bdc5d43a504cb (diff)
downloaddino-45755727db79a2935376d24e7bde7eadb0f2f7ca.tar.gz
dino-45755727db79a2935376d24e7bde7eadb0f2f7ca.zip
Merge branch 'master' into add-yourselfadd-yourself
Diffstat (limited to 'libdino/src/service')
-rw-r--r--libdino/src/service/avatar_manager.vala43
-rw-r--r--libdino/src/service/conversation_manager.vala8
-rw-r--r--libdino/src/service/database.vala32
-rw-r--r--libdino/src/service/entity_info.vala35
-rw-r--r--libdino/src/service/fallback_body.vala15
-rw-r--r--libdino/src/service/message_correction.vala28
-rw-r--r--libdino/src/service/message_processor.vala70
-rw-r--r--libdino/src/service/muc_manager.vala21
-rw-r--r--libdino/src/service/replies.vala15
9 files changed, 156 insertions, 111 deletions
diff --git a/libdino/src/service/avatar_manager.vala b/libdino/src/service/avatar_manager.vala
index 3bd38e72..f99f37d4 100644
--- a/libdino/src/service/avatar_manager.vala
+++ b/libdino/src/service/avatar_manager.vala
@@ -160,30 +160,32 @@ public class AvatarManager : StreamInteractionModule, Object {
}
}
+ public void unset_avatar(Account account) {
+ XmppStream stream = stream_interactor.get_stream(account);
+ if (stream == null) return;
+ Xmpp.Xep.UserAvatars.unset_avatar(stream);
+ }
+
private void on_account_added(Account account) {
stream_interactor.module_manager.get_module(account, Xep.UserAvatars.Module.IDENTITY).received_avatar_hash.connect((stream, jid, id) =>
- on_user_avatar_received.begin(account, jid, id)
+ on_user_avatar_received(account, jid, id)
);
+ stream_interactor.module_manager.get_module(account, Xep.UserAvatars.Module.IDENTITY).avatar_removed.connect((stream, jid) => {
+ on_user_avatar_removed(account, jid);
+ });
stream_interactor.module_manager.get_module(account, Xep.VCard.Module.IDENTITY).received_avatar_hash.connect((stream, jid, id) =>
- on_vcard_avatar_received.begin(account, jid, id)
+ on_vcard_avatar_received(account, jid, id)
);
foreach (var entry in get_avatar_hashes(account, Source.USER_AVATARS).entries) {
- on_user_avatar_received.begin(account, entry.key, entry.value);
+ on_user_avatar_received(account, entry.key, entry.value);
}
foreach (var entry in get_avatar_hashes(account, Source.VCARD).entries) {
-
- // FIXME: remove. temporary to remove falsely saved avatars.
- if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(entry.key, account)) {
- db.avatar.delete().with(db.avatar.jid_id, "=", db.get_jid_id(entry.key)).perform();
- continue;
- }
-
- on_vcard_avatar_received.begin(account, entry.key, entry.value);
+ on_vcard_avatar_received(account, entry.key, entry.value);
}
}
- private async void on_user_avatar_received(Account account, Jid jid_, string id) {
+ private void on_user_avatar_received(Account account, Jid jid_, string id) {
Jid jid = jid_.bare_jid;
if (!user_avatars.has_key(jid) || user_avatars[jid] != id) {
@@ -193,7 +195,14 @@ public class AvatarManager : StreamInteractionModule, Object {
received_avatar(jid, account);
}
- private async void on_vcard_avatar_received(Account account, Jid jid_, string id) {
+ private void on_user_avatar_removed(Account account, Jid jid_) {
+ Jid jid = jid_.bare_jid;
+ user_avatars.unset(jid);
+ remove_avatar_hash(account, jid, Source.USER_AVATARS);
+ received_avatar(jid, account);
+ }
+
+ private void on_vcard_avatar_received(Account account, Jid jid_, string id) {
bool is_gc = stream_interactor.get_module(MucManager.IDENTITY).might_be_groupchat(jid_.bare_jid, account);
Jid jid = is_gc ? jid_ : jid_.bare_jid;
@@ -215,6 +224,14 @@ public class AvatarManager : StreamInteractionModule, Object {
.perform();
}
+ public void remove_avatar_hash(Account account, Jid jid, int type) {
+ db.avatar.delete()
+ .with(db.avatar.jid_id, "=", db.get_jid_id(jid))
+ .with(db.avatar.account_id, "=", account.id)
+ .with(db.avatar.type_, "=", type)
+ .perform();
+ }
+
public HashMap<Jid, string> get_avatar_hashes(Account account, int type) {
HashMap<Jid, string> ret = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
foreach (Row row in db.avatar.select({db.avatar.jid_id, db.avatar.hash})
diff --git a/libdino/src/service/conversation_manager.vala b/libdino/src/service/conversation_manager.vala
index f966ccc7..a757e8af 100644
--- a/libdino/src/service/conversation_manager.vala
+++ b/libdino/src/service/conversation_manager.vala
@@ -48,6 +48,14 @@ public class ConversationManager : StreamInteractionModule, Object {
// Create a new converation
Conversation conversation = new Conversation(jid, account, type);
+ // Set encryption for conversation
+ if (type == Conversation.Type.CHAT ||
+ (type == Conversation.Type.GROUPCHAT && stream_interactor.get_module(MucManager.IDENTITY).is_private_room(account, jid))) {
+ conversation.encryption = Application.get_default().settings.get_default_encryption(account);
+ } else {
+ conversation.encryption = Encryption.NONE;
+ }
+
add_conversation(conversation);
conversation.persist(db);
return conversation;
diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala
index dc1d68f3..eba8b7ca 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 = 26;
+ private const int VERSION = 27;
public class AccountTable : Table {
public Column<int> id = new Column.Integer("id") { primary_key = true, auto_increment = true };
@@ -354,6 +354,29 @@ public class Database : Qlite.Database {
}
}
+ public class AccountSettingsTable : 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") { not_null = true };
+ public Column<string> key = new Column.Text("key") { not_null = true };
+ public Column<string> value = new Column.Text("value");
+
+ internal AccountSettingsTable(Database db) {
+ base(db, "account_settings");
+ init({id, account_id, key, value});
+ unique({account_id, key}, "REPLACE");
+ }
+
+ public string? get_value(int account_id, string key) {
+ var row_opt = select({value})
+ .with(this.account_id, "=", account_id)
+ .with(this.key, "=", key)
+ .single()
+ .row();
+ if (row_opt.is_present()) return row_opt[value];
+ return null;
+ }
+ }
+
public class ConversationSettingsTable : Table {
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};
@@ -388,6 +411,7 @@ public class Database : Qlite.Database {
public MamCatchupTable mam_catchup { get; private set; }
public ReactionTable reaction { get; private set; }
public SettingsTable settings { get; private set; }
+ public AccountSettingsTable account_settings { get; private set; }
public ConversationSettingsTable conversation_settings { get; private set; }
public Map<int, Jid> jid_table_cache = new HashMap<int, Jid>();
@@ -417,8 +441,9 @@ public class Database : Qlite.Database {
mam_catchup = new MamCatchupTable(this);
reaction = new ReactionTable(this);
settings = new SettingsTable(this);
+ account_settings = new AccountSettingsTable(this);
conversation_settings = new ConversationSettingsTable(this);
- init({ account, jid, entity, content_item, message, body_meta, message_correction, reply, real_jid, occupantid, file_transfer, call, call_counterpart, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, reaction, settings, conversation_settings });
+ init({ account, jid, entity, content_item, message, body_meta, message_correction, reply, real_jid, occupantid, file_transfer, call, call_counterpart, conversation, avatar, entity_identity, entity_feature, roster, mam_catchup, reaction, settings, account_settings, conversation_settings });
try {
exec("PRAGMA journal_mode = WAL");
@@ -576,6 +601,9 @@ public class Database : Qlite.Database {
foreach(Row row in account.select()) {
try {
Account account = new Account.from_row(this, row);
+ if (account_table_cache.has_key(account.id)) {
+ account = account_table_cache[account.id];
+ }
ret.add(account);
account_table_cache[account.id] = account;
} catch (InvalidJidError e) {
diff --git a/libdino/src/service/entity_info.vala b/libdino/src/service/entity_info.vala
index d1217e81..83e27d4b 100644
--- a/libdino/src/service/entity_info.vala
+++ b/libdino/src/service/entity_info.vala
@@ -90,6 +90,20 @@ public class EntityInfo : StreamInteractionModule, Object {
return info_result.features.contains(feature);
}
+ public bool has_feature_offline(Account account, Jid jid, string feature) {
+ int ret = has_feature_cached_int(account, jid, feature);
+ if (ret == -1) {
+ return db.entity.select()
+ .with(db.entity.account_id, "=", account.id)
+ .with(db.entity.jid_id, "=", db.get_jid_id(jid))
+ .with(db.entity.resource, "=", jid.resourcepart ?? "")
+ .join_with(db.entity_feature, db.entity.caps_hash, db.entity_feature.entity)
+ .with(db.entity_feature.feature, "=", feature)
+ .count() > 0;
+ }
+ return ret == 1;
+ }
+
public bool has_feature_cached(Account account, Jid jid, string feature) {
return has_feature_cached_int(account, jid, feature) == 1;
}
@@ -203,13 +217,24 @@ public class EntityInfo : StreamInteractionModule, Object {
ServiceDiscovery.InfoResult? info_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, jid);
if (info_result == null) return null;
- if (hash != null && EntityCapabilities.Module.compute_hash_for_info_result(info_result) == hash) {
- store_features(hash, info_result.features);
- store_identities(hash, info_result.identities);
+ var computed_hash = EntityCapabilities.Module.compute_hash_for_info_result(info_result);
+
+ if (hash == null || computed_hash == hash) {
+ db.entity.upsert()
+ .value(db.entity.account_id, account.id, true)
+ .value(db.entity.jid_id, db.get_jid_id(jid), true)
+ .value(db.entity.resource, jid.resourcepart ?? "", true)
+ .value(db.entity.last_seen, (long)(new DateTime.now_local()).to_unix())
+ .value(db.entity.caps_hash, computed_hash)
+ .perform();
+
+ store_features(computed_hash, info_result.features);
+ store_identities(computed_hash, info_result.identities);
} else {
- jid_features[jid] = info_result.features;
- jid_identity[jid] = info_result.identities;
+ warning("Claimed entity caps hash from %s doesn't match computed one", jid.to_string());
}
+ jid_features[jid] = info_result.features;
+ jid_identity[jid] = info_result.identities;
return info_result;
}
diff --git a/libdino/src/service/fallback_body.vala b/libdino/src/service/fallback_body.vala
index 13323427..0ce89ade 100644
--- a/libdino/src/service/fallback_body.vala
+++ b/libdino/src/service/fallback_body.vala
@@ -46,20 +46,9 @@ public class Dino.FallbackBody : StreamInteractionModule, Object {
if (fallbacks.is_empty) return false;
foreach (var fallback in fallbacks) {
- if (fallback.ns_uri != Xep.Replies.NS_URI) continue;
-
- foreach (var location in fallback.locations) {
- db.body_meta.insert()
- .value(db.body_meta.message_id, message.id)
- .value(db.body_meta.info_type, Xep.FallbackIndication.NS_URI)
- .value(db.body_meta.info, fallback.ns_uri)
- .value(db.body_meta.from_char, location.from_char)
- .value(db.body_meta.to_char, location.to_char)
- .perform();
- }
-
- message.set_fallbacks(fallbacks);
+ if (fallback.ns_uri != Xep.Replies.NS_URI) continue; // TODO what if it's not
}
+ message.set_fallbacks(fallbacks);
return false;
}
diff --git a/libdino/src/service/message_correction.vala b/libdino/src/service/message_correction.vala
index 8f9770d8..e6401a05 100644
--- a/libdino/src/service/message_correction.vala
+++ b/libdino/src/service/message_correction.vala
@@ -39,27 +39,21 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
});
}
- public void send_correction(Conversation conversation, Message old_message, string correction_text) {
- string stanza_id = old_message.edit_to ?? old_message.stanza_id;
+ public void set_correction(Conversation conversation, Message message, Message old_message) {
+ string reference_stanza_id = old_message.edit_to ?? old_message.stanza_id;
- Message out_message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(correction_text, conversation);
- out_message.edit_to = stanza_id;
- out_message.quoted_item_id = old_message.quoted_item_id;
- outstanding_correction_nodes[out_message.stanza_id] = stanza_id;
- stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(out_message, conversation);
+ outstanding_correction_nodes[message.stanza_id] = reference_stanza_id;
db.message_correction.insert()
- .value(db.message_correction.message_id, out_message.id)
- .value(db.message_correction.to_stanza_id, stanza_id)
- .perform();
+ .value(db.message_correction.message_id, message.id)
+ .value(db.message_correction.to_stanza_id, reference_stanza_id)
+ .perform();
db.content_item.update()
- .with(db.content_item.foreign_id, "=", old_message.id)
- .with(db.content_item.content_type, "=", 1)
- .set(db.content_item.foreign_id, out_message.id)
- .perform();
-
- on_received_correction(conversation, out_message.id);
+ .with(db.content_item.foreign_id, "=", old_message.id)
+ .with(db.content_item.content_type, "=", 1)
+ .set(db.content_item.foreign_id, message.id)
+ .perform();
}
public bool is_own_correction_allowed(Conversation conversation, Message message) {
@@ -145,7 +139,7 @@ public class MessageCorrection : StreamInteractionModule, MessageListener {
return false;
}
- private void on_received_correction(Conversation conversation, int message_id) {
+ public void on_received_correction(Conversation conversation, int message_id) {
ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_foreign(conversation, 1, message_id);
if (content_item != null) {
received_correction(content_item);
diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala
index baab37ce..d8ea3e2d 100644
--- a/libdino/src/service/message_processor.vala
+++ b/libdino/src/service/message_processor.vala
@@ -38,6 +38,7 @@ public class MessageProcessor : StreamInteractionModule, Object {
received_pipeline.connect(new FilterMessageListener());
received_pipeline.connect(new StoreMessageListener(this, stream_interactor));
received_pipeline.connect(new StoreContentItemListener(stream_interactor));
+ received_pipeline.connect(new MarkupListener(stream_interactor));
stream_interactor.account_added.connect(on_account_added);
@@ -45,18 +46,6 @@ public class MessageProcessor : StreamInteractionModule, Object {
stream_interactor.stream_resumed.connect(send_unsent_chat_messages);
}
- public Entities.Message send_text(string text, Conversation conversation) {
- Entities.Message message = create_out_message(text, conversation);
- return send_message(message, conversation);
- }
-
- public Entities.Message send_message(Entities.Message message, Conversation conversation) {
- stream_interactor.get_module(ContentItemStore.IDENTITY).insert_message(message, conversation);
- send_xmpp_message(message, conversation);
- message_sent(message, conversation);
- return message;
- }
-
private void convert_sending_to_unsent_msgs(Account account) {
db.message.update()
.with(db.message.account_id, "=", account.id)
@@ -344,6 +333,25 @@ public class MessageProcessor : StreamInteractionModule, Object {
}
}
+ private class MarkupListener : MessageListener {
+
+ public string[] after_actions_const = new string[]{ "STORE" };
+ public override string action_group { get { return "Markup"; } }
+ public override string[] after_actions { get { return after_actions_const; } }
+
+ private StreamInteractor stream_interactor;
+
+ public MarkupListener(StreamInteractor stream_interactor) {
+ this.stream_interactor = stream_interactor;
+ }
+
+ public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) {
+ Gee.List<MessageMarkup.Span> markups = MessageMarkup.get_spans(stanza);
+ message.persist_markups(markups, message.id);
+ return false;
+ }
+ }
+
private class StoreContentItemListener : MessageListener {
public string[] after_actions_const = new string[]{ "DEDUPLICATE", "DECRYPT", "FILTER_EMPTY", "STORE", "CORRECTION", "MESSAGE_REINTERPRETING" };
@@ -406,8 +414,22 @@ public class MessageProcessor : StreamInteractionModule, Object {
new_message.type_ = MessageStanza.TYPE_CHAT;
}
- string? fallback = get_fallback_body_set_infos(message, new_message, conversation);
- new_message.body = fallback == null ? message.body : fallback + message.body;
+ if (message.quoted_item_id != 0) {
+ ContentItem? quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, message.quoted_item_id);
+ if (quoted_content_item != null) {
+ Jid? quoted_sender = message.from;
+ string? quoted_stanza_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, quoted_content_item);
+ if (quoted_sender != null && quoted_stanza_id != null) {
+ Xep.Replies.set_reply_to(new_message, new Xep.Replies.ReplyTo(quoted_sender, quoted_stanza_id));
+ }
+
+ foreach (var fallback in message.get_fallbacks()) {
+ Xep.FallbackIndication.set_fallback(new_message, fallback);
+ }
+ }
+ }
+
+ MessageMarkup.add_spans(new_message, message.get_markups());
build_message_stanza(message, new_message, conversation);
pre_message_send(message, new_message, conversation);
@@ -456,26 +478,6 @@ public class MessageProcessor : StreamInteractionModule, Object {
}
});
}
-
- public string? get_fallback_body_set_infos(Entities.Message message, MessageStanza new_stanza, Conversation conversation) {
- if (message.quoted_item_id == 0) return null;
-
- ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, message.quoted_item_id);
- if (content_item == null) return null;
-
- Jid? quoted_sender = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_sender_for_content_item(conversation, content_item);
- string? quoted_stanza_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, content_item);
- if (quoted_sender != null && quoted_stanza_id != null) {
- Xep.Replies.set_reply_to(new_stanza, new Xep.Replies.ReplyTo(quoted_sender, quoted_stanza_id));
- }
-
- string fallback = FallbackBody.get_quoted_fallback_body(content_item);
-
- var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback.char_count());
- Xep.FallbackIndication.set_fallback(new_stanza, new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location }));
-
- return fallback;
- }
}
public abstract class MessageListener : Xmpp.OrderedListener {
diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala
index 119079f0..111ace22 100644
--- a/libdino/src/service/muc_manager.vala
+++ b/libdino/src/service/muc_manager.vala
@@ -84,11 +84,6 @@ public class MucManager : StreamInteractionModule, Object {
}
mucs_joining[account].add(jid);
- if (!mucs_todo.has_key(account)) {
- mucs_todo[account] = new HashSet<Jid>(Jid.hash_bare_func, Jid.equals_bare_func);
- }
- mucs_todo[account].add(jid.with_resource(nick_));
-
Muc.JoinResult? res = yield stream.get_module(Xep.Muc.Module.IDENTITY).enter(stream, jid.bare_jid, nick_, password, history_since, receive_history, null);
mucs_joining[account].remove(jid);
@@ -127,6 +122,11 @@ public class MucManager : StreamInteractionModule, Object {
enter_errors[jid] = res.muc_error;
}
+ if (!mucs_todo.has_key(account)) {
+ mucs_todo[account] = new HashSet<Jid>(Jid.hash_bare_func, Jid.equals_bare_func);
+ }
+ mucs_todo[account].add(jid.with_resource(res.nick ?? nick_));
+
return res;
}
@@ -232,15 +232,8 @@ public class MucManager : StreamInteractionModule, Object {
//the term `private room` is a short hand for members-only+non-anonymous rooms
public bool is_private_room(Account account, Jid jid) {
- XmppStream? stream = stream_interactor.get_stream(account);
- if (stream == null) {
- return false;
- }
- Xep.Muc.Flag? flag = stream.get_flag(Xep.Muc.Flag.IDENTITY);
- if (flag == null) {
- return false;
- }
- return flag.has_room_feature(jid, Xep.Muc.Feature.NON_ANONYMOUS) && flag.has_room_feature(jid, Xep.Muc.Feature.MEMBERS_ONLY);
+ var entity_info = stream_interactor.get_module(EntityInfo.IDENTITY);
+ return entity_info.has_feature_offline(account, jid, "muc_membersonly") && entity_info.has_feature_offline(account, jid, "muc_nonanonymous");
}
public bool is_moderated_room(Account account, Jid jid) {
diff --git a/libdino/src/service/replies.vala b/libdino/src/service/replies.vala
index 58d44b37..cc9f43cc 100644
--- a/libdino/src/service/replies.vala
+++ b/libdino/src/service/replies.vala
@@ -38,17 +38,6 @@ public class Dino.Replies : StreamInteractionModule, Object {
return null;
}
- public void set_message_is_reply_to(Message message, ContentItem reply_to) {
- message.quoted_item_id = reply_to.id;
-
- db.reply.upsert()
- .value(db.reply.message_id, message.id, true)
- .value(db.reply.quoted_content_item_id, reply_to.id)
- .value_null(db.reply.quoted_message_stanza_id)
- .value_null(db.reply.quoted_message_from)
- .perform();
- }
-
private void on_incoming_message(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) {
// Check if a previous message was in reply to this one
var reply_qry = db.reply.select();
@@ -67,7 +56,7 @@ public class Dino.Replies : StreamInteractionModule, Object {
ContentItem? message_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_foreign(conversation, 1, message.id);
Message? reply_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(reply_row[db.message.id], conversation);
if (message_item != null && reply_message != null) {
- set_message_is_reply_to(reply_message, message_item);
+ reply_message.set_quoted_item(message_item.id);
}
}
@@ -78,7 +67,7 @@ public class Dino.Replies : StreamInteractionModule, Object {
ContentItem? quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_for_message_id(conversation, reply_to.to_message_id);
if (quoted_content_item == null) return;
- set_message_is_reply_to(message, quoted_content_item);
+ message.set_quoted_item(quoted_content_item.id);
}
private class ReceivedMessageListener : MessageListener {