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 { diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index 8dbc3dc8..52a26f33 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -10,19 +10,44 @@ namespace Dino.Ui { public class FileMetaItem : ConversationSummary.ContentMetaItem { private StreamInteractor stream_interactor; + private FileItem file_item; + private FileTransfer file_transfer; public FileMetaItem(ContentItem content_item, StreamInteractor stream_interactor) { base(content_item); this.stream_interactor = stream_interactor; + this.file_item = content_item as FileItem; + this.file_transfer = file_item.file_transfer; } public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType type) { - FileItem file_item = content_item as FileItem; - FileTransfer transfer = file_item.file_transfer; - return new FileWidget(stream_interactor, transfer); + return new FileWidget(stream_interactor, file_transfer); } - public override Gee.List? get_item_actions(Plugins.WidgetType type) { return null; } + public override Gee.List? get_item_actions(Plugins.WidgetType type) { + if (file_transfer.provider != 0 || file_transfer.info == null) return null; + + Gee.List actions = new ArrayList(); + + if (stream_interactor.get_module(ContentItemStore.IDENTITY).get_message_id_for_content_item(file_item.conversation, content_item) != null) { + Plugins.MessageAction reply_action = new Plugins.MessageAction(); + reply_action.icon_name = "mail-reply-sender-symbolic"; + reply_action.callback = (button, content_meta_item_activated, widget) => { + GLib.Application.get_default().activate_action("quote", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(file_item.conversation.id), new GLib.Variant.int32(content_item.id) })); + }; + actions.add(reply_action); + + Plugins.MessageAction action2 = new Plugins.MessageAction(); + action2.icon_name = "dino-emoticon-add-symbolic"; + EmojiChooser chooser = new EmojiChooser(); + chooser.emoji_picked.connect((emoji) => { + stream_interactor.get_module(Reactions.IDENTITY).add_reaction(file_item.conversation, content_item, emoji); + }); + action2.popover = chooser; + actions.add(action2); + } + return actions; + } } public class FileWidget : SizeRequestBox { diff --git a/main/src/ui/conversation_content_view/message_widget.vala b/main/src/ui/conversation_content_view/message_widget.vala index f4e1d22c..fb4ba162 100644 --- a/main/src/ui/conversation_content_view/message_widget.vala +++ b/main/src/ui/conversation_content_view/message_widget.vala @@ -198,7 +198,7 @@ public class MessageMetaItem : ContentMetaItem { if (quoted_content_item != null) { var quote_model = new Quote.Model.from_content_item(quoted_content_item, message_item.conversation, stream_interactor); quote_model.jump_to.connect(() => { - GLib.Application.get_default().activate_action("jump-to-conversation-message", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(message_item.conversation.id), new GLib.Variant.int32(message_item.id) })); + GLib.Application.get_default().activate_action("jump-to-conversation-message", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(message_item.conversation.id), new GLib.Variant.int32(quoted_content_item.id) })); }); var quote_widget = Quote.get_widget(quote_model); outer.set_widget(quote_widget, Plugins.WidgetType.GTK4, 1); @@ -226,7 +226,7 @@ public class MessageMetaItem : ContentMetaItem { Plugins.MessageAction reply_action = new Plugins.MessageAction(); reply_action.icon_name = "mail-reply-sender-symbolic"; reply_action.callback = (button, content_meta_item_activated, widget) => { - GLib.Application.get_default().activate_action("quote", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(message_item.conversation.id), new GLib.Variant.int32(message_item.id) })); + GLib.Application.get_default().activate_action("quote", new GLib.Variant.tuple(new GLib.Variant[] { new GLib.Variant.int32(message_item.conversation.id), new GLib.Variant.int32(content_item.id) })); }; actions.add(reply_action); diff --git a/main/src/ui/conversation_content_view/quote_widget.vala b/main/src/ui/conversation_content_view/quote_widget.vala index f627c852..cfe2f153 100644 --- a/main/src/ui/conversation_content_view/quote_widget.vala +++ b/main/src/ui/conversation_content_view/quote_widget.vala @@ -27,7 +27,8 @@ namespace Dino.Ui.Quote { var message = ((MessageItem) content_item).message; this.message = Dino.message_body_without_reply_fallback(message); } else if (content_item.type_ == FileItem.TYPE) { - this.message = "[File]"; + var file_transfer = ((FileItem) content_item).file_transfer; + this.message = _("File") + ": " + file_transfer.file_name; } this.message_time = content_item.time; diff --git a/xmpp-vala/src/module/message/module.vala b/xmpp-vala/src/module/message/module.vala index 2eced5c1..ef39a663 100644 --- a/xmpp-vala/src/module/message/module.vala +++ b/xmpp-vala/src/module/message/module.vala @@ -17,7 +17,11 @@ namespace Xmpp { public async void send_message(XmppStream stream, MessageStanza message) throws IOStreamError { yield send_pipeline.run(stream, message); - yield stream.write_async(message.stanza); + try { + yield stream.write_async(message.stanza); + } catch (IOStreamError e) { + throw new SendError.IO(e.message); + } } public async void received_message_stanza_async(XmppStream stream, StanzaNode node) { diff --git a/xmpp-vala/src/module/xep/0444_reactions.vala b/xmpp-vala/src/module/xep/0444_reactions.vala index 3501ca42..8e8a1706 100644 --- a/xmpp-vala/src/module/xep/0444_reactions.vala +++ b/xmpp-vala/src/module/xep/0444_reactions.vala @@ -11,7 +11,7 @@ public class Module : XmppStreamModule { private ReceivedPipelineListener received_pipeline_listener = new ReceivedPipelineListener(); - public void send_reaction(XmppStream stream, Jid jid, string stanza_type, string message_id, Gee.List reactions) { + public async void send_reaction(XmppStream stream, Jid jid, string stanza_type, string message_id, Gee.List reactions) throws SendError { StanzaNode reactions_node = new StanzaNode.build("reactions", NS_URI).add_self_xmlns(); reactions_node.put_attribute("id", message_id); foreach (string reaction in reactions) { @@ -25,7 +25,7 @@ public class Module : XmppStreamModule { MessageProcessingHints.set_message_hint(message, MessageProcessingHints.HINT_STORE); - stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, message); + yield stream.get_module(MessageModule.IDENTITY).send_message(stream, message); } public override void attach(XmppStream stream) { diff --git a/xmpp-vala/src/util.vala b/xmpp-vala/src/util.vala index 6c0d0c9b..fda21f88 100644 --- a/xmpp-vala/src/util.vala +++ b/xmpp-vala/src/util.vala @@ -38,3 +38,11 @@ public long from_hex(string numeral) { } } + +namespace Xmpp { + public errordomain SendError { + IO, + NoStream, + Misc + } +}