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