/[LeafOK_CVS]/lbbs/src/section_list_loader.c
ViewVC logotype

Contents of /lbbs/src/section_list_loader.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.8 - (show annotations)
Wed May 28 07:30:23 2025 UTC (9 months, 2 weeks ago) by sysadm
Branch: MAIN
Changes since 1.7: +21 -19 lines
Content type: text/x-csrc
Add section_list_display

1 /***************************************************************************
2 section_list_loader.c - description
3 -------------------
4 Copyright : (C) 2004-2025 by Leaflet
5 Email : leaflet@leafok.com
6 ***************************************************************************/
7
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 3 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 #include "section_list_loader.h"
18 #include "log.h"
19 #include "database.h"
20 #include "menu.h"
21 #include <stdio.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <signal.h>
25 #include <stdlib.h>
26 #include <strings.h>
27 #include <unistd.h>
28
29 #define _POSIX_C_SOURCE 200809L
30 #include <string.h>
31
32 #define SECTION_LIST_LOAD_INTERVAL 10 // second
33
34 int section_list_loader_pid;
35 int last_article_op_log_mid;
36
37 int load_section_config_from_db(void)
38 {
39 MYSQL *db;
40 MYSQL_RES *rs, *rs2;
41 MYSQL_ROW row, row2;
42 char sql[SQL_BUFFER_LEN];
43 int32_t sid;
44 char master_list[(BBS_username_max_len + 1) * 3 + 1];
45 SECTION_LIST *p_section;
46 int ret;
47
48 db = db_open();
49 if (db == NULL)
50 {
51 log_error("db_open() error: %s\n", mysql_error(db));
52 return -2;
53 }
54
55 snprintf(sql, sizeof(sql),
56 "SELECT section_config.SID, sname, section_config.title, section_config.CID, "
57 "read_user_level, write_user_level, section_config.enable * section_class.enable AS enable "
58 "FROM section_config INNER JOIN section_class ON section_config.CID = section_class.CID "
59 "ORDER BY section_config.SID");
60
61 if (mysql_query(db, sql) != 0)
62 {
63 log_error("Query section_list error: %s\n", mysql_error(db));
64 return -3;
65 }
66 if ((rs = mysql_store_result(db)) == NULL)
67 {
68 log_error("Get section_list data failed\n");
69 return -3;
70 }
71
72 ret = 0;
73 while ((row = mysql_fetch_row(rs)))
74 {
75 sid = atoi(row[0]);
76
77 // Query section master
78 snprintf(sql, sizeof(sql),
79 "SELECT username FROM section_master "
80 "INNER JOIN user_list ON section_master.UID = user_list.UID "
81 "WHERE SID = %d AND section_master.enable AND (NOW() BETWEEN begin_dt AND end_dt) "
82 "ORDER BY major DESC, begin_dt ASC LIMIT 3",
83 sid);
84
85 if (mysql_query(db, sql) != 0)
86 {
87 log_error("Query section_master error: %s\n", mysql_error(db));
88 ret = -3;
89 break;
90 }
91 if ((rs2 = mysql_store_result(db)) == NULL)
92 {
93 log_error("Get section_master data failed\n");
94 ret = -3;
95 break;
96 }
97
98 master_list[0] = '\0';
99 while ((row2 = mysql_fetch_row(rs2)))
100 {
101 strncat(master_list, row2[0], sizeof(master_list) - 1 - strnlen(master_list, sizeof(master_list)));
102 strncat(master_list, " ", sizeof(master_list) - 1 - strnlen(master_list, sizeof(master_list)));
103 }
104 mysql_free_result(rs2);
105
106 p_section = section_list_find_by_sid(sid);
107
108 if (p_section == NULL)
109 {
110 p_section = section_list_create(sid, row[1], row[2], master_list);
111 if (p_section == NULL)
112 {
113 log_error("section_list_create() error: load new section sid = %d sname = %s\n", sid, row[1]);
114 ret = -4;
115 break;
116 }
117
118 // acquire rw lock
119 ret = section_list_rw_lock(p_section);
120 if (ret < 0)
121 {
122 break;
123 }
124 }
125 else
126 {
127 // acquire rw lock
128 ret = section_list_rw_lock(p_section);
129 if (ret < 0)
130 {
131 break;
132 }
133
134 strncpy(p_section->sname, row[1], sizeof(p_section->sname) - 1);
135 p_section->sname[sizeof(p_section->sname) - 1] = '\0';
136 strncpy(p_section->stitle, row[1], sizeof(p_section->stitle) - 1);
137 p_section->stitle[sizeof(p_section->stitle) - 1] = '\0';
138 strncpy(p_section->master_list, master_list, sizeof(p_section->master_list) - 1);
139 p_section->master_list[sizeof(p_section->master_list) - 1] = '\0';
140 }
141
142 p_section->class_id = atoi(row[3]);
143 p_section->read_user_level = atoi(row[4]);
144 p_section->write_user_level = atoi(row[5]);
145 p_section->enable = (int8_t)atoi(row[6]);
146
147 // release rw lock
148 ret = section_list_rw_unlock(p_section);
149 if (ret < 0)
150 {
151 break;
152 }
153 }
154 mysql_free_result(rs);
155
156 mysql_close(db);
157
158 return ret;
159 }
160
161 int append_articles_from_db(int32_t start_aid, int global_lock)
162 {
163 MYSQL *db;
164 MYSQL_RES *rs;
165 MYSQL_ROW row;
166 char sql[SQL_BUFFER_LEN];
167 ARTICLE article;
168 ARTICLE *p_topic;
169 SECTION_LIST *p_section = NULL;
170 int32_t last_sid = 0;
171 int ret = 0;
172 int i;
173
174 db = db_open();
175 if (db == NULL)
176 {
177 log_error("db_open() error: %s\n", mysql_error(db));
178 return -2;
179 }
180
181 snprintf(sql, sizeof(sql),
182 "SELECT AID, TID, SID, CID, UID, visible, excerption, ontop, `lock`, "
183 "transship, username, nickname, title, UNIX_TIMESTAMP(sub_dt) AS sub_dt "
184 "FROM bbs WHERE AID >= %d ORDER BY AID",
185 start_aid);
186
187 if (mysql_query(db, sql) != 0)
188 {
189 log_error("Query article list error: %s\n", mysql_error(db));
190 return -3;
191 }
192 if ((rs = mysql_use_result(db)) == NULL)
193 {
194 log_error("Get article list data failed\n");
195 return -3;
196 }
197
198 // acquire global lock
199 if (global_lock)
200 {
201 if ((ret = section_list_rw_lock(NULL)) < 0)
202 {
203 log_error("section_list_rw_lock(sid = 0) error\n");
204 goto cleanup;
205 }
206 }
207
208 while ((row = mysql_fetch_row(rs)))
209 {
210 bzero(&article, sizeof(ARTICLE));
211
212 // copy data of article
213 i = 0;
214
215 article.aid = atoi(row[i++]);
216 article.tid = atoi(row[i++]);
217 article.sid = atoi(row[i++]);
218 article.cid = atoi(row[i++]);
219 article.uid = atoi(row[i++]);
220 article.visible = (int8_t)atoi(row[i++]);
221 article.excerption = (int8_t)atoi(row[i++]);
222 article.ontop = (int8_t)atoi(row[i++]);
223 article.lock = (int8_t)atoi(row[i++]);
224 article.transship = (int8_t)atoi(row[i++]);
225
226 strncpy(article.username, row[i++], sizeof(article.username) - 1);
227 article.username[sizeof(article.username) - 1] = '\0';
228 strncpy(article.nickname, row[i++], sizeof(article.nickname) - 1);
229 article.nickname[sizeof(article.nickname) - 1] = '\0';
230 strncpy(article.title, row[i++], sizeof(article.title) - 1);
231 article.title[sizeof(article.title) - 1] = '\0';
232
233 article.sub_dt = atol(row[i++]);
234
235 // release lock of last section if different from current one
236 if (!global_lock && article.sid != last_sid && last_sid != 0)
237 {
238 if ((ret = section_list_rw_unlock(p_section)) < 0)
239 {
240 log_error("section_list_rw_unlock(sid = %d) error\n", p_section->sid);
241 break;
242 }
243 }
244
245 if ((p_section = section_list_find_by_sid(article.sid)) == NULL)
246 {
247 log_error("section_list_find_by_sid(%d) error: unknown section, try reloading section config\n", article.sid);
248 ret = ERR_UNKNOWN_SECTION; // Unknown section found
249 break;
250 }
251
252 if (article.visible != 0 && article.tid != 0)
253 {
254 // Check if topic article is visible
255 p_topic = article_block_find_by_aid(article.tid);
256 if (p_topic == NULL || p_topic->visible == 0)
257 {
258 // log_error("Set article (aid = %d) as invisible due to invisible or non-existing topic head\n", article.aid);
259 article.tid = 0;
260 article.visible = 0;
261 }
262 }
263
264 // acquire lock of current section if different from last one
265 if (!global_lock && article.sid != last_sid)
266 {
267 if ((ret = section_list_rw_lock(p_section)) < 0)
268 {
269 log_error("section_list_rw_lock(sid = 0) error\n");
270 break;
271 }
272 }
273
274 // append article to section list
275 last_sid = article.sid;
276
277 if (section_list_append_article(p_section, &article) < 0)
278 {
279 log_error("section_list_append_article(sid = %d, aid = %d) error\n",
280 p_section->sid, article.aid);
281 ret = -3;
282 break;
283 }
284 }
285
286 // release lock of last section
287 if (!global_lock && last_sid != 0)
288 {
289 if ((ret = section_list_rw_unlock(p_section)) < 0)
290 {
291 log_error("section_list_rw_unlock(sid = %d) error\n", p_section->sid);
292 }
293 }
294
295 // release global lock
296 if (global_lock)
297 {
298 if ((ret = section_list_rw_unlock(NULL)) < 0)
299 {
300 log_error("section_list_rw_unlock(sid = 0) error\n");
301 }
302 }
303
304 cleanup:
305 mysql_free_result(rs);
306
307 mysql_close(db);
308
309 return ret;
310 }
311
312 int set_last_article_op_log_from_db(void)
313 {
314 MYSQL *db;
315 MYSQL_RES *rs;
316 MYSQL_ROW row;
317 char sql[SQL_BUFFER_LEN];
318
319 db = db_open();
320 if (db == NULL)
321 {
322 log_error("db_open() error: %s\n", mysql_error(db));
323 return -1;
324 }
325
326 snprintf(sql, sizeof(sql),
327 "SELECT MID FROM bbs_article_op ORDER BY MID DESC LIMIT 1");
328
329 if (mysql_query(db, sql) != 0)
330 {
331 log_error("Query article op error: %s\n", mysql_error(db));
332 return -2;
333 }
334 if ((rs = mysql_store_result(db)) == NULL)
335 {
336 log_error("Get article op data failed\n");
337 return -2;
338 }
339
340 if ((row = mysql_fetch_row(rs)))
341 {
342 last_article_op_log_mid = atoi(row[0]);
343 }
344
345 mysql_free_result(rs);
346
347 mysql_close(db);
348
349 return last_article_op_log_mid;
350 }
351
352 int apply_article_op_log_from_db(void)
353 {
354 MYSQL *db;
355 MYSQL_RES *rs, *rs2;
356 MYSQL_ROW row, row2;
357 char sql[SQL_BUFFER_LEN];
358 ARTICLE *p_article;
359 SECTION_LIST *p_section = NULL;
360 SECTION_LIST *p_section_dest;
361 int32_t last_sid = 0;
362 int32_t sid_dest;
363 int ret = 0;
364
365 db = db_open();
366 if (db == NULL)
367 {
368 log_error("db_open() error: %s\n", mysql_error(db));
369 return -3;
370 }
371
372 snprintf(sql, sizeof(sql),
373 "SELECT MID, AID, type FROM bbs_article_op "
374 "WHERE MID > %d AND type NOT IN ('A', 'M') ORDER BY MID",
375 last_article_op_log_mid);
376
377 if (mysql_query(db, sql) != 0)
378 {
379 log_error("Query article log error: %s\n", mysql_error(db));
380 return -3;
381 }
382 if ((rs = mysql_store_result(db)) == NULL)
383 {
384 log_error("Get article log data failed\n");
385 return -3;
386 }
387
388 while ((row = mysql_fetch_row(rs)))
389 {
390 p_article = article_block_find_by_aid(atoi(row[1]));
391 if (p_article == NULL) // related article has not been appended yet
392 {
393 ret = -2;
394 break;
395 }
396
397 // release lock of last section if different from current one
398 if (p_article->sid != last_sid && last_sid != 0)
399 {
400 if ((ret = section_list_rw_unlock(p_section)) < 0)
401 {
402 log_error("section_list_rw_unlock(sid = %d) error\n", p_section->sid);
403 break;
404 }
405 }
406
407 if ((p_section = section_list_find_by_sid(p_article->sid)) == NULL)
408 {
409 log_error("section_list_find_by_sid(%d) error: unknown section, try reloading section config\n", p_article->sid);
410 ret = ERR_UNKNOWN_SECTION; // Unknown section found
411 break;
412 }
413
414 // acquire lock of current section if different from last one
415 if (p_article->sid != last_sid)
416 {
417 if ((ret = section_list_rw_lock(p_section)) < 0)
418 {
419 log_error("section_list_rw_lock(sid = 0) error\n");
420 break;
421 }
422 }
423
424 last_sid = p_article->sid;
425
426 switch (row[2][0])
427 {
428 case 'A': // Add article
429 log_error("Operation type=A should not be found\n");
430 break;
431 case 'D': // Delete article
432 case 'X': // Delete article by Admin
433 p_article->visible = 0;
434 if (p_article->tid == 0)
435 {
436 // Set articles in the topic to be invisible
437 do
438 {
439 p_article = p_article->p_topic_next;
440 p_article->visible = 0;
441 } while (p_article->tid != 0);
442 }
443 break;
444 case 'S': // Restore article
445 p_article->visible = 1;
446 break;
447 case 'L': // Lock article
448 p_article->lock = 1;
449 break;
450 case 'U': // Unlock article
451 p_article->lock = 0;
452 break;
453 case 'M': // Modify article
454 log_error("Operation type=M should not be found\n");
455 break;
456 case 'T': // Move article
457 snprintf(sql, sizeof(sql),
458 "SELECT SID FROM bbs WHERE AID = %d",
459 p_article->aid);
460
461 if (mysql_query(db, sql) != 0)
462 {
463 log_error("Query article error: %s\n", mysql_error(db));
464 ret = -3;
465 break;
466 }
467 if ((rs2 = mysql_store_result(db)) == NULL)
468 {
469 log_error("Get article data failed\n");
470 ret = -3;
471 break;
472 }
473 if ((row2 = mysql_fetch_row(rs2)))
474 {
475 sid_dest = atoi(row2[0]);
476 }
477 else
478 {
479 sid_dest = 0;
480 ret = -4;
481 }
482 mysql_free_result(rs2);
483
484 if (sid_dest > 0 && sid_dest != p_article->sid)
485 {
486 p_section_dest = section_list_find_by_sid(sid_dest);
487 if (p_section_dest == NULL)
488 {
489 ret = ERR_UNKNOWN_SECTION;
490 break;
491 }
492 // acquire lock of dest section
493 if ((ret = section_list_rw_lock(p_section_dest)) < 0)
494 {
495 log_error("section_list_rw_lock(sid = %d) error\n", p_section_dest);
496 break;
497 }
498 // Move topic
499 if ((ret = section_list_move_topic(p_section, p_section_dest, p_article->aid)) < 0)
500 {
501 log_error("section_list_move_topic(src_sid=%d, dest_sid=%d, aid=%d) error (%d), retry in the next loop\n",
502 p_section->sid, p_section_dest->sid, p_article->aid, ret);
503 }
504 // release lock of dest section
505 if (section_list_rw_unlock(p_section_dest) < 0)
506 {
507 log_error("section_list_rw_unlock(sid = %d) error\n", p_section_dest);
508 ret = -1;
509 }
510 }
511 break;
512 case 'E': // Set article as excerption
513 p_article->excerption = 1;
514 break;
515 case 'O': // Unset article as excerption
516 p_article->excerption = 0;
517 break;
518 case 'F': // Set article on top
519 p_article->ontop = 1;
520 break;
521 case 'V': // Unset article on top
522 p_article->ontop = 0;
523 break;
524 case 'Z': // Set article as trnasship
525 p_article->transship = 1;
526 break;
527 default:
528 // log_error("Operation type=%s unknown, mid=%s\n", row[2], row[0]);
529 break;
530 }
531
532 if (ret < 0)
533 {
534 break;
535 }
536
537 // Update MID with last successfully proceeded article_op_log
538 last_article_op_log_mid = atoi(row[0]);
539 }
540
541 // release lock of last section
542 if (last_sid != 0)
543 {
544 if ((ret = section_list_rw_unlock(p_section)) < 0)
545 {
546 log_error("section_list_rw_unlock(sid = %d) error\n", p_section->sid);
547 }
548 }
549
550 mysql_free_result(rs);
551
552 mysql_close(db);
553
554 return ret;
555 }
556
557 int section_list_loader_launch(void)
558 {
559 int pid;
560 int ret;
561 int32_t last_aid;
562 int article_count;
563 int load_count;
564 int last_mid;
565 int i;
566
567 if (section_list_loader_pid != 0)
568 {
569 log_error("section_list_loader already running, pid = %d\n", section_list_loader_pid);
570 return -2;
571 }
572
573 pid = fork();
574
575 if (pid > 0) // Parent process
576 {
577 SYS_child_process_count++;
578 section_list_loader_pid = pid;
579 log_std("Section list loader process (%d) start\n", pid);
580 return 0;
581 }
582 else if (pid < 0) // Error
583 {
584 log_error("fork() error (%d)\n", errno);
585 return -1;
586 }
587
588 // Child process
589 SYS_child_process_count = 0;
590
591 // Detach menu in shared memory
592 detach_menu_shm(p_bbs_menu);
593 free(p_bbs_menu);
594 p_bbs_menu = NULL;
595
596 // Do section data loader periodically
597 while (!SYS_server_exit)
598 {
599 if (SYS_section_list_reload)
600 {
601 SYS_section_list_reload = 0;
602
603 // Load section config
604 if (load_section_config_from_db() < 0)
605 {
606 log_error("load_section_config_from_db() error\n");
607 }
608 else
609 {
610 log_error("Reload section config successfully\n");
611 }
612 }
613
614 // Load section articles
615 last_aid = article_block_last_aid();
616 article_count = article_block_article_count();
617
618 if ((ret = append_articles_from_db(last_aid + 1, 0)) < 0)
619 {
620 log_error("append_articles_from_db(%d, 0) error\n", last_aid + 1);
621
622 if (ret == ERR_UNKNOWN_SECTION)
623 {
624 SYS_section_list_reload = 1; // Force reload section_list
625 }
626 }
627
628 load_count = article_block_article_count() - article_count;
629
630 if (load_count > 0)
631 {
632 log_std("Incrementally load %d articles, last_aid = %d\n", load_count, article_block_last_aid());
633 }
634
635 if (SYS_section_list_reload)
636 {
637 continue;
638 }
639
640 // Load article_op log
641 last_mid = last_article_op_log_mid;
642
643 if ((ret = apply_article_op_log_from_db()) < 0)
644 {
645 log_error("apply_article_op_log_from_db() error\n");
646
647 if (ret == ERR_UNKNOWN_SECTION)
648 {
649 SYS_section_list_reload = 1; // Force reload section_list
650 }
651 }
652
653 if (last_article_op_log_mid > last_mid)
654 {
655 log_std("Proceeded %d article logs, last_mid = %d\n", last_article_op_log_mid - last_mid, last_article_op_log_mid);
656 }
657
658 if (SYS_section_list_reload)
659 {
660 continue;
661 }
662
663 for (i = 0; i < SECTION_LIST_LOAD_INTERVAL && !SYS_server_exit && !SYS_section_list_reload; i++)
664 {
665 sleep(1);
666 }
667 }
668
669 // Child process exit
670
671 // Detach data pools shm
672 detach_section_list_shm();
673 detach_article_block_shm();
674 detach_trie_dict_shm();
675
676 log_std("Section list loader process exit normally\n");
677 log_end();
678
679 section_list_loader_pid = 0;
680
681 _exit(0);
682
683 return 0;
684 }
685
686 int section_list_loader_reload(void)
687 {
688 if (section_list_loader_pid == 0)
689 {
690 log_error("section_list_loader not running\n");
691 return -2;
692 }
693
694 if (kill(section_list_loader_pid, SIGHUP) < 0)
695 {
696 log_error("Send SIGTERM signal failed (%d)\n", errno);
697 return -1;
698 }
699
700 return 0;
701 }
702
703 int query_section_articles(SECTION_LIST *p_section, int32_t page_id, ARTICLE *p_articles[], int32_t *p_article_count)
704 {
705 ARTICLE *p_article;
706 ARTICLE *p_next_page_first_article;
707 int ret = 0;
708
709 if (p_section == NULL || p_articles == NULL || p_article_count == NULL)
710 {
711 log_error("query_section_articles() NULL pointer error\n");
712 return -1;
713 }
714
715 // acquire lock of section
716 if ((ret = section_list_rd_lock(p_section)) < 0)
717 {
718 log_error("section_list_rd_lock(sid = %d) error\n", p_section->sid);
719 return -2;
720 }
721
722 if (page_id < 0 || page_id >= p_section->page_count)
723 {
724 log_error("Invalid page_id=%d, not in range [0, %d)\n", page_id, p_section->page_count);
725 ret = -3;
726 }
727 else
728 {
729 ret = page_id;
730 p_article = p_section->p_page_first_article[page_id];
731 p_next_page_first_article =
732 (page_id == p_section->page_count - 1 ? p_section->p_article_head : p_section->p_page_first_article[page_id + 1]);
733 *p_article_count = 0;
734
735 do
736 {
737 if (p_article->visible)
738 {
739 p_articles[*p_article_count] = p_article;
740 (*p_article_count)++;
741 }
742 p_article = p_article->p_next;
743 } while (p_article != p_next_page_first_article && (*p_article_count) <= BBS_article_limit_per_page);
744
745 if (*p_article_count != (page_id < p_section->page_count - 1 ? BBS_article_limit_per_page : p_section->last_page_visible_article_count))
746 {
747 log_error("Inconsistent visible article count %d detected in section %d page %d\n", *p_article_count, p_section->sid, page_id);
748 }
749 }
750
751 // release lock of section
752 if (section_list_rd_unlock(p_section) < 0)
753 {
754 log_error("section_list_rd_unlock(sid = %d) error\n", p_section->sid);
755 ret = -2;
756 }
757
758 return ret;
759 }

webmaster@leafok.com
ViewVC Help
Powered by ViewVC 1.3.0-beta1