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

Contents of /lbbs/src/article_favor.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.13 - (show annotations)
Sat Jan 3 10:27:14 2026 UTC (2 months, 1 week ago) by sysadm
Branch: MAIN
CVS Tags: HEAD
Changes since 1.12: +1 -1 lines
Content type: text/x-csrc
Update copyright info

1 /* SPDX-License-Identifier: GPL-3.0-or-later */
2 /*
3 * article_favor
4 * - data model and basic operations of user favorite articles
5 *
6 * Copyright (C) 2004-2026 Leaflet <leaflet@leafok.com>
7 */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #include "article_favor.h"
14 #include "common.h"
15 #include "database.h"
16 #include "log.h"
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/param.h>
20
21 ARTICLE_FAVOR BBS_article_favor;
22
23 int article_favor_load(int uid, ARTICLE_FAVOR *p_favor, int keep_inc)
24 {
25 MYSQL *db;
26 MYSQL_RES *rs;
27 MYSQL_ROW row;
28 char sql[SQL_BUFFER_LEN];
29
30 if (p_favor == NULL)
31 {
32 log_error("NULL pointer error");
33 return -1;
34 }
35
36 p_favor->uid = uid;
37
38 if (uid == 0)
39 {
40 p_favor->aid_base_cnt = 0;
41
42 if (!keep_inc)
43 {
44 p_favor->aid_inc_cnt = 0;
45 }
46
47 return 0;
48 }
49
50 if ((db = db_open()) == NULL)
51 {
52 log_error("article_favor_load() error: Unable to open DB");
53 return -2;
54 }
55
56 snprintf(sql, sizeof(sql),
57 "SELECT AID FROM article_favorite WHERE UID = %d "
58 "ORDER BY AID",
59 uid);
60 if (mysql_query(db, sql) != 0)
61 {
62 log_error("Query article_favorite error: %s", mysql_error(db));
63 return -3;
64 }
65 if ((rs = mysql_use_result(db)) == NULL)
66 {
67 log_error("Get article_favorite data failed");
68 return -3;
69 }
70
71 p_favor->aid_base_cnt = 0;
72
73 while ((row = mysql_fetch_row(rs)))
74 {
75 p_favor->aid_base[p_favor->aid_base_cnt] = atoi(row[0]);
76 (p_favor->aid_base_cnt)++;
77 if (p_favor->aid_base_cnt >= MAX_FAVOR_AID_BASE_CNT)
78 {
79 log_error("Too many article_favorite records for uid=%d",
80 uid);
81 break;
82 }
83 }
84 mysql_free_result(rs);
85
86 mysql_close(db);
87
88 log_common("Loaded %d article_favorite records for uid=%d", p_favor->aid_base_cnt, uid);
89
90 if (!keep_inc)
91 {
92 p_favor->aid_inc_cnt = 0;
93 }
94
95 return 0;
96 }
97
98 int article_favor_unload(ARTICLE_FAVOR *p_favor)
99 {
100 if (p_favor == NULL)
101 {
102 log_error("NULL pointer error");
103 return -1;
104 }
105
106 p_favor->aid_base_cnt = 0;
107
108 return 0;
109 }
110
111 int article_favor_save_inc(const ARTICLE_FAVOR *p_favor)
112 {
113 MYSQL *db = NULL;
114 char sql_add[SQL_BUFFER_LEN];
115 char sql_del[SQL_BUFFER_LEN];
116 char tuple_tmp[LINE_BUFFER_LEN];
117 int i;
118 int j;
119 int cnt_pending_add = 0;
120 int cnt_pending_del = 0;
121 int cnt_total_add = 0;
122 int cnt_total_del = 0;
123
124 if (p_favor == NULL)
125 {
126 log_error("NULL pointer error");
127 return -1;
128 }
129
130 if (p_favor->uid <= 0 || p_favor->aid_inc_cnt == 0)
131 {
132 return 0;
133 }
134
135 if ((db = db_open()) == NULL)
136 {
137 log_error("article_favor_load() error: Unable to open DB");
138 return -2;
139 }
140
141 snprintf(sql_add, sizeof(sql_add),
142 "INSERT IGNORE INTO article_favorite(AID, UID) VALUES ");
143 snprintf(sql_del, sizeof(sql_add),
144 "DELETE FROM article_favorite WHERE UID = %d AND AID IN (",
145 p_favor->uid);
146
147 for (i = 0, j = 0; j < p_favor->aid_inc_cnt;)
148 {
149 if (i < p_favor->aid_base_cnt && p_favor->aid_base[i] == p_favor->aid_inc[j]) // XOR - delete record
150 {
151 snprintf(tuple_tmp, sizeof(tuple_tmp), "%d, ", p_favor->aid_inc[j]);
152 strncat(sql_del, tuple_tmp, sizeof(sql_del) - 1 - strnlen(sql_del, sizeof(sql_del)));
153
154 cnt_pending_del++;
155 i++;
156 j++;
157 }
158 else if (i < p_favor->aid_base_cnt && p_favor->aid_base[i] < p_favor->aid_inc[j]) // skip existing record
159 {
160 i++;
161 }
162 else // if (i >= p_favor->aid_base_cnt || p_favor->aid_base[i] > p_favor->aid_inc[j])
163 {
164 snprintf(tuple_tmp, sizeof(tuple_tmp),
165 "(%d, %d), ",
166 p_favor->aid_inc[j], p_favor->uid);
167 strncat(sql_add, tuple_tmp, sizeof(sql_add) - 1 - strnlen(sql_add, sizeof(sql_add)));
168
169 cnt_pending_add++;
170 j++;
171 }
172
173 if ((cnt_pending_add >= 100 || (j + 1) >= p_favor->aid_inc_cnt) && cnt_pending_add > 0)
174 {
175 // Add
176 sql_add[strnlen(sql_add, sizeof(sql_add)) - 2] = '\0'; // remove last ", "
177
178 if (mysql_query(db, sql_add) != 0)
179 {
180 log_error("Add article_favorite error: %s", mysql_error(db));
181 log_error("%s", sql_add);
182 mysql_close(db);
183 return -3;
184 }
185
186 cnt_total_add += (int)mysql_affected_rows(db);
187 cnt_pending_add = 0;
188
189 snprintf(sql_add, sizeof(sql_add),
190 "INSERT IGNORE INTO article_favorite(AID, UID) VALUES ");
191 }
192
193 if ((cnt_pending_del >= 100 || (j + 1) >= p_favor->aid_inc_cnt) && cnt_pending_del > 0)
194 {
195 // Delete
196 sql_del[strnlen(sql_del, sizeof(sql_del)) - 2] = ')'; // replace last ", " with ") "
197
198 if (mysql_query(db, sql_del) != 0)
199 {
200 log_error("Delete article_favorite error: %s", mysql_error(db));
201 log_error("%s", sql_del);
202 mysql_close(db);
203 return -3;
204 }
205
206 cnt_total_del += (int)mysql_affected_rows(db);
207 cnt_pending_del = 0;
208
209 snprintf(sql_del, sizeof(sql_add),
210 "DELETE FROM article_favorite WHERE UID = %d AND AID IN (",
211 p_favor->uid);
212 }
213 }
214
215 log_common("Saved %d and deleted %d article_favorite records for uid=%d",
216 cnt_total_add, cnt_total_del, p_favor->uid);
217
218 mysql_close(db);
219
220 return 0;
221 }
222
223 int article_favor_merge_inc(ARTICLE_FAVOR *p_favor)
224 {
225 int32_t aid_new[MAX_FAVOR_AID_BASE_CNT];
226 int i, j, k;
227 int len;
228
229 if (p_favor == NULL)
230 {
231 log_error("NULL pointer error");
232 return -1;
233 }
234
235 if (p_favor->aid_inc_cnt == 0) // Nothing to be merged
236 {
237 return 0;
238 }
239
240 for (i = 0, j = 0, k = 0; i < p_favor->aid_base_cnt && j < p_favor->aid_inc_cnt && k < MAX_FAVOR_AID_BASE_CNT;)
241 {
242 if (p_favor->aid_base[i] == p_favor->aid_inc[j]) // XOR - discard duplicate pair
243 {
244 i++;
245 j++;
246 }
247 else if (p_favor->aid_base[i] < p_favor->aid_inc[j])
248 {
249 aid_new[k++] = p_favor->aid_base[i++];
250 }
251 else // if (p_favor->aid_base[i] > p_favor->aid_inc[j])
252 {
253 aid_new[k++] = p_favor->aid_inc[j++];
254 }
255 }
256
257 len = MIN(p_favor->aid_base_cnt - i, MAX_FAVOR_AID_BASE_CNT - k);
258 if (len > 0)
259 {
260 memcpy(aid_new + k, p_favor->aid_base + i,
261 sizeof(int32_t) * (size_t)len);
262 i += len;
263 k += len;
264 }
265 if (i < p_favor->aid_base_cnt)
266 {
267 log_error("Too many base aids, %d will be discarded", p_favor->aid_base_cnt - i);
268 }
269
270 len = MIN(p_favor->aid_inc_cnt - j, MAX_FAVOR_AID_BASE_CNT - k);
271 if (len > 0)
272 {
273 memcpy(aid_new + k, p_favor->aid_inc + j,
274 sizeof(int32_t) * (size_t)len);
275 j += len;
276 k += len;
277 }
278 if (j < p_favor->aid_inc_cnt)
279 {
280 log_error("Too many inc aids, %d will be discarded", p_favor->aid_inc_cnt - j);
281 }
282
283 memcpy(p_favor->aid_base, aid_new, sizeof(int32_t) * (size_t)k);
284
285 p_favor->aid_base_cnt = k;
286 p_favor->aid_inc_cnt = 0;
287
288 return 0;
289 }
290
291 int article_favor_check(int32_t aid, const ARTICLE_FAVOR *p_favor)
292 {
293 int left;
294 int right;
295 int mid;
296 int i;
297 int is_set = 0;
298
299 if (p_favor == NULL)
300 {
301 log_error("NULL pointer error");
302 return -1;
303 }
304
305 for (i = 0; i < 2; i++)
306 {
307 left = 0;
308 right = (i == 0 ? p_favor->aid_base_cnt : p_favor->aid_inc_cnt) - 1;
309
310 if (right < 0)
311 {
312 continue;
313 }
314
315 while (left < right)
316 {
317 mid = (left + right) / 2;
318 if (aid < (i == 0 ? p_favor->aid_base[mid] : p_favor->aid_inc[mid]))
319 {
320 right = mid - 1;
321 }
322 else if (aid > (i == 0 ? p_favor->aid_base[mid] : p_favor->aid_inc[mid]))
323 {
324 left = mid + 1;
325 }
326 else // if (aid == p_favor->aid_base[mid])
327 {
328 left = mid;
329 break;
330 }
331 }
332
333 if (aid == (i == 0 ? p_favor->aid_base[left] : p_favor->aid_inc[left]))
334 {
335 is_set = (is_set ? 0 : 1);
336 }
337 }
338
339 return is_set;
340 }
341
342 int article_favor_set(int32_t aid, ARTICLE_FAVOR *p_favor, int state)
343 {
344 int left;
345 int right;
346 int mid;
347 int i;
348 int is_set = 0;
349
350 if (p_favor == NULL)
351 {
352 log_error("NULL pointer error");
353 return -1;
354 }
355
356 for (i = 0; i < 2; i++)
357 {
358 left = 0;
359 right = (i == 0 ? p_favor->aid_base_cnt : p_favor->aid_inc_cnt) - 1;
360
361 if (right < 0)
362 {
363 continue;
364 }
365
366 while (left < right)
367 {
368 mid = (left + right) / 2;
369 if (aid < (i == 0 ? p_favor->aid_base[mid] : p_favor->aid_inc[mid]))
370 {
371 right = mid - 1;
372 }
373 else if (aid > (i == 0 ? p_favor->aid_base[mid] : p_favor->aid_inc[mid]))
374 {
375 left = mid + 1;
376 }
377 else // if (aid == p_favor->aid_base[mid])
378 {
379 left = mid;
380 break;
381 }
382 }
383
384 if (aid == (i == 0 ? p_favor->aid_base[left] : p_favor->aid_inc[left]))
385 {
386 is_set = (is_set ? 0 : 1);
387 }
388 }
389
390 if ((is_set ^ (state ? 1 : 0)) == 0) // No change
391 {
392 return 0;
393 }
394
395 if (aid == p_favor->aid_inc[left] && p_favor->aid_inc_cnt > 0) // Unset
396 {
397 if (p_favor->aid_inc_cnt > left + 1)
398 {
399 memmove(p_favor->aid_inc + left,
400 p_favor->aid_inc + left + 1,
401 sizeof(int32_t) * (size_t)(p_favor->aid_inc_cnt - left - 1));
402 }
403
404 (p_favor->aid_inc_cnt)--;
405
406 return 2; // Unset complete
407 }
408
409 // Merge if Inc is full
410 if (p_favor->aid_inc_cnt >= MAX_FAVOR_AID_INC_CNT)
411 {
412 // Save incremental article favorite
413 if (article_favor_save_inc(p_favor) < 0)
414 {
415 log_error("article_favor_save_inc() error");
416 return -2;
417 }
418
419 article_favor_merge_inc(p_favor);
420
421 p_favor->aid_inc[(p_favor->aid_inc_cnt)++] = aid;
422
423 return 1; // Set complete
424 }
425
426 if (right < 0)
427 {
428 right = 0;
429 }
430 else if (aid > p_favor->aid_inc[left])
431 {
432 right = left + 1;
433 }
434
435 if (p_favor->aid_inc_cnt > right)
436 {
437 memmove(p_favor->aid_inc + right + 1,
438 p_favor->aid_inc + right,
439 sizeof(int32_t) * (size_t)(p_favor->aid_inc_cnt - right));
440 }
441
442 p_favor->aid_inc[right] = aid;
443 (p_favor->aid_inc_cnt)++;
444
445 return 1; // Set complete
446 }
447
448 int query_favor_articles(ARTICLE_FAVOR *p_favor, int page_id, const ARTICLE **p_articles,
449 char p_snames[][BBS_section_name_max_len + 1], int *p_article_count, int *p_page_count)
450 {
451 SECTION_LIST *p_section;
452 int32_t aid;
453 int i;
454
455 if (p_favor == NULL || p_articles == NULL || p_article_count == NULL || p_page_count == NULL)
456 {
457 log_error("NULL pointer error");
458 return -1;
459 }
460
461 if (article_favor_save_inc(p_favor) < 0)
462 {
463 log_error("article_favor_save_inc() error");
464 return -2;
465 }
466 if (article_favor_merge_inc(p_favor) < 0)
467 {
468 log_error("article_favor_merge_inc() error");
469 return -2;
470 }
471
472 *p_page_count = (p_favor->aid_base_cnt + BBS_article_limit_per_page - 1) / BBS_article_limit_per_page;
473 *p_article_count = 0;
474
475 if (p_favor->aid_base_cnt == 0)
476 {
477 // empty list
478 return 0;
479 }
480
481 if (page_id < 0 || page_id >= *p_page_count)
482 {
483 log_error("Invalid page_id = %d, not in range [0, %d)", page_id, *p_page_count);
484 return -1;
485 }
486
487 for (i = 0;
488 i < BBS_article_limit_per_page &&
489 page_id * BBS_article_limit_per_page + i < p_favor->aid_base_cnt;
490 i++)
491 {
492 aid = p_favor->aid_base[page_id * BBS_article_limit_per_page + i];
493 p_articles[i] = article_block_find_by_aid(aid);
494 if (p_articles[i] == NULL)
495 {
496 log_error("article_block_find_by_aid(aid=%d) error: page_id=%d, i=%d", aid, page_id, i);
497 return -3;
498 }
499
500 p_section = section_list_find_by_sid(p_articles[i]->sid);
501 if (p_section == NULL)
502 {
503 log_error("section_list_find_by_sid(%d) error", p_articles[i]->sid);
504 return -3;
505 }
506
507 if (get_section_info(p_section, p_snames[i], NULL, NULL) < 0)
508 {
509 log_error("get_section_info(sid=%d) error", p_section->sid);
510 return -4;
511 }
512 }
513 *p_article_count = i;
514
515 return 0;
516 }

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