/[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.7 - (show annotations)
Tue May 27 12:24:43 2025 UTC (9 months, 2 weeks ago) by sysadm
Branch: MAIN
Changes since 1.6: +57 -0 lines
Content type: text/x-csrc
Add query_section_articles()

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

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