aboutsummaryrefslogtreecommitdiff
path: root/libdino/src/service/counterpart_interaction_manager.vala
blob: b994ca64dfbec5225f23aa718660b2c050c40cfe (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
using Gee;

using Xmpp;
using Dino.Entities;

namespace Dino {
public class CounterpartInteractionManager : StreamInteractionModule, Object {
    public static ModuleIdentity<CounterpartInteractionManager> IDENTITY = new ModuleIdentity<CounterpartInteractionManager>("counterpart_interaction_manager");
    public string id { get { return IDENTITY.id; } }

    public signal void received_state(Account account, Jid jid, string state);
    public signal void received_marker(Account account, Jid jid, Entities.Message message, Entities.Message.Marked marker);
    public signal void received_message_received(Account account, Jid jid, Entities.Message message);
    public signal void received_message_displayed(Account account, Jid jid, Entities.Message message);

    private StreamInteractor stream_interactor;
    private HashMap<Conversation, HashMap<Jid, string>> chat_states = new HashMap<Conversation, HashMap<Jid, string>>(Conversation.hash_func, Conversation.equals_func);
    private HashMap<string, string> marker_wo_message = new HashMap<string, string>();

    public static void start(StreamInteractor stream_interactor) {
        CounterpartInteractionManager m = new CounterpartInteractionManager(stream_interactor);
        stream_interactor.add_module(m);
    }

    private CounterpartInteractionManager(StreamInteractor stream_interactor) {
        this.stream_interactor = stream_interactor;
        stream_interactor.account_added.connect(on_account_added);
        stream_interactor.get_module(MessageProcessor.IDENTITY).received_pipeline.connect(new ReceivedMessageListener(this));
        stream_interactor.get_module(MessageProcessor.IDENTITY).message_sent.connect(check_if_got_marker);
        stream_interactor.stream_negotiated.connect(() => chat_states.clear() );
    }

    public HashMap? get_chat_states(Conversation conversation) {
        if (stream_interactor.connection_manager.get_state(conversation.account) != ConnectionManager.ConnectionState.CONNECTED) return null;
        return chat_states[conversation];
    }

    private void on_account_added(Account account) {
        stream_interactor.module_manager.get_module(account, Xep.ChatMarkers.Module.IDENTITY).marker_received.connect( (stream, jid, marker, id) => {
            on_chat_marker_received(account, jid, marker, id);
        });
        stream_interactor.module_manager.get_module(account, Xep.MessageDeliveryReceipts.Module.IDENTITY).receipt_received.connect((stream, jid, id) => {
            on_receipt_received(account, jid, id);
        });
        stream_interactor.module_manager.get_module(account, Xep.ChatStateNotifications.Module.IDENTITY).chat_state_received.connect((stream, jid, state, stanza) => {
            on_chat_state_received.begin(account, jid, state, stanza);
        });
    }

    private async void on_chat_state_received(Account account, Jid jid, string state, MessageStanza stanza) {
        // Don't show our own (other devices) typing notification
        if (jid.equals_bare(account.bare_jid)) return;

        Message 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(message);
        if (conversation == null) return;

        // Don't show our own typing notification in MUCs
        if (conversation.type_ == Conversation.Type.GROUPCHAT) {
            Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(jid.bare_jid, account);
            if (own_muc_jid != null && own_muc_jid.equals(jid)) {
                return;
            }
        }

        if (!chat_states.has_key(conversation)) {
            chat_states[conversation] = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
        }
        if (state == Xmpp.Xep.ChatStateNotifications.STATE_ACTIVE) {
            chat_states[conversation].unset(jid);
        } else {
            chat_states[conversation][jid] = state;
        }
        received_state(account, jid, state);
    }

    private void on_chat_marker_received(Account account, Jid jid, string marker, string stanza_id) {

        // Check if the marker comes from ourselves (own jid or our jid in a MUC)
        bool own_marker = account.bare_jid.to_string() == jid.bare_jid.to_string();
        if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account)) {
            Jid? own_muc_jid = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(jid.bare_jid, account);
            if (own_muc_jid != null && own_muc_jid.equals(jid)) {
                own_marker = true;
            }
        }

        if (own_marker) {
            // If we received a display marker from ourselves (other device), set the conversation read up to that message.
            if (marker != Xep.ChatMarkers.MARKER_DISPLAYED && marker != Xep.ChatMarkers.MARKER_ACKNOWLEDGED) return;
            Conversation? conversation = stream_interactor.get_module(MessageStorage.IDENTITY).get_conversation_for_stanza_id(account, stanza_id);
            if (conversation == null || conversation.type_ == Conversation.Type.GROUPCHAT) return;
            Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza_id, conversation);
            if (message == null) return;
            // Don't move read marker backwards because we get old info from another client
            if (conversation.read_up_to == null || conversation.read_up_to.local_time.compare(message.local_time) > 0) return;
            conversation.read_up_to = message;
        } else {
            // We received a marker from someone else. Search the respective message and mark it.
            foreach (Conversation conversation in stream_interactor.get_module(ConversationManager.IDENTITY).get_conversations(jid, account)) {

                // We can't currently handle chat markers in MUCs
                if (conversation.type_ == Conversation.Type.GROUPCHAT) continue;

                Entities.Message? message = stream_interactor.get_module(MessageStorage.IDENTITY).get_message_by_stanza_id(stanza_id, conversation);
                if (message != null) {
                    switch (marker) {
                        case Xep.ChatMarkers.MARKER_RECEIVED:
                            // If we got a received marker, mark the respective message received.
                            received_message_received(account, jid, message);
                            message.marked = Entities.Message.Marked.RECEIVED;
                            break;
                        case Xep.ChatMarkers.MARKER_DISPLAYED:
                            // If we got a display marker, set all messages up to that message as read (if we know they've been received).
                            received_message_displayed(account, jid, message);
                            Gee.List<Entities.Message> messages = stream_interactor.get_module(MessageStorage.IDENTITY).get_messages(conversation);
                            foreach (Entities.Message m in messages) {
                                if (m.equals(message)) break;
                                if (m.marked == Entities.Message.Marked.RECEIVED) m.marked = Entities.Message.Marked.READ;
                            }
                            message.marked = Entities.Message.Marked.READ;
                            break;
                    }
                } else {
                    // We might get a marker before the actual message (on catchup). Save the marker.
                    if (marker_wo_message.has_key(stanza_id) &&
                        marker_wo_message[stanza_id] == Xep.ChatMarkers.MARKER_DISPLAYED && marker == Xep.ChatMarkers.MARKER_RECEIVED) {
                        return;
                    }
                    marker_wo_message[stanza_id] = marker;
                }
            }
        }
    }

    private void check_if_got_marker(Entities.Message message, Conversation conversation) {
        if (marker_wo_message.has_key(message.stanza_id)) {
            on_chat_marker_received(conversation.account, conversation.counterpart, marker_wo_message[message.stanza_id], message.stanza_id);
            marker_wo_message.unset(message.stanza_id);
        }
    }

    private class ReceivedMessageListener : MessageListener {

        public string[] after_actions_const = new string[]{ "DEDUPLICATE" };
        public override string action_group { get { return "STORE"; } }
        public override string[] after_actions { get { return after_actions_const; } }

        private CounterpartInteractionManager outer;

        public ReceivedMessageListener(CounterpartInteractionManager outer) {
            this.outer = outer;
        }

        public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) {
            outer.on_chat_state_received.begin(conversation.account, conversation.counterpart, Xep.ChatStateNotifications.STATE_ACTIVE, stanza);
            return false;
        }
    }

    private void on_receipt_received(Account account, Jid jid, string id) {
        on_chat_marker_received(account, jid, Xep.ChatMarkers.MARKER_RECEIVED, id);
    }
}

}