/[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.8 - (show annotations)
Tue Nov 4 14:58:56 2025 UTC (4 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.7: +1 -1 lines
Content type: text/x-csrc
Refine file header information comments

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

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