Download
jcs
/subtext
/board.c
(View History)
jcs board: Delete cached index if id_map fails allocation | Latest amendment: 505 on 2023-05-01 |
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 "subtext.h" |
23 | #include "ansi.h" |
24 | #include "binkp.h" |
25 | #include "board.h" |
26 | #include "logger.h" |
27 | #include "user.h" |
28 | |
29 | #define POSTS_PER_PAGE 20 |
30 | |
31 | #define POST_READ_RETURN_DONE -1 |
32 | #define POST_READ_RETURN_LIST -2 |
33 | #define POST_READ_RETURN_FIND -3 |
34 | #define POST_READ_RETURN_NEWER -4 |
35 | #define POST_READ_RETURN_OLDER -5 |
36 | |
37 | const struct struct_field board_fields[] = { |
38 | { "Board ID", CONFIG_TYPE_LONG, |
39 | offsetof(struct board, id), |
40 | 1, LONG_MAX }, |
41 | { "Name", CONFIG_TYPE_STRING, |
42 | offsetof(struct board, name), |
43 | 1, member_size(struct board, name) }, |
44 | { "Description", CONFIG_TYPE_STRING, |
45 | offsetof(struct board, description), |
46 | 0, member_size(struct board, description) }, |
47 | { "Restricted Posting", CONFIG_TYPE_BOOLEAN, |
48 | offsetof(struct board, restricted_posting), |
49 | 0, 0 }, |
50 | { "Restricted Viewing", CONFIG_TYPE_BOOLEAN, |
51 | offsetof(struct board, restricted_viewing), |
52 | 0, 0 }, |
53 | { "Days of Retention", CONFIG_TYPE_SHORT, |
54 | offsetof(struct board, retention_days), |
55 | 0, USHRT_MAX }, |
56 | { "FTN Area Name", CONFIG_TYPE_STRING, |
57 | offsetof(struct board, ftn_area), |
58 | 0, member_size(struct board, ftn_area) }, |
59 | }; |
60 | const size_t nboard_fields = nitems(board_fields); |
61 | |
62 | const struct bile_object_field board_object_fields[] = { |
63 | { offsetof(struct board, id), |
64 | member_size(struct board, id), -1 }, |
65 | { offsetof(struct board, name), |
66 | member_size(struct board, name), -1 }, |
67 | { offsetof(struct board, description), |
68 | member_size(struct board, description), -1 }, |
69 | { offsetof(struct board, restricted_posting), |
70 | member_size(struct board, restricted_posting), -1 }, |
71 | { offsetof(struct board, restricted_viewing), |
72 | member_size(struct board, restricted_viewing), -1 }, |
73 | { offsetof(struct board, retention_days), |
74 | member_size(struct board, retention_days), -1 }, |
75 | { offsetof(struct board, last_post_at), |
76 | member_size(struct board, last_post_at), -1 }, |
77 | { offsetof(struct board, post_count), |
78 | member_size(struct board, post_count), -1 }, |
79 | { offsetof(struct board, ftn_area), |
80 | member_size(struct board, ftn_area), -1 }, |
81 | }; |
82 | const size_t nboard_object_fields = nitems(board_object_fields); |
83 | |
84 | const struct bile_object_field board_post_object_fields[] = { |
85 | { offsetof(struct board_post, id), |
86 | member_size(struct board_post, id), -1 }, |
87 | { offsetof(struct board_post, thread_id), |
88 | member_size(struct board_post, thread_id), -1 }, |
89 | { offsetof(struct board_post, time), |
90 | member_size(struct board_post, time), -1 }, |
91 | { offsetof(struct board_post, sender_user_id), |
92 | member_size(struct board_post, sender_user_id), -1 }, |
93 | { offsetof(struct board_post, body_size), |
94 | member_size(struct board_post, body_size), -1 }, |
95 | { offsetof(struct board_post, body), |
96 | -1, offsetof(struct board_post, body_size) }, |
97 | { offsetof(struct board_post, parent_post_id), |
98 | member_size(struct board_post, parent_post_id), -1 }, |
99 | { offsetof(struct board_post, via), |
100 | member_size(struct board_post, via), -1 }, |
101 | }; |
102 | const size_t nboard_post_object_fields = nitems(board_post_object_fields); |
103 | |
104 | const struct bile_object_field board_ftn_post_object_fields[] = { |
105 | { offsetof(struct board_ftn_post, id), |
106 | member_size(struct board_ftn_post, id), -1 }, |
107 | { offsetof(struct board_ftn_post, msgid), |
108 | member_size(struct board_ftn_post, msgid), -1 }, |
109 | { offsetof(struct board_ftn_post, time), |
110 | member_size(struct board_ftn_post, time), -1 }, |
111 | { offsetof(struct board_ftn_post, from), |
112 | member_size(struct board_ftn_post, from), -1 }, |
113 | { offsetof(struct board_ftn_post, subject), |
114 | member_size(struct board_ftn_post, subject), -1 }, |
115 | { offsetof(struct board_ftn_post, msgid_orig), |
116 | member_size(struct board_ftn_post, msgid_orig), -1 }, |
117 | { offsetof(struct board_ftn_post, origin), |
118 | member_size(struct board_ftn_post, origin), -1 }, |
119 | { offsetof(struct board_ftn_post, reply), |
120 | member_size(struct board_ftn_post, reply), -1 }, |
121 | { offsetof(struct board_ftn_post, to), |
122 | member_size(struct board_ftn_post, to), -1 }, |
123 | { offsetof(struct board_ftn_post, body_size), |
124 | member_size(struct board_ftn_post, body_size), -1 }, |
125 | { offsetof(struct board_ftn_post, body), |
126 | -1, offsetof(struct board_ftn_post, body_size) }, |
127 | }; |
128 | const size_t nboard_ftn_post_object_fields = |
129 | nitems(board_ftn_post_object_fields); |
130 | |
131 | const struct bile_object_field board_thread_object_fields[] = { |
132 | { offsetof(struct board_thread, thread_id), |
133 | member_size(struct board_thread, thread_id), -1 }, |
134 | { offsetof(struct board_thread, last_post_at), |
135 | member_size(struct board_thread, last_post_at), -1 }, |
136 | { offsetof(struct board_thread, subject_size), |
137 | member_size(struct board_thread, subject_size), -1 }, |
138 | { offsetof(struct board_thread, subject), |
139 | -1, offsetof(struct board_thread, subject_size) }, |
140 | { offsetof(struct board_thread, nposts), |
141 | member_size(struct board_thread, nposts), -1 }, |
142 | { offsetof(struct board_thread, post_ids), |
143 | -(sizeof(long)), offsetof(struct board_thread, nposts) }, |
144 | { offsetof(struct board_thread, parent_post_ids), |
145 | -(sizeof(long)), offsetof(struct board_thread, nposts) }, |
146 | }; |
147 | const size_t nboard_thread_object_fields = nitems(board_thread_object_fields); |
148 | |
149 | unsigned long board_compose(struct session *s, struct board *board, |
150 | struct board_thread *thread, struct board_post *parent_post, |
151 | struct board_ftn_post *ftn_parent_post, char *initial_subject, |
152 | char *initial_body); |
153 | short board_post_create(struct board *board, struct board_thread *thread, |
154 | struct board_ftn_post *ftn_parent_post, struct board_post *post); |
155 | void board_list_posts(struct session *s, struct board *board, |
156 | size_t npost_Ids, unsigned long *post_ids, size_t page, size_t pages); |
157 | short board_post_read(struct session *s, struct board *board, |
158 | char *prompt_prefix, unsigned long id, short idx); |
159 | size_t board_find_post_ids(struct session *s, struct board *board, |
160 | size_t *npost_ids, unsigned long **post_ids, size_t offset, |
161 | size_t limit); |
162 | void board_delete_cached_index(struct board *board); |
163 | |
164 | void |
165 | board_list_boards(struct session *s) |
166 | { |
167 | static struct session_menu_option opts[] = { |
168 | { '#', "#", "Enter board number to read" }, |
169 | { 'l', "Ll", "List boards" }, |
170 | { 'q', "QqXx", "Return to main menu" }, |
171 | { '?', "?", "List menu options" }, |
172 | }; |
173 | static const char prompt_help[] = |
174 | "#:View Board L:List Boards Q:Return ?:Help"; |
175 | struct board *lboards = NULL, tboard; |
176 | size_t nlboards; |
177 | char title[] = "List Boards"; |
178 | char c; |
179 | short an, n, i, j; |
180 | bool done, show_list, show_help; |
181 | |
182 | lboards = xcalloc(sizeof(struct board), db->nboards); |
183 | if (lboards == NULL) |
184 | return; |
185 | |
186 | nlboards = 0; |
187 | for (n = 0; n < db->nboards; n++) { |
188 | if (db->boards[n].ftn_area[0] == '\0') { |
189 | memcpy(&lboards[nlboards], &db->boards[n], |
190 | sizeof(struct board)); |
191 | nlboards++; |
192 | } |
193 | } |
194 | |
195 | /* sort by board name */ |
196 | for (i = 0; i < nlboards; i++) { |
197 | for (j = 0; j < nlboards - i - 1; j++) { |
198 | if (strcmp(lboards[j].name, lboards[j + 1].name) > 0) { |
199 | tboard = lboards[j]; |
200 | lboards[j] = lboards[j + 1]; |
201 | lboards[j + 1] = tboard; |
202 | } |
203 | } |
204 | } |
205 | |
206 | show_list = true; |
207 | show_help = false; |
208 | done = false; |
209 | |
210 | snprintf(title, sizeof(title), "Message Boards"); |
211 | |
212 | while (!done && !s->ending) { |
213 | if (show_list) { |
214 | session_printf(s, "{{B}}%s{{/B}}\r\n", title); |
215 | session_printf(s, "%s # Board Description%s\r\n", |
216 | ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); |
217 | session_flush(s); |
218 | |
219 | for (n = 0; n < nlboards; n++) { |
220 | session_printf(s, "%2d %- 13.13s %s\r\n", |
221 | n + 1, |
222 | lboards[n].name, |
223 | lboards[n].description); |
224 | } |
225 | session_flush(s); |
226 | |
227 | show_list = false; |
228 | } |
229 | |
230 | c = session_menu(s, title, "Boards", |
231 | (char *)prompt_help, opts, nitems(opts), show_help, "Board #", |
232 | &an); |
233 | show_help = false; |
234 | |
235 | switch (c) { |
236 | case 'l': |
237 | show_list = true; |
238 | break; |
239 | case '#': |
240 | if (an < 1 || an > nlboards) { |
241 | session_printf(s, "Invalid board\r\n"); |
242 | session_flush(s); |
243 | break; |
244 | } |
245 | board_show(s, lboards[an - 1].id, "Boards"); |
246 | break; |
247 | case '?': |
248 | show_help = true; |
249 | break; |
250 | default: |
251 | done = true; |
252 | break; |
253 | } |
254 | } |
255 | |
256 | if (lboards != NULL) |
257 | xfree(&lboards); |
258 | } |
259 | |
260 | void |
261 | board_list_ftn_areas(struct session *s) |
262 | { |
263 | static struct session_menu_option opts[] = { |
264 | { '#', "#", "Enter area number to read" }, |
265 | { 'l', "Ll", "..." }, |
266 | { 'q', "QqXx", "Return to main menu" }, |
267 | { '?', "?", "List menu options" }, |
268 | }; |
269 | static const char prompt_help[] = |
270 | "#:View Area L:List Areas Q:Return ?:Help"; |
271 | struct board *fboards = NULL, tboard; |
272 | struct fidopkt_address our_address; |
273 | size_t nfboards; |
274 | char title[50], latest[10]; |
275 | char c; |
276 | short an, n, i, j; |
277 | bool done, show_list, show_help; |
278 | |
279 | if (!fidopkt_parse_address(db->config.ftn_node_addr, &our_address)) { |
280 | session_printf(s, "{{B}}Error:{{/B}} FTN Areas are not supported " |
281 | "on this system.\r\n"); |
282 | session_flush(s); |
283 | return; |
284 | } |
285 | |
286 | snprintf(opts[1].title, sizeof(opts[1].title), "List %s Areas", |
287 | db->config.ftn_network); |
288 | |
289 | fboards = xcalloc(sizeof(struct board), db->nboards); |
290 | if (fboards == NULL) |
291 | return; |
292 | |
293 | nfboards = 0; |
294 | for (n = 0; n < db->nboards; n++) { |
295 | if (db->boards[n].ftn_area[0]) { |
296 | memcpy(&fboards[nfboards], &db->boards[n], |
297 | sizeof(struct board)); |
298 | nfboards++; |
299 | } |
300 | } |
301 | |
302 | /* sort by area name */ |
303 | for (i = 0; i < nfboards; i++) { |
304 | for (j = 0; j < nfboards - i - 1; j++) { |
305 | if (strcmp(fboards[j].ftn_area, |
306 | fboards[j + 1].ftn_area) > 0) { |
307 | tboard = fboards[j]; |
308 | fboards[j] = fboards[j + 1]; |
309 | fboards[j + 1] = tboard; |
310 | } |
311 | } |
312 | } |
313 | |
314 | show_list = true; |
315 | show_help = false; |
316 | done = false; |
317 | |
318 | snprintf(title, sizeof(title), "%s Areas (Local Node %s)", |
319 | db->config.ftn_network, db->config.ftn_node_addr); |
320 | |
321 | while (!done && !s->ending) { |
322 | if (show_list) { |
323 | session_printf(s, "{{B}}%s{{/B}}\r\n", title); |
324 | session_printf(s, "%s # Latest Area Description%s\r\n", |
325 | ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); |
326 | session_flush(s); |
327 | |
328 | for (n = 0; n < nfboards; n++) { |
329 | if (fboards[n].last_post_at) |
330 | strftime(latest, sizeof(latest), "%b %d", |
331 | localtime(&fboards[n].last_post_at)); |
332 | else |
333 | latest[0] = '\0'; |
334 | |
335 | session_printf(s, "%2d % 6s %- 13.13s %s\r\n", |
336 | n + 1, |
337 | latest, |
338 | fboards[n].ftn_area, |
339 | fboards[n].description); |
340 | } |
341 | session_flush(s); |
342 | |
343 | show_list = false; |
344 | } |
345 | |
346 | c = session_menu(s, title, db->config.ftn_network, |
347 | (char *)prompt_help, opts, nitems(opts), show_help, "Area #", |
348 | &an); |
349 | show_help = false; |
350 | |
351 | switch (c) { |
352 | case 'l': |
353 | show_list = true; |
354 | break; |
355 | case '#': |
356 | if (an < 1 || an > nfboards) { |
357 | session_printf(s, "Invalid area\r\n"); |
358 | session_flush(s); |
359 | break; |
360 | } |
361 | board_show(s, fboards[an - 1].id, db->config.ftn_network); |
362 | break; |
363 | case '?': |
364 | show_help = true; |
365 | break; |
366 | default: |
367 | done = true; |
368 | break; |
369 | } |
370 | } |
371 | |
372 | if (fboards != NULL) |
373 | xfree(&fboards); |
374 | } |
375 | |
376 | void |
377 | board_show(struct session *s, short id, char *prompt_prefix) |
378 | { |
379 | static const struct session_menu_option opts[] = { |
380 | { '#', "#", "Enter post to read" }, |
381 | { '<', "<", "View newer posts" }, |
382 | { 'l', "Ll", "List posts" }, |
383 | { '>', ">", "View older posts" }, |
384 | { 'p', "Pp", "Post new thread" }, |
385 | { 'q', "QqXx", "Return to main menu" }, |
386 | { '?', "?", "List menu options" }, |
387 | }; |
388 | static const char prompt_help[] = |
389 | "#:Read <:Newer >:Older L:List P:New Q:Return ?:Help"; |
390 | char prompt[7 + member_size(struct board, name)]; |
391 | struct board *board = NULL; |
392 | size_t n, nall_post_ids, page, pages, npost_ids; |
393 | unsigned long *post_ids = NULL; |
394 | short ppp, ret, pn; |
395 | char c, next_c; |
396 | bool done, find_post_ids, show_list, show_help; |
397 | |
398 | for (n = 0; n < db->nboards; n++) { |
399 | if (db->boards[n].id == id) { |
400 | board = &db->boards[n]; |
401 | break; |
402 | } |
403 | } |
404 | |
405 | if (!board) { |
406 | session_printf(s, "Invalid board\r\n"); |
407 | session_flush(s); |
408 | return; |
409 | } |
410 | |
411 | page = 0; |
412 | find_post_ids = true; |
413 | show_list = true; |
414 | show_help = false; |
415 | done = false; |
416 | next_c = 0; |
417 | |
418 | if (prompt_prefix == NULL) |
419 | prompt_prefix = "Boards"; |
420 | |
421 | snprintf(prompt, sizeof(prompt), "%s:%s", prompt_prefix, board->name); |
422 | |
423 | while (!done && !s->ending) { |
424 | if (find_post_ids) { |
425 | if (post_ids != NULL) { |
426 | xfree(&post_ids); |
427 | post_ids = NULL; |
428 | } |
429 | ppp = POSTS_PER_PAGE; |
430 | if (s->terminal_lines < ppp + 3) |
431 | ppp = BOUND(ppp, 5, s->terminal_lines - 3); |
432 | nall_post_ids = board_find_post_ids(s, board, &npost_ids, |
433 | &post_ids, page * ppp, ppp); |
434 | /* ceil(nall_post_ids / ppp) */ |
435 | pages = (nall_post_ids + ppp - 1) / ppp; |
436 | |
437 | if (page >= pages) |
438 | page = pages - 1; |
439 | |
440 | find_post_ids = false; |
441 | } |
442 | |
443 | if (show_list) { |
444 | board_list_posts(s, board, npost_ids, post_ids, page + 1, |
445 | pages); |
446 | show_list = false; |
447 | } |
448 | |
449 | if (next_c) { |
450 | c = next_c; |
451 | next_c = 0; |
452 | } else { |
453 | c = session_menu(s, board->description, prompt, |
454 | (char *)prompt_help, opts, nitems(opts), show_help, "Post #", |
455 | &pn); |
456 | show_help = false; |
457 | } |
458 | |
459 | handle_opt: |
460 | switch (c) { |
461 | case 'l': |
462 | show_list = true; |
463 | break; |
464 | case 'p': |
465 | if (board_compose(s, board, NULL, NULL, NULL, NULL, NULL)) { |
466 | find_post_ids = true; |
467 | show_list = true; |
468 | } |
469 | break; |
470 | case '>': |
471 | case '<': |
472 | if (c == '>' && page == pages - 1) { |
473 | session_printf(s, "You are at the last page of posts\r\n"); |
474 | session_flush(s); |
475 | break; |
476 | } |
477 | if (c == '<' && page == 0) { |
478 | session_printf(s, "You are already at the first page\r\n"); |
479 | session_flush(s); |
480 | break; |
481 | } |
482 | if (c == '>') |
483 | page++; |
484 | else |
485 | page--; |
486 | find_post_ids = true; |
487 | show_list = true; |
488 | break; |
489 | case '#': |
490 | check_pn: |
491 | if (pn < 1 || pn > npost_ids) { |
492 | session_printf(s, "Invalid post\r\n"); |
493 | session_flush(s); |
494 | break; |
495 | } |
496 | ret = board_post_read(s, board, prompt, post_ids[pn - 1], pn); |
497 | switch (ret) { |
498 | case POST_READ_RETURN_DONE: |
499 | break; |
500 | case POST_READ_RETURN_LIST: |
501 | show_list = true; |
502 | break; |
503 | case POST_READ_RETURN_FIND: |
504 | find_post_ids = true; |
505 | show_list = true; |
506 | break; |
507 | case POST_READ_RETURN_NEWER: |
508 | if (pn == 1) { |
509 | if (page == 0) { |
510 | session_printf(s, "No newer posts.\r\n"); |
511 | session_flush(s); |
512 | break; |
513 | } else { |
514 | page--; |
515 | find_post_ids = true; |
516 | pn = npost_ids; |
517 | next_c = '#'; |
518 | } |
519 | } else { |
520 | pn--; |
521 | goto check_pn; |
522 | } |
523 | break; |
524 | case POST_READ_RETURN_OLDER: |
525 | if (pn == npost_ids) { |
526 | if (page == pages - 1) { |
527 | session_printf(s, "No more posts.\r\n"); |
528 | session_flush(s); |
529 | break; |
530 | } else { |
531 | page++; |
532 | find_post_ids = true; |
533 | pn = 1; |
534 | next_c = '#'; |
535 | } |
536 | } else { |
537 | pn++; |
538 | goto check_pn; |
539 | } |
540 | break; |
541 | default: |
542 | c = ret; |
543 | goto handle_opt; |
544 | } |
545 | break; |
546 | case '?': |
547 | show_help = true; |
548 | break; |
549 | default: |
550 | done = true; |
551 | break; |
552 | } |
553 | } |
554 | |
555 | if (post_ids != NULL) |
556 | xfree(&post_ids); |
557 | } |
558 | |
559 | void |
560 | board_list_posts(struct session *s, struct board *board, size_t npost_ids, |
561 | unsigned long *post_ids, unsigned long page, unsigned long pages) |
562 | { |
563 | char time[24]; |
564 | unsigned long indent_parent_ids[10] = { 0 }; |
565 | char indent_s[22]; |
566 | size_t n, size; |
567 | struct username_cache *user; |
568 | struct board_thread thread = { 0 }; |
569 | struct board_post post; |
570 | struct board_ftn_post fpost; |
571 | short indent, j, k, ret; |
572 | char *data; |
573 | |
574 | session_printf(s, "{{B}}%s: %s (Page %ld of %ld){{/B}}\r\n", |
575 | board->name, board->description, page, pages); |
576 | session_printf(s, "%s # N Date From Subject%s\r\n", |
577 | ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); |
578 | session_flush(s); |
579 | |
580 | if (npost_ids == 0) { |
581 | session_printf(s, "No posts here yet.\r\n"); |
582 | session_flush(s); |
583 | return; |
584 | } |
585 | |
586 | for (n = 0; n < npost_ids; n++) { |
587 | if (board->ftn_area[0]) { |
588 | size = bile_read_alloc(board->bile, BOARD_FTN_POST_RTYPE, |
589 | post_ids[n], &data); |
590 | ret = bile_unmarshall_object(board->bile, |
591 | board_ftn_post_object_fields, nboard_ftn_post_object_fields, |
592 | data, size, &fpost, sizeof(fpost), false); |
593 | xfree(&data); |
594 | if (ret == BILE_ERR_NO_MEMORY) |
595 | break; |
596 | |
597 | strftime(time, sizeof(time), "%b %d", localtime(&fpost.time)); |
598 | |
599 | session_printf(s, "%s%2ld %c %s {{#}}%-10.10s %s%.50s%s\r\n", |
600 | true ? "" : ansi(s, ANSI_BOLD, ANSI_END), |
601 | n + 1, |
602 | true ? ' ' : 'N', |
603 | time, |
604 | fpost.from, |
605 | (fpost.reply[0] && strncasecmp(fpost.subject, "Re:", 3) != 0 ? |
606 | "Re: " : ""), |
607 | fpost.subject, |
608 | true ? "" : ansi(s, ANSI_RESET, ANSI_END)); |
609 | } else { |
610 | size = bile_read_alloc(board->bile, BOARD_POST_RTYPE, |
611 | post_ids[n], &data); |
612 | ret = bile_unmarshall_object(board->bile, |
613 | board_post_object_fields, nboard_post_object_fields, data, |
614 | size, &post, sizeof(post), false); |
615 | xfree(&data); |
616 | if (ret != 0) |
617 | break; |
618 | |
619 | if (post.thread_id != thread.thread_id) { |
620 | if (thread.thread_id) { |
621 | if (thread.subject != NULL) |
622 | xfree(&thread.subject); |
623 | if (thread.post_ids != NULL) |
624 | xfree(&thread.post_ids); |
625 | if (thread.parent_post_ids != NULL) |
626 | xfree(&thread.parent_post_ids); |
627 | } |
628 | size = bile_read_alloc(board->bile, BOARD_THREAD_RTYPE, |
629 | post.thread_id, &data); |
630 | ret = bile_unmarshall_object(board->bile, |
631 | board_thread_object_fields, nboard_thread_object_fields, |
632 | data, size, &thread, sizeof(thread), true); |
633 | xfree(&data); |
634 | if (ret == BILE_ERR_NO_MEMORY) |
635 | break; |
636 | |
637 | for (j = 0; j < nitems(indent_parent_ids); j++) |
638 | indent_parent_ids[j] = 0; |
639 | } |
640 | |
641 | user = user_username(post.sender_user_id); |
642 | strftime(time, sizeof(time), "%b %d", localtime(&post.time)); |
643 | |
644 | if (post.parent_post_id == 0) { |
645 | indent_s[0] = '\0'; |
646 | } else { |
647 | indent = -1; |
648 | for (j = 0; j < nitems(indent_parent_ids); j++) { |
649 | if (indent_parent_ids[j] == post.parent_post_id || |
650 | indent_parent_ids[j] == 0) { |
651 | indent = j; |
652 | indent_parent_ids[j] = post.parent_post_id; |
653 | for (k = j + 1; k < nitems(indent_parent_ids); |
654 | k++) { |
655 | if (indent_parent_ids[k] == 0) |
656 | break; |
657 | indent_parent_ids[k] = 0; |
658 | } |
659 | break; |
660 | } |
661 | } |
662 | if (indent == -1) |
663 | indent = nitems(indent_parent_ids) - 1; |
664 | |
665 | for (j = 0; j < indent; j++) { |
666 | indent_s[j] = ' '; |
667 | } |
668 | indent_s[j] = '`'; |
669 | indent_s[j + 1] = '-'; |
670 | indent_s[j + 2] = '>'; |
671 | indent_s[j + 3] = '\0'; |
672 | } |
673 | session_printf(s, "%s%2ld %c %s %-10.10s %s{{#}}%.40s%s\r\n", |
674 | true ? "" : ansi(s, ANSI_BOLD, ANSI_END), |
675 | n + 1, |
676 | true ? ' ' : 'N', |
677 | time, |
678 | user ? user->username : "(unknown)", |
679 | post.parent_post_id != 0 && n == 0 ? "Re: " : "", |
680 | post.parent_post_id == 0 || n == 0 ? thread.subject : indent_s, |
681 | true ? "" : ansi(s, ANSI_RESET, ANSI_END)); |
682 | } |
683 | } |
684 | session_flush(s); |
685 | |
686 | if (thread.subject != NULL) |
687 | xfree(&thread.subject); |
688 | if (thread.post_ids != NULL) |
689 | xfree(&thread.post_ids); |
690 | if (thread.parent_post_ids != NULL) |
691 | xfree(&thread.parent_post_ids); |
692 | } |
693 | |
694 | unsigned long |
695 | board_compose(struct session *s, struct board *board, |
696 | struct board_thread *thread, struct board_post *parent_post, |
697 | struct board_ftn_post *ftn_parent_post, char *initial_subject, |
698 | char *initial_body) |
699 | { |
700 | struct board_post post = { 0 }; |
701 | char *tmp = NULL; |
702 | short c; |
703 | |
704 | if (!s->user) { |
705 | session_printf(s, "Posting is not available to guests.\r\n" |
706 | "Please create an account first.\r\n"); |
707 | session_flush(s); |
708 | return 0; |
709 | } |
710 | |
711 | if (board->restricted_posting && !s->user->is_sysop) { |
712 | session_printf(s, "Posting to this board is not permitted.\r\n"); |
713 | session_flush(s); |
714 | return 0; |
715 | } |
716 | |
717 | if (initial_body) { |
718 | post.body = xstrdup(initial_body); |
719 | if (post.body == NULL) |
720 | return 0; |
721 | } |
722 | |
723 | if (thread) { |
724 | post.thread_id = thread->thread_id; |
725 | post.parent_post_id = parent_post->id; |
726 | } else { |
727 | thread = xmalloczero(sizeof(struct board_thread)); |
728 | if (thread == NULL) |
729 | return 0; |
730 | } |
731 | post.sender_user_id = s->user->id; |
732 | strlcpy(post.via, s->via, sizeof(post.via)); |
733 | |
734 | session_printf(s, "{{B}}Compose %s{{/B}}\r\n", |
735 | parent_post ? "Reply" : "New Post"); |
736 | session_printf(s, "{{B}}From:{{/B}} %s (via %s)\r\n", |
737 | s->user->username, s->via); |
738 | session_printf(s, "{{B}}To:{{/B}} %s\r\n", board->name); |
739 | |
740 | post_compose_start: |
741 | if (parent_post) { |
742 | session_printf(s, "{{B}}Subject:{{/B}}{{#}} Re: %s\r\n", |
743 | thread->subject); |
744 | session_flush(s); |
745 | } else if (ftn_parent_post) { |
746 | session_printf(s, "{{B}}Subject:{{/B}}{{#}} %s%s\r\n", |
747 | strncmp("Re:", ftn_parent_post->subject, 3) == 1 ? |
748 | "" : "Re: ", ftn_parent_post->subject); |
749 | session_flush(s); |
750 | } else { |
751 | if (initial_subject && !thread->subject) { |
752 | thread->subject = xstrdup(initial_subject); |
753 | if (thread->subject == NULL) |
754 | return 0; |
755 | } |
756 | |
757 | for (;;) { |
758 | session_printf(s, "{{B}}Subject:{{/B}} "); |
759 | session_flush(s); |
760 | |
761 | tmp = session_field_input(s, 100, 50, thread->subject, |
762 | false, 0); |
763 | if (thread->subject != NULL) |
764 | xfree(&thread->subject); |
765 | thread->subject = tmp; |
766 | session_output(s, "\r\n", 2); |
767 | session_flush(s); |
768 | |
769 | if (thread->subject == NULL) |
770 | goto post_compose_done; |
771 | |
772 | rtrim(thread->subject, "\r\n\t "); |
773 | |
774 | if (thread->subject[0] == '\0') { |
775 | session_printf(s, "{{B}}Error:{{/B}} Subject " |
776 | "cannot be blank (^C to cancel)\r\n"); |
777 | session_flush(s); |
778 | xfree(&thread->subject); |
779 | continue; |
780 | } |
781 | thread->subject_size = strlen(thread->subject) + 1; |
782 | break; |
783 | } |
784 | } |
785 | |
786 | for (;;) { |
787 | session_printf(s, "{{B}}Message (^D when finished):{{/B}}\r\n"); |
788 | session_flush(s); |
789 | |
790 | tmp = session_field_input(s, 2048, s->terminal_columns - 1, |
791 | post.body, true, 0); |
792 | if (post.body != NULL) |
793 | xfree(&post.body); |
794 | post.body = tmp; |
795 | session_output(s, "\r\n", 2); |
796 | session_flush(s); |
797 | |
798 | if (post.body == NULL) |
799 | goto post_compose_done; |
800 | |
801 | rtrim(post.body, "\r\n\t "); |
802 | |
803 | if (post.body[0] == '\0') { |
804 | xfree(&post.body); |
805 | goto post_compose_done; |
806 | } |
807 | post.body_size = strlen(post.body) + 1; |
808 | break; |
809 | } |
810 | |
811 | for (;;) { |
812 | session_printf(s, "\r\n{{B}}(P){{/B}}ost message, " |
813 | "{{B}}(E){{/B}}dit again, or {{B}}(C){{/B}}ancel? "); |
814 | session_flush(s); |
815 | |
816 | c = session_input_char(s); |
817 | if (c == 0 || s->ending) |
818 | goto post_compose_done; |
819 | |
820 | switch (c) { |
821 | case 'p': |
822 | case 'P': |
823 | case 'y': |
824 | session_printf(s, "%c\r\n", c); |
825 | session_flush(s); |
826 | /* FALLTHROUGH */ |
827 | case '\n': |
828 | case '\r': |
829 | /* send */ |
830 | session_printf(s, "Posting message... "); |
831 | session_flush(s); |
832 | |
833 | if (board_post_create(board, thread, ftn_parent_post, |
834 | &post) == 0) { |
835 | session_logf(s, "Posted message %ld to %s", post.id, |
836 | board->name); |
837 | session_printf(s, "done\r\n"); |
838 | } else |
839 | session_printf(s, "failed!\r\n"); |
840 | |
841 | session_flush(s); |
842 | |
843 | goto post_compose_done; |
844 | case 'e': |
845 | case 'E': |
846 | session_printf(s, "%c\r\n", c); |
847 | session_flush(s); |
848 | goto post_compose_start; |
849 | case 'c': |
850 | case 'C': |
851 | session_printf(s, "%c\r\n", c); |
852 | session_flush(s); |
853 | /* FALLTHROUGH */ |
854 | case CONTROL_C: |
855 | goto post_compose_done; |
856 | } |
857 | } |
858 | |
859 | post_compose_error: |
860 | session_printf(s, "Failed saving message!\r\n"); |
861 | session_flush(s); |
862 | |
863 | post_compose_done: |
864 | if (parent_post == NULL) { |
865 | if (thread->subject != NULL) |
866 | xfree(&thread->subject); |
867 | xfree(&thread); |
868 | } |
869 | if (post.body) |
870 | xfree(&post.body); |
871 | |
872 | return post.id; |
873 | } |
874 | |
875 | short |
876 | board_post_read(struct session *s, struct board *board, char *prompt_prefix, |
877 | unsigned long id, short idx) |
878 | { |
879 | static const struct session_menu_option opts[] = { |
880 | { '<', "<Nn", "Read newer post" }, |
881 | { '>', ">Pp", "Read older post" }, |
882 | { 'r', "Rr", "Reply to this post" }, |
883 | { 'd', "Dd", "Delete this post" }, |
884 | { 'q', "QqXx", "Return to threads" }, |
885 | { '?', "?", "List these options" }, |
886 | }; |
887 | static const char prompt_help[] = |
888 | "<:Newer >:Older R:Reply D:Delete Q:Return ?:Help"; |
889 | char time[32], prompt[7 + member_size(struct board, name) + 8]; |
890 | struct board_thread thread; |
891 | struct board_post post; |
892 | struct board_ftn_post fpost; |
893 | struct username_cache *sender; |
894 | struct session_menu_option *dopts = NULL; |
895 | size_t size, plain_post_size, j; |
896 | short ret = POST_READ_RETURN_DONE; |
897 | char c; |
898 | char *data, *subject, *plain_post, *tplain_post; |
899 | short cc, bcret; |
900 | bool done = false, show_help = false; |
901 | |
902 | dopts = xmalloc(sizeof(opts)); |
903 | if (dopts == NULL) |
904 | return 0; |
905 | memcpy(dopts, opts, sizeof(opts)); |
906 | |
907 | if (board->ftn_area[0]) { |
908 | size = bile_read_alloc(board->bile, BOARD_FTN_POST_RTYPE, id, |
909 | &data); |
910 | if (size == 0) |
911 | panic("failed fetching message %ld: %d", id, |
912 | bile_error(board->bile)); |
913 | |
914 | ret = bile_unmarshall_object(board->bile, |
915 | board_ftn_post_object_fields, nboard_ftn_post_object_fields, |
916 | data, size, &fpost, sizeof(fpost), true); |
917 | xfree(&data); |
918 | if (ret == BILE_ERR_NO_MEMORY) |
919 | goto done_reading; |
920 | |
921 | if (!(s->user && s->user->is_sysop)) |
922 | /* disable deleting */ |
923 | dopts[1].key[0] = '\0'; |
924 | |
925 | strftime(time, sizeof(time), "%Y-%m-%d %H:%M:%S", |
926 | localtime(&fpost.time)); |
927 | |
928 | session_printf(s, "{{B}}From:{{/B}}{{#}} %s\r\n", fpost.from); |
929 | session_printf(s, "{{B}}Origin:{{/B}}{{#}} %s\r\n", fpost.origin); |
930 | session_printf(s, "{{B}}To:{{/B}}{{#}} %s@%s\r\n", fpost.to, |
931 | board->name); |
932 | session_printf(s, "{{B}}Date:{{/B}}{{#}} %s %s\r\n", time, |
933 | db->config.timezone); |
934 | session_printf(s, "{{B}}Subject:{{/B}}{{#}} %s%s\r\n", |
935 | (fpost.reply[0] && strncasecmp(fpost.subject, "Re:", 3) != 0 ? |
936 | "Re: " : ""), fpost.subject); |
937 | session_printf(s, "\r\n"); |
938 | session_flush(s); |
939 | |
940 | plain_post_size = 0; |
941 | plain_post = xmalloc(fpost.body_size); |
942 | if (plain_post == NULL) |
943 | session_paginate(s, fpost.body, fpost.body_size, 6); |
944 | else { |
945 | /* strip out renegade-style pipe color codes ("abc|01def") */ |
946 | for (j = 0; j < fpost.body_size; j++) { |
947 | if (fpost.body[j] == '|' && |
948 | fpost.body[j + 1] >= '0' && fpost.body[j + 1] <= '9' && |
949 | fpost.body[j + 2] >= '0' && fpost.body[j + 2] <= '9') { |
950 | j += 2; |
951 | continue; |
952 | } |
953 | plain_post[plain_post_size++] = fpost.body[j]; |
954 | } |
955 | tplain_post = xrealloc(plain_post, plain_post_size); |
956 | if (tplain_post != NULL) |
957 | plain_post = tplain_post; |
958 | session_paginate(s, plain_post, plain_post_size, 6); |
959 | xfree(&plain_post); |
960 | } |
961 | } else { |
962 | size = bile_read_alloc(board->bile, BOARD_POST_RTYPE, id, &data); |
963 | if (size == 0) |
964 | panic("failed fetching message %ld: %d", id, |
965 | bile_error(board->bile)); |
966 | ret = bile_unmarshall_object(board->bile, board_post_object_fields, |
967 | nboard_post_object_fields, data, size, &post, sizeof(post), true); |
968 | xfree(&data); |
969 | if (ret == BILE_ERR_NO_MEMORY) |
970 | goto done_reading; |
971 | |
972 | size = bile_read_alloc(board->bile, BOARD_THREAD_RTYPE, |
973 | post.thread_id, &data); |
974 | if (size == 0) |
975 | panic("failed fetching thread %ld: %d", post.thread_id, |
976 | bile_error(board->bile)); |
977 | ret = bile_unmarshall_object(board->bile, board_thread_object_fields, |
978 | nboard_thread_object_fields, data, size, &thread, |
979 | sizeof(thread), true); |
980 | xfree(&data); |
981 | if (ret == BILE_ERR_NO_MEMORY) |
982 | goto done_reading; |
983 | |
984 | if (!(s->user && (s->user->is_sysop || |
985 | s->user->id == post.sender_user_id))) |
986 | /* disable deleting */ |
987 | dopts[1].key[0] = '\0'; |
988 | |
989 | sender = user_username(post.sender_user_id); |
990 | |
991 | strftime(time, sizeof(time), "%Y-%m-%d %H:%M:%S", |
992 | localtime(&post.time)); |
993 | |
994 | session_printf(s, "{{B}}From:{{/B}}{{#}} %s", |
995 | sender ? sender->username : "(unknown)"); |
996 | if (post.via[0]) |
997 | session_printf(s, " (via %s)", post.via); |
998 | session_printf(s, "\r\n"); |
999 | session_printf(s, "{{B}}To:{{/B}}{{#}} %s\r\n", board->name); |
1000 | session_printf(s, "{{B}}Date:{{/B}}{{#}} %s %s\r\n", time, |
1001 | db->config.timezone); |
1002 | session_printf(s, "{{B}}Subject:{{/B}}{{#}} %s%s\r\n", |
1003 | (post.parent_post_id ? "Re: " : ""), thread.subject); |
1004 | session_printf(s, "\r\n"); |
1005 | session_flush(s); |
1006 | session_paginate(s, post.body, post.body_size, 5); |
1007 | } |
1008 | |
1009 | snprintf(prompt, sizeof(prompt), "%s:%d", prompt_prefix, idx); |
1010 | |
1011 | if (board->ftn_area[0]) |
1012 | subject = fpost.subject; |
1013 | else |
1014 | subject = thread.subject; |
1015 | |
1016 | while (!done && !s->ending) { |
1017 | c = session_menu(s, subject, prompt, (char *)prompt_help, dopts, |
1018 | nitems(opts), show_help, NULL, NULL); |
1019 | show_help = false; |
1020 | |
1021 | switch (c) { |
1022 | case 'd': |
1023 | if (!(s->user && (s->user->is_sysop || |
1024 | s->user->id == post.sender_user_id))) { |
1025 | session_printf(s, "Invalid option\r\n"); |
1026 | session_flush(s); |
1027 | break; |
1028 | } |
1029 | |
1030 | session_printf(s, "Are you sure you want to permanently " |
1031 | "delete this post? [y/N] "); |
1032 | session_flush(s); |
1033 | |
1034 | cc = session_input_char(s); |
1035 | if (cc == 'y' || c == 'Y') { |
1036 | session_printf(s, "%c\r\n", cc); |
1037 | session_flush(s); |
1038 | |
1039 | if (board->ftn_area[0]) { |
1040 | board_delete_ftn_post(board, &fpost); |
1041 | session_logf(s, "Deleted %s post %ld", |
1042 | db->config.ftn_network, fpost.id); |
1043 | } else { |
1044 | board_delete_post(board, &post, &thread); |
1045 | session_logf(s, "Deleted post %ld (thread %ld)", |
1046 | post.id, thread.thread_id); |
1047 | } |
1048 | |
1049 | session_printf(s, "\r\n{{B}}Post deleted!{{/B}}\r\n"); |
1050 | session_flush(s); |
1051 | ret = POST_READ_RETURN_FIND; |
1052 | done = true; |
1053 | } else { |
1054 | session_printf(s, "\r\nPost not deleted.\r\n"); |
1055 | session_flush(s); |
1056 | } |
1057 | break; |
1058 | case 'r': |
1059 | if (board->ftn_area[0]) |
1060 | bcret = board_compose(s, board, NULL, NULL, &fpost, NULL, |
1061 | NULL); |
1062 | else |
1063 | bcret = board_compose(s, board, &thread, &post, NULL, NULL, |
1064 | NULL); |
1065 | |
1066 | if (bcret) { |
1067 | ret = POST_READ_RETURN_FIND; |
1068 | done = true; |
1069 | } |
1070 | break; |
1071 | case '<': |
1072 | ret = POST_READ_RETURN_NEWER; |
1073 | done = true; |
1074 | break; |
1075 | case '>': |
1076 | ret = POST_READ_RETURN_OLDER; |
1077 | done = true; |
1078 | break; |
1079 | case 'q': |
1080 | ret = POST_READ_RETURN_DONE; |
1081 | done = true; |
1082 | break; |
1083 | case '?': |
1084 | show_help = true; |
1085 | break; |
1086 | } |
1087 | } |
1088 | |
1089 | done_reading: |
1090 | xfree(&dopts); |
1091 | |
1092 | if (board->ftn_area[0]) { |
1093 | if (fpost.body != NULL) |
1094 | xfree(&fpost.body); |
1095 | } else { |
1096 | if (post.body != NULL) |
1097 | xfree(&post.body); |
1098 | if (thread.subject != NULL) |
1099 | xfree(&thread.subject); |
1100 | if (thread.post_ids != NULL) |
1101 | xfree(&thread.post_ids); |
1102 | if (thread.parent_post_ids != NULL) |
1103 | xfree(&thread.parent_post_ids); |
1104 | } |
1105 | |
1106 | return ret; |
1107 | } |
1108 | |
1109 | size_t |
1110 | board_find_post_ids(struct session *s, struct board *board, |
1111 | size_t *npost_ids, unsigned long **post_ids, size_t offset, size_t limit) |
1112 | { |
1113 | struct board_id_time_map *all_post_id_map; |
1114 | size_t n, size, nall_post_ids; |
1115 | |
1116 | *post_ids = NULL; |
1117 | *npost_ids = 0; |
1118 | |
1119 | size = bile_read_alloc(board->bile, BOARD_SORTED_ID_MAP_RTYPE, 1, |
1120 | &all_post_id_map); |
1121 | if (all_post_id_map == NULL) { |
1122 | session_printf(s, "%sPlease wait, re-indexing board posts...%s", |
1123 | ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); |
1124 | session_flush(s); |
1125 | nall_post_ids = board_index_sorted_post_ids(board, |
1126 | &all_post_id_map); |
1127 | session_output(s, "\r\n", 2); |
1128 | session_flush(s); |
1129 | if (nall_post_ids == 0) |
1130 | return 0; |
1131 | } else |
1132 | nall_post_ids = size / sizeof(struct board_id_time_map); |
1133 | |
1134 | *post_ids = xcalloc(sizeof(long), MIN(limit, nall_post_ids)); |
1135 | if (*post_ids == NULL) |
1136 | return 0; |
1137 | |
1138 | for (n = 0; n < nall_post_ids; n++) { |
1139 | if (n < offset) |
1140 | continue; |
1141 | |
1142 | (*post_ids)[*npost_ids] = all_post_id_map[n].id; |
1143 | (*npost_ids)++; |
1144 | |
1145 | if (*npost_ids >= limit) |
1146 | break; |
1147 | } |
1148 | |
1149 | if (all_post_id_map != NULL) |
1150 | xfree(&all_post_id_map); |
1151 | |
1152 | return nall_post_ids; |
1153 | } |
1154 | |
1155 | short |
1156 | board_post_create(struct board *board, struct board_thread *thread, |
1157 | struct board_ftn_post *ftn_parent_post, struct board_post *post) |
1158 | { |
1159 | struct board_ftn_post ftn_post = { 0 }; |
1160 | struct fidopkt_message fidomsg = { 0 }; |
1161 | struct username_cache *user; |
1162 | struct fidopkt_address our_address, hub_address; |
1163 | struct bile_object *o; |
1164 | short ret; |
1165 | char *data; |
1166 | size_t size, insert, npost_ids; |
1167 | ssize_t n, j; |
1168 | unsigned long *post_ids, *parent_post_ids; |
1169 | struct board_id_time_map *id_map; |
1170 | |
1171 | if (board->ftn_area[0]) { |
1172 | if (!post->id) |
1173 | post->id = bile_next_id(board->bile, BOARD_FTN_POST_RTYPE); |
1174 | if (!post->time) |
1175 | post->time = Time; |
1176 | |
1177 | if (!fidopkt_parse_address(db->config.ftn_node_addr, |
1178 | &our_address)) { |
1179 | logger_printf("[board] Invalid FTN local node address, can't " |
1180 | "create board post"); |
1181 | post->id = 0; |
1182 | goto done; |
1183 | } |
1184 | if (!fidopkt_parse_address(db->config.ftn_hub_addr, |
1185 | &hub_address)) { |
1186 | logger_printf("[board] Invalid FTN hub address, can't " |
1187 | "create board post"); |
1188 | post->id = 0; |
1189 | goto done; |
1190 | } |
1191 | |
1192 | ftn_post.id = post->id; |
1193 | ftn_post.time = post->time; |
1194 | |
1195 | if (ftn_parent_post) { |
1196 | snprintf(ftn_post.subject, sizeof(ftn_post.subject), |
1197 | "%s%s", |
1198 | strncmp("Re:", ftn_parent_post->subject, 3) == 1 ? |
1199 | "" : "Re: ", |
1200 | ftn_parent_post->subject); |
1201 | strlcpy(ftn_post.reply, ftn_parent_post->msgid_orig, |
1202 | sizeof(ftn_post.reply)); |
1203 | strlcpy(ftn_post.to, ftn_parent_post->from, |
1204 | sizeof(ftn_post.to)); |
1205 | } else { |
1206 | strlcpy(ftn_post.subject, thread->subject, |
1207 | sizeof(ftn_post.subject)); |
1208 | strlcpy(ftn_post.to, "All", sizeof(ftn_post.to)); |
1209 | } |
1210 | |
1211 | user = user_username(post->sender_user_id); |
1212 | if (user == NULL) { |
1213 | logger_printf("[board] Can't find username of user posting " |
1214 | "new message"); |
1215 | post->id = 0; |
1216 | goto done; |
1217 | } |
1218 | strlcpy(ftn_post.from, user->username, sizeof(ftn_post.from)); |
1219 | |
1220 | ftn_post.body = post->body; |
1221 | ftn_post.body_size = post->body_size; |
1222 | |
1223 | /* make each board's posts have ids unique to our zone/net/node */ |
1224 | ftn_post.msgid.id = 0x10000000 | |
1225 | ((unsigned long)our_address.node << 24) | |
1226 | ((unsigned long)(board->id) << 16) | post->id; |
1227 | ftn_post.msgid.zone = our_address.zone; |
1228 | ftn_post.msgid.net = our_address.net; |
1229 | ftn_post.msgid.node = our_address.node; |
1230 | ftn_post.msgid.point = our_address.point; |
1231 | |
1232 | snprintf(ftn_post.origin, sizeof(ftn_post.origin), |
1233 | "%s | %s (%s)", |
1234 | db->config.name, db->config.hostname, |
1235 | db->config.ftn_node_addr); |
1236 | |
1237 | fidomsg.time = ftn_post.time; |
1238 | memcpy(&fidomsg.header.orig, &our_address, |
1239 | sizeof(fidomsg.header.orig)); |
1240 | memcpy(&fidomsg.header.dest, &hub_address, |
1241 | sizeof(fidomsg.header.dest)); |
1242 | strlcpy(fidomsg.area, board->ftn_area, sizeof(fidomsg.area)); |
1243 | strlcpy(fidomsg.to, ftn_post.to, sizeof(fidomsg.to)); |
1244 | strlcpy(fidomsg.from, ftn_post.from, sizeof(fidomsg.from)); |
1245 | strlcpy(fidomsg.subject, ftn_post.subject, sizeof(fidomsg.subject)); |
1246 | fidomsg.body = ftn_post.body; |
1247 | fidomsg.body_len = ftn_post.body_size - 1; |
1248 | strlcpy(fidomsg.reply, ftn_post.reply, sizeof(fidomsg.reply)); |
1249 | memcpy(&fidomsg.msgid, &ftn_post.msgid, sizeof(fidomsg.msgid)); |
1250 | strlcpy(fidomsg.origin, ftn_post.origin, sizeof(fidomsg.origin)); |
1251 | |
1252 | if (!binkp_scan_message(&fidomsg)) { |
1253 | logger_printf("[board] Failed scanning new FTN message being " |
1254 | "posted"); |
1255 | post->id = 0; |
1256 | goto done; |
1257 | } |
1258 | |
1259 | ret = bile_marshall_object(board->bile, |
1260 | board_ftn_post_object_fields, nboard_ftn_post_object_fields, |
1261 | &ftn_post, &data, &size); |
1262 | if (ret != 0 || size == 0) { |
1263 | logger_printf("[board] Failed to marshall new FTN post object"); |
1264 | post->id = 0; |
1265 | goto done; |
1266 | } |
1267 | if (bile_write(board->bile, BOARD_FTN_POST_RTYPE, ftn_post.id, |
1268 | data, size) != size) { |
1269 | warn("bile_write of new post failed! %d", |
1270 | bile_error(board->bile)); |
1271 | post->id = 0; |
1272 | xfree(&data); |
1273 | goto done; |
1274 | } |
1275 | xfree(&data); |
1276 | } else { |
1277 | if (!post->id) |
1278 | post->id = bile_next_id(board->bile, BOARD_POST_RTYPE); |
1279 | if (!post->time) |
1280 | post->time = Time; |
1281 | |
1282 | if (post->parent_post_id == 0) { |
1283 | thread->thread_id = bile_next_id(board->bile, |
1284 | BOARD_THREAD_RTYPE); |
1285 | post->thread_id = thread->thread_id; |
1286 | } |
1287 | |
1288 | ret = bile_marshall_object(board->bile, board_post_object_fields, |
1289 | nboard_post_object_fields, post, &data, &size); |
1290 | if (ret != 0 || size == 0) { |
1291 | logger_printf("[board] Failed to marshall new post object"); |
1292 | post->id = 0; |
1293 | goto done; |
1294 | } |
1295 | if (bile_write(board->bile, BOARD_POST_RTYPE, post->id, data, |
1296 | size) != size) { |
1297 | warn("bile_write of new post failed! %d", |
1298 | bile_error(board->bile)); |
1299 | post->id = 0; |
1300 | xfree(&data); |
1301 | goto done; |
1302 | } |
1303 | xfree(&data); |
1304 | |
1305 | if (post->time > thread->last_post_at) |
1306 | thread->last_post_at = post->time; |
1307 | thread->nposts++; |
1308 | post_ids = xreallocarray(thread->post_ids, thread->nposts, |
1309 | sizeof(long)); |
1310 | if (post_ids == NULL) |
1311 | return 0; |
1312 | thread->post_ids = post_ids; |
1313 | parent_post_ids = xreallocarray(thread->parent_post_ids, |
1314 | thread->nposts, sizeof(long)); |
1315 | if (parent_post_ids == NULL) |
1316 | return 0; |
1317 | thread->parent_post_ids = parent_post_ids; |
1318 | |
1319 | /* |
1320 | * Add new post id to thread post_ids, but put it in the right |
1321 | * place so that reading post_ids will present the tree in order. |
1322 | * Walk parent_post_ids and find the first match of our parent, |
1323 | * then insert our new post there. This puts newest replies at |
1324 | * the top. |
1325 | */ |
1326 | |
1327 | insert = thread->nposts - 1; |
1328 | for (n = 0; n < thread->nposts - 1; n++) { |
1329 | if (thread->post_ids[n] != post->parent_post_id) |
1330 | continue; |
1331 | |
1332 | for (j = thread->nposts - 2; j > n; j--) { |
1333 | thread->post_ids[j + 1] = thread->post_ids[j]; |
1334 | thread->parent_post_ids[j + 1] = |
1335 | thread->parent_post_ids[j]; |
1336 | } |
1337 | insert = n + 1; |
1338 | break; |
1339 | } |
1340 | thread->post_ids[insert] = post->id; |
1341 | thread->parent_post_ids[insert] = post->parent_post_id; |
1342 | |
1343 | ret = bile_marshall_object(board->bile, board_thread_object_fields, |
1344 | nboard_thread_object_fields, thread, &data, &size); |
1345 | if (ret != 0 || size == 0) { |
1346 | logger_printf("[board] Failed to marshall new thread object"); |
1347 | post->id = 0; |
1348 | goto done; |
1349 | } |
1350 | if (bile_write(board->bile, BOARD_THREAD_RTYPE, thread->thread_id, |
1351 | data, size) != size) { |
1352 | warn("bile_write of thread failed! %d", |
1353 | bile_error(board->bile)); |
1354 | post->id = 0; |
1355 | xfree(&data); |
1356 | goto done; |
1357 | } |
1358 | xfree(&data); |
1359 | } |
1360 | |
1361 | /* it would be nice not to have to rebuild this every time... */ |
1362 | board_delete_cached_index(board); |
1363 | bile_flush(board->bile, true); |
1364 | |
1365 | if (post->time > board->last_post_at) |
1366 | board->last_post_at = post->time; |
1367 | |
1368 | done: |
1369 | return (post->id == 0 ? -1 : 0); |
1370 | } |
1371 | |
1372 | void |
1373 | board_delete_post(struct board *board, struct board_post *post, |
1374 | struct board_thread *thread) |
1375 | { |
1376 | size_t size, n, nn; |
1377 | char *data, *body; |
1378 | char del[50]; |
1379 | short ret; |
1380 | bool childs = false; |
1381 | unsigned long *new_post_ids; |
1382 | unsigned long *new_parent_post_ids; |
1383 | |
1384 | if (thread->nposts == 1) { |
1385 | bile_delete(board->bile, BOARD_THREAD_RTYPE, thread->thread_id, |
1386 | 0); |
1387 | bile_delete(board->bile, BOARD_POST_RTYPE, post->id, |
1388 | BILE_DELETE_FLAG_PURGE); |
1389 | board_delete_cached_index(board); |
1390 | return; |
1391 | } |
1392 | |
1393 | for (n = 0; n < thread->nposts; n++) { |
1394 | if (thread->parent_post_ids[n] == post->id) { |
1395 | childs = true; |
1396 | break; |
1397 | } |
1398 | } |
1399 | |
1400 | if (!childs) { |
1401 | /* just zap this off the end of the tree */ |
1402 | new_post_ids = xcalloc(thread->nposts - 1, sizeof(unsigned long)); |
1403 | if (new_post_ids == NULL) |
1404 | return; |
1405 | new_parent_post_ids = xcalloc(thread->nposts - 1, |
1406 | sizeof(unsigned long)); |
1407 | if (new_parent_post_ids == NULL) |
1408 | return; |
1409 | |
1410 | for (n = 0, nn = 0; n < thread->nposts; n++) { |
1411 | if (thread->post_ids[n] == post->id) |
1412 | continue; |
1413 | |
1414 | new_post_ids[nn] = thread->post_ids[n]; |
1415 | nn++; |
1416 | } |
1417 | |
1418 | for (n = 0, nn = 0; n < thread->nposts; n++) { |
1419 | if (thread->post_ids[n] == post->id) |
1420 | continue; |
1421 | |
1422 | new_parent_post_ids[nn] = thread->parent_post_ids[n]; |
1423 | nn++; |
1424 | } |
1425 | |
1426 | thread->nposts--; |
1427 | |
1428 | xfree(&thread->post_ids); |
1429 | thread->post_ids = new_post_ids; |
1430 | xfree(&thread->parent_post_ids); |
1431 | thread->parent_post_ids = new_parent_post_ids; |
1432 | |
1433 | ret = bile_marshall_object(board->bile, board_thread_object_fields, |
1434 | nboard_thread_object_fields, thread, &data, &size); |
1435 | if (ret != 0 || size == 0) { |
1436 | logger_printf("[board] Failed to marshall thread object " |
1437 | "during post deletion"); |
1438 | return; |
1439 | } |
1440 | if (bile_write(board->bile, BOARD_THREAD_RTYPE, thread->thread_id, |
1441 | data, size) != size) { |
1442 | warn("bile_write of updated thread after post delete failed! " |
1443 | "%d", bile_error(board->bile)); |
1444 | xfree(&data); |
1445 | return; |
1446 | } |
1447 | xfree(&data); |
1448 | |
1449 | bile_delete(board->bile, BOARD_POST_RTYPE, post->id, |
1450 | BILE_DELETE_FLAG_ZERO | BILE_DELETE_FLAG_PURGE); |
1451 | board_delete_cached_index(board); |
1452 | |
1453 | bile_flush(board->bile, true); |
1454 | return; |
1455 | } |
1456 | |
1457 | /* all we can do is change the post to say deleted */ |
1458 | if (post->body != NULL) |
1459 | xfree(&post->body); |
1460 | snprintf(del, sizeof(del), "[ Post deleted ]"); |
1461 | body = xstrdup(del); |
1462 | if (body == NULL) |
1463 | return; |
1464 | post->body = body; |
1465 | post->body_size = strlen(post->body) + 1; |
1466 | |
1467 | ret = bile_marshall_object(board->bile, board_post_object_fields, |
1468 | nboard_post_object_fields, post, &data, &size); |
1469 | if (ret != 0 || size == 0) { |
1470 | logger_printf("[board] Failed to marshall updated post object"); |
1471 | return; |
1472 | } |
1473 | if (bile_write(board->bile, BOARD_POST_RTYPE, post->id, data, |
1474 | size) != size) { |
1475 | warn("bile_write of updated post failed! %d", |
1476 | bile_error(board->bile)); |
1477 | xfree(&data); |
1478 | return; |
1479 | } |
1480 | |
1481 | xfree(&data); |
1482 | } |
1483 | |
1484 | void |
1485 | board_delete_ftn_post(struct board *board, struct board_ftn_post *post) |
1486 | { |
1487 | size_t size, npost_ids, n; |
1488 | struct board_id_time_map *id_map; |
1489 | |
1490 | bile_delete(board->bile, BOARD_FTN_POST_RTYPE, post->id, |
1491 | BILE_DELETE_FLAG_PURGE); |
1492 | |
1493 | size = bile_read_alloc(board->bile, BOARD_SORTED_ID_MAP_RTYPE, 1, |
1494 | &id_map); |
1495 | if (size == 0 || id_map == NULL) |
1496 | return; |
1497 | |
1498 | npost_ids = size / sizeof(struct board_id_time_map); |
1499 | |
1500 | for (n = 0; n < npost_ids; n++) { |
1501 | if (id_map[n].id == post->id) { |
1502 | for (; n < npost_ids - 1; n++) |
1503 | id_map[n] = id_map[n + 1]; |
1504 | npost_ids--; |
1505 | break; |
1506 | } |
1507 | } |
1508 | |
1509 | if (npost_ids == 0) |
1510 | board_delete_cached_index(board); |
1511 | else |
1512 | bile_write(board->bile, BOARD_SORTED_ID_MAP_RTYPE, 1, id_map, |
1513 | sizeof(struct board_id_time_map) * npost_ids); |
1514 | } |
1515 | |
1516 | size_t |
1517 | board_index_sorted_post_ids(struct board *board, |
1518 | struct board_id_time_map **sorted_id_map) |
1519 | { |
1520 | struct thread_time_map { |
1521 | unsigned long id; |
1522 | time_t time; |
1523 | size_t nposts; |
1524 | }; |
1525 | struct board_ftn_post fpost; |
1526 | struct board_post post; |
1527 | struct board_thread thread; |
1528 | size_t ret, size, i, j, n, npost_ids, nthread_ids; |
1529 | unsigned long *post_ids, *thread_ids; |
1530 | struct board_id_time_map *id_map, tmp_id_map; |
1531 | struct thread_time_map *thread_map, tmp_thread_map; |
1532 | char *data; |
1533 | |
1534 | if (board->ftn_area[0]) { |
1535 | npost_ids = bile_ids_by_type(board->bile, BOARD_FTN_POST_RTYPE, |
1536 | &post_ids); |
1537 | if (npost_ids == 0) |
1538 | goto write_out; |
1539 | |
1540 | id_map = xcalloc(sizeof(struct board_id_time_map), npost_ids); |
1541 | if (id_map == NULL) |
1542 | goto write_out; |
1543 | |
1544 | for (n = 0; n < npost_ids; n++) { |
1545 | /* only read as far as the time */ |
1546 | ret = bile_read(board->bile, BOARD_FTN_POST_RTYPE, |
1547 | post_ids[n], &fpost, |
1548 | offsetof(struct board_ftn_post, time) + |
1549 | member_size(struct board_ftn_post, time)); |
1550 | if (ret == 0) |
1551 | goto write_out; |
1552 | |
1553 | id_map[n].id = fpost.id; |
1554 | id_map[n].time = fpost.time; |
1555 | } |
1556 | |
1557 | /* sort by date descending */ |
1558 | for (i = 0; i < npost_ids; i++) { |
1559 | for (j = 0; j < npost_ids - i - 1; j++) { |
1560 | if (id_map[j].time < id_map[j + 1].time) { |
1561 | tmp_id_map = id_map[j]; |
1562 | id_map[j] = id_map[j + 1]; |
1563 | id_map[j + 1] = tmp_id_map; |
1564 | } |
1565 | } |
1566 | } |
1567 | } else { |
1568 | npost_ids = 0; |
1569 | nthread_ids = bile_ids_by_type(board->bile, BOARD_THREAD_RTYPE, |
1570 | &thread_ids); |
1571 | if (nthread_ids == 0) |
1572 | goto write_out; |
1573 | |
1574 | thread_map = xcalloc(sizeof(struct thread_time_map), nthread_ids); |
1575 | if (thread_map == NULL) |
1576 | goto write_out; |
1577 | |
1578 | for (n = 0; n < nthread_ids; n++) { |
1579 | size = bile_read_alloc(board->bile, BOARD_THREAD_RTYPE, |
1580 | thread_ids[n], &data); |
1581 | ret = bile_unmarshall_object(board->bile, |
1582 | board_thread_object_fields, nboard_thread_object_fields, data, |
1583 | size, &thread, sizeof(thread), false); |
1584 | xfree(&data); |
1585 | if (ret != 0) |
1586 | goto write_out; |
1587 | |
1588 | thread_map[n].id = thread.thread_id; |
1589 | thread_map[n].time = thread.last_post_at; |
1590 | thread_map[n].nposts = thread.nposts; |
1591 | npost_ids += thread.nposts; |
1592 | } |
1593 | |
1594 | xfree(&thread_ids); |
1595 | |
1596 | /* sort by last post date descending */ |
1597 | for (i = 0; i < nthread_ids; i++) { |
1598 | for (j = 0; j < nthread_ids - i - 1; j++) { |
1599 | if (thread_map[j].time < thread_map[j + 1].time) { |
1600 | tmp_thread_map = thread_map[j]; |
1601 | thread_map[j] = thread_map[j + 1]; |
1602 | thread_map[j + 1] = tmp_thread_map; |
1603 | } |
1604 | } |
1605 | } |
1606 | |
1607 | id_map = xcalloc(sizeof(struct board_id_time_map), npost_ids); |
1608 | if (id_map == NULL) |
1609 | goto write_out; |
1610 | |
1611 | npost_ids = 0; |
1612 | for (j = 0; j < nthread_ids; j++) { |
1613 | size = bile_read_alloc(board->bile, BOARD_THREAD_RTYPE, |
1614 | thread_map[j].id, &data); |
1615 | if (data == NULL) |
1616 | goto write_out; |
1617 | ret = bile_unmarshall_object(board->bile, |
1618 | board_thread_object_fields, nboard_thread_object_fields, data, |
1619 | size, &thread, sizeof(thread), true); |
1620 | xfree(&data); |
1621 | if (ret != 0) |
1622 | goto write_out; |
1623 | |
1624 | /* these are already sorted, and we want to keep thread sort */ |
1625 | for (i = 0; i < thread.nposts; i++) { |
1626 | /* only read as far as the time */ |
1627 | size = bile_read(board->bile, BOARD_POST_RTYPE, |
1628 | thread.post_ids[i], &post, |
1629 | offsetof(struct board_post, time) + |
1630 | member_size(struct board_post, time)); |
1631 | if (size == 0) { |
1632 | logger_printf("[board] Board %s thread %ld post %ld " |
1633 | "is missing", board->name, thread.thread_id, |
1634 | thread.post_ids[i]); |
1635 | continue; |
1636 | } |
1637 | |
1638 | id_map[npost_ids].id = post.id; |
1639 | id_map[npost_ids].time = post.time; |
1640 | npost_ids++; |
1641 | } |
1642 | |
1643 | if (thread.subject != NULL) |
1644 | xfree(&thread.subject); |
1645 | if (thread.post_ids != NULL) |
1646 | xfree(&thread.post_ids); |
1647 | if (thread.parent_post_ids != NULL) |
1648 | xfree(&thread.parent_post_ids); |
1649 | } |
1650 | |
1651 | xfree(&thread_map); |
1652 | } |
1653 | |
1654 | write_out: |
1655 | if (npost_ids == 0 || id_map == NULL) { |
1656 | board_delete_cached_index(board); |
1657 | if (sorted_id_map != NULL) |
1658 | *sorted_id_map = NULL; |
1659 | return 0; |
1660 | } |
1661 | |
1662 | bile_write(board->bile, BOARD_SORTED_ID_MAP_RTYPE, 1, id_map, |
1663 | sizeof(struct board_id_time_map) * npost_ids); |
1664 | if (sorted_id_map == NULL) |
1665 | xfree(&id_map); |
1666 | else |
1667 | *sorted_id_map = id_map; |
1668 | return npost_ids; |
1669 | } |
1670 | |
1671 | short |
1672 | board_toss_ftn_message(struct board *board, |
1673 | struct fidopkt_message *fidomsg) |
1674 | { |
1675 | struct board_ftn_msgid_cache { |
1676 | unsigned long id; |
1677 | struct fidopkt_msgid msgid; |
1678 | } *msgid_cache = NULL; |
1679 | struct bile_object *o; |
1680 | struct board_ftn_post post; |
1681 | struct fidopkt_msgid msgid; |
1682 | unsigned long *post_ids; |
1683 | char *pdata; |
1684 | size_t asize, cache_size, psize, npost_ids; |
1685 | short n, ret, bret; |
1686 | bool dirty_cache = false; |
1687 | |
1688 | msgid = fidomsg->msgid; |
1689 | |
1690 | o = bile_find(board->bile, BOARD_FTN_MSGID_CACHE_RTYPE, 1); |
1691 | if (o) { |
1692 | /* allocate its size plus one entry that we may add */ |
1693 | asize = o->size + sizeof(struct board_ftn_msgid_cache); |
1694 | msgid_cache = xmalloc(asize); |
1695 | if (msgid_cache == NULL) { |
1696 | logger_printf("[board] toss: malloc(%ld) failed", |
1697 | asize); |
1698 | return -1; |
1699 | } |
1700 | bile_read(board->bile, o->type, o->id, msgid_cache, o->size); |
1701 | npost_ids = o->size / sizeof(struct board_ftn_msgid_cache); |
1702 | xfree(&o); |
1703 | } else { |
1704 | npost_ids = bile_ids_by_type(board->bile, BOARD_FTN_POST_RTYPE, |
1705 | &post_ids); |
1706 | msgid_cache = xcalloc(sizeof(struct board_ftn_msgid_cache), |
1707 | npost_ids + 1); |
1708 | if (msgid_cache == NULL) { |
1709 | logger_printf("[board] toss: calloc(%ld, %ld) failed", |
1710 | sizeof(struct board_ftn_msgid_cache), npost_ids + 1); |
1711 | return -1; |
1712 | } |
1713 | for (n = 0; n < npost_ids; n++) { |
1714 | /* only read as far as we have to */ |
1715 | bile_read(board->bile, BOARD_FTN_POST_RTYPE, post_ids[n], |
1716 | &post, offsetof(struct board_ftn_post, msgid) + |
1717 | member_size(struct board_ftn_post, msgid)); |
1718 | |
1719 | msgid_cache[n].id = post.id; |
1720 | memcpy(&msgid_cache[n].msgid, &post.msgid, |
1721 | sizeof(post.msgid)); |
1722 | } |
1723 | dirty_cache = true; |
1724 | } |
1725 | |
1726 | uthread_yield(); |
1727 | |
1728 | for (n = 0; n < npost_ids; n++) { |
1729 | if (memcmp(&msgid_cache[n].msgid, &msgid, sizeof(msgid)) == 0) { |
1730 | logger_printf("[board] Already have %s EchoMail %s in %s " |
1731 | "(%ld), skipping", db->config.ftn_network, |
1732 | fidomsg->msgid_orig, board->name, msgid_cache[n].id); |
1733 | ret = 0; |
1734 | goto done; |
1735 | } |
1736 | } |
1737 | |
1738 | memset(&post, 0, sizeof(post)); |
1739 | post.time = fidomsg->time; |
1740 | post.body_size = fidomsg->body_len + 1; |
1741 | post.body = fidomsg->body; |
1742 | strlcpy(post.reply, fidomsg->reply, sizeof(post.reply)); |
1743 | strlcpy(post.from, fidomsg->from, sizeof(post.from)); |
1744 | strlcpy(post.subject, fidomsg->subject, sizeof(post.subject)); |
1745 | strlcpy(post.to, fidomsg->to, sizeof(post.to)); |
1746 | strlcpy(post.origin, fidomsg->origin, sizeof(post.origin)); |
1747 | strlcpy(post.msgid_orig, fidomsg->msgid_orig, sizeof(post.msgid_orig)); |
1748 | post.msgid = msgid; |
1749 | |
1750 | post.id = bile_next_id(board->bile, BOARD_FTN_POST_RTYPE); |
1751 | if (!post.id) { |
1752 | logger_printf("[board] Failed get next id for %s", board->name); |
1753 | ret = -1; |
1754 | goto done; |
1755 | } |
1756 | |
1757 | bret = bile_marshall_object(board->bile, |
1758 | board_ftn_post_object_fields, nboard_ftn_post_object_fields, |
1759 | &post, &pdata, &psize); |
1760 | if (bret != 0 || psize == 0) { |
1761 | logger_printf("[board] Failed to marshall new post %s %s: %d", |
1762 | fidomsg->area, fidomsg->msgid_orig, bile_error(board->bile)); |
1763 | ret = -1; |
1764 | goto done; |
1765 | } |
1766 | if (bile_write(board->bile, BOARD_FTN_POST_RTYPE, post.id, pdata, |
1767 | psize) != psize) { |
1768 | logger_printf("[fidopkt] Failed to save new post %s %s: %d", |
1769 | fidomsg->area, fidomsg->msgid_orig, bile_error(board->bile)); |
1770 | ret = -1; |
1771 | xfree(&pdata); |
1772 | goto done; |
1773 | } |
1774 | xfree(&pdata); |
1775 | |
1776 | ret = 1; |
1777 | |
1778 | /* we already allocated one empty space at the end of the cache */ |
1779 | msgid_cache[npost_ids].id = post.id; |
1780 | memcpy(&msgid_cache[npost_ids].msgid, &post.msgid, sizeof(post.msgid)); |
1781 | npost_ids++; |
1782 | dirty_cache = true; |
1783 | |
1784 | board_delete_cached_index(board); |
1785 | |
1786 | if (post.time > board->last_post_at) |
1787 | board->last_post_at = post.time; |
1788 | |
1789 | logger_printf("[board] Tossed %s EchoMail %s as %ld", |
1790 | fidomsg->area, fidomsg->msgid_orig, post.id); |
1791 | uthread_yield(); |
1792 | |
1793 | done: |
1794 | if (dirty_cache) { |
1795 | cache_size = npost_ids * sizeof(struct board_ftn_msgid_cache); |
1796 | if (bile_write(board->bile, BOARD_FTN_MSGID_CACHE_RTYPE, 1, |
1797 | msgid_cache, cache_size) != cache_size) { |
1798 | logger_printf("[board] Failed to save msgid cache for %s: %d", |
1799 | fidomsg->area, bile_error(board->bile)); |
1800 | ret = -1; |
1801 | } |
1802 | } |
1803 | if (msgid_cache != NULL) |
1804 | xfree(&msgid_cache); |
1805 | |
1806 | return ret; |
1807 | } |
1808 | |
1809 | void |
1810 | board_delete_cached_index(struct board *board) |
1811 | { |
1812 | bile_delete(board->bile, BOARD_SORTED_ID_MAP_RTYPE, 1, |
1813 | BILE_DELETE_FLAG_PURGE); |
1814 | } |
1815 | |
1816 | void |
1817 | board_prune_old_posts(struct board *board) |
1818 | { |
1819 | size_t nposts, n; |
1820 | struct board_id_time_map *sorted_id_map; |
1821 | struct board_ftn_post fpost; |
1822 | struct board_post post; |
1823 | struct board_thread thread; |
1824 | time_t oldest; |
1825 | size_t size, deleted; |
1826 | char *data; |
1827 | short ret; |
1828 | |
1829 | if (board->retention_days == 0) |
1830 | return; |
1831 | |
1832 | deleted = 0; |
1833 | |
1834 | nposts = board_index_sorted_post_ids(board, &sorted_id_map); |
1835 | if (nposts == 0) |
1836 | goto done; |
1837 | |
1838 | oldest = Time - ((unsigned long)(board->retention_days) * |
1839 | (60UL * 60UL * 24UL)); |
1840 | |
1841 | for (n = 0; n < nposts; n++) { |
1842 | if (sorted_id_map[n].time >= oldest) |
1843 | continue; |
1844 | |
1845 | if (board->ftn_area[0]) { |
1846 | size = bile_read_alloc(board->bile, BOARD_FTN_POST_RTYPE, |
1847 | sorted_id_map[n].id, &data); |
1848 | if (!size) |
1849 | continue; |
1850 | ret = bile_unmarshall_object(board->bile, |
1851 | board_ftn_post_object_fields, nboard_ftn_post_object_fields, |
1852 | data, size, &fpost, sizeof(fpost), false); |
1853 | xfree(&data); |
1854 | if (ret == BILE_ERR_NO_MEMORY) |
1855 | goto done; |
1856 | |
1857 | board_delete_ftn_post(board, &fpost); |
1858 | } else { |
1859 | size = bile_read_alloc(board->bile, BOARD_POST_RTYPE, |
1860 | sorted_id_map[n].id, &data); |
1861 | if (!size) |
1862 | continue; |
1863 | ret = bile_unmarshall_object(board->bile, |
1864 | board_post_object_fields, nboard_post_object_fields, |
1865 | data, size, &post, sizeof(post), false); |
1866 | xfree(&data); |
1867 | if (ret == BILE_ERR_NO_MEMORY) |
1868 | goto done; |
1869 | |
1870 | size = bile_read_alloc(board->bile, BOARD_THREAD_RTYPE, |
1871 | post.thread_id, &data); |
1872 | if (!size) |
1873 | continue; |
1874 | ret = bile_unmarshall_object(board->bile, |
1875 | board_thread_object_fields, nboard_thread_object_fields, |
1876 | data, size, &thread, sizeof(thread), false); |
1877 | xfree(&data); |
1878 | if (ret == BILE_ERR_NO_MEMORY) |
1879 | goto done; |
1880 | |
1881 | board_delete_post(board, &post, &thread); |
1882 | } |
1883 | |
1884 | deleted++; |
1885 | uthread_yield(); |
1886 | } |
1887 | |
1888 | done: |
1889 | if (sorted_id_map != NULL) |
1890 | xfree(&sorted_id_map); |
1891 | |
1892 | if (deleted) { |
1893 | board_index_sorted_post_ids(board, NULL); |
1894 | |
1895 | logger_printf("[board] Pruned %lu post%s in %s older than %d day%s", |
1896 | deleted, deleted == 1 ? "" : "s", board->name, |
1897 | board->retention_days, board->retention_days == 1 ? "" : "s"); |
1898 | } |
1899 | } |