/[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.1 - (show annotations)
Wed Oct 15 00:45:52 2025 UTC (5 months ago) by sysadm
Branch: MAIN
Content type: text/x-csrc
Add article_favor

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

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