From cb3b19b01deb8460627578b885339e7528411f6f Mon Sep 17 00:00:00 2001 From: fiaxh Date: Fri, 6 Jan 2023 16:14:47 +0100 Subject: Support replies and reactions to files --- libdino/src/service/content_item_store.vala | 95 +++++++++++++++++++++++++++-- libdino/src/service/message_processor.vala | 51 ++++++++++------ libdino/src/service/message_storage.vala | 4 +- libdino/src/service/reactions.vala | 78 +++++++++-------------- libdino/src/service/replies.vala | 17 +----- libdino/src/service/util.vala | 1 + 6 files changed, 154 insertions(+), 92 deletions(-) (limited to 'libdino/src') diff --git a/libdino/src/service/content_item_store.vala b/libdino/src/service/content_item_store.vala index 6a9e691f..b3e32cf4 100644 --- a/libdino/src/service/content_item_store.vala +++ b/libdino/src/service/content_item_store.vala @@ -44,11 +44,8 @@ public class ContentItemStore : StreamInteractionModule, Object { Gee.TreeSet items = new Gee.TreeSet(ContentItem.compare_func); foreach (var row in select) { - int id = row[db.content_item.id]; - int content_type = row[db.content_item.content_type]; - int foreign_id = row[db.content_item.foreign_id]; - DateTime time = new DateTime.from_unix_utc(row[db.content_item.time]); - items.add(get_item(conversation, id, content_type, foreign_id, time)); + ContentItem content_item = get_item_from_row(row, conversation); + items.add(content_item); } Gee.List ret = new ArrayList(); @@ -58,6 +55,14 @@ public class ContentItemStore : StreamInteractionModule, Object { return ret; } + private ContentItem get_item_from_row(Row row, Conversation conversation) throws Error { + int id = row[db.content_item.id]; + int content_type = row[db.content_item.content_type]; + int foreign_id = row[db.content_item.foreign_id]; + DateTime time = new DateTime.from_unix_utc(row[db.content_item.time]); + return get_item(conversation, id, content_type, foreign_id, time); + } + private ContentItem get_item(Conversation conversation, int id, int content_type, int foreign_id, DateTime time) throws Error { switch (content_type) { case 1: @@ -112,6 +117,86 @@ public class ContentItemStore : StreamInteractionModule, Object { return item.size > 0 ? item[0] : null; } + public string? get_message_id_for_content_item(Conversation conversation, ContentItem content_item) { + Message? message = get_message_for_content_item(conversation, content_item); + if (message == null) return null; + + if (conversation.type_ == Conversation.Type.CHAT) { + return message.stanza_id; + } else { + return message.server_id; + } + } + + public Jid? get_message_sender_for_content_item(Conversation conversation, ContentItem content_item) { + Message? message = get_message_for_content_item(conversation, content_item); + if (message == null) return null; + return message.from; + } + + private Message? get_message_for_content_item(Conversation conversation, ContentItem content_item) { + FileItem? file_item = content_item as FileItem; + if (file_item != null) { + if (file_item.file_transfer.provider != 0 || file_item.file_transfer.info == null) return null; + + int message_db_id = int.parse(file_item.file_transfer.info); + return stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(message_db_id, conversation); + } + MessageItem? message_item = content_item as MessageItem; + if (message_item != null) { + return message_item.message; + } + return null; + } + + public ContentItem? get_content_item_for_message_id(Conversation conversation, string message_id) { + Row? row = get_content_item_row_for_message_id(conversation, message_id); + if (row != null) { + return get_item_from_row(row, conversation); + } + return null; + } + + public int get_content_item_id_for_message_id(Conversation conversation, string message_id) { + Row? row = get_content_item_row_for_message_id(conversation, message_id); + if (row != null) { + return row[db.content_item.id]; + } + return -1; + } + + private Row? get_content_item_row_for_message_id(Conversation conversation, string message_id) { + var content_item_row = db.content_item.select(); + + Message? message = null; + if (conversation.type_ == Conversation.Type.CHAT) { + message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(message_id, conversation); + } else { + message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_server_id(message_id, conversation); + } + if (message == null) return null; + + RowOption file_transfer_row = db.file_transfer.select() + .with(db.file_transfer.account_id, "=", conversation.account.id) + .with(db.file_transfer.counterpart_id, "=", db.get_jid_id(conversation.counterpart)) + .with(db.file_transfer.info, "=", message.id.to_string()) + .order_by(db.file_transfer.time, "DESC") + .single().row(); + + if (file_transfer_row.is_present()) { + content_item_row.with(db.content_item.foreign_id, "=", file_transfer_row[db.file_transfer.id]) + .with(db.content_item.content_type, "=", 2); + } else { + content_item_row.with(db.content_item.foreign_id, "=", message.id) + .with(db.content_item.content_type, "=", 1); + } + RowOption content_item_row_option = content_item_row.single().row(); + if (content_item_row_option.is_present()) { + return content_item_row_option.inner; + } + return null; + } + public ContentItem? get_latest(Conversation conversation) { Gee.List items = get_n_latest(conversation, 1); if (items.size > 0) { diff --git a/libdino/src/service/message_processor.vala b/libdino/src/service/message_processor.vala index 8d544b45..770ae0a6 100644 --- a/libdino/src/service/message_processor.vala +++ b/libdino/src/service/message_processor.vala @@ -425,24 +425,8 @@ public class MessageProcessor : StreamInteractionModule, Object { new_message.type_ = MessageStanza.TYPE_CHAT; } - if (message.quoted_item_id > 0) { - ContentItem? content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_id(conversation, message.quoted_item_id); - if (content_item != null && content_item.type_ == MessageItem.TYPE) { - Message? quoted_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(((MessageItem) content_item).message.id, conversation); - if (quoted_message != null) { - Xep.Replies.set_reply_to(new_message, new Xep.Replies.ReplyTo(quoted_message.from, quoted_message.stanza_id)); - - string body_with_fallback = "> " + Dino.message_body_without_reply_fallback(quoted_message); - body_with_fallback = body_with_fallback.replace("\n", "\n> "); - body_with_fallback += "\n"; - long fallback_length = body_with_fallback.length; - body_with_fallback += message.body; - new_message.body = body_with_fallback; - var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback_length); - Xep.FallbackIndication.set_fallback(new_message, new Xep.FallbackIndication.Fallback(Xep.Replies.NS_URI, new Xep.FallbackIndication.FallbackLocation[] { fallback_location })); - } - } - } + string? fallback = get_fallback_body_set_infos(message, new_message, conversation); + new_message.body = fallback == null ? message.body : fallback + message.body; build_message_stanza(message, new_message, conversation); pre_message_send(message, new_message, conversation); @@ -487,6 +471,37 @@ 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 = "> "; + + if (content_item.type_ == MessageItem.TYPE) { + Message? quoted_message = ((MessageItem) content_item).message; + fallback += Dino.message_body_without_reply_fallback(quoted_message); + fallback = fallback.replace("\n", "\n> "); + } else if (content_item.type_ == FileItem.TYPE) { + FileTransfer? quoted_file = ((FileItem) content_item).file_transfer; + fallback += quoted_file.file_name; + } + fallback += "\n"; + + long fallback_length = fallback.length; + var fallback_location = new Xep.FallbackIndication.FallbackLocation(0, (int)fallback_length); + 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/message_storage.vala b/libdino/src/service/message_storage.vala index fbdbcf8a..3dadab7b 100644 --- a/libdino/src/service/message_storage.vala +++ b/libdino/src/service/message_storage.vala @@ -116,9 +116,7 @@ public class MessageStorage : StreamInteractionModule, Object { .outer_join_with(db.message_correction, db.message_correction.message_id, db.message.id) .outer_join_with(db.reply, db.reply.message_id, db.message.id); - if (conversation.counterpart.resourcepart == null) { - query.with_null(db.message.counterpart_resource); - } else { + if (conversation.counterpart.resourcepart != null) { query.with(db.message.counterpart_resource, "=", conversation.counterpart.resourcepart); } diff --git a/libdino/src/service/reactions.vala b/libdino/src/service/reactions.vala index fa273f39..f65394bb 100644 --- a/libdino/src/service/reactions.vala +++ b/libdino/src/service/reactions.vala @@ -10,7 +10,6 @@ public class Dino.Reactions : StreamInteractionModule, Object { public string id { get { return IDENTITY.id; } } public signal void reaction_added(Account account, int content_item_id, Jid jid, string reaction); -// [Signal(detailed=true)] public signal void reaction_removed(Account account, int content_item_id, Jid jid, string reaction); private StreamInteractor stream_interactor; @@ -35,15 +34,19 @@ public class Dino.Reactions : StreamInteractionModule, Object { if (!reactions.contains(reaction)) { reactions.add(reaction); } - send_reactions(conversation, content_item, reactions); - reaction_added(conversation.account, content_item.id, conversation.account.bare_jid, reaction); + try { + send_reactions(conversation, content_item, reactions); + reaction_added(conversation.account, content_item.id, conversation.account.bare_jid, reaction); + } catch (SendError e) {} } public void remove_reaction(Conversation conversation, ContentItem content_item, string reaction) { Gee.List reactions = get_own_reactions(conversation, content_item); reactions.remove(reaction); - send_reactions(conversation, content_item, reactions); - reaction_removed(conversation.account, content_item.id, conversation.account.bare_jid, reaction); + try { + send_reactions(conversation, content_item, reactions); + reaction_removed(conversation.account, content_item.id, conversation.account.bare_jid, reaction); + } catch (SendError e) {} } public Gee.List get_item_reactions(Conversation conversation, ContentItem content_item) { @@ -80,35 +83,28 @@ public class Dino.Reactions : StreamInteractionModule, Object { return false; } - private void send_reactions(Conversation conversation, ContentItem content_item, Gee.List reactions) { - Message? message = null; + private void send_reactions(Conversation conversation, ContentItem content_item, Gee.List reactions) throws SendError { + string? message_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(conversation, content_item); + if (message_id == null) throw new SendError.Misc("No message for content_item"); - FileItem? file_item = content_item as FileItem; - if (file_item != null) { - int message_id = int.parse(file_item.file_transfer.info); - message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_id(message_id, conversation); - } - MessageItem? message_item = content_item as MessageItem; - if (message_item != null) { - message = message_item.message; - } + XmppStream? stream = stream_interactor.get_stream(conversation.account); + if (stream == null) throw new SendError.NoStream(""); - if (message == null) { - return; - } + var reactions_module = stream.get_module(Xmpp.Xep.Reactions.Module.IDENTITY); - XmppStream stream = stream_interactor.get_stream(conversation.account); - if (conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) { - if (conversation.type_ == Conversation.Type.GROUPCHAT) { - stream.get_module(Xmpp.Xep.Reactions.Module.IDENTITY).send_reaction(stream, conversation.counterpart, "groupchat", message.server_id ?? message.stanza_id, reactions); - } else if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { - stream.get_module(Xmpp.Xep.Reactions.Module.IDENTITY).send_reaction(stream, conversation.counterpart, "chat", message.server_id ?? message.stanza_id, reactions); - } + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + reactions_module.send_reaction.begin(stream, conversation.counterpart, "groupchat", message_id, reactions); // We save the reaction when it gets reflected back to us + } else if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { + reactions_module.send_reaction(stream, conversation.counterpart, "chat", message_id, reactions); } else if (conversation.type_ == Conversation.Type.CHAT) { - stream.get_module(Xmpp.Xep.Reactions.Module.IDENTITY).send_reaction(stream, conversation.counterpart, "chat", message.stanza_id, reactions); int64 now_millis = GLib.get_real_time () / 1000; - save_chat_reactions(conversation.account, conversation.account.bare_jid, content_item.id, now_millis, reactions); + reactions_module.send_reaction.begin(stream, conversation.counterpart, "chat", message_id, reactions, (_, res) => { + try { + reactions_module.send_reaction.end(res); + save_chat_reactions(conversation.account, conversation.account.bare_jid, content_item.id, now_millis, reactions); + } catch (SendError e) {} + }); } } @@ -251,11 +247,11 @@ public class Dino.Reactions : StreamInteractionModule, Object { Message reaction_message = yield stream_interactor.get_module(MessageProcessor.IDENTITY).parse_message_stanza(account, stanza); Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_for_message(reaction_message); - Message? message = get_message_for_reaction(conversation, message_id); + int content_item_id = stream_interactor.get_module(ContentItemStore.IDENTITY).get_content_item_id_for_message_id(conversation, message_id); var reaction_info = new ReactionInfo() { account=account, from_jid=from_jid, reactions=reactions, stanza=stanza, received_time=new DateTime.now() }; - if (message != null) { - process_reaction_for_message(message.id, reaction_info); + if (content_item_id != -1) { + process_reaction_for_message(content_item_id, reaction_info); return; } @@ -317,30 +313,12 @@ public class Dino.Reactions : StreamInteractionModule, Object { } } - private void process_reaction_for_message(int message_db_id, ReactionInfo reaction_info) { + private void process_reaction_for_message(int content_item_id, ReactionInfo reaction_info) { Account account = reaction_info.account; MessageStanza stanza = reaction_info.stanza; Jid from_jid = reaction_info.from_jid; Gee.List reactions = reaction_info.reactions; - RowOption file_transfer_row = db.file_transfer.select() - .with(db.file_transfer.account_id, "=", account.id) - .with(db.file_transfer.info, "=", message_db_id.to_string()) - .single().row(); // TODO better - - var content_item_row = db.content_item.select(); - - if (file_transfer_row.is_present()) { - content_item_row.with(db.content_item.foreign_id, "=", file_transfer_row[db.file_transfer.id]) - .with(db.content_item.content_type, "=", 2); - } else { - content_item_row.with(db.content_item.foreign_id, "=", message_db_id) - .with(db.content_item.content_type, "=", 1); - } - var content_item_row_opt = content_item_row.single().row(); - if (!content_item_row_opt.is_present()) return; - int content_item_id = content_item_row_opt[db.content_item.id]; - // Get reaction time DateTime? reaction_time = null; DelayedDelivery.MessageFlag? delayed_message_flag = DelayedDelivery.MessageFlag.get_flag(stanza); diff --git a/libdino/src/service/replies.vala b/libdino/src/service/replies.vala index 6a9bced4..97db70ee 100644 --- a/libdino/src/service/replies.vala +++ b/libdino/src/service/replies.vala @@ -77,22 +77,7 @@ public class Dino.Replies : StreamInteractionModule, Object { Xep.Replies.ReplyTo? reply_to = Xep.Replies.get_reply_to(stanza); if (reply_to == null) return; - Message? quoted_message = null; - if (conversation.type_ == Conversation.Type.GROUPCHAT) { - quoted_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_server_id(reply_to.to_message_id, conversation); - } else { - quoted_message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(reply_to.to_message_id, conversation); - } - if (quoted_message == null) { - db.reply.upsert() - .value(db.reply.message_id, message.id, true) - .value(db.reply.quoted_message_stanza_id, reply_to.to_message_id) - .value(db.reply.quoted_message_from, reply_to.to_jid.to_string()) - .perform(); - return; - } - - ContentItem? quoted_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_item_by_foreign(conversation, 1, quoted_message.id); + 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); diff --git a/libdino/src/service/util.vala b/libdino/src/service/util.vala index 3cbb48d9..1d04ffcf 100644 --- a/libdino/src/service/util.vala +++ b/libdino/src/service/util.vala @@ -1,4 +1,5 @@ using Dino.Entities; +using Qlite; namespace Dino { -- cgit v1.2.3-54-g00ecf