Download
jcs
/subtext
/mail.c
(View History)
jcs mail: constify fields | Latest amendment: 277 on 2022-11-11 |
1 | /* |
2 | * Copyright (c) 2022 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdarg.h> |
18 | #include <stdio.h> |
19 | #include <stdlib.h> |
20 | #include <string.h> |
21 | |
22 | #include "ansi.h" |
23 | #include "mail.h" |
24 | #include "subtext.h" |
25 | #include "session.h" |
26 | #include "user.h" |
27 | #include "uthread.h" |
28 | #include "util.h" |
29 | |
30 | #define MSGS_PER_PAGE 10 |
31 | |
32 | #define MAIL_READ_RETURN_DONE -1 |
33 | #define MAIL_READ_RETURN_LIST -2 |
34 | #define MAIL_READ_RETURN_FIND -3 |
35 | |
36 | static const struct bile_object_field mail_object_fields[] = { |
37 | { offsetof(struct mail_message, recipient_user_id), |
38 | member_size(struct mail_message, recipient_user_id), -1 }, |
39 | { offsetof(struct mail_message, id), |
40 | member_size(struct mail_message, id), -1 }, |
41 | { offsetof(struct mail_message, time), |
42 | member_size(struct mail_message, time), -1 }, |
43 | { offsetof(struct mail_message, read), |
44 | member_size(struct mail_message, read), -1 }, |
45 | { offsetof(struct mail_message, sender_user_id), |
46 | member_size(struct mail_message, sender_user_id), -1 }, |
47 | { offsetof(struct mail_message, subject_size), |
48 | member_size(struct mail_message, subject_size), -1 }, |
49 | { offsetof(struct mail_message, subject), |
50 | -1, offsetof(struct mail_message, subject_size) }, |
51 | { offsetof(struct mail_message, body_size), |
52 | member_size(struct mail_message, body_size), -1 }, |
53 | { offsetof(struct mail_message, body), |
54 | -1, offsetof(struct mail_message, body_size) }, |
55 | { offsetof(struct mail_message, parent_message_id), |
56 | member_size(struct mail_message, parent_message_id), -1 }, |
57 | }; |
58 | static const size_t nmail_object_fields = nitems(mail_object_fields); |
59 | |
60 | void mail_free_message_strings(struct mail_message *msg); |
61 | short mail_save(struct session *s, struct mail_message *msg); |
62 | short mail_read(struct session *s, unsigned long id, short idx); |
63 | void mail_list(struct session *s, size_t nmsgs, unsigned long *mail_ids, |
64 | size_t page, size_t pages); |
65 | short mail_get_message_id(struct session *s, size_t nmsgs, char *prompt, |
66 | short initial); |
67 | |
68 | void |
69 | mail_free_message_strings(struct mail_message *msg) |
70 | { |
71 | if (msg->subject != NULL) { |
72 | xfree(&msg->subject); |
73 | msg->subject = NULL; |
74 | } |
75 | if (msg->body != NULL) { |
76 | xfree(&msg->body); |
77 | msg->body = NULL; |
78 | } |
79 | } |
80 | |
81 | short |
82 | mail_get_message_id(struct session *s, size_t nmsgs, char *prompt, |
83 | short initial) |
84 | { |
85 | char *tmp = NULL; |
86 | char start_s[10] = { 0 }; |
87 | short ret; |
88 | |
89 | if (initial > -1) |
90 | snprintf(start_s, sizeof(start_s), "%d", initial); |
91 | |
92 | get_id: |
93 | if (prompt) |
94 | session_printf(s, "{{B}}%s:{{/B}} ", prompt); |
95 | |
96 | tmp = session_field_input(s, 5, 5, start_s, false, 0); |
97 | session_output(s, "\r\n", 2); |
98 | session_flush(s); |
99 | if (tmp == NULL || s->ending) { |
100 | ret = -1; |
101 | goto get_id_done; |
102 | } |
103 | |
104 | if (sscanf(tmp, "%d", &ret) != 1 || ret < 1 || ret > nmsgs) { |
105 | session_printf(s, "{{B}}Invalid message ID{{/B}} (^C to cancel)\r\n"); |
106 | session_flush(s); |
107 | xfree(&tmp); |
108 | goto get_id; |
109 | } |
110 | |
111 | get_id_done: |
112 | if (tmp != NULL) |
113 | xfree(&tmp); |
114 | return ret; |
115 | } |
116 | |
117 | void |
118 | mail_menu(struct session *s) |
119 | { |
120 | static const struct session_menu_option opts[] = { |
121 | { '#', "#0123456789", "Read mail message [#]" }, |
122 | { '<', "<", "Previous page of messages" }, |
123 | { 'l', "Ll", "List mail messages" }, |
124 | { '>', ">", "Next page of messages" }, |
125 | { 'm', "MmNn", "Compose new mail message" }, |
126 | { 'q', "QqXx", "Return to main menu" }, |
127 | { '?', "?", "Show this help menu" }, |
128 | }; |
129 | size_t nmsgs, nmail_ids, id; |
130 | unsigned long *mail_ids = NULL, page, pages; |
131 | short ret; |
132 | char c; |
133 | bool show_help = false; |
134 | bool done = false; |
135 | bool find_message_ids = true; |
136 | bool show_list = true; |
137 | |
138 | if (!s->user) { |
139 | session_printf(s, "Mail is not available to guests.\r\n" |
140 | "Please create an account first.\r\n"); |
141 | session_flush(s); |
142 | return; |
143 | } |
144 | |
145 | page = 0; |
146 | |
147 | while (!done && !s->ending) { |
148 | if (find_message_ids) { |
149 | if (mail_ids != NULL) |
150 | xfree(&mail_ids); |
151 | nmsgs = mail_find_ids_for_user(s->user, &nmail_ids, &mail_ids, |
152 | page * MSGS_PER_PAGE, MSGS_PER_PAGE, false); |
153 | /* ceil(nmsgs / MSGS_PER_PAGE) */ |
154 | pages = (nmsgs + MSGS_PER_PAGE - 1) / MSGS_PER_PAGE; |
155 | |
156 | if (page >= pages) |
157 | page = pages - 1; |
158 | |
159 | find_message_ids = false; |
160 | } |
161 | |
162 | if (show_list) { |
163 | mail_list(s, nmail_ids, mail_ids, page + 1, pages); |
164 | show_list = false; |
165 | } |
166 | |
167 | c = session_menu(s, "Private Mail", "Mail", opts, nitems(opts), |
168 | show_help); |
169 | show_help = false; |
170 | |
171 | handle_opt: |
172 | switch (c) { |
173 | case 'm': |
174 | mail_compose(s, NULL, NULL, NULL); |
175 | break; |
176 | case 'l': |
177 | show_list = true; |
178 | break; |
179 | case '>': |
180 | case '<': |
181 | if (c == '>' && page == pages - 1) { |
182 | session_printf(s, "You are at the last page of messages\r\n"); |
183 | session_flush(s); |
184 | break; |
185 | } |
186 | if (c == '<' && page == 0) { |
187 | session_printf(s, "You are already at the first page\r\n"); |
188 | session_flush(s); |
189 | break; |
190 | } |
191 | if (c == '>') |
192 | page++; |
193 | else |
194 | page--; |
195 | find_message_ids = true; |
196 | show_list = true; |
197 | break; |
198 | case 0: |
199 | case 1: |
200 | case 2: |
201 | case 3: |
202 | case 4: |
203 | case 5: |
204 | case 6: |
205 | case 7: |
206 | case 8: |
207 | case 9: |
208 | if (c >= nmail_ids) { |
209 | session_printf(s, "Invalid message\r\n"); |
210 | session_flush(s); |
211 | break; |
212 | } |
213 | ret = mail_read(s, mail_ids[c], c); |
214 | switch (ret) { |
215 | case MAIL_READ_RETURN_DONE: |
216 | break; |
217 | case MAIL_READ_RETURN_LIST: |
218 | show_list = true; |
219 | break; |
220 | case MAIL_READ_RETURN_FIND: |
221 | find_message_ids = true; |
222 | show_list = true; |
223 | break; |
224 | default: |
225 | c = ret; |
226 | goto handle_opt; |
227 | } |
228 | break; |
229 | case '?': |
230 | show_help = true; |
231 | break; |
232 | default: |
233 | done = true; |
234 | break; |
235 | } |
236 | } |
237 | |
238 | if (mail_ids != NULL) |
239 | xfree(&mail_ids); |
240 | } |
241 | |
242 | void |
243 | mail_compose(struct session *s, char *initial_to, char *initial_subject, |
244 | char *initial_body) |
245 | { |
246 | struct user *to_user = NULL; |
247 | struct mail_message msg = { 0 }; |
248 | char *to_username = NULL, *tmp; |
249 | char c; |
250 | |
251 | if (!s->user) { |
252 | session_printf(s, "Mail is not available to guests.\r\n" |
253 | "Please create an account first.\r\n"); |
254 | session_flush(s); |
255 | return; |
256 | } |
257 | |
258 | if (initial_to) |
259 | to_username = xstrdup(initial_to, "mail_compose"); |
260 | if (initial_subject) |
261 | msg.subject = xstrdup(initial_subject, "mail_compose"); |
262 | if (initial_body) |
263 | msg.body = xstrdup(initial_body, "mail_compose"); |
264 | |
265 | session_printf(s, "{{B}}Compose New Private Mail{{/B}}\r\n"); |
266 | session_printf(s, "{{B}}From: {{/B}} %s\r\n", s->user->username); |
267 | session_flush(s); |
268 | |
269 | mail_compose_start: |
270 | for (;;) { |
271 | session_printf(s, "{{B}}To: {{/B}} "); |
272 | session_flush(s); |
273 | |
274 | tmp = session_field_input(s, DB_USERNAME_LENGTH + 1, |
275 | DB_USERNAME_LENGTH + 1, to_username, false, 0); |
276 | if (to_username != NULL) |
277 | xfree(&to_username); |
278 | to_username = tmp; |
279 | session_output(s, "\r\n", 2); |
280 | session_flush(s); |
281 | if (to_username == NULL || to_username[0] == '\0') |
282 | goto mail_compose_done; |
283 | |
284 | to_user = user_find_by_username(to_username); |
285 | if (to_user == NULL) { |
286 | session_printf(s, "{{B}}Error:{{/B}}{{#}} No such user \"%s\" " |
287 | "(^C to cancel)\r\n", to_username); |
288 | session_flush(s); |
289 | xfree(&to_username); |
290 | to_username = NULL; |
291 | continue; |
292 | } |
293 | msg.recipient_user_id = to_user->id; |
294 | xfree(&to_user); |
295 | break; |
296 | } |
297 | |
298 | for (;;) { |
299 | session_printf(s, "{{B}}Subject:{{/B}} "); |
300 | session_flush(s); |
301 | |
302 | tmp = session_field_input(s, 50, 50, msg.subject, false, 0); |
303 | if (msg.subject != NULL) |
304 | xfree(&msg.subject); |
305 | msg.subject = tmp; |
306 | session_output(s, "\r\n", 2); |
307 | session_flush(s); |
308 | |
309 | if (msg.subject == NULL) |
310 | goto mail_compose_done; |
311 | |
312 | rtrim(msg.subject, "\r\n\t "); |
313 | |
314 | if (msg.subject[0] == '\0') { |
315 | session_printf(s, "{{B}}Error:{{/B}} Subject cannot " |
316 | "be blank (^C to cancel)\r\n"); |
317 | session_flush(s); |
318 | xfree(&msg.subject); |
319 | msg.subject = NULL; |
320 | continue; |
321 | } |
322 | msg.subject_size = strlen(msg.subject) + 1; |
323 | break; |
324 | } |
325 | |
326 | for (;;) { |
327 | session_printf(s, |
328 | "{{B}}Message (^D when finished):{{/B}}\r\n"); |
329 | session_flush(s); |
330 | |
331 | tmp = session_field_input(s, 2048, s->terminal_columns - 1, |
332 | msg.body, true, 0); |
333 | if (msg.body != NULL) |
334 | xfree(&msg.body); |
335 | msg.body = tmp; |
336 | session_output(s, "\r\n", 2); |
337 | session_flush(s); |
338 | |
339 | if (msg.body == NULL) |
340 | goto mail_compose_done; |
341 | |
342 | rtrim(msg.body, "\r\n\t "); |
343 | |
344 | if (msg.body[0] == '\0') { |
345 | session_printf(s, "{{B}}Error:{{/B}} Message cannot " |
346 | "be blank (^C to cancel)\r\n"); |
347 | session_flush(s); |
348 | xfree(&msg.body); |
349 | msg.body = NULL; |
350 | continue; |
351 | } |
352 | msg.body_size = strlen(msg.body) + 1; |
353 | break; |
354 | } |
355 | |
356 | for (;;) { |
357 | session_printf(s, "\r\n{{B}}(S){{/B}}end message, " |
358 | "{{B}}(E){{/B}}dit again, or {{B}}(C){{/B}}ancel? "); |
359 | session_flush(s); |
360 | |
361 | c = session_input_char(s); |
362 | if (c == 0 && s->obuflen > 0) { |
363 | s->node_funcs->output(s); |
364 | uthread_yield(); |
365 | if (s->ending) |
366 | goto mail_compose_done; |
367 | continue; |
368 | } |
369 | |
370 | session_printf(s, "%c\r\n", c); |
371 | session_flush(s); |
372 | |
373 | switch (c) { |
374 | case 's': |
375 | case 'S': |
376 | case 'y': |
377 | case '\n': |
378 | case '\r': |
379 | /* send */ |
380 | session_printf(s, "Sending mail..."); |
381 | session_flush(s); |
382 | |
383 | msg.time = Time; |
384 | msg.sender_user_id = s->user->id; |
385 | mail_save(s, &msg); |
386 | |
387 | session_printf(s, " sent!\r\n"); |
388 | session_flush(s); |
389 | |
390 | goto mail_compose_done; |
391 | break; |
392 | case 'e': |
393 | case 'E': |
394 | goto mail_compose_start; |
395 | case 'c': |
396 | case 'C': |
397 | case CONTROL_C: |
398 | goto mail_compose_done; |
399 | } |
400 | } |
401 | |
402 | mail_compose_done: |
403 | if (to_username != NULL) |
404 | xfree(&to_username); |
405 | mail_free_message_strings(&msg); |
406 | } |
407 | |
408 | void |
409 | mail_list(struct session *s, size_t nmail_ids, unsigned long *mail_ids, |
410 | size_t page, size_t pages) |
411 | { |
412 | char time[10]; |
413 | size_t n, size; |
414 | struct mail_message msg; |
415 | struct username_cache *user; |
416 | char *data; |
417 | |
418 | session_printf(s, "{{B}}Private Mail (Page %ld of %ld){{/B}}\r\n", |
419 | page, pages); |
420 | session_printf(s, "%s# Flg Date From Subject%s\r\n", |
421 | ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); |
422 | session_flush(s); |
423 | |
424 | for (n = 0; n < nmail_ids; n++) { |
425 | size = bile_read_alloc(db->bile, DB_MESSAGE_RTYPE, mail_ids[n], |
426 | &data); |
427 | if (size == 0) |
428 | break; |
429 | bile_unmarshall_object(db->bile, mail_object_fields, |
430 | nitems(mail_object_fields), data, size, &msg, sizeof(msg), true, |
431 | "mail_list"); |
432 | xfree(&data); |
433 | |
434 | user = user_username(msg.sender_user_id); |
435 | strftime(time, sizeof(time), "%b %d", localtime(&msg.time)); |
436 | |
437 | session_printf(s, "%s%ld %c %s %- 10s {{#}}%- 40s%s\r\n", |
438 | msg.read ? "" : ansi(s, ANSI_BOLD, ANSI_END), |
439 | n, |
440 | msg.read ? ' ' : 'N', |
441 | time, |
442 | user ? user->username : "(unknown)", |
443 | msg.subject, |
444 | msg.read ? "" : ansi(s, ANSI_RESET, ANSI_END)); |
445 | |
446 | mail_free_message_strings(&msg); |
447 | } |
448 | |
449 | session_flush(s); |
450 | } |
451 | |
452 | short |
453 | mail_read(struct session *s, unsigned long id, short idx) |
454 | { |
455 | static const struct session_menu_option opts[] = { |
456 | { 'r', "Rr", "Reply to this message" }, |
457 | { 'd', "Dd", "Delete this message" }, |
458 | { 'u', "Uu", "Mark this message unread" }, |
459 | { 'l', "Ll", "Return and list mail messages" }, |
460 | { '#', "#0123456789", "Return and read mail message [#]" }, |
461 | { 'q', "QqXx", "Return to message list" }, |
462 | { '?', "?", "Show this help menu" }, |
463 | }; |
464 | char time[32]; |
465 | char prompt[24]; |
466 | char title[50]; |
467 | size_t size; |
468 | struct mail_message msg; |
469 | struct username_cache *sender, *recipient; |
470 | short ret = MAIL_READ_RETURN_DONE; |
471 | char *data, *reply_subject; |
472 | bool done = false, show_help = false; |
473 | char c; |
474 | |
475 | size = bile_read_alloc(db->bile, DB_MESSAGE_RTYPE, id, &data); |
476 | if (size == 0) { |
477 | session_printf(s, "{{B}}Error:{{/B}} Can't find message\r\n"); |
478 | session_flush(s); |
479 | return MAIL_READ_RETURN_DONE; |
480 | } |
481 | |
482 | bile_unmarshall_object(db->bile, mail_object_fields, |
483 | nitems(mail_object_fields), data, size, &msg, sizeof(msg), true, |
484 | "mail_read"); |
485 | xfree(&data); |
486 | |
487 | sender = user_username(msg.sender_user_id); |
488 | recipient = user_username(msg.recipient_user_id); |
489 | |
490 | strftime(time, sizeof(time), "%Y-%m-%d %H:%M:%S", |
491 | localtime(&msg.time)); |
492 | |
493 | session_printf(s, "{{B}}From:{{/B}} %s\r\n", |
494 | sender ? sender->username : "(unknown)"); |
495 | session_printf(s, "{{B}}To:{{/B}} %s\r\n", |
496 | recipient ? recipient->username : "(unknown)"); |
497 | session_printf(s, "{{B}}Date:{{/B}} %s %s\r\n", time, |
498 | db->config.timezone); |
499 | session_printf(s, "{{B}}Subject:{{/B}}{{#}} %s\r\n", msg.subject); |
500 | session_flush(s); |
501 | session_printf(s, "\r\n"); |
502 | session_output(s, msg.body, msg.body_size); |
503 | session_printf(s, "\r\n"); |
504 | session_flush(s); |
505 | |
506 | if (!msg.read) { |
507 | msg.read = Time; |
508 | if (mail_save(s, &msg) != 0) |
509 | session_printf(s, "Failed marking message read!\r\n"); |
510 | } |
511 | |
512 | snprintf(prompt, sizeof(prompt), "Mail:Message %d", idx); |
513 | snprintf(title, sizeof(title), "Private Mail: Message %d", idx); |
514 | |
515 | while (!done && !s->ending) { |
516 | c = session_menu(s, title, prompt, opts, nitems(opts), |
517 | show_help); |
518 | show_help = false; |
519 | |
520 | switch (c) { |
521 | case 'r': |
522 | if (!sender) |
523 | break; |
524 | |
525 | reply_subject = xmalloc(strlen(msg.subject) + 5, |
526 | "mail_read subject"); |
527 | if (strncmp(msg.subject, "Re:", 3) == 0) |
528 | strlcpy(reply_subject, msg.subject, |
529 | strlen(msg.subject) + 1); |
530 | else |
531 | sprintf(reply_subject, "Re: %s", msg.subject); |
532 | |
533 | mail_compose(s, sender->username, reply_subject, NULL); |
534 | |
535 | xfree(&reply_subject); |
536 | break; |
537 | case 'd': |
538 | if (bile_delete(db->bile, DB_MESSAGE_RTYPE, msg.id) == 0) |
539 | session_printf(s, "Deleted message {{B}}%d{{/B}}\r\n", idx); |
540 | else |
541 | session_printf(s, "Failed deleting message " |
542 | "{{B}}%d{{/B}}! (%d)\r\n", idx, bile_error(db->bile)); |
543 | ret = MAIL_READ_RETURN_FIND; |
544 | done = true; |
545 | break; |
546 | case 'u': |
547 | msg.read = 0; |
548 | if (mail_save(s, &msg) == 0) |
549 | session_printf(s, "Marked message {{B}}%d{{/B}} unread\r\n", |
550 | idx); |
551 | else |
552 | session_printf(s, "Failed updating message " |
553 | "{{B}}%d{{/B}}! (%d)\r\n", idx, bile_error(db->bile)); |
554 | ret = MAIL_READ_RETURN_DONE; |
555 | done = true; |
556 | break; |
557 | case 'l': |
558 | done = true; |
559 | ret = MAIL_READ_RETURN_LIST; |
560 | break; |
561 | case 'q': |
562 | done = true; |
563 | ret = MAIL_READ_RETURN_DONE; |
564 | break; |
565 | case 0: |
566 | case 1: |
567 | case 2: |
568 | case 3: |
569 | case 4: |
570 | case 5: |
571 | case 6: |
572 | case 7: |
573 | case 8: |
574 | case 9: |
575 | ret = c; |
576 | done = true; |
577 | break; |
578 | case '?': |
579 | show_help = true; |
580 | break; |
581 | } |
582 | } |
583 | |
584 | mail_free_message_strings(&msg); |
585 | |
586 | return ret; |
587 | } |
588 | |
589 | short |
590 | mail_save(struct session *s, struct mail_message *msg) |
591 | { |
592 | size_t size; |
593 | char *data; |
594 | short ret; |
595 | |
596 | if (!msg->id) |
597 | msg->id = bile_next_id(db->bile, DB_MESSAGE_RTYPE); |
598 | |
599 | ret = bile_marshall_object(db->bile, mail_object_fields, |
600 | nitems(mail_object_fields), msg, &data, &size, "mail_save"); |
601 | if (ret != 0 || size == 0) { |
602 | warn("mail_save: failed to marshall object"); |
603 | return -1; |
604 | } |
605 | |
606 | if (bile_write(db->bile, DB_MESSAGE_RTYPE, msg->id, data, |
607 | size) != size) { |
608 | warn("mail_save: bile_write failed! %d", bile_error(db->bile)); |
609 | return bile_error(db->bile); |
610 | } |
611 | |
612 | return 0; |
613 | } |
614 | |
615 | size_t |
616 | mail_find_ids_for_user(struct user *user, size_t *nmail_ids, |
617 | unsigned long **mail_ids, size_t offset, size_t limit, bool only_unread) |
618 | { |
619 | unsigned long msg_user_id; |
620 | struct bile_object *o; |
621 | struct mail_message msg; |
622 | size_t n, nmsgs_for_user, nret, mail_ids_size, id, size; |
623 | short i, j; |
624 | char *data; |
625 | bool read; |
626 | |
627 | mail_ids_size = sizeof(long) * 16; |
628 | if (mail_ids != NULL) |
629 | *mail_ids = xmalloc(mail_ids_size, "mail_find_ids_for_user ids"); |
630 | if (nmail_ids != NULL) |
631 | *nmail_ids = 0; |
632 | |
633 | nmsgs_for_user = 0; |
634 | for (n = 0; o = bile_get_nth_of_type(db->bile, n, DB_MESSAGE_RTYPE); |
635 | n++) { |
636 | id = o->id; |
637 | bile_read(db->bile, DB_MESSAGE_RTYPE, id, (char *)&msg_user_id, |
638 | sizeof(msg_user_id)); |
639 | xfree(&o); |
640 | |
641 | if (msg_user_id != user->id) |
642 | continue; |
643 | |
644 | if (only_unread) { |
645 | size = bile_read_alloc(db->bile, DB_MESSAGE_RTYPE, id, &data); |
646 | bile_unmarshall_object(db->bile, mail_object_fields, |
647 | nmail_object_fields, data, size, &msg, sizeof(msg), false, |
648 | "mail_find_ids_for_user"); |
649 | xfree(&data); |
650 | read = msg.read; |
651 | if (read) |
652 | continue; |
653 | } |
654 | |
655 | if (mail_ids != NULL) { |
656 | EXPAND_TO_FIT(*mail_ids, mail_ids_size, |
657 | (nmsgs_for_user + 1) * sizeof(long), sizeof(long), |
658 | sizeof(long) * 16); |
659 | (*mail_ids)[nmsgs_for_user] = id; |
660 | } |
661 | |
662 | nmsgs_for_user++; |
663 | } |
664 | |
665 | if (mail_ids != NULL) { |
666 | /* sort by message id for consistent ordering */ |
667 | for (i = 0; i < nmsgs_for_user; i++) { |
668 | for (j = 0; j < nmsgs_for_user - i - 1; j++) { |
669 | if ((*mail_ids)[j] > (*mail_ids)[j + 1]) { |
670 | id = (*mail_ids)[j]; |
671 | (*mail_ids)[j] = (*mail_ids)[j + 1]; |
672 | (*mail_ids)[j + 1] = id; |
673 | } |
674 | } |
675 | } |
676 | } |
677 | |
678 | if (nmail_ids != NULL) |
679 | *nmail_ids = nmsgs_for_user; |
680 | |
681 | if (offset) { |
682 | if (offset >= nmsgs_for_user) { |
683 | if (nmail_ids != NULL) |
684 | *nmail_ids = 0; |
685 | if (mail_ids != NULL) |
686 | xfree(&(*mail_ids)); |
687 | } else { |
688 | if (mail_ids != NULL) { |
689 | for (j = offset, i = 0; j < nmsgs_for_user; j++, i++) |
690 | (*mail_ids)[i] = (*mail_ids)[j]; |
691 | } |
692 | if (nmail_ids != NULL) |
693 | *nmail_ids -= offset; |
694 | } |
695 | } |
696 | |
697 | if (nmail_ids != NULL && *nmail_ids > limit) |
698 | *nmail_ids = limit; |
699 | |
700 | return nmsgs_for_user; |
701 | } |