diff options
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | RELEASE_NOTES.md | 8 | ||||
| -rw-r--r-- | TODO.md | 8 | ||||
| -rw-r--r-- | activitypub.c | 42 | ||||
| -rw-r--r-- | data.c | 778 | ||||
| -rw-r--r-- | doc/snac.1 | 5 | ||||
| -rw-r--r-- | doc/snac.8 | 26 | ||||
| -rw-r--r-- | html.c | 20 | ||||
| -rw-r--r-- | httpd.c | 2 | ||||
| -rw-r--r-- | main.c | 27 | ||||
| -rw-r--r-- | snac.h | 30 | ||||
| -rw-r--r-- | upgrade.c | 131 | ||||
| -rw-r--r-- | utils.c | 5 | ||||
| -rw-r--r-- | xs.h | 5 | ||||
| -rw-r--r-- | xs_glob.h | 2 | ||||
| -rw-r--r-- | xs_io.h | 2 | ||||
| -rw-r--r-- | xs_regex.h | 4 | ||||
| -rw-r--r-- | xs_set.h | 17 | ||||
| -rw-r--r-- | xs_version.h | 2 |
19 files changed, 888 insertions, 227 deletions
| @@ -7,6 +7,7 @@ A simple, minimalistic ActivityPub instance | |||
| 7 | - Lightweight, minimal dependencies | 7 | - Lightweight, minimal dependencies |
| 8 | - Extensive support of ActivityPub operations, e.g. write public notes, follow users, be followed, reply to the notes of others, admire wonderful content (like or boost), write private messages... | 8 | - Extensive support of ActivityPub operations, e.g. write public notes, follow users, be followed, reply to the notes of others, admire wonderful content (like or boost), write private messages... |
| 9 | - Simple but effective web interface | 9 | - Simple but effective web interface |
| 10 | - Multiuser | ||
| 10 | - Easily-accessed MUTE button to silence morons | 11 | - Easily-accessed MUTE button to silence morons |
| 11 | - Tested interoperability with related software | 12 | - Tested interoperability with related software |
| 12 | - No database needed | 13 | - No database needed |
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a0706c8..1d8a3ea 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md | |||
| @@ -1,5 +1,13 @@ | |||
| 1 | # Release Notes | 1 | # Release Notes |
| 2 | 2 | ||
| 3 | ## 2.13 | ||
| 4 | |||
| 5 | A big disk layout rework, to make it more efficient when timelines get very big. Please take note that you must run `snac upgrade` when you install this version over an already existing one. | ||
| 6 | |||
| 7 | Fixed HTML loose close tag. | ||
| 8 | |||
| 9 | Fixed bug when closing sendmail pipe. | ||
| 10 | |||
| 3 | ## 2.12 | 11 | ## 2.12 |
| 4 | 12 | ||
| 5 | Fixed some bugs triggered when a GET query does not have an `Accept:` header. | 13 | Fixed some bugs triggered when a GET query does not have an `Accept:` header. |
| @@ -2,7 +2,11 @@ | |||
| 2 | 2 | ||
| 3 | ## Open | 3 | ## Open |
| 4 | 4 | ||
| 5 | Dropping on input those messages that have their parent hidden is not a good idea, as children of *these* dropped messages will pass unharmed. | 5 | Add an ?skip=NNN parameter to the admin page, to see older timeline. |
| 6 | |||
| 7 | Add support for Edit + Note. | ||
| 8 | |||
| 9 | Add domain/subdomain flexibility according to https://codeberg.org/grunfink/snac2/issues/3 | ||
| 6 | 10 | ||
| 7 | Add support for uploading the avatar, instead of needing an URL to an image. As a kludgy workaround, you can post something with an attached image, copy the auto-generated URL and use it. You can even delete the post, as attached images are never deleted (I said it was kludgy). | 11 | Add support for uploading the avatar, instead of needing an URL to an image. As a kludgy workaround, you can post something with an attached image, copy the auto-generated URL and use it. You can even delete the post, as attached images are never deleted (I said it was kludgy). |
| 8 | 12 | ||
| @@ -177,3 +181,5 @@ Add a purge timeout also for the local timeline (2022-11-12T08:32:56+0100). | |||
| 177 | Add a switch for sensitive posts (2022-11-16T12:17:50+0100). | 181 | Add a switch for sensitive posts (2022-11-16T12:17:50+0100). |
| 178 | 182 | ||
| 179 | Add an RSS to the local timeline (2022-11-18T11:43:54+0100). | 183 | Add an RSS to the local timeline (2022-11-18T11:43:54+0100). |
| 184 | |||
| 185 | Dropping on input those messages that have their parent hidden is not a good idea, as children of *these* dropped messages will pass unharmed (2022-11-28T11:34:56+0100). | ||
diff --git a/activitypub.c b/activitypub.c index 5f26f73..7f63310 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "xs_openssl.h" | 9 | #include "xs_openssl.h" |
| 10 | #include "xs_regex.h" | 10 | #include "xs_regex.h" |
| 11 | #include "xs_time.h" | 11 | #include "xs_time.h" |
| 12 | #include "xs_set.h" | ||
| 12 | 13 | ||
| 13 | #include "snac.h" | 14 | #include "snac.h" |
| 14 | 15 | ||
| @@ -169,11 +170,13 @@ int send_to_actor(snac *snac, char *actor, char *msg, d_char **payload, int *p_s | |||
| 169 | d_char *recipient_list(snac *snac, char *msg, int expand_public) | 170 | d_char *recipient_list(snac *snac, char *msg, int expand_public) |
| 170 | /* returns the list of recipients for a message */ | 171 | /* returns the list of recipients for a message */ |
| 171 | { | 172 | { |
| 172 | d_char *list = xs_list_new(); | ||
| 173 | char *to = xs_dict_get(msg, "to"); | 173 | char *to = xs_dict_get(msg, "to"); |
| 174 | char *cc = xs_dict_get(msg, "cc"); | 174 | char *cc = xs_dict_get(msg, "cc"); |
| 175 | xs_set rcpts; | ||
| 175 | int n; | 176 | int n; |
| 176 | 177 | ||
| 178 | xs_set_init(&rcpts); | ||
| 179 | |||
| 177 | char *lists[] = { to, cc, NULL }; | 180 | char *lists[] = { to, cc, NULL }; |
| 178 | for (n = 0; lists[n]; n++) { | 181 | for (n = 0; lists[n]; n++) { |
| 179 | char *l = lists[n]; | 182 | char *l = lists[n]; |
| @@ -192,45 +195,41 @@ d_char *recipient_list(snac *snac, char *msg, int expand_public) | |||
| 192 | if (expand_public && strcmp(v, public_address) == 0) { | 195 | if (expand_public && strcmp(v, public_address) == 0) { |
| 193 | /* iterate the followers and add them */ | 196 | /* iterate the followers and add them */ |
| 194 | xs *fwers = follower_list(snac); | 197 | xs *fwers = follower_list(snac); |
| 195 | char *fw; | 198 | char *actor; |
| 196 | 199 | ||
| 197 | char *p = fwers; | 200 | char *p = fwers; |
| 198 | while (xs_list_iter(&p, &fw)) { | 201 | while (xs_list_iter(&p, &actor)) |
| 199 | char *actor = xs_dict_get(fw, "actor"); | 202 | xs_set_add(&rcpts, actor); |
| 200 | |||
| 201 | if (xs_list_in(list, actor) == -1) | ||
| 202 | list = xs_list_append(list, actor); | ||
| 203 | } | ||
| 204 | } | 203 | } |
| 205 | else | 204 | else |
| 206 | if (xs_list_in(list, v) == -1) | 205 | xs_set_add(&rcpts, v); |
| 207 | list = xs_list_append(list, v); | ||
| 208 | } | 206 | } |
| 209 | } | 207 | } |
| 210 | 208 | ||
| 211 | return list; | 209 | return xs_set_result(&rcpts); |
| 212 | } | 210 | } |
| 213 | 211 | ||
| 214 | 212 | ||
| 215 | d_char *inbox_list(snac *snac, char *msg) | 213 | d_char *inbox_list(snac *snac, char *msg) |
| 216 | /* returns the list of inboxes that are recipients of this message */ | 214 | /* returns the list of inboxes that are recipients of this message */ |
| 217 | { | 215 | { |
| 218 | d_char *list = xs_list_new(); | 216 | xs *rcpts = recipient_list(snac, msg, 1); |
| 219 | xs *rcpts = recipient_list(snac, msg, 1); | 217 | xs_set inboxes; |
| 220 | char *p, *v; | 218 | char *p, *v; |
| 221 | 219 | ||
| 220 | xs_set_init(&inboxes); | ||
| 221 | |||
| 222 | p = rcpts; | 222 | p = rcpts; |
| 223 | while (xs_list_iter(&p, &v)) { | 223 | while (xs_list_iter(&p, &v)) { |
| 224 | xs *inbox; | 224 | xs *inbox; |
| 225 | 225 | ||
| 226 | if ((inbox = get_actor_inbox(snac, v)) != NULL) { | 226 | if ((inbox = get_actor_inbox(snac, v)) != NULL) { |
| 227 | /* add the inbox if it's not already there */ | 227 | /* add the inbox if it's not already there */ |
| 228 | if (xs_list_in(list, inbox) == -1) | 228 | xs_set_add(&inboxes, inbox); |
| 229 | list = xs_list_append(list, inbox); | ||
| 230 | } | 229 | } |
| 231 | } | 230 | } |
| 232 | 231 | ||
| 233 | return list; | 232 | return xs_set_result(&inboxes); |
| 234 | } | 233 | } |
| 235 | 234 | ||
| 236 | 235 | ||
| @@ -824,7 +823,7 @@ int process_message(snac *snac, char *msg, char *req) | |||
| 824 | 823 | ||
| 825 | timeline_add(snac, xs_dict_get(f_msg, "id"), f_msg, NULL, NULL); | 824 | timeline_add(snac, xs_dict_get(f_msg, "id"), f_msg, NULL, NULL); |
| 826 | 825 | ||
| 827 | follower_add(snac, actor, f_msg); | 826 | follower_add(snac, actor); |
| 828 | 827 | ||
| 829 | snac_log(snac, xs_fmt("New follower %s", actor)); | 828 | snac_log(snac, xs_fmt("New follower %s", actor)); |
| 830 | do_notify = 1; | 829 | do_notify = 1; |
| @@ -918,6 +917,7 @@ int process_message(snac *snac, char *msg, char *req) | |||
| 918 | if (strcmp(type, "Update") == 0) { | 917 | if (strcmp(type, "Update") == 0) { |
| 919 | if (strcmp(utype, "Person") == 0) { | 918 | if (strcmp(utype, "Person") == 0) { |
| 920 | actor_add(snac, actor, xs_dict_get(msg, "object")); | 919 | actor_add(snac, actor, xs_dict_get(msg, "object")); |
| 920 | |||
| 921 | snac_log(snac, xs_fmt("updated actor %s", actor)); | 921 | snac_log(snac, xs_fmt("updated actor %s", actor)); |
| 922 | } | 922 | } |
| 923 | else | 923 | else |
| @@ -1017,10 +1017,10 @@ void process_queue(snac *snac) | |||
| 1017 | FILE *f; | 1017 | FILE *f; |
| 1018 | int ok = 0; | 1018 | int ok = 0; |
| 1019 | 1019 | ||
| 1020 | f = popen("/usr/sbin/sendmail -t", "w"); | 1020 | if ((f = popen("/usr/sbin/sendmail -t", "w")) != NULL) { |
| 1021 | if (f) { | ||
| 1022 | fprintf(f, "%s\n", msg); | 1021 | fprintf(f, "%s\n", msg); |
| 1023 | if (pclose(f) != EOF) //this is a pipe stream not just a file | 1022 | |
| 1023 | if (pclose(f) != -1) | ||
| 1024 | ok = 1; | 1024 | ok = 1; |
| 1025 | } | 1025 | } |
| 1026 | 1026 | ||
| @@ -1089,7 +1089,7 @@ int activitypub_get_handler(d_char *req, char *q_path, | |||
| 1089 | if (p_path == NULL) { | 1089 | if (p_path == NULL) { |
| 1090 | /* if there was no component after the user, it's an actor request */ | 1090 | /* if there was no component after the user, it's an actor request */ |
| 1091 | msg = msg_actor(&snac); | 1091 | msg = msg_actor(&snac); |
| 1092 | *ctype = "application/ld+json"; | 1092 | *ctype = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""; |
| 1093 | } | 1093 | } |
| 1094 | else | 1094 | else |
| 1095 | if (strcmp(p_path, "outbox") == 0) { | 1095 | if (strcmp(p_path, "outbox") == 0) { |
| @@ -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 | ||
| 17 | double db_layout = 2.1; | 18 | double db_layout = 2.5; |
| 18 | 19 | ||
| 19 | 20 | ||
| 20 | int db_upgrade(d_char **error); | 21 | int db_upgrade(d_char **error); |
| 21 | 22 | ||
| 22 | int srv_open(char *basedir) | 23 | int 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 | ||
| 181 | double mtime(char *fn) | 189 | double 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 | |||
| 212 | int 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 | |||
| 234 | int 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 | |||
| 242 | int 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 | |||
| 280 | int 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 | |||
| 305 | int 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 | |||
| 313 | int 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 | |||
| 334 | d_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 | |||
| 360 | d_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 | ||
| 196 | d_char *_object_fn_by_md5(const char *md5) | 395 | d_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 | ||
| 206 | d_char *_object_fn_by_id(const char *id) | 405 | d_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 | ||
| 214 | int object_get(const char *id, d_char **obj, const char *type) | 412 | int 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 | ||
| 250 | int object_add(const char *id, d_char *obj) | 448 | int 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 | |||
| 456 | int _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 | ||
| 272 | d_char *_follower_fn(snac *snac, char *actor) | 505 | int 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 | ||
| 279 | int follower_add(snac *snac, char *actor, char *msg) | 512 | int 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); | 519 | int 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 | |||
| 547 | int 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 | |||
| 555 | int 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 | ||
| 301 | int follower_del(snac *snac, char *actor) | 569 | d_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 | |||
| 580 | int 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 | ||
| 318 | int follower_check(snac *snac, char *actor) | 598 | int _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 | |||
| 620 | int 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 | |||
| 627 | int 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 | |||
| 634 | int 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 | |||
| 644 | d_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 | |||
| 656 | int 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 | |||
| 667 | int 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 | |||
| 678 | int 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 | ||
| 327 | d_char *follower_list(snac *snac) | 685 | d_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 | |||
| 358 | double timeline_mtime(snac *snac) | 711 | double 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 | ||
| 1000 | void 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 | |||
| 651 | int timeline_add(snac *snac, char *id, char *o_msg, char *parent, char *referrer) | 1010 | int 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 | ||
| 1053 | d_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 | |||
| 690 | void timeline_admire(snac *snac, char *id, char *admirer, int like) | 1097 | void 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 | |||
| 740 | int 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 | ||
| 784 | d_char *_following_fn(snac *snac, char *actor) | 1155 | d_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 | ||
| 813 | int following_del(snac *snac, char *actor) | 1184 | int 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 | ||
| 826 | int following_check(snac *snac, char *actor) | 1197 | int 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) | |||
| 891 | d_char *_muted_fn(snac *snac, char *actor) | 1266 | d_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 | ||
| 933 | d_char *_actor_fn(snac *snac, char *actor) | 1308 | d_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 | ||
| 941 | int actor_add(snac *snac, char *actor, char *msg) | 1315 | void 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); | 1345 | int 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 | |||
| 1354 | int 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 | ||
| 965 | int actor_get(snac *snac, char *actor, d_char **data) | 1361 | int 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 | |||
| 1124 | static int _enqueue_put(char *fn, char *msg) | 1509 | static 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 | |||
| 1657 | static 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 | |||
| 1270 | static void _purge_subdir(snac *snac, const char *subdir, int days) | 1668 | static 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 | |||
| 1684 | void 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 | ||
| 1291 | void purge(snac *snac) | 1717 | void 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 | } |
| @@ -145,6 +145,11 @@ Initializes the database. This is an interactive command; necessary | |||
| 145 | information will be prompted for. The | 145 | information will be prompted for. The |
| 146 | .Ar basedir | 146 | .Ar basedir |
| 147 | directory must not exist. | 147 | directory must not exist. |
| 148 | .It Cm upgrade Ar basedir | ||
| 149 | Upgrades the database disk layout after installing a new version. | ||
| 150 | Only necessary if | ||
| 151 | .Nm | ||
| 152 | complains and demands it. | ||
| 148 | .It Cm purge Ar basedir | 153 | .It Cm purge Ar basedir |
| 149 | Purges old data from the timeline of all users. | 154 | Purges old data from the timeline of all users. |
| 150 | .It Cm adduser Ar basedir Op uid | 155 | .It Cm adduser Ar basedir Op uid |
| @@ -14,6 +14,19 @@ This is the admin manual. For user operation, see | |||
| 14 | .Xr snac 1 . | 14 | .Xr snac 1 . |
| 15 | For file and data formats, see | 15 | For file and data formats, see |
| 16 | .Xr snac 5 . | 16 | .Xr snac 5 . |
| 17 | .Ss Special cares about your snac you must know beforehand | ||
| 18 | .Nm | ||
| 19 | makes heavy use of hard links and link reference counts for its work, so | ||
| 20 | don't even think of using it on a filesystem that doesn't support this | ||
| 21 | feature. Most UNIX-like operating systems (Linux, the BSDs, the old DEC | ||
| 22 | Ultrix machine in your grandfather basement, probably MacOS) support hard | ||
| 23 | links on their native filesystems. Don't do fancy things like moving the | ||
| 24 | subdirectories to different filesystems. Also, if you move your | ||
| 25 | .Nm | ||
| 26 | installation to another server, do it with a tool that respect hard | ||
| 27 | link counts. Remember: | ||
| 28 | .Nm | ||
| 29 | is a very UNIXy program that loves hard links. | ||
| 17 | .Ss Building and Installation | 30 | .Ss Building and Installation |
| 18 | A C compiler must be installed in the system, as well as the development | 31 | A C compiler must be installed in the system, as well as the development |
| 19 | headers and libraries for OpenSSL and curl. To build | 32 | headers and libraries for OpenSSL and curl. To build |
| @@ -69,6 +82,19 @@ startup scripts and configuration data in the | |||
| 69 | directory. | 82 | directory. |
| 70 | For other operating systems, please read the appropriate documentation | 83 | For other operating systems, please read the appropriate documentation |
| 71 | on how to install a daemon as a non-root service. | 84 | on how to install a daemon as a non-root service. |
| 85 | .Ss Upgrading to a new version | ||
| 86 | Sometimes, the database disk layout changes between versions. If there | ||
| 87 | is such a change, | ||
| 88 | .Nm | ||
| 89 | will refuse to run and require an upgrade. Do this by running | ||
| 90 | .Bd -literal -offset indent | ||
| 91 | snac upgrade $HOME/snac-data | ||
| 92 | .Ed | ||
| 93 | Take special care to execute this upgrade operation without any | ||
| 94 | .Nm | ||
| 95 | processes serving on the same folder. You can break everything. I know | ||
| 96 | this because Tyler knows this. | ||
| 97 | .Pp | ||
| 72 | .Ss Server Setup | 98 | .Ss Server Setup |
| 73 | .Pp | 99 | .Pp |
| 74 | An http server with TLS and proxying support must already be | 100 | An http server with TLS and proxying support must already be |
| @@ -196,14 +196,14 @@ d_char *html_user_header(snac *snac, d_char *s, int local) | |||
| 196 | "<a href=\"%s.rss\">%s</a> - " | 196 | "<a href=\"%s.rss\">%s</a> - " |
| 197 | "<a href=\"%s/admin\" rel=\"nofollow\">%s</a></nav>\n", | 197 | "<a href=\"%s/admin\" rel=\"nofollow\">%s</a></nav>\n", |
| 198 | snac->actor, L("RSS"), | 198 | snac->actor, L("RSS"), |
| 199 | snac->actor, L("admin")); | 199 | snac->actor, L("private")); |
| 200 | else | 200 | else |
| 201 | s1 = xs_fmt( | 201 | s1 = xs_fmt( |
| 202 | "<a href=\"%s\">%s</a> - " | 202 | "<a href=\"%s\">%s</a> - " |
| 203 | "<a href=\"%s/admin\">%s</a> - " | 203 | "<a href=\"%s/admin\">%s</a> - " |
| 204 | "<a href=\"%s/people\">%s</a></nav>\n", | 204 | "<a href=\"%s/people\">%s</a></nav>\n", |
| 205 | snac->actor, L("public"), | 205 | snac->actor, L("public"), |
| 206 | snac->actor, L("admin"), | 206 | snac->actor, L("private"), |
| 207 | snac->actor, L("people")); | 207 | snac->actor, L("people")); |
| 208 | 208 | ||
| 209 | s = xs_str_cat(s, s1); | 209 | s = xs_str_cat(s, s1); |
| @@ -497,7 +497,7 @@ d_char *html_entry(snac *snac, d_char *os, char *msg, xs_set *seen, int local, i | |||
| 497 | xs *s = xs_str_new(NULL); | 497 | xs *s = xs_str_new(NULL); |
| 498 | 498 | ||
| 499 | /* top wrap */ | 499 | /* top wrap */ |
| 500 | if ((v = xs_dict_get(meta, "hidden")) && xs_type(v) == XSTYPE_TRUE) | 500 | if (is_hidden(snac, id)) |
| 501 | s = xs_str_cat(s, "<div style=\"display: none\">\n"); | 501 | s = xs_str_cat(s, "<div style=\"display: none\">\n"); |
| 502 | else | 502 | else |
| 503 | s = xs_str_cat(s, "<div>\n"); | 503 | s = xs_str_cat(s, "<div>\n"); |
| @@ -840,13 +840,12 @@ d_char *html_people_list(snac *snac, d_char *os, d_char *list, const char *heade | |||
| 840 | { | 840 | { |
| 841 | xs *s = xs_str_new(NULL); | 841 | xs *s = xs_str_new(NULL); |
| 842 | xs *h = xs_fmt("<h2>%s</h2>\n", header); | 842 | xs *h = xs_fmt("<h2>%s</h2>\n", header); |
| 843 | char *p, *v; | 843 | char *p, *actor_id; |
| 844 | 844 | ||
| 845 | s = xs_str_cat(s, h); | 845 | s = xs_str_cat(s, h); |
| 846 | 846 | ||
| 847 | p = list; | 847 | p = list; |
| 848 | while (xs_list_iter(&p, &v)) { | 848 | while (xs_list_iter(&p, &actor_id)) { |
| 849 | char *actor_id = xs_dict_get(v, "actor"); | ||
| 850 | xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); | 849 | xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); |
| 851 | xs *actor = NULL; | 850 | xs *actor = NULL; |
| 852 | 851 | ||
| @@ -1008,7 +1007,7 @@ int html_get_handler(d_char *req, char *q_path, char **body, int *b_size, char * | |||
| 1008 | status = 200; | 1007 | status = 200; |
| 1009 | } | 1008 | } |
| 1010 | else { | 1009 | else { |
| 1011 | xs *list = local_list(&snac, 0xfffffff); | 1010 | xs *list = local_list(&snac, XS_ALL); |
| 1012 | 1011 | ||
| 1013 | *body = html_timeline(&snac, list, 1); | 1012 | *body = html_timeline(&snac, list, 1); |
| 1014 | *b_size = strlen(*body); | 1013 | *b_size = strlen(*body); |
| @@ -1034,7 +1033,7 @@ int html_get_handler(d_char *req, char *q_path, char **body, int *b_size, char * | |||
| 1034 | else { | 1033 | else { |
| 1035 | snac_debug(&snac, 1, xs_fmt("building timeline")); | 1034 | snac_debug(&snac, 1, xs_fmt("building timeline")); |
| 1036 | 1035 | ||
| 1037 | xs *list = timeline_list(&snac, 0xfffffff); | 1036 | xs *list = timeline_list(&snac, XS_ALL); |
| 1038 | 1037 | ||
| 1039 | *body = html_timeline(&snac, list, 0); | 1038 | *body = html_timeline(&snac, list, 0); |
| 1040 | *b_size = strlen(*body); | 1039 | *b_size = strlen(*body); |
| @@ -1300,7 +1299,7 @@ int html_post_handler(d_char *req, char *q_path, d_char *payload, int p_size, | |||
| 1300 | } | 1299 | } |
| 1301 | else | 1300 | else |
| 1302 | if (strcmp(action, L("Hide")) == 0) { | 1301 | if (strcmp(action, L("Hide")) == 0) { |
| 1303 | timeline_hide(&snac, id, 1); | 1302 | hide(&snac, id); |
| 1304 | } | 1303 | } |
| 1305 | else | 1304 | else |
| 1306 | if (strcmp(action, L("Follow")) == 0) { | 1305 | if (strcmp(action, L("Follow")) == 0) { |
| @@ -1341,6 +1340,9 @@ int html_post_handler(d_char *req, char *q_path, d_char *payload, int p_size, | |||
| 1341 | 1340 | ||
| 1342 | post(&snac, msg); | 1341 | post(&snac, msg); |
| 1343 | 1342 | ||
| 1343 | /* FIXME: also post this Tombstone to people | ||
| 1344 | that Announce'd it */ | ||
| 1345 | |||
| 1344 | snac_log(&snac, xs_fmt("posted tombstone for %s", id)); | 1346 | snac_log(&snac, xs_fmt("posted tombstone for %s", id)); |
| 1345 | } | 1347 | } |
| 1346 | 1348 | ||
| @@ -222,7 +222,7 @@ static void *queue_thread(void *arg) | |||
| 222 | time_t purge_time; | 222 | time_t purge_time; |
| 223 | 223 | ||
| 224 | /* first purge time */ | 224 | /* first purge time */ |
| 225 | purge_time = time(NULL) + 15 * 60; | 225 | purge_time = time(NULL) + 10 * 60; |
| 226 | 226 | ||
| 227 | srv_log(xs_fmt("queue thread start")); | 227 | srv_log(xs_fmt("queue thread start")); |
| 228 | 228 | ||
| @@ -16,6 +16,7 @@ int usage(void) | |||
| 16 | printf("Commands:\n"); | 16 | printf("Commands:\n"); |
| 17 | printf("\n"); | 17 | printf("\n"); |
| 18 | printf("init [{basedir}] Initializes the database\n"); | 18 | printf("init [{basedir}] Initializes the database\n"); |
| 19 | printf("upgrade {basedir} Upgrade to a new version\n"); | ||
| 19 | printf("adduser {basedir} [{uid}] Adds a new user\n"); | 20 | printf("adduser {basedir} [{uid}] Adds a new user\n"); |
| 20 | printf("httpd {basedir} Starts the HTTPD daemon\n"); | 21 | printf("httpd {basedir} Starts the HTTPD daemon\n"); |
| 21 | printf("purge {basedir} Purges old data\n"); | 22 | printf("purge {basedir} Purges old data\n"); |
| @@ -76,6 +77,19 @@ int main(int argc, char *argv[]) | |||
| 76 | return initdb(basedir); | 77 | return initdb(basedir); |
| 77 | } | 78 | } |
| 78 | 79 | ||
| 80 | if (strcmp(cmd, "upgrade") == 0) { | ||
| 81 | int ret; | ||
| 82 | |||
| 83 | /* database upgrade */ | ||
| 84 | if ((basedir = GET_ARGV()) == NULL) | ||
| 85 | return usage(); | ||
| 86 | |||
| 87 | if ((ret = srv_open(basedir, 1)) == 1) | ||
| 88 | srv_log(xs_dup("OK")); | ||
| 89 | |||
| 90 | return ret; | ||
| 91 | } | ||
| 92 | |||
| 79 | if (strcmp(cmd, "markdown") == 0) { | 93 | if (strcmp(cmd, "markdown") == 0) { |
| 80 | /* undocumented, for testing only */ | 94 | /* undocumented, for testing only */ |
| 81 | xs *c = xs_readall(stdin); | 95 | xs *c = xs_readall(stdin); |
| @@ -88,7 +102,7 @@ int main(int argc, char *argv[]) | |||
| 88 | if ((basedir = GET_ARGV()) == NULL) | 102 | if ((basedir = GET_ARGV()) == NULL) |
| 89 | return usage(); | 103 | return usage(); |
| 90 | 104 | ||
| 91 | if (!srv_open(basedir)) { | 105 | if (!srv_open(basedir, 0)) { |
| 92 | srv_log(xs_fmt("error opening database at %s", basedir)); | 106 | srv_log(xs_fmt("error opening database at %s", basedir)); |
| 93 | return 1; | 107 | return 1; |
| 94 | } | 108 | } |
| @@ -142,12 +156,21 @@ int main(int argc, char *argv[]) | |||
| 142 | } | 156 | } |
| 143 | 157 | ||
| 144 | if (strcmp(cmd, "timeline") == 0) { | 158 | if (strcmp(cmd, "timeline") == 0) { |
| 145 | xs *list = local_list(&snac, 0xfffffff); | 159 | #if 0 |
| 160 | xs *list = local_list(&snac, XS_ALL); | ||
| 146 | xs *body = html_timeline(&snac, list, 1); | 161 | xs *body = html_timeline(&snac, list, 1); |
| 147 | 162 | ||
| 148 | printf("%s\n", body); | 163 | printf("%s\n", body); |
| 149 | user_free(&snac); | 164 | user_free(&snac); |
| 150 | srv_free(); | 165 | srv_free(); |
| 166 | #endif | ||
| 167 | |||
| 168 | xs *idx = xs_fmt("%s/private.idx", snac.basedir); | ||
| 169 | xs *list = index_list_desc(idx, 256); | ||
| 170 | xs *tl = timeline_top_level(&snac, list); | ||
| 171 | |||
| 172 | xs *j = xs_json_dumps_pp(tl, 4); | ||
| 173 | printf("%s\n", j); | ||
| 151 | 174 | ||
| 152 | return 0; | 175 | return 0; |
| 153 | } | 176 | } |
| @@ -24,7 +24,7 @@ double ftime(void); | |||
| 24 | void srv_debug(int level, d_char *str); | 24 | void srv_debug(int level, d_char *str); |
| 25 | #define srv_log(str) srv_debug(0, str) | 25 | #define srv_log(str) srv_debug(0, str) |
| 26 | 26 | ||
| 27 | int srv_open(char *basedir); | 27 | int srv_open(char *basedir, int auto_upgrade); |
| 28 | void srv_free(void); | 28 | void srv_free(void); |
| 29 | 29 | ||
| 30 | typedef struct _snac { | 30 | typedef struct _snac { |
| @@ -50,11 +50,21 @@ int check_password(char *uid, char *passwd, char *hash); | |||
| 50 | void srv_archive(char *direction, char *req, char *payload, int p_size, | 50 | void srv_archive(char *direction, char *req, char *payload, int p_size, |
| 51 | int status, char *headers, char *body, int b_size); | 51 | int status, char *headers, char *body, int b_size); |
| 52 | 52 | ||
| 53 | double mtime(char *fn); | 53 | double mtime_nl(const char *fn, int *n_link); |
| 54 | #define mtime(fn) mtime_nl(fn, NULL) | ||
| 54 | 55 | ||
| 55 | int follower_add(snac *snac, char *actor, char *msg); | 56 | int index_add(const char *fn, const char *md5); |
| 56 | int follower_del(snac *snac, char *actor); | 57 | int index_del(const char *fn, const char *md5); |
| 57 | int follower_check(snac *snac, char *actor); | 58 | int index_first(const char *fn, char *buf, int size); |
| 59 | d_char *index_list(const char *fn, int max); | ||
| 60 | d_char *index_list_desc(const char *fn, int max); | ||
| 61 | |||
| 62 | int object_del(const char *id); | ||
| 63 | int object_del_if_unref(const char *id); | ||
| 64 | |||
| 65 | int follower_add(snac *snac, const char *actor); | ||
| 66 | int follower_del(snac *snac, const char *actor); | ||
| 67 | int follower_check(snac *snac, const char *actor); | ||
| 58 | d_char *follower_list(snac *snac); | 68 | d_char *follower_list(snac *snac); |
| 59 | 69 | ||
| 60 | double timeline_mtime(snac *snac); | 70 | double timeline_mtime(snac *snac); |
| @@ -66,7 +76,8 @@ d_char *timeline_get(snac *snac, char *fn); | |||
| 66 | d_char *timeline_list(snac *snac, int max); | 76 | d_char *timeline_list(snac *snac, int max); |
| 67 | int timeline_add(snac *snac, char *id, char *o_msg, char *parent, char *referrer); | 77 | int timeline_add(snac *snac, char *id, char *o_msg, char *parent, char *referrer); |
| 68 | void timeline_admire(snac *snac, char *id, char *admirer, int like); | 78 | void timeline_admire(snac *snac, char *id, char *admirer, int like); |
| 69 | int timeline_hide(snac *snac, char *id, int hide); | 79 | |
| 80 | d_char *timeline_top_level(snac *snac, d_char *list); | ||
| 70 | 81 | ||
| 71 | d_char *local_list(snac *snac, int max); | 82 | d_char *local_list(snac *snac, int max); |
| 72 | 83 | ||
| @@ -80,8 +91,11 @@ void mute(snac *snac, char *actor); | |||
| 80 | void unmute(snac *snac, char *actor); | 91 | void unmute(snac *snac, char *actor); |
| 81 | int is_muted(snac *snac, char *actor); | 92 | int is_muted(snac *snac, char *actor); |
| 82 | 93 | ||
| 83 | int actor_add(snac *snac, char *actor, char *msg); | 94 | void hide(snac *snac, const char *id); |
| 84 | int actor_get(snac *snac, char *actor, d_char **data); | 95 | int is_hidden(snac *snac, const char *id); |
| 96 | |||
| 97 | int actor_add(snac *snac, const char *actor, d_char *msg); | ||
| 98 | int actor_get(snac *snac, const char *actor, d_char **data); | ||
| 85 | 99 | ||
| 86 | int static_get(snac *snac, const char *id, d_char **data, int *size); | 100 | int static_get(snac *snac, const char *id, d_char **data, int *size); |
| 87 | void static_put(snac *snac, const char *id, const char *data, int size); | 101 | void static_put(snac *snac, const char *id, const char *data, int size); |
| @@ -40,6 +40,137 @@ int db_upgrade(d_char **error) | |||
| 40 | 40 | ||
| 41 | nf = 2.1; | 41 | nf = 2.1; |
| 42 | } | 42 | } |
| 43 | else | ||
| 44 | if (f < 2.2) { | ||
| 45 | xs *users = user_list(); | ||
| 46 | char *p, *v; | ||
| 47 | |||
| 48 | p = users; | ||
| 49 | while (xs_list_iter(&p, &v)) { | ||
| 50 | snac snac; | ||
| 51 | |||
| 52 | if (user_open(&snac, v)) { | ||
| 53 | xs *spec = xs_fmt("%s/actors/" "*.json", snac.basedir); | ||
| 54 | xs *list = xs_glob(spec, 0, 0); | ||
| 55 | char *g, *fn; | ||
| 56 | |||
| 57 | g = list; | ||
| 58 | while (xs_list_iter(&g, &fn)) { | ||
| 59 | xs *l = xs_split(fn, "/"); | ||
| 60 | char *b = xs_list_get(l, -1); | ||
| 61 | xs *dir = xs_fmt("%s/object/%c%c", srv_basedir, b[0], b[1]); | ||
| 62 | xs *nfn = xs_fmt("%s/%s", dir, b); | ||
| 63 | |||
| 64 | mkdir(dir, 0755); | ||
| 65 | rename(fn, nfn); | ||
| 66 | } | ||
| 67 | |||
| 68 | xs *odir = xs_fmt("%s/actors", snac.basedir); | ||
| 69 | rmdir(odir); | ||
| 70 | |||
| 71 | user_free(&snac); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | nf = 2.2; | ||
| 76 | } | ||
| 77 | else | ||
| 78 | if (f < 2.3) { | ||
| 79 | xs *users = user_list(); | ||
| 80 | char *p, *v; | ||
| 81 | |||
| 82 | p = users; | ||
| 83 | while (xs_list_iter(&p, &v)) { | ||
| 84 | snac snac; | ||
| 85 | |||
| 86 | if (user_open(&snac, v)) { | ||
| 87 | char *p, *v; | ||
| 88 | xs *dir = xs_fmt("%s/hidden", snac.basedir); | ||
| 89 | |||
| 90 | /* create the hidden directory */ | ||
| 91 | mkdir(dir, 0755); | ||
| 92 | |||
| 93 | /* rename all muted files incorrectly named .json */ | ||
| 94 | xs *spec = xs_fmt("%s/muted/" "*.json", snac.basedir); | ||
| 95 | xs *fns = xs_glob(spec, 0, 0); | ||
| 96 | |||
| 97 | p = fns; | ||
| 98 | while (xs_list_iter(&p, &v)) { | ||
| 99 | xs *nfn = xs_replace(v, ".json", ""); | ||
| 100 | rename(v, nfn); | ||
| 101 | } | ||
| 102 | |||
| 103 | user_free(&snac); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | nf = 2.3; | ||
| 108 | } | ||
| 109 | else | ||
| 110 | if (f < 2.4) { | ||
| 111 | xs *users = user_list(); | ||
| 112 | char *p, *v; | ||
| 113 | |||
| 114 | p = users; | ||
| 115 | while (xs_list_iter(&p, &v)) { | ||
| 116 | snac snac; | ||
| 117 | |||
| 118 | if (user_open(&snac, v)) { | ||
| 119 | xs *dir = xs_fmt("%s/public", snac.basedir); | ||
| 120 | mkdir(dir, 0755); | ||
| 121 | |||
| 122 | dir = xs_replace_i(dir, "public", "private"); | ||
| 123 | mkdir(dir, 0755); | ||
| 124 | |||
| 125 | user_free(&snac); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | nf = 2.4; | ||
| 130 | } | ||
| 131 | else | ||
| 132 | if (f < 2.5) { | ||
| 133 | /* upgrade followers */ | ||
| 134 | xs *users = user_list(); | ||
| 135 | char *p, *v; | ||
| 136 | |||
| 137 | p = users; | ||
| 138 | while (xs_list_iter(&p, &v)) { | ||
| 139 | snac snac; | ||
| 140 | |||
| 141 | if (user_open(&snac, v)) { | ||
| 142 | xs *spec = xs_fmt("%s/followers/" "*.json", snac.basedir); | ||
| 143 | xs *dir = xs_glob(spec, 0, 0); | ||
| 144 | char *p, *v; | ||
| 145 | |||
| 146 | p = dir; | ||
| 147 | while (xs_list_iter(&p, &v)) { | ||
| 148 | FILE *f; | ||
| 149 | |||
| 150 | if ((f = fopen(v, "r")) != NULL) { | ||
| 151 | xs *s = xs_readall(f); | ||
| 152 | xs *o = xs_json_loads(s); | ||
| 153 | fclose(f); | ||
| 154 | |||
| 155 | char *type = xs_dict_get(o, "type"); | ||
| 156 | |||
| 157 | if (!xs_is_null(type) && strcmp(type, "Follow") == 0) { | ||
| 158 | unlink(v); | ||
| 159 | |||
| 160 | char *actor = xs_dict_get(o, "actor"); | ||
| 161 | |||
| 162 | if (!xs_is_null(actor)) | ||
| 163 | follower_add(&snac, actor); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | user_free(&snac); | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | nf = 2.5; | ||
| 173 | } | ||
| 43 | 174 | ||
| 44 | if (f < nf) { | 175 | if (f < nf) { |
| 45 | f = nf; | 176 | f = nf; |
| @@ -237,8 +237,9 @@ int adduser(char *uid) | |||
| 237 | } | 237 | } |
| 238 | 238 | ||
| 239 | const char *dirs[] = { | 239 | const char *dirs[] = { |
| 240 | "actors", "followers", "following", "local", "muted", | 240 | "followers", "following", "local", "muted", "hidden", |
| 241 | "queue", "static", "timeline", "history", NULL }; | 241 | "public", "private", "queue", "history", |
| 242 | "static", "timeline", NULL }; | ||
| 242 | int n; | 243 | int n; |
| 243 | 244 | ||
| 244 | for (n = 0; dirs[n]; n++) { | 245 | for (n = 0; dirs[n]; n++) { |
| @@ -34,6 +34,9 @@ typedef char d_char; | |||
| 34 | /* auto-destroyable strings */ | 34 | /* auto-destroyable strings */ |
| 35 | #define xs __attribute__ ((__cleanup__ (_xs_destroy))) d_char | 35 | #define xs __attribute__ ((__cleanup__ (_xs_destroy))) d_char |
| 36 | 36 | ||
| 37 | /* not really all, just very much */ | ||
| 38 | #define XS_ALL 0xfffffff | ||
| 39 | |||
| 37 | void *xs_free(void *ptr); | 40 | void *xs_free(void *ptr); |
| 38 | void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func); | 41 | void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func); |
| 39 | #define xs_realloc(ptr, size) _xs_realloc(ptr, size, __FILE__, __LINE__, __FUNCTION__) | 42 | #define xs_realloc(ptr, size) _xs_realloc(ptr, size, __FILE__, __LINE__, __FUNCTION__) |
| @@ -74,7 +77,7 @@ d_char *xs_list_pop(d_char *list, char **data); | |||
| 74 | int xs_list_in(char *list, const char *val); | 77 | int xs_list_in(char *list, const char *val); |
| 75 | d_char *xs_join(char *list, const char *sep); | 78 | d_char *xs_join(char *list, const char *sep); |
| 76 | d_char *xs_split_n(const char *str, const char *sep, int times); | 79 | d_char *xs_split_n(const char *str, const char *sep, int times); |
| 77 | #define xs_split(str, sep) xs_split_n(str, sep, 0xfffffff) | 80 | #define xs_split(str, sep) xs_split_n(str, sep, XS_ALL) |
| 78 | d_char *xs_dict_new(void); | 81 | d_char *xs_dict_new(void); |
| 79 | d_char *xs_dict_append_m(d_char *dict, const char *key, const char *mem, int dsz); | 82 | d_char *xs_dict_append_m(d_char *dict, const char *key, const char *mem, int dsz); |
| 80 | #define xs_dict_append(dict, key, data) xs_dict_append_m(dict, key, data, xs_size(data)) | 83 | #define xs_dict_append(dict, key, data) xs_dict_append_m(dict, key, data, xs_size(data)) |
| @@ -5,7 +5,7 @@ | |||
| 5 | #define _XS_GLOB_H | 5 | #define _XS_GLOB_H |
| 6 | 6 | ||
| 7 | d_char *xs_glob_n(const char *spec, int basename, int reverse, int max); | 7 | d_char *xs_glob_n(const char *spec, int basename, int reverse, int max); |
| 8 | #define xs_glob(spec, basename, reverse) xs_glob_n(spec, basename, reverse, 0xfffffff) | 8 | #define xs_glob(spec, basename, reverse) xs_glob_n(spec, basename, reverse, XS_ALL) |
| 9 | 9 | ||
| 10 | 10 | ||
| 11 | #ifdef XS_IMPLEMENTATION | 11 | #ifdef XS_IMPLEMENTATION |
| @@ -79,7 +79,7 @@ d_char *xs_read(FILE *f, int *sz) | |||
| 79 | d_char *xs_readall(FILE *f) | 79 | d_char *xs_readall(FILE *f) |
| 80 | /* reads the rest of the file into a string */ | 80 | /* reads the rest of the file into a string */ |
| 81 | { | 81 | { |
| 82 | int size = 0xfffffff; | 82 | int size = XS_ALL; |
| 83 | 83 | ||
| 84 | return xs_read(f, &size); | 84 | return xs_read(f, &size); |
| 85 | } | 85 | } |
| @@ -5,9 +5,9 @@ | |||
| 5 | #define _XS_REGEX_H | 5 | #define _XS_REGEX_H |
| 6 | 6 | ||
| 7 | d_char *xs_regex_split_n(const char *str, const char *rx, int count); | 7 | d_char *xs_regex_split_n(const char *str, const char *rx, int count); |
| 8 | #define xs_regex_split(str, rx) xs_regex_split_n(str, rx, 0xfffffff) | 8 | #define xs_regex_split(str, rx) xs_regex_split_n(str, rx, XS_ALL) |
| 9 | d_char *xs_regex_match_n(const char *str, const char *rx, int count); | 9 | d_char *xs_regex_match_n(const char *str, const char *rx, int count); |
| 10 | #define xs_regex_match(str, rx) xs_regex_match_n(str, rx, 0xfffffff) | 10 | #define xs_regex_match(str, rx) xs_regex_match_n(str, rx, XS_ALL) |
| 11 | 11 | ||
| 12 | #ifdef XS_IMPLEMENTATION | 12 | #ifdef XS_IMPLEMENTATION |
| 13 | 13 | ||
| @@ -12,6 +12,7 @@ typedef struct _xs_set { | |||
| 12 | } xs_set; | 12 | } xs_set; |
| 13 | 13 | ||
| 14 | void xs_set_init(xs_set *s); | 14 | void xs_set_init(xs_set *s); |
| 15 | d_char *xs_set_result(xs_set *s); | ||
| 15 | void xs_set_free(xs_set *s); | 16 | void xs_set_free(xs_set *s); |
| 16 | int xs_set_add(xs_set *s, const char *data); | 17 | int xs_set_add(xs_set *s, const char *data); |
| 17 | 18 | ||
| @@ -32,11 +33,21 @@ void xs_set_init(xs_set *s) | |||
| 32 | } | 33 | } |
| 33 | 34 | ||
| 34 | 35 | ||
| 35 | void xs_set_free(xs_set *s) | 36 | d_char *xs_set_result(xs_set *s) |
| 36 | /* frees a set */ | 37 | /* returns the set as a list and frees it */ |
| 37 | { | 38 | { |
| 39 | d_char *list = s->list; | ||
| 40 | s->list = NULL; | ||
| 38 | s->hash = xs_free(s->hash); | 41 | s->hash = xs_free(s->hash); |
| 39 | s->list = xs_free(s->list); | 42 | |
| 43 | return list; | ||
| 44 | } | ||
| 45 | |||
| 46 | |||
| 47 | void xs_set_free(xs_set *s) | ||
| 48 | /* frees a set, dropping the list */ | ||
| 49 | { | ||
| 50 | free(xs_set_result(s)); | ||
| 40 | } | 51 | } |
| 41 | 52 | ||
| 42 | 53 | ||
diff --git a/xs_version.h b/xs_version.h index ac5d43f..baefcba 100644 --- a/xs_version.h +++ b/xs_version.h | |||
| @@ -1 +1 @@ | |||
| /* a78beb97d364ff31cbaa504e275118afeaea7a59 */ | /* c18371e1f1d3de0f872354f93024a736caebea4d */ | ||