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

Contents of /lbbs/src/section_list.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.9 - (show annotations)
Thu May 22 14:12:33 2025 UTC (9 months, 3 weeks ago) by sysadm
Branch: MAIN
Changes since 1.8: +73 -2 lines
Content type: text/x-csrc
Add section_list_calculate_page()

1 /***************************************************************************
2 section_list.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.h"
18 #include "log.h"
19 #include "trie_dict.h"
20 #include <stdio.h>
21 #include <string.h>
22 #include <signal.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <sys/param.h>
27 #include <sys/shm.h>
28 #include <sys/ipc.h>
29
30 #define ARTICLE_BLOCK_PER_SHM 400 // sizeof(ARTICLE_BLOCK) * ARTICLE_BLOCK_PER_SHM is the size of each shm segment to allocate
31 #define ARTICLE_BLOCK_SHM_COUNT_LIMIT 256 // limited by length (8-bit) of proj_id in ftok(path, proj_id)
32 #define ARTICLE_BLOCK_PER_POOL (ARTICLE_BLOCK_PER_SHM * ARTICLE_BLOCK_SHM_COUNT_LIMIT)
33
34 struct article_block_t
35 {
36 ARTICLE articles[ARTICLE_PER_BLOCK];
37 int32_t article_count;
38 struct article_block_t *p_next_block;
39 };
40 typedef struct article_block_t ARTICLE_BLOCK;
41
42 struct article_block_shm_t
43 {
44 int shmid;
45 void *p_shm;
46 };
47 typedef struct article_block_shm_t ARTICLE_BLOCK_SHM;
48
49 struct article_block_pool_t
50 {
51 ARTICLE_BLOCK_SHM shm_pool[ARTICLE_BLOCK_SHM_COUNT_LIMIT];
52 int shm_count;
53 ARTICLE_BLOCK *p_block_free_list;
54 ARTICLE_BLOCK *p_block[ARTICLE_BLOCK_PER_POOL];
55 int32_t block_count;
56 };
57 typedef struct article_block_pool_t ARTICLE_BLOCK_POOL;
58
59 static ARTICLE_BLOCK_POOL *p_article_block_pool = NULL;
60
61 static SECTION_LIST *p_section_list_pool = NULL;
62 static int section_list_count = 0;
63 static TRIE_NODE *p_trie_dict_section_list = NULL;
64
65 int article_block_init(const char *filename, int block_count)
66 {
67 int shmid;
68 int proj_id;
69 key_t key;
70 size_t size;
71 void *p_shm;
72 int i;
73 int block_count_in_shm;
74 ARTICLE_BLOCK *p_block_in_shm;
75 ARTICLE_BLOCK **pp_block_next;
76
77 if (p_article_block_pool != NULL)
78 {
79 log_error("article_block_pool already initialized\n");
80 return -1;
81 }
82
83 if (block_count > ARTICLE_BLOCK_PER_POOL)
84 {
85 log_error("article_block_count exceed limit %d\n", ARTICLE_BLOCK_PER_POOL);
86 return -2;
87 }
88
89 p_article_block_pool = calloc(1, sizeof(ARTICLE_BLOCK_POOL));
90 if (p_article_block_pool == NULL)
91 {
92 log_error("calloc(ARTICLE_BLOCK_POOL) OOM\n");
93 return -2;
94 }
95
96 // Allocate shared memory
97 p_article_block_pool->shm_count = 0;
98 pp_block_next = &(p_article_block_pool->p_block_free_list);
99
100 while (block_count > 0)
101 {
102 block_count_in_shm = MIN(block_count, ARTICLE_BLOCK_PER_SHM);
103 block_count -= block_count_in_shm;
104
105 proj_id = getpid() + p_article_block_pool->shm_count;
106 key = ftok(filename, proj_id);
107 if (key == -1)
108 {
109 log_error("ftok(%s, %d) error (%d)\n", filename, proj_id, errno);
110 article_block_cleanup();
111 return -3;
112 }
113
114 size = sizeof(shmid) + sizeof(ARTICLE_BLOCK) * (size_t)block_count_in_shm;
115 shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0600);
116 if (shmid == -1)
117 {
118 log_error("shmget(shm_index = %d, size = %d) error (%d)\n", p_article_block_pool->shm_count, size, errno);
119 article_block_cleanup();
120 return -3;
121 }
122 p_shm = shmat(shmid, NULL, 0);
123 if (p_shm == (void *)-1)
124 {
125 log_error("shmat(shmid = %d) error (%d)\n", shmid, errno);
126 article_block_cleanup();
127 return -3;
128 }
129
130 (p_article_block_pool->shm_pool + p_article_block_pool->shm_count)->shmid = shmid;
131 (p_article_block_pool->shm_pool + p_article_block_pool->shm_count)->p_shm = p_shm;
132 p_article_block_pool->shm_count++;
133
134 p_block_in_shm = p_shm;
135 *pp_block_next = p_block_in_shm;
136
137 for (i = 0; i < block_count_in_shm; i++)
138 {
139 if (i < block_count_in_shm - 1)
140 {
141 (p_block_in_shm + i)->p_next_block = (p_block_in_shm + i + 1);
142 }
143 else
144 {
145 (p_block_in_shm + i)->p_next_block = NULL;
146 pp_block_next = &((p_block_in_shm + i)->p_next_block);
147 }
148 }
149 }
150
151 p_article_block_pool->block_count = 0;
152
153 return 0;
154 }
155
156 void article_block_cleanup(void)
157 {
158 if (p_article_block_pool != NULL)
159 {
160 for (int i = 0; i < p_article_block_pool->shm_count; i++)
161 {
162 if (shmdt((p_article_block_pool->shm_pool + i)->p_shm) == -1)
163 {
164 log_error("shmdt(shmid = %d) error (%d)\n", (p_article_block_pool->shm_pool + i)->shmid, errno);
165 }
166
167 if (shmctl((p_article_block_pool->shm_pool + i)->shmid, IPC_RMID, NULL) == -1)
168 {
169 log_error("shmctl(shmid = %d, IPC_RMID) error (%d)\n", (p_article_block_pool->shm_pool + i)->shmid, errno);
170 }
171 }
172
173 free(p_article_block_pool);
174 p_article_block_pool = NULL;
175 }
176 }
177
178 inline static ARTICLE_BLOCK *pop_free_article_block(void)
179 {
180 ARTICLE_BLOCK *p_block = NULL;
181
182 if (p_article_block_pool->p_block_free_list != NULL)
183 {
184 p_block = p_article_block_pool->p_block_free_list;
185 p_article_block_pool->p_block_free_list = p_block->p_next_block;
186 p_block->p_next_block = NULL;
187 p_block->article_count = 0;
188 }
189
190 return p_block;
191 }
192
193 inline static void push_free_article_block(ARTICLE_BLOCK *p_block)
194 {
195 p_block->p_next_block = p_article_block_pool->p_block_free_list;
196 p_article_block_pool->p_block_free_list = p_block;
197 }
198
199 int article_block_reset(void)
200 {
201 ARTICLE_BLOCK *p_block;
202
203 if (p_article_block_pool == NULL)
204 {
205 log_error("article_block_pool not initialized\n");
206 return -1;
207 }
208
209 while (p_article_block_pool->block_count > 0)
210 {
211 p_article_block_pool->block_count--;
212 p_block = p_article_block_pool->p_block[p_article_block_pool->block_count];
213 push_free_article_block(p_block);
214 }
215
216 return 0;
217 }
218
219 ARTICLE *article_block_find_by_aid(int32_t aid)
220 {
221 ARTICLE_BLOCK *p_block;
222 int left;
223 int right;
224 int mid;
225
226 if (p_article_block_pool == NULL)
227 {
228 log_error("article_block_pool not initialized\n");
229 return NULL;
230 }
231
232 if (p_article_block_pool->block_count == 0) // empty
233 {
234 return NULL;
235 }
236
237 left = 0;
238 right = p_article_block_pool->block_count;
239
240 // aid in the range [ head aid of blocks[left], tail aid of blocks[right - 1] ]
241 while (left < right - 1)
242 {
243 // get block offset no less than mid value of left and right block offsets
244 mid = (left + right) / 2 + (right - left) % 2;
245
246 if (mid >= p_article_block_pool->block_count)
247 {
248 log_error("block(mid = %d) is out of boundary\n", mid);
249 return NULL;
250 }
251
252 if (aid < p_article_block_pool->p_block[mid]->articles[0].aid)
253 {
254 right = mid;
255 }
256 else
257 {
258 left = mid;
259 }
260 }
261
262 p_block = p_article_block_pool->p_block[left];
263
264 left = 0;
265 right = p_block->article_count - 1;
266
267 // aid in the range [ aid of articles[left], aid of articles[right] ]
268 while (left < right)
269 {
270 mid = (left + right) / 2;
271
272 if (aid <= p_block->articles[mid].aid)
273 {
274 right = mid;
275 }
276 else
277 {
278 left = mid + 1;
279 }
280 }
281
282 return (p_block->articles + left);
283 }
284
285 ARTICLE *article_block_find_by_index(int index)
286 {
287 ARTICLE_BLOCK *p_block;
288
289 if (p_article_block_pool == NULL)
290 {
291 log_error("article_block_pool not initialized\n");
292 return NULL;
293 }
294
295 if (index < 0 || index / ARTICLE_PER_BLOCK >= p_article_block_pool->block_count)
296 {
297 log_error("section_data_find_article_by_index(%d) is out of boundary of block [0, %d)\n", index, p_article_block_pool->block_count);
298 return NULL;
299 }
300
301 p_block = p_article_block_pool->p_block[index / ARTICLE_PER_BLOCK];
302
303 if (index % ARTICLE_PER_BLOCK >= p_block->article_count)
304 {
305 log_error("section_data_find_article_by_index(%d) is out of boundary of article [0, %d)\n", index, p_block->article_count);
306 return NULL;
307 }
308
309 return (p_block->articles + (index % ARTICLE_PER_BLOCK));
310 }
311
312 SECTION_LIST *section_list_create(const char *sname, const char *stitle, const char *master_name)
313 {
314 SECTION_LIST *p_section;
315
316 if (p_section_list_pool == NULL)
317 {
318 p_section_list_pool = calloc(BBS_max_section, sizeof(SECTION_LIST));
319 if (p_section_list_pool == NULL)
320 {
321 log_error("calloc(%d SECTION_LIST) OOM\n", BBS_max_section);
322 return NULL;
323 }
324
325 section_list_count = 0;
326 }
327
328 if (p_trie_dict_section_list == NULL)
329 {
330 p_trie_dict_section_list = trie_dict_create();
331 if (p_trie_dict_section_list == NULL)
332 {
333 log_error("trie_dict_create() OOM\n", BBS_max_section);
334 return NULL;
335 }
336 }
337
338 if (section_list_count >= BBS_max_section)
339 {
340 log_error("section_list_count exceed limit %d\n", BBS_max_section);
341 return NULL;
342 }
343
344 p_section = p_section_list_pool + section_list_count;
345
346 strncpy(p_section->sname, sname, sizeof(p_section->sname - 1));
347 p_section->sname[sizeof(p_section->sname - 1)] = '\0';
348
349 strncpy(p_section->stitle, stitle, sizeof(p_section->stitle - 1));
350 p_section->stitle[sizeof(p_section->stitle - 1)] = '\0';
351
352 strncpy(p_section->master_name, master_name, sizeof(p_section->master_name - 1));
353 p_section->master_name[sizeof(p_section->master_name - 1)] = '\0';
354
355 if (trie_dict_set(p_trie_dict_section_list, sname, section_list_count) != 1)
356 {
357 log_error("trie_dict_set(section_data, %s, %d) error\n", sname, section_list_count);
358 return NULL;
359 }
360
361 section_list_reset_articles(p_section);
362
363 section_list_count++;
364
365 return p_section;
366 }
367
368 void section_list_reset_articles(SECTION_LIST *p_section)
369 {
370 p_section->article_count = 0;
371 p_section->topic_count = 0;
372 p_section->visible_article_count = 0;
373 p_section->visible_topic_count = 0;
374 p_section->p_article_head = NULL;
375 p_section->p_article_tail = NULL;
376
377 p_section->page_count = 0;
378 p_section->last_page_visible_article_count = 0;
379 }
380
381 void section_list_cleanup(void)
382 {
383 if (p_trie_dict_section_list != NULL)
384 {
385 trie_dict_destroy(p_trie_dict_section_list);
386 p_trie_dict_section_list = NULL;
387 }
388
389 if (p_section_list_pool != NULL)
390 {
391 free(p_section_list_pool);
392 p_section_list_pool = NULL;
393 }
394
395 section_list_count = 0;
396 }
397
398 SECTION_LIST *section_list_find_by_name(const char *sname)
399 {
400 int64_t index;
401
402 if (p_section_list_pool == NULL || p_trie_dict_section_list == NULL)
403 {
404 log_error("section_list not initialized\n");
405 return NULL;
406 }
407
408 if (trie_dict_get(p_trie_dict_section_list, sname, &index) != 1)
409 {
410 log_error("trie_dict_get(section_data, %s) error\n", sname);
411 return NULL;
412 }
413
414 return (p_section_list_pool + index);
415 }
416
417 int section_list_append_article(SECTION_LIST *p_section, const ARTICLE *p_article_src)
418 {
419 ARTICLE_BLOCK *p_block;
420 int32_t last_aid = 0;
421 ARTICLE *p_article;
422 ARTICLE *p_topic_head;
423 ARTICLE *p_topic_tail;
424
425 if (p_section == NULL || p_article_src == NULL)
426 {
427 log_error("section_list_append_article() NULL pointer error\n");
428 return -1;
429 }
430
431 if (p_article_block_pool == NULL)
432 {
433 log_error("article_block_pool not initialized\n");
434 return -1;
435 }
436
437 if (p_section->article_count >= BBS_article_limit_per_section)
438 {
439 log_error("section_list_append_article() error: article_count reach limit in section %d\n", p_section->sid);
440 return -2;
441 }
442
443 if (p_article_block_pool->block_count == 0 ||
444 p_article_block_pool->p_block[p_article_block_pool->block_count - 1]->article_count >= ARTICLE_PER_BLOCK)
445 {
446 if ((p_block = pop_free_article_block()) == NULL)
447 {
448 log_error("pop_free_article_block() error\n");
449 return -2;
450 }
451
452 if (p_article_block_pool->block_count > 0)
453 {
454 last_aid = p_article_block_pool->p_block[p_article_block_pool->block_count - 1]->articles[ARTICLE_PER_BLOCK - 1].aid;
455 }
456
457 p_article_block_pool->p_block[p_article_block_pool->block_count] = p_block;
458 p_article_block_pool->block_count++;
459 }
460 else
461 {
462 p_block = p_article_block_pool->p_block[p_article_block_pool->block_count - 1];
463 last_aid = p_block->articles[p_block->article_count - 1].aid;
464 }
465
466 // AID of articles should be strictly ascending
467 if (p_article_src->aid <= last_aid)
468 {
469 log_error("section_data_append_article(aid=%d) error: last_aid=%d\n", p_article_src->aid, last_aid);
470 return -3;
471 }
472
473 p_article = (p_block->articles + p_block->article_count);
474 p_block->article_count++;
475 p_section->article_count++;
476
477 // Copy article data
478 *p_article = *p_article_src;
479
480 if (p_article->visible)
481 {
482 p_section->visible_article_count++;
483 }
484
485 // Link appended article as tail node of topic bi-directional list
486 if (p_article->tid != 0)
487 {
488 p_topic_head = article_block_find_by_aid(p_article->tid);
489 if (p_topic_head == NULL)
490 {
491 log_error("search head of topic (aid=%d) error\n", p_article->tid);
492 return -4;
493 }
494
495 p_topic_tail = p_topic_head->p_topic_prior;
496 if (p_topic_tail == NULL)
497 {
498 log_error("tail of topic (aid=%d) is NULL\n", p_article->tid);
499 return -4;
500 }
501 }
502 else
503 {
504 p_section->topic_count++;
505
506 if (p_article->visible)
507 {
508 p_section->visible_topic_count++;
509 }
510
511 p_topic_head = p_article;
512 p_topic_tail = p_article;
513 }
514
515 p_article->p_topic_prior = p_topic_tail;
516 p_article->p_topic_next = p_topic_head;
517 p_topic_head->p_topic_prior = p_article;
518 p_topic_tail->p_topic_next = p_article;
519
520 // Link appended article as tail node of article bi-directional list
521 if (p_section->p_article_head == NULL)
522 {
523 p_section->p_article_head = p_article;
524 p_section->p_article_tail = p_article;
525 }
526 p_article->p_prior = p_section->p_article_tail;
527 p_article->p_next = p_section->p_article_head;
528 p_section->p_article_head->p_prior = p_article;
529 p_section->p_article_tail->p_next = p_article;
530 p_section->p_article_tail = p_article;
531
532 // Update page
533 if (p_article->visible && p_section->last_page_visible_article_count % BBS_article_limit_per_page == 0)
534 {
535 p_section->p_page_first_article[p_section->page_count] = p_article;
536 p_section->page_count++;
537 p_section->last_page_visible_article_count = 0;
538 }
539
540 if (p_article->visible)
541 {
542 p_section->last_page_visible_article_count++;
543 }
544
545 return 0;
546 }
547
548 int section_list_set_article_visible(SECTION_LIST *p_section, int32_t aid, int8_t visible)
549 {
550 ARTICLE *p_article;
551 ARTICLE *p_reply;
552 int affected_count = 0;
553
554 if (p_section == NULL)
555 {
556 log_error("section_list_set_article_visible() NULL pointer error\n");
557 return -2;
558 }
559
560 p_article = article_block_find_by_aid(aid);
561 if (p_article == NULL)
562 {
563 return -1; // Not found
564 }
565
566 if (p_article->visible == visible)
567 {
568 return 0; // Already set
569 }
570
571 if (visible == 0) // 1 -> 0
572 {
573 p_section->visible_article_count--;
574
575 if (p_article->tid == 0)
576 {
577 p_section->visible_topic_count--;
578
579 // Set related visible replies to invisible
580 for (p_reply = p_article->p_topic_next; p_reply->tid != 0; p_reply = p_reply->p_topic_next)
581 {
582 if (p_reply->tid != aid)
583 {
584 log_error("Inconsistent tid = %d found in reply %d of topic %d\n", p_reply->tid, p_reply->aid, aid);
585 continue;
586 }
587
588 if (p_reply->visible == 1)
589 {
590 p_reply->visible = 0;
591 p_section->visible_article_count--;
592 affected_count++;
593 }
594 }
595 }
596 }
597 else // 0 -> 1
598 {
599 p_section->visible_article_count++;
600
601 if (p_article->tid == 0)
602 {
603 p_section->visible_topic_count++;
604 }
605 }
606
607 p_article->visible = visible;
608 affected_count++;
609
610 return affected_count;
611 }
612
613 ARTICLE *section_list_find_article_with_offset(SECTION_LIST *p_section, int32_t aid, int32_t *p_page, int32_t *p_offset)
614 {
615 ARTICLE *p_article;
616 int left;
617 int right;
618 int mid;
619
620 *p_page = -1;
621 *p_offset = -1;
622
623 if (p_section == NULL)
624 {
625 log_error("section_list_find_article_with_offset() NULL pointer error\n");
626 return NULL;
627 }
628
629 if (p_section->article_count == 0) // empty
630 {
631 *p_page = 0;
632 *p_offset = 0;
633 return NULL;
634 }
635
636 left = 0;
637 right = p_section->page_count;
638
639 // aid in the range [ head aid of pages[left], tail aid of pages[right - 1] ]
640 while (left < right - 1)
641 {
642 // get page id no less than mid value of left page id and right page id
643 mid = (left + right) / 2 + (right - left) % 2;
644
645 if (mid >= p_section->page_count)
646 {
647 log_error("page id (mid = %d) is out of boundary\n", mid);
648 return NULL;
649 }
650
651 if (aid < p_section->p_page_first_article[mid]->aid)
652 {
653 right = mid;
654 }
655 else
656 {
657 left = mid;
658 }
659 }
660
661 *p_page = left;
662
663 p_article = p_section->p_page_first_article[*p_page];
664
665 // p_section->p_page_first_article[*p_page]->aid <= aid < p_section->p_page_first_article[*p_page + 1]->aid
666 right = (*p_page == p_section->page_count - 1 ? INT32_MAX : p_section->p_page_first_article[*p_page + 1]->aid);
667
668 // left will be the offset of article found or offset to insert
669 left = 0;
670
671 while (1)
672 {
673 if (aid == p_article->aid) // found
674 {
675 break;
676 }
677 else if (aid < p_article->aid) // not exist
678 {
679 p_article = NULL;
680 break;
681 }
682
683 // aid > p_article->aid
684 p_article = p_article->p_next;
685 left++;
686
687 // over last article in the page
688 if (p_article == p_section->p_article_head || p_article->aid >= right)
689 {
690 p_article = NULL;
691 break;
692 }
693 }
694
695 *p_offset = left;
696
697 return p_article;
698 }
699
700 int section_list_calculate_page(SECTION_LIST *p_section, int32_t start_aid)
701 {
702 ARTICLE *p_article;
703 int32_t page;
704 int32_t offset;
705 int visible_article_count;
706 int page_head_set;
707
708 if (p_section == NULL)
709 {
710 log_error("section_list_calculate_page() NULL pointer error\n");
711 return -1;
712 }
713
714 p_article = section_list_find_article_with_offset(p_section, start_aid, &page, &offset);
715 if (p_article == NULL)
716 {
717 if (page < 0)
718 {
719 return 0;
720 }
721
722 log_error("section_list_calculate_page() aid = %d not found in section sid = %d\n", start_aid, p_section->sid);
723 }
724
725 if (offset > 0)
726 {
727 p_article = p_section->p_page_first_article[page];
728 }
729
730 visible_article_count = 0;
731 page_head_set = 0;
732
733 do
734 {
735 if (!page_head_set && visible_article_count == 0)
736 {
737 p_section->p_page_first_article[page] = p_article;
738 page_head_set = 1;
739 }
740
741 if (p_article->visible)
742 {
743 visible_article_count++;
744 }
745
746 p_article = p_article->p_next;
747
748 // skip remaining invisible articles
749 while (p_article->visible == 0 && p_article != p_section->p_article_head)
750 {
751 p_article = p_article->p_next;
752 }
753
754 if (visible_article_count >= BBS_article_limit_per_page && p_article != p_section->p_article_head)
755 {
756 page++;
757 visible_article_count = 0;
758 page_head_set = 0;
759
760 if (page >= BBS_article_limit_per_section / BBS_article_limit_per_page && p_article != p_section->p_article_head)
761 {
762 log_error("Count of page exceed limit in section %d\n", p_section->sid);
763 break;
764 }
765 }
766 } while (p_article != p_section->p_article_head);
767
768 p_section->page_count = page + (visible_article_count > 0 ? 1 : 0);
769 p_section->last_page_visible_article_count = visible_article_count;
770
771 return 0;
772 }

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