summaryrefslogtreecommitdiff
path: root/data.c
diff options
context:
space:
mode:
Diffstat (limited to 'data.c')
-rw-r--r--data.c778
1 files changed, 604 insertions, 174 deletions
diff --git a/data.c b/data.c
index 49f215b..51ee2c7 100644
--- a/data.c
+++ b/data.c
@@ -6,6 +6,7 @@
6#include "xs_json.h" 6#include "xs_json.h"
7#include "xs_openssl.h" 7#include "xs_openssl.h"
8#include "xs_glob.h" 8#include "xs_glob.h"
9#include "xs_set.h"
9 10
10#include "snac.h" 11#include "snac.h"
11 12
@@ -14,12 +15,12 @@
14#include <sys/file.h> 15#include <sys/file.h>
15#include <fcntl.h> 16#include <fcntl.h>
16 17
17double db_layout = 2.1; 18double db_layout = 2.5;
18 19
19 20
20int db_upgrade(d_char **error); 21int db_upgrade(d_char **error);
21 22
22int srv_open(char *basedir) 23int srv_open(char *basedir, int auto_upgrade)
23/* opens a server */ 24/* opens a server */
24{ 25{
25 int ret = 0; 26 int ret = 0;
@@ -69,7 +70,14 @@ int srv_open(char *basedir)
69 error = xs_fmt("DEBUG level set to %d from environment", dbglevel); 70 error = xs_fmt("DEBUG level set to %d from environment", dbglevel);
70 } 71 }
71 72
72 ret = db_upgrade(&error); 73 if (auto_upgrade)
74 ret = db_upgrade(&error);
75 else {
76 if (xs_number_get(xs_dict_get(srv_config, "layout")) < db_layout)
77 error = xs_fmt("ERROR: disk layout changed - execute 'snac upgrade' first");
78 else
79 ret = 1;
80 }
73 } 81 }
74 82
75 } 83 }
@@ -178,24 +186,215 @@ d_char *user_list(void)
178} 186}
179 187
180 188
181double mtime(char *fn) 189double mtime_nl(const char *fn, int *n_link)
182/* returns the mtime of a file or directory, or 0.0 */ 190/* returns the mtime and number of links of a file or directory, or 0.0 */
183{ 191{
184 struct stat st; 192 struct stat st;
185 double r = 0.0; 193 double r = 0.0;
194 int n = 0;
186 195
187 if (fn && stat(fn, &st) != -1) 196 if (fn && stat(fn, &st) != -1) {
188 r = (double)st.st_mtim.tv_sec; 197 r = (double) st.st_mtim.tv_sec;
198 n = st.st_nlink;
199 }
200
201 if (n_link)
202 *n_link = n;
189 203
190 return r; 204 return r;
191} 205}
192 206
193 207
194/** object database 2.1+ **/ 208/** database 2.1+ **/
209
210/** indexes **/
211
212int index_add_md5(const char *fn, const char *md5)
213/* adds an md5 to an index */
214{
215 int status = 201; /* Created */
216 FILE *f;
217
218 if ((f = fopen(fn, "a")) != NULL) {
219 flock(fileno(f), LOCK_EX);
220
221 /* ensure the position is at the end after getting the lock */
222 fseek(f, 0, SEEK_END);
223
224 fprintf(f, "%s\n", md5);
225 fclose(f);
226 }
227 else
228 status = 500;
229
230 return status;
231}
232
233
234int index_add(const char *fn, const char *id)
235/* adds an id to an index */
236{
237 xs *md5 = xs_md5_hex(id, strlen(id));
238 return index_add_md5(fn, md5);
239}
240
241
242int index_del(const char *fn, const char *md5)
243/* deletes an md5 from an index */
244{
245 int status = 404;
246 FILE *i, *o;
247
248 if ((i = fopen(fn, "r")) != NULL) {
249 flock(fileno(i), LOCK_EX);
250
251 xs *nfn = xs_fmt("%s.new", fn);
252 char line[256];
253
254 if ((o = fopen(nfn, "w")) != NULL) {
255 while (fgets(line, sizeof(line), i) != NULL) {
256 line[32] = '\0';
257 if (memcmp(line, md5, 32) != 0)
258 fprintf(o, "%s\n", line);
259 }
260
261 fclose(o);
262
263 xs *ofn = xs_fmt("%s.bak", fn);
264
265 link(fn, ofn);
266 rename(nfn, fn);
267 }
268 else
269 status = 500;
270
271 fclose(i);
272 }
273 else
274 status = 500;
275
276 return status;
277}
278
279
280int index_in_md5(const char *fn, const char *md5)
281/* checks if the md5 is already in the index */
282{
283 FILE *f;
284 int ret = 0;
285
286 if ((f = fopen(fn, "r")) != NULL) {
287 flock(fileno(f), LOCK_SH);
288
289 char line[256];
290
291 while (!ret && fgets(line, sizeof(line), f) != NULL) {
292 line[32] = '\0';
293
294 if (strcmp(line, md5) == 0)
295 ret = 1;
296 }
297
298 fclose(f);
299 }
300
301 return ret;
302}
303
304
305int index_in(const char *fn, const char *id)
306/* checks if the object id is already in the index */
307{
308 xs *md5 = xs_md5_hex(id, strlen(id));
309 return index_in_md5(fn, md5);
310}
311
312
313int index_first(const char *fn, char *line, int size)
314/* reads the first entry of an index */
315{
316 FILE *f;
317 int ret = 0;
318
319 if ((f = fopen(fn, "r")) != NULL) {
320 flock(fileno(f), LOCK_SH);
321
322 if (fgets(line, size, f) != NULL) {
323 line[32] = '\0';
324 ret = 1;
325 }
326
327 fclose(f);
328 }
329
330 return ret;
331}
332
333
334d_char *index_list(const char *fn, int max)
335/* returns an index as a list */
336{
337 d_char *list = NULL;
338 FILE *f;
339 int n = 0;
340
341 if ((f = fopen(fn, "r")) != NULL) {
342 flock(fileno(f), LOCK_SH);
343
344 char line[256];
345 list = xs_list_new();
346
347 while (n < max && fgets(line, sizeof(line), f) != NULL) {
348 line[32] = '\0';
349 list = xs_list_append(list, line);
350 n++;
351 }
352
353 fclose(f);
354 }
355
356 return list;
357}
358
359
360d_char *index_list_desc(const char *fn, int max)
361/* returns an index as a list, in reverse order */
362{
363 d_char *list = NULL;
364 FILE *f;
365 int n = 0;
366
367 if ((f = fopen(fn, "r")) != NULL) {
368 flock(fileno(f), LOCK_SH);
369
370 char line[256];
371 list = xs_list_new();
372
373 /* move to the end minus one entry */
374 if (!fseek(f, 0, SEEK_END) && !fseek(f, -33, SEEK_CUR)) {
375 while (n < max && fgets(line, sizeof(line), f) != NULL) {
376 line[32] = '\0';
377 list = xs_list_append(list, line);
378 n++;
379
380 /* move backwards 2 entries */
381 if (fseek(f, -66, SEEK_CUR) == -1)
382 break;
383 }
384 }
385
386 fclose(f);
387 }
388
389 return list;
390}
391
392
393/** objects **/
195 394
196d_char *_object_fn_by_md5(const char *md5) 395d_char *_object_fn_by_md5(const char *md5)
197{ 396{
198 xs *bfn = xs_fmt("%s/object/%c%c/", srv_basedir, md5[0], md5[1]); 397 xs *bfn = xs_fmt("%s/object/%c%c", srv_basedir, md5[0], md5[1]);
199 398
200 mkdir(bfn, 0755); 399 mkdir(bfn, 0755);
201 400
@@ -203,19 +402,18 @@ d_char *_object_fn_by_md5(const char *md5)
203} 402}
204 403
205 404
206d_char *_object_fn_by_id(const char *id) 405d_char *_object_fn(const char *id)
207{ 406{
208 xs *md5 = xs_md5_hex(id, strlen(id)); 407 xs *md5 = xs_md5_hex(id, strlen(id));
209
210 return _object_fn_by_md5(md5); 408 return _object_fn_by_md5(md5);
211} 409}
212 410
213 411
214int object_get(const char *id, d_char **obj, const char *type) 412int object_get_by_md5(const char *md5, d_char **obj, const char *type)
215/* returns a loaded object, optionally of the requested type */ 413/* returns a stored object, optionally of the requested type */
216{ 414{
217 int status = 404; 415 int status = 404;
218 xs *fn = _object_fn_by_id(id); 416 xs *fn = _object_fn_by_md5(md5);
219 FILE *f; 417 FILE *f;
220 418
221 if ((f = fopen(fn, "r")) != NULL) { 419 if ((f = fopen(fn, "r")) != NULL) {
@@ -247,13 +445,27 @@ int object_get(const char *id, d_char **obj, const char *type)
247} 445}
248 446
249 447
250int object_add(const char *id, d_char *obj) 448int object_get(const char *id, d_char **obj, const char *type)
449/* returns a stored object, optionally of the requested type */
450{
451 xs *md5 = xs_md5_hex(id, strlen(id));
452 return object_get_by_md5(md5, obj, type);
453}
454
455
456int _object_add(const char *id, d_char *obj, int ow)
251/* stores an object */ 457/* stores an object */
252{ 458{
253 int status = 201; /* Created */ 459 int status = 201; /* Created */
254 xs *fn = _object_fn_by_id(id); 460 xs *fn = _object_fn(id);
255 FILE *f; 461 FILE *f;
256 462
463 if (!ow && mtime(fn) > 0.0) {
464 /* object already here */
465 srv_debug(0, xs_fmt("object_add object already here %s", id));
466 return 204; /* No content */
467 }
468
257 if ((f = fopen(fn, "w")) != NULL) { 469 if ((f = fopen(fn, "w")) != NULL) {
258 flock(fileno(f), LOCK_EX); 470 flock(fileno(f), LOCK_EX);
259 471
@@ -261,100 +473,241 @@ int object_add(const char *id, d_char *obj)
261 473
262 fwrite(j, strlen(j), 1, f); 474 fwrite(j, strlen(j), 1, f);
263 fclose(f); 475 fclose(f);
476
477 /* does this object has a parent? */
478 char *in_reply_to = xs_dict_get(obj, "inReplyTo");
479
480 if (!xs_is_null(in_reply_to) && *in_reply_to) {
481 /* update the children index of the parent */
482 xs *c_idx = _object_fn(in_reply_to);
483
484 c_idx = xs_replace_i(c_idx, ".json", "_c.idx");
485 index_add(c_idx, id);
486
487 srv_debug(0, xs_fmt("object_add added child %s to %s", id, c_idx));
488
489 /* create a one-element index with the parent */
490 xs *p_idx = xs_replace(fn, ".json", "_p.idx");
491 index_add(p_idx, in_reply_to);
492
493 srv_debug(0, xs_fmt("object_add added parent %s to %s", in_reply_to, p_idx));
494 }
264 } 495 }
265 else 496 else
266 status = 500; 497 status = 500;
267 498
499 srv_debug(0, xs_fmt("object_add %s %s %d", id, fn, status));
500
268 return status; 501 return status;
269} 502}
270 503
271 504
272d_char *_follower_fn(snac *snac, char *actor) 505int object_add(const char *id, d_char *obj)
506/* stores an object */
273{ 507{
274 xs *md5 = xs_md5_hex(actor, strlen(actor)); 508 return _object_add(id, obj, 0);
275 return xs_fmt("%s/followers/%s.json", snac->basedir, md5);
276} 509}
277 510
278 511
279int follower_add(snac *snac, char *actor, char *msg) 512int object_add_ow(const char *id, d_char *obj)
280/* adds a follower */ 513/* stores an object (overwriting allowed) */
281{ 514{
282 int ret = 201; /* created */ 515 return _object_add(id, obj, 1);
283 xs *fn = _follower_fn(snac, actor); 516}
284 FILE *f;
285 517
286 if ((f = fopen(fn, "w")) != NULL) {
287 xs *j = xs_json_dumps_pp(msg, 4);
288 518
289 fwrite(j, 1, strlen(j), f); 519int object_del_by_md5(const char *md5)
290 fclose(f); 520/* deletes an object by its md5 */
521{
522 int status = 404;
523 xs *fn = _object_fn_by_md5(md5);
524
525 if (fn != NULL && unlink(fn) != -1) {
526 status = 200;
527
528 /* also delete associated indexes */
529 xs *spec = xs_dup(fn);
530 spec = xs_replace_i(spec, ".json", "*.idx");
531 xs *files = xs_glob(spec, 0, 0);
532 char *p, *v;
533
534 p = files;
535 while (xs_list_iter(&p, &v)) {
536 srv_debug(0, xs_fmt("object_del index %s", v));
537 unlink(v);
538 }
291 } 539 }
292 else
293 ret = 500;
294 540
295 snac_debug(snac, 2, xs_fmt("follower_add %s %s", actor, fn)); 541 srv_debug(0, xs_fmt("object_del %s %d", fn, status));
542
543 return status;
544}
545
546
547int object_del(const char *id)
548/* deletes an object */
549{
550 xs *md5 = xs_md5_hex(id, strlen(id));
551 return object_del_by_md5(md5);
552}
553
554
555int object_del_if_unref(const char *id)
556/* deletes an object if its n_links < 2 */
557{
558 xs *fn = _object_fn(id);
559 int n_links;
560 int ret = 0;
561
562 if (mtime_nl(fn, &n_links) > 0.0 && n_links < 2)
563 ret = object_del(id);
296 564
297 return ret; 565 return ret;
298} 566}
299 567
300 568
301int follower_del(snac *snac, char *actor) 569d_char *object_children(const char *id)
302/* deletes a follower */ 570/* returns the list of an object's children */
571{
572 xs *fn = _object_fn(id);
573
574 fn = xs_replace_i(fn, ".json", "_c.idx");
575
576 return index_list(fn, XS_ALL);
577}
578
579
580int object_admire(const char *id, const char *actor, int like)
581/* actor likes or announces this object */
303{ 582{
304 int status = 200; 583 int status = 200;
305 xs *fn = _follower_fn(snac, actor); 584 xs *fn = _object_fn(id);
306 585
307 if (fn != NULL) 586 fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx");
308 unlink(fn);
309 else
310 status = 404;
311 587
312 snac_debug(snac, 2, xs_fmt("follower_del %s %s", actor, fn)); 588 if (!index_in(fn, actor)) {
589 status = index_add(fn, actor);
590
591 srv_debug(0, xs_fmt("object_admire (%s) %s %s", like ? "Like" : "Announce", actor, fn));
592 }
313 593
314 return status; 594 return status;
315} 595}
316 596
317 597
318int follower_check(snac *snac, char *actor) 598int _object_user_cache(snac *snac, const char *id, const char *cachedir, int del)
319/* checks if someone is a follower */ 599/* adds or deletes from a user cache */
320{ 600{
321 xs *fn = _follower_fn(snac, actor); 601 xs *ofn = _object_fn(id);
602 xs *l = xs_split(ofn, "/");
603 xs *cfn = xs_fmt("%s/%s/%s", snac->basedir, cachedir, xs_list_get(l, -1));
604 xs *idx = xs_fmt("%s/%s.idx", snac->basedir, cachedir);
605 int ret;
606
607 if (del) {
608 if ((ret = unlink(cfn)) != -1)
609 index_del(idx, id);
610 }
611 else {
612 index_add(idx, id);
613 ret = link(ofn, cfn);
614 }
322 615
323 return !!(mtime(fn) != 0.0); 616 return ret;
617}
618
619
620int object_user_cache_add(snac *snac, const char *id, const char *cachedir)
621/* caches an object into a user cache */
622{
623 return _object_user_cache(snac, id, cachedir, 0);
624}
625
626
627int object_user_cache_del(snac *snac, const char *id, const char *cachedir)
628/* deletes an object from a user cache */
629{
630 return _object_user_cache(snac, id, cachedir, 1);
631}
632
633
634int object_user_cache_in(snac *snac, const char *id, const char *cachedir)
635/* checks if an object is stored in a cache */
636{
637 xs *md5 = xs_md5_hex(id, strlen(id));
638 xs *cfn = xs_fmt("%s/%s/%s.json", snac->basedir, cachedir, md5);
639
640 return !!(mtime(cfn) != 0.0);
641}
642
643
644d_char *object_user_cache_list(snac *snac, const char *cachedir, int max)
645/* returns the objects in a cache as a list */
646{
647 xs *idx = xs_fmt("%s/%s.idx", snac->basedir, cachedir);
648 return index_list(idx, max);
649}
650
651
652/** specialized functions **/
653
654/** followers **/
655
656int follower_add(snac *snac, const char *actor)
657/* adds a follower */
658{
659 int ret = object_user_cache_add(snac, actor, "followers");
660
661 snac_debug(snac, 2, xs_fmt("follower_add %s %s", actor));
662
663 return ret == -1 ? 500 : 200;
664}
665
666
667int follower_del(snac *snac, const char *actor)
668/* deletes a follower */
669{
670 int ret = object_user_cache_del(snac, actor, "followers");
671
672 snac_debug(snac, 2, xs_fmt("follower_del %s %s", actor));
673
674 return ret == -1 ? 404 : 200;
675}
676
677
678int follower_check(snac *snac, const char *actor)
679/* checks if someone is a follower */
680{
681 return object_user_cache_in(snac, actor, "followers");
324} 682}
325 683
326 684
327d_char *follower_list(snac *snac) 685d_char *follower_list(snac *snac)
328/* returns the list of followers */ 686/* returns the list of followers */
329{ 687{
330 xs *spec = xs_fmt("%s/followers/" "*.json", snac->basedir); 688 xs *list = object_user_cache_list(snac, "followers", XS_ALL);
331 xs *glist = xs_glob(spec, 0, 0); 689 d_char *fwers = xs_list_new();
332 char *p, *v; 690 char *p, *v;
333 d_char *list = xs_list_new();
334 691
335 /* iterate the list of files */ 692 /* resolve the list of md5 to be a list of actors */
336 p = glist; 693 p = list;
337 while (xs_list_iter(&p, &v)) { 694 while (xs_list_iter(&p, &v)) {
338 FILE *f; 695 xs *a_obj = NULL;
339
340 /* load the follower data */
341 if ((f = fopen(v, "r")) != NULL) {
342 xs *j = xs_readall(f);
343 fclose(f);
344 696
345 if (j != NULL) { 697 if (valid_status(object_get_by_md5(v, &a_obj, NULL))) {
346 xs *o = xs_json_loads(j); 698 char *actor = xs_dict_get(a_obj, "id");
347 699
348 if (o != NULL) 700 if (!xs_is_null(actor))
349 list = xs_list_append(list, o); 701 fwers = xs_list_append(fwers, actor);
350 }
351 } 702 }
352 } 703 }
353 704
354 return list; 705 return fwers;
355} 706}
356 707
357 708
709/** timeline **/
710
358double timeline_mtime(snac *snac) 711double timeline_mtime(snac *snac)
359{ 712{
360 xs *fn = xs_fmt("%s/timeline", snac->basedir); 713 xs *fn = xs_fmt("%s/timeline", snac->basedir);
@@ -437,6 +790,13 @@ int timeline_del(snac *snac, char *id)
437 ret = 200; 790 ret = 200;
438 } 791 }
439 792
793 /* delete from the user's caches */
794 object_user_cache_del(snac, id, "public");
795 object_user_cache_del(snac, id, "private");
796
797 /* try to delete the object if it's not used elsewhere */
798 object_del_if_unref(id);
799
440 return ret; 800 return ret;
441} 801}
442 802
@@ -511,22 +871,11 @@ int _timeline_write(snac *snac, char *id, char *msg, char *parent, char *referre
511 871
512 if (pfn != NULL && (f = fopen(pfn, "r")) != NULL) { 872 if (pfn != NULL && (f = fopen(pfn, "r")) != NULL) {
513 xs *j; 873 xs *j;
514 char *v;
515 874
516 j = xs_readall(f); 875 j = xs_readall(f);
517 fclose(f); 876 fclose(f);
518 877
519 p_msg = xs_json_loads(j); 878 p_msg = xs_json_loads(j);
520
521 if ((v = xs_dict_get(p_msg, "_snac")) != NULL) {
522 /* is parent hidden? */
523 if ((v = xs_dict_get(v, "hidden")) && xs_type(v) == XSTYPE_TRUE) {
524 snac_debug(snac, 1,
525 xs_fmt("_timeline_write dropping due to hidden parent %s (%s)", id, parent));
526
527 return 0;
528 }
529 }
530 } 879 }
531 } 880 }
532 881
@@ -648,6 +997,16 @@ int _timeline_write(snac *snac, char *id, char *msg, char *parent, char *referre
648} 997}
649 998
650 999
1000void timeline_update_indexes(snac *snac, const char *id)
1001/* updates the indexes */
1002{
1003 object_user_cache_add(snac, id, "private");
1004
1005 if (xs_startswith(id, snac->actor))
1006 object_user_cache_add(snac, id, "public");
1007}
1008
1009
651int timeline_add(snac *snac, char *id, char *o_msg, char *parent, char *referrer) 1010int timeline_add(snac *snac, char *id, char *o_msg, char *parent, char *referrer)
652/* adds a message to the timeline */ 1011/* adds a message to the timeline */
653{ 1012{
@@ -680,13 +1039,61 @@ int timeline_add(snac *snac, char *id, char *o_msg, char *parent, char *referrer
680 1039
681 msg = xs_dict_set(msg, "_snac", md); 1040 msg = xs_dict_set(msg, "_snac", md);
682 1041
683 if ((ret = _timeline_write(snac, id, msg, parent, referrer))) 1042 if ((ret = _timeline_write(snac, id, msg, parent, referrer))) {
684 snac_debug(snac, 1, xs_fmt("timeline_add %s", id)); 1043 snac_debug(snac, 1, xs_fmt("timeline_add %s", id));
685 1044
1045 object_add(id, o_msg);
1046 timeline_update_indexes(snac, id);
1047 }
1048
686 return ret; 1049 return ret;
687} 1050}
688 1051
689 1052
1053d_char *timeline_top_level(snac *snac, d_char *list)
1054/* returns the top level md5 entries from this index */
1055{
1056 d_char *tl = xs_list_new();
1057 xs_set seen;
1058 char *p, *v;
1059
1060 xs_set_init(&seen);
1061
1062 p = list;
1063 while (xs_list_iter(&p, &v)) {
1064 char line[256] = "";
1065
1066 strcpy(line, v);
1067
1068 for (;;) {
1069 char line2[256];
1070 xs *fn = _object_fn_by_md5(line);
1071 fn = xs_replace_i(fn, ".json", "_p.idx");
1072
1073 /* if it doesn't have a parent, use this */
1074 if (index_first(fn, line2, sizeof(line2)) == 0)
1075 break;
1076
1077 xs *pfn = _object_fn_by_md5(line2);
1078
1079 /* well, there is a parent... but if it's not there, use this */
1080 if (mtime(pfn) == 0.0)
1081 break;
1082
1083 /* it's here! try again with its own parent */
1084 strcpy(line, line2);
1085 }
1086
1087 if (xs_set_add(&seen, line) == 1)
1088 tl = xs_list_append(tl, line);
1089 }
1090
1091 xs_set_free(&seen);
1092
1093 return tl;
1094}
1095
1096
690void timeline_admire(snac *snac, char *id, char *admirer, int like) 1097void timeline_admire(snac *snac, char *id, char *admirer, int like)
691/* updates a timeline entry with a new admiration */ 1098/* updates a timeline entry with a new admiration */
692{ 1099{
@@ -734,52 +1141,16 @@ void timeline_admire(snac *snac, char *id, char *admirer, int like)
734 } 1141 }
735 else 1142 else
736 snac_log(snac, xs_fmt("timeline_admire ignored for unknown object %s", id)); 1143 snac_log(snac, xs_fmt("timeline_admire ignored for unknown object %s", id));
737}
738
739
740int timeline_hide(snac *snac, char *id, int hide)
741/* hides/unhides a timeline entry */
742{
743 int ret = 0;
744 xs *fn = _timeline_find_fn(snac, id);
745 FILE *f;
746
747 if (fn != NULL && (f = fopen(fn, "r")) != NULL) {
748 xs *s1 = xs_readall(f);
749 xs *msg = xs_json_loads(s1);
750 xs *meta = xs_dup(xs_dict_get(msg, "_snac"));
751 xs *hdn = xs_val_new(hide ? XSTYPE_TRUE : XSTYPE_FALSE);
752 char *p, *v;
753
754 fclose(f);
755
756 /* if it's already in this hidden state, we're done */
757 if ((v = xs_dict_get(meta, "hidden")) && xs_type(v) == xs_type(hdn))
758 return ret;
759
760 meta = xs_dict_set(meta, "hidden", hdn);
761 msg = xs_dict_set(msg, "_snac", meta);
762
763 if ((f = fopen(fn, "w")) != NULL) {
764 xs *j1 = xs_json_dumps_pp(msg, 4);
765
766 fwrite(j1, strlen(j1), 1, f);
767 fclose(f);
768 1144
769 snac_debug(snac, 1, xs_fmt("timeline_hide %d %s", hide, id)); 1145 object_admire(id, admirer, like);
1146}
770 1147
771 /* now hide the children */
772 p = xs_dict_get(meta, "children");
773 while (xs_list_iter(&p, &v))
774 timeline_hide(snac, v, hide);
775 1148
776 ret = 1; 1149/** following **/
777 }
778 }
779
780 return ret;
781}
782 1150
1151/* this needs special treatment and cannot use the object db as is,
1152 with a link to a cached author, because we need the Follow object
1153 in case we need to unfollow (Undo + original Follow) */
783 1154
784d_char *_following_fn(snac *snac, char *actor) 1155d_char *_following_fn(snac *snac, char *actor)
785{ 1156{
@@ -811,7 +1182,7 @@ int following_add(snac *snac, char *actor, char *msg)
811 1182
812 1183
813int following_del(snac *snac, char *actor) 1184int following_del(snac *snac, char *actor)
814/* someone is no longer following us */ 1185/* we're not following this actor any longer */
815{ 1186{
816 xs *fn = _following_fn(snac, actor); 1187 xs *fn = _following_fn(snac, actor);
817 1188
@@ -824,7 +1195,7 @@ int following_del(snac *snac, char *actor)
824 1195
825 1196
826int following_check(snac *snac, char *actor) 1197int following_check(snac *snac, char *actor)
827/* checks if someone is following us */ 1198/* checks if we are following this actor */
828{ 1199{
829 xs *fn = _following_fn(snac, actor); 1200 xs *fn = _following_fn(snac, actor);
830 1201
@@ -877,8 +1248,12 @@ d_char *following_list(snac *snac)
877 if (o != NULL) { 1248 if (o != NULL) {
878 char *type = xs_dict_get(o, "type"); 1249 char *type = xs_dict_get(o, "type");
879 1250
880 if (!xs_is_null(type) && strcmp(type, "Accept") == 0) 1251 if (!xs_is_null(type) && strcmp(type, "Accept") == 0) {
881 list = xs_list_append(list, o); 1252 char *actor = xs_dict_get(o, "actor");
1253
1254 if (!xs_is_null(actor))
1255 list = xs_list_append(list, actor);
1256 }
882 } 1257 }
883 } 1258 }
884 } 1259 }
@@ -891,7 +1266,7 @@ d_char *following_list(snac *snac)
891d_char *_muted_fn(snac *snac, char *actor) 1266d_char *_muted_fn(snac *snac, char *actor)
892{ 1267{
893 xs *md5 = xs_md5_hex(actor, strlen(actor)); 1268 xs *md5 = xs_md5_hex(actor, strlen(actor));
894 return xs_fmt("%s/muted/%s.json", snac->basedir, md5); 1269 return xs_fmt("%s/muted/%s", snac->basedir, md5);
895} 1270}
896 1271
897 1272
@@ -930,65 +1305,89 @@ int is_muted(snac *snac, char *actor)
930} 1305}
931 1306
932 1307
933d_char *_actor_fn(snac *snac, char *actor) 1308d_char *_hidden_fn(snac *snac, const char *id)
934/* returns the file name for an actor */
935{ 1309{
936 xs *md5 = xs_md5_hex(actor, strlen(actor)); 1310 xs *md5 = xs_md5_hex(id, strlen(id));
937 return xs_fmt("%s/actors/%s.json", snac->basedir, md5); 1311 return xs_fmt("%s/hidden/%s", snac->basedir, md5);
938} 1312}
939 1313
940 1314
941int actor_add(snac *snac, char *actor, char *msg) 1315void hide(snac *snac, const char *id)
942/* adds an actor */ 1316/* hides a message tree */
943{ 1317{
944 int ret = 201; /* created */ 1318 xs *fn = _hidden_fn(snac, id);
945 xs *fn = _actor_fn(snac, actor);
946 FILE *f; 1319 FILE *f;
947 1320
948 if ((f = fopen(fn, "w")) != NULL) { 1321 if ((f = fopen(fn, "w")) != NULL) {
949 xs *j = xs_json_dumps_pp(msg, 4); 1322 fprintf(f, "%s\n", id);
950
951 fwrite(j, 1, strlen(j), f);
952 fclose(f); 1323 fclose(f);
1324
1325 snac_debug(snac, 2, xs_fmt("hidden %s %s", id, fn));
1326
1327 /* hide all the children */
1328 xs *chld = object_children(id);
1329 char *p, *v;
1330
1331 p = chld;
1332 while (xs_list_iter(&p, &v)) {
1333 xs *co = NULL;
1334
1335 /* resolve to get the id */
1336 if (valid_status(object_get_by_md5(v, &co, NULL))) {
1337 if ((v = xs_dict_get(co, "id")) != NULL)
1338 hide(snac, v);
1339 }
1340 }
953 } 1341 }
954 else 1342}
955 ret = 500;
956 1343
957 snac_debug(snac, 2, xs_fmt("actor_add %s %s", actor, fn));
958 1344
959// object_add(actor, msg); 1345int is_hidden(snac *snac, const char *id)
1346/* check is id is hidden */
1347{
1348 xs *fn = _hidden_fn(snac, id);
960 1349
961 return ret; 1350 return !!(mtime(fn) != 0.0);
1351}
1352
1353
1354int actor_add(snac *snac, const char *actor, d_char *msg)
1355/* adds an actor */
1356{
1357 return object_add_ow(actor, msg);
962} 1358}
963 1359
964 1360
965int actor_get(snac *snac, char *actor, d_char **data) 1361int actor_get(snac *snac, const char *actor, d_char **data)
966/* returns an already downloaded actor */ 1362/* returns an already downloaded actor */
967{ 1363{
968 xs *fn = _actor_fn(snac, actor); 1364 int status = 200;
969 double t; 1365 char *d;
970 double max_time;
971 int status;
972 FILE *f;
973 1366
974 if (strcmp(actor, snac->actor) == 0) { 1367 if (strcmp(actor, snac->actor) == 0) {
1368 /* this actor */
975 if (data) 1369 if (data)
976 *data = msg_actor(snac); 1370 *data = msg_actor(snac);
977 1371
978 return 200; 1372 return status;
979 } 1373 }
980 1374
981 t = mtime(fn); 1375 /* read the object */
1376 if (!valid_status(status = object_get(actor, &d, NULL)))
1377 return status;
982 1378
983 /* no mtime? there is nothing here */ 1379 if (data)
984 if (t == 0.0) 1380 *data = d;
985 return 404; 1381
1382 xs *fn = _object_fn(actor);
1383 double max_time;
986 1384
987 /* maximum time for the actor data to be considered stale */ 1385 /* maximum time for the actor data to be considered stale */
988 max_time = 3600.0 * 36.0; 1386 max_time = 3600.0 * 36.0;
989 1387
990 if (t + max_time < (double) time(NULL)) { 1388 if (mtime(fn) + max_time < (double) time(NULL)) {
991 /* actor data exists but also stinks */ 1389 /* actor data exists but also stinks */
1390 FILE *f;
992 1391
993 if ((f = fopen(fn, "a")) != NULL) { 1392 if ((f = fopen(fn, "a")) != NULL) {
994 /* write a blank at the end to 'touch' the file */ 1393 /* write a blank at the end to 'touch' the file */
@@ -998,22 +1397,6 @@ int actor_get(snac *snac, char *actor, d_char **data)
998 1397
999 status = 205; /* "205: Reset Content" "110: Response Is Stale" */ 1398 status = 205; /* "205: Reset Content" "110: Response Is Stale" */
1000 } 1399 }
1001 else {
1002 /* it's still valid */
1003 status = 200;
1004 }
1005
1006 if (data) {
1007 if ((f = fopen(fn, "r")) != NULL) {
1008 xs *j = xs_readall(f);
1009
1010 fclose(f);
1011
1012 *data = xs_json_loads(j);
1013 }
1014 else
1015 status = 500;
1016 }
1017 1400
1018 return status; 1401 return status;
1019} 1402}
@@ -1033,7 +1416,7 @@ int static_get(snac *snac, const char *id, d_char **data, int *size)
1033 FILE *f; 1416 FILE *f;
1034 int status = 404; 1417 int status = 404;
1035 1418
1036 *size = 0xfffffff; 1419 *size = XS_ALL;
1037 1420
1038 if ((f = fopen(fn, "rb")) != NULL) { 1421 if ((f = fopen(fn, "rb")) != NULL) {
1039 *data = xs_read(f, size); 1422 *data = xs_read(f, size);
@@ -1121,6 +1504,8 @@ d_char *history_list(snac *snac)
1121} 1504}
1122 1505
1123 1506
1507/** the queue **/
1508
1124static int _enqueue_put(char *fn, char *msg) 1509static int _enqueue_put(char *fn, char *msg)
1125/* writes safely to the queue */ 1510/* writes safely to the queue */
1126{ 1511{
@@ -1267,38 +1652,81 @@ d_char *dequeue(snac *snac, char *fn)
1267} 1652}
1268 1653
1269 1654
1655/** the purge **/
1656
1657static void _purge_file(const char *fn, time_t mt)
1658/* purge fn if it's older than days */
1659{
1660 if (mtime(fn) < mt) {
1661 /* older than the minimum time: delete it */
1662 unlink(fn);
1663 srv_debug(1, xs_fmt("purged %s", fn));
1664 }
1665}
1666
1667
1270static void _purge_subdir(snac *snac, const char *subdir, int days) 1668static void _purge_subdir(snac *snac, const char *subdir, int days)
1271/* purges all files in subdir older than days */ 1669/* purges all files in subdir older than days */
1272{ 1670{
1273 if (days) { 1671 if (days) {
1274 time_t mt = time(NULL) - days * 24 * 3600; 1672 time_t mt = time(NULL) - days * 24 * 3600;
1275 xs *spec = xs_fmt("%s/%s/" "*.json", snac->basedir, subdir); 1673 xs *spec = xs_fmt("%s/%s/" "*", snac->basedir, subdir);
1276 xs *list = xs_glob(spec, 0, 0); 1674 xs *list = xs_glob(spec, 0, 0);
1277 char *p, *v; 1675 char *p, *v;
1278 1676
1279 p = list; 1677 p = list;
1280 while (xs_list_iter(&p, &v)) { 1678 while (xs_list_iter(&p, &v))
1281 if (mtime(v) < mt) { 1679 _purge_file(v, mt);
1282 /* older than the minimum time: delete it */ 1680 }
1283 unlink(v); 1681}
1284 snac_debug(snac, 1, xs_fmt("purged %s", v)); 1682
1683
1684void purge_server(void)
1685/* purge global server data */
1686{
1687 int tpd = xs_number_get(xs_dict_get(srv_config, "timeline_purge_days"));
1688 xs *spec = xs_fmt("%s/object/??", srv_basedir);
1689 xs *dirs = xs_glob(spec, 0, 0);
1690 char *p, *v;
1691
1692 time_t mt = time(NULL) - tpd * 24 * 3600;
1693
1694 p = dirs;
1695 while (xs_list_iter(&p, &v)) {
1696 xs *spec2 = xs_fmt("%s/" "*.json", v);
1697 xs *files = xs_glob(spec2, 0, 0);
1698 char *p2, *v2;
1699
1700 p2 = files;
1701 while (xs_list_iter(&p2, &v2)) {
1702 int n_link;
1703
1704 /* old and with no hard links? */
1705 if (mtime_nl(v2, &n_link) < mt && n_link < 2) {
1706 xs *s1 = xs_replace(v2, ".json", "");
1707 xs *l = xs_split(s1, "/");
1708 char *md5 = xs_list_get(l, -1);
1709
1710 object_del_by_md5(md5);
1285 } 1711 }
1286 } 1712 }
1287 } 1713 }
1288} 1714}
1289 1715
1290 1716
1291void purge(snac *snac) 1717void purge_user(snac *snac)
1292/* do the purge */ 1718/* do the purge for this user */
1293{ 1719{
1294 int days; 1720 int days;
1295 1721
1296 days = xs_number_get(xs_dict_get(srv_config, "timeline_purge_days")); 1722 days = xs_number_get(xs_dict_get(srv_config, "timeline_purge_days"));
1297 _purge_subdir(snac, "timeline", days); 1723 _purge_subdir(snac, "timeline", days);
1298 _purge_subdir(snac, "actors", days); 1724 _purge_subdir(snac, "hidden", days);
1725 _purge_subdir(snac, "private", days);
1299 1726
1300 days = xs_number_get(xs_dict_get(srv_config, "local_purge_days")); 1727 days = xs_number_get(xs_dict_get(srv_config, "local_purge_days"));
1301 _purge_subdir(snac, "local", days); 1728 _purge_subdir(snac, "local", days);
1729 _purge_subdir(snac, "public", days);
1302} 1730}
1303 1731
1304 1732
@@ -1312,8 +1740,10 @@ void purge_all(void)
1312 p = list; 1740 p = list;
1313 while (xs_list_iter(&p, &uid)) { 1741 while (xs_list_iter(&p, &uid)) {
1314 if (user_open(&snac, uid)) { 1742 if (user_open(&snac, uid)) {
1315 purge(&snac); 1743 purge_user(&snac);
1316 user_free(&snac); 1744 user_free(&snac);
1317 } 1745 }
1318 } 1746 }
1747
1748 purge_server();
1319} 1749}