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