/[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.4 - (show annotations)
Wed Oct 15 12:02:38 2025 UTC (5 months ago) by sysadm
Branch: MAIN
Changes since 1.3: +4 -4 lines
Content type: text/x-csrc
Fix bug

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

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