1 #include "groups.h"
2 
3 #include "flist.h"
4 #include "debug.h"
5 #include "macros.h"
6 #include "self.h"
7 #include "settings.h"
8 #include "text.h"
9 
10 #include "av/audio.h"
11 #include "av/utox_av.h"
12 
13 #include "native/notify.h"
14 
15 #include "ui/edit.h"
16 
17 #include "layout/group.h"
18 
19 #include <pthread.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <tox/tox.h>
23 
24 static GROUPCHAT *group = NULL;
25 
get_group(uint32_t group_number)26 GROUPCHAT *get_group(uint32_t group_number) {
27     if (group_number >= self.groups_list_size) {
28         LOG_ERR("get_group", " index: %u is out of bounds." , group_number);
29         return NULL;
30     }
31 
32     return &group[group_number];
33 }
34 
35 /*
36  * Create a new slot for the group if group_number is greater than self.groups_list_size and return a pointer to it
37  * If group_number is less than self.groups_list_size return a pointer to that slot
38  */
group_make(uint32_t group_number)39 static GROUPCHAT *group_make(uint32_t group_number) {
40     if (group_number >= self.groups_list_size) {
41         LOG_INFO("Groupchats", "Reallocating groupchat array to %u. Current size: %u", (group_number + 1), self.groups_list_size);
42         GROUPCHAT *tmp = realloc(group, sizeof(GROUPCHAT) * (group_number + 1));
43         if (!tmp) {
44             LOG_FATAL_ERR(EXIT_MALLOC, "Groupchats", "Could not reallocate groupchat array to %u.", group_number + 1);
45         }
46 
47         group = tmp;
48         self.groups_list_size++;
49     }
50 
51     memset(&group[group_number], 0, sizeof(GROUPCHAT));
52 
53     return &group[group_number];
54 }
55 
group_create(uint32_t group_number,bool av_group)56 bool group_create(uint32_t group_number, bool av_group) {
57     GROUPCHAT *g = group_make(group_number);
58     if (!g) {
59         LOG_ERR("Groupchats", "Could not get/create group %u", group_number);
60         return false;
61     }
62 
63     group_init(g, group_number, av_group);
64     return true;
65 }
66 
group_init(GROUPCHAT * g,uint32_t group_number,bool av_group)67 void group_init(GROUPCHAT *g, uint32_t group_number, bool av_group) {
68     pthread_mutex_lock(&messages_lock); /* make sure that messages has posted before we continue */
69     if (!g->peer) {
70         g->peer = calloc(UTOX_MAX_GROUP_PEERS, sizeof(GROUP_PEER *));
71         if (!g->peer) {
72             LOG_FATAL_ERR(EXIT_MALLOC, "Groupchats", "Could not alloc for group peers (%uB)",
73                           UTOX_MAX_GROUP_PEERS * sizeof(GROUP_PEER *));
74         }
75     }
76 
77     snprintf((char *)g->name, sizeof(g->name), "Groupchat #%u", group_number);
78     g->name_length = strnlen(g->name, sizeof(g->name) - 1);
79 
80     g->topic_length = sizeof("Drag friends to invite them") - 1;
81     memcpy(g->topic, "Drag friends to invite them", sizeof("Drag friends to invite them") - 1);
82 
83     g->msg.scroll               = 1.0;
84     g->msg.panel.type           = PANEL_MESSAGES;
85     g->msg.panel.content_scroll = &scrollbar_group;
86     g->msg.panel.y              = MAIN_TOP;
87     g->msg.panel.height         = CHAT_BOX_TOP;
88     g->msg.panel.width          = -SCROLL_WIDTH;
89     g->msg.is_groupchat         = true;
90 
91     g->number   = group_number;
92     g->notify   = settings.group_notifications;
93     g->av_group = av_group;
94     pthread_mutex_unlock(&messages_lock);
95     self.groups_list_count++;
96 }
97 
group_add_message(GROUPCHAT * g,uint32_t peer_id,const uint8_t * message,size_t length,uint8_t m_type)98 uint32_t group_add_message(GROUPCHAT *g, uint32_t peer_id, const uint8_t *message, size_t length, uint8_t m_type) {
99     pthread_mutex_lock(&messages_lock); /* make sure that messages has posted before we continue */
100 
101     if (peer_id >= UTOX_MAX_GROUP_PEERS) {
102         LOG_ERR("Groupchats", "Unable to add message from peer %u - peer id too large.", peer_id);
103         pthread_mutex_unlock(&messages_lock);
104         return UINT32_MAX;
105     }
106 
107     const GROUP_PEER *peer = g->peer[peer_id];
108     if (!peer) {
109         LOG_ERR("Groupchats", "Unable to get peer %u for adding message.", peer_id);
110         pthread_mutex_unlock(&messages_lock);
111         return UINT32_MAX;
112     }
113 
114     MSG_HEADER *msg = calloc(1, sizeof(MSG_HEADER));
115     if (!msg) {
116         LOG_ERR("Groupchats", "Unable to allocate memory for message header.");
117         pthread_mutex_unlock(&messages_lock);
118         return UINT32_MAX;
119     }
120 
121     msg->our_msg  = (g->our_peer_number == peer_id ? true : false);
122     msg->msg_type = m_type;
123 
124     msg->via.grp.length    = length;
125     msg->via.grp.author_id = peer_id;
126 
127     msg->via.grp.author_length = peer->name_length;
128     msg->via.grp.author_color  = peer->name_color;
129     time(&msg->time);
130 
131     msg->via.grp.author = calloc(1, peer->name_length);
132     if (!msg->via.grp.author) {
133         LOG_ERR("Groupchat", "Unable to allocate space for author nickname.");
134         free(msg);
135         pthread_mutex_unlock(&messages_lock);
136         return UINT32_MAX;
137     }
138     memcpy(msg->via.grp.author, peer->name, peer->name_length);
139 
140     msg->via.grp.msg = calloc(1, length);
141     if (!msg->via.grp.msg) {
142         LOG_ERR("Groupchat", "Unable to allocate space for message.");
143         free(msg->via.grp.author);
144         free(msg);
145         pthread_mutex_unlock(&messages_lock);
146         return UINT32_MAX;
147     }
148     memcpy(msg->via.grp.msg, message, length);
149 
150     pthread_mutex_unlock(&messages_lock);
151 
152     MESSAGES *m = &g->msg;
153     return message_add_group(m, msg);
154 }
155 
group_peer_add(GROUPCHAT * g,uint32_t peer_id,bool UNUSED (our_peer_number),uint32_t name_color)156 void group_peer_add(GROUPCHAT *g, uint32_t peer_id, bool UNUSED(our_peer_number), uint32_t name_color) {
157     pthread_mutex_lock(&messages_lock); /* make sure that messages has posted before we continue */
158     if (!g->peer) {
159         g->peer = calloc(UTOX_MAX_GROUP_PEERS, sizeof(GROUP_PEER *));
160         if (!g->peer) {
161             LOG_FATAL_ERR(EXIT_MALLOC, "Groupchats", "Could not alloc for group peers (%uB)",
162                           UTOX_MAX_GROUP_PEERS * sizeof(GROUP_PEER *));
163         }
164         LOG_NOTE("Groupchat", "Needed to calloc peers for this group chat. (%u)" , peer_id);
165     }
166 
167     const char *default_peer_name = "<unknown>";
168 
169     // Allocate space for the struct and the dynamic array holding the peer's name.
170     GROUP_PEER *peer = calloc(1, sizeof(GROUP_PEER) + strlen(default_peer_name) + 1);
171     if (!peer) {
172         LOG_FATAL_ERR(EXIT_MALLOC, "Groupchat", "Unable to allocate space for group peer.");
173     }
174     strcpy2(peer->name, default_peer_name);
175     peer->name_length = 0;
176     peer->name_color  = name_color;
177     peer->id          = peer_id;
178 
179     g->peer[peer_id] = peer;
180     g->peer_count++;
181 
182     if (g->av_group) {
183         group_av_peer_add(g, peer_id); //add a source for the peer
184     }
185 
186     pthread_mutex_unlock(&messages_lock);
187 }
188 
group_peer_del(GROUPCHAT * g,uint32_t peer_id)189 void group_peer_del(GROUPCHAT *g, uint32_t peer_id) {
190     group_add_message(g, peer_id, (uint8_t *)"<- has Quit!", 12, MSG_TYPE_NOTICE);
191 
192     pthread_mutex_lock(&messages_lock); /* make sure that messages has posted before we continue */
193 
194     if (!g->peer) {
195         LOG_TRACE("Groupchat", "Unable to del peer from NULL group");
196         pthread_mutex_unlock(&messages_lock);
197         return;
198     }
199 
200     GROUP_PEER *peer = g->peer[peer_id];
201 
202     if (peer) {
203         LOG_TRACE("Groupchat", "Freeing peer %u, name %.*s" , peer_id, (int)peer->name_length, peer->name);
204         free(peer);
205     } else {
206         LOG_TRACE("Groupchat", "Unable to find peer for deletion");
207         pthread_mutex_unlock(&messages_lock);
208         return;
209     }
210     g->peer_count--;
211     g->peer[peer_id] = NULL;
212     pthread_mutex_unlock(&messages_lock);
213 }
214 
group_peer_name_change(GROUPCHAT * g,uint32_t peer_id,const uint8_t * name,size_t length)215 void group_peer_name_change(GROUPCHAT *g, uint32_t peer_id, const uint8_t *name, size_t length) {
216     pthread_mutex_lock(&messages_lock); /* make sure that messages has posted before we continue */
217     if (!g->peer) {
218         LOG_TRACE("Groupchat", "Unable to add peer to NULL group");
219         pthread_mutex_unlock(&messages_lock);
220         return;
221     }
222 
223     GROUP_PEER *peer = g->peer[peer_id];
224     if (!peer) {
225         LOG_FATAL_ERR(EXIT_FAILURE, "Groupchat", "We can't set a name for a null peer! %u" , peer_id);
226     }
227 
228     if (peer->name_length) {
229         char old[TOX_MAX_NAME_LENGTH];
230         char msg[TOX_MAX_NAME_LENGTH];
231 
232         memcpy(old, peer->name, peer->name_length);
233         snprintf(msg, sizeof(msg), "<- has changed their name from %.*s",
234                  peer->name_length, old);
235 
236         GROUP_PEER *new_peer = realloc(peer, sizeof(GROUP_PEER) + sizeof(char) * length);
237         if (!new_peer) {
238             free(peer);
239             LOG_FATAL_ERR(EXIT_MALLOC, "Groupchat", "couldn't realloc for group peer name!");
240         }
241 
242         peer = new_peer;
243         peer->name_length = utf8_validate(name, length);
244         memcpy(peer->name, name, length);
245         g->peer[peer_id] = peer;
246 
247         pthread_mutex_unlock(&messages_lock);
248         size_t msg_length = strnlen(msg, sizeof(msg) - 1);
249         group_add_message(g, peer_id, (uint8_t *)msg, msg_length, MSG_TYPE_NOTICE);
250         return;
251     }
252 
253     /* Hopefully, they just joined, because that's the UX message we're going with! */
254     GROUP_PEER *new_peer = realloc(peer, sizeof(GROUP_PEER) + sizeof(char) * length);
255     if (!new_peer) {
256         free(peer);
257         LOG_FATAL_ERR(EXIT_MALLOC, "Groupchat", "Unable to realloc for group peer who just joined.");
258     }
259 
260     peer = new_peer;
261     peer->name_length = utf8_validate(name, length);
262     memcpy(peer->name, name, length);
263     g->peer[peer_id] = peer;
264 
265     pthread_mutex_unlock(&messages_lock);
266     group_add_message(g, peer_id, (uint8_t *)"<- has joined the chat!", 23, MSG_TYPE_NOTICE);
267 }
268 
group_reset_peerlist(GROUPCHAT * g)269 void group_reset_peerlist(GROUPCHAT *g) {
270     /* ARE YOU KIDDING... WHO THOUGHT THIS API WAS OKAY?! */
271     for (size_t i = 0; i < g->peer_count; ++i) {
272         if (g->peer[i]) {
273             free(g->peer[i]);
274         }
275     }
276     free(g->peer);
277 }
278 
group_free(GROUPCHAT * g)279 void group_free(GROUPCHAT *g) {
280     LOG_INFO("Groupchats", "Freeing group %u", g->number);
281     for (size_t i = 0; i < g->edit_history_length; ++i) {
282         free(g->edit_history[i]);
283     }
284 
285     free(g->edit_history);
286 
287     group_reset_peerlist(g);
288 
289     for (size_t i = 0; i < g->msg.number; ++i) {
290         free(g->msg.data[i]->via.grp.author);
291 
292         // Freeing this here was causing a double free.
293         // TODO: Is it needed to prevent a memory leak in some cases?
294         // free(g->msg.data[i]->via.grp.msg);
295 
296         message_free(g->msg.data[i]);
297     }
298     free(g->msg.data);
299 
300     memset(g, 0, sizeof(GROUPCHAT));
301 
302     self.groups_list_count--;
303 }
304 
raze_groups(void)305 void raze_groups(void) {
306     LOG_INFO("Groupchats", "Freeing groupchat array");
307     for (size_t i = 0; i < self.groups_list_count; i++) {
308         GROUPCHAT *g = get_group(i);
309         if (!g) {
310             LOG_ERR("Groupchats", "Could not get group %u. Skipping...", i);
311             continue;
312         }
313         group_free(g);
314     }
315 
316     free(group);
317     group = NULL;
318 }
319 
init_groups(Tox * tox)320 void init_groups(Tox *tox) {
321     self.groups_list_size = tox_conference_get_chatlist_size(tox);
322 
323     if (self.groups_list_size == 0) {
324         return;
325     }
326 
327     LOG_INFO("Groupchats", "Group list size: %u", self.groups_list_size);
328     group = calloc(self.groups_list_size, sizeof(GROUPCHAT));
329     if (!group) {
330         LOG_FATAL_ERR(EXIT_MALLOC, "Groupchats", "Could not allocate memory for groupchat array with size of: %u", self.groups_list_size);
331     }
332 
333     uint32_t groups[self.groups_list_size];
334     tox_conference_get_chatlist(tox, groups);
335 
336     for (size_t i = 0; i < self.groups_list_size; i++) {
337         group_create(groups[i], false); //TODO: figure out if groupchats are text or audio
338     }
339     LOG_INFO("Groupchat", "Initialzied groupchat array with %u groups", self.groups_list_size);
340 }
341 
342 
group_notify_msg(GROUPCHAT * g,const char * msg,size_t msg_length)343 void group_notify_msg(GROUPCHAT *g, const char *msg, size_t msg_length) {
344     if (g->notify == GNOTIFY_NEVER) {
345         return;
346     }
347 
348     if (g->notify == GNOTIFY_HIGHLIGHTS && strstr(msg, self.name) == NULL) {
349         return;
350     }
351 
352     char title[g->name_length + 25];
353 
354     snprintf(title, sizeof(title), "uTox new message in %.*s", g->name_length, g->name);
355     size_t title_length = strnlen(title, sizeof(title) - 1);
356     notify(title, title_length, msg, msg_length, g, 1);
357 
358     if (flist_get_groupchat() != g) {
359         postmessage_audio(UTOXAUDIO_PLAY_NOTIFICATION, NOTIFY_TONE_FRIEND_NEW_MSG, 0, NULL);
360     }
361 }
362