2.70
upgrade.c
1/* snac - A simple, minimalistic ActivityPub instance */
2/* copyright (c) 2022 - 2025 grunfink et al. / MIT license */
3
4#include "xs.h"
5#include "xs_io.h"
6#include "xs_json.h"
7#include "xs_glob.h"
8
9#include "snac.h"
10
11#include <sys/stat.h>
12
13
14int snac_upgrade(xs_str **error)
15{
16 int ret = 1;
17 int changed = 0;
18 double f = 0.0;
19
20 for (;;) {
21 const char *layout = xs_dict_get(srv_config, "layout");
22 double nf;
23
24 f = nf = xs_number_get(layout);
25
26 if (!(f < disk_layout))
27 break;
28
29 srv_log(xs_fmt("disk layout upgrade needed (%1.1lf < %1.1lf)", f, disk_layout));
30
31 if (f < 2.0) {
32 *error = xs_fmt("ERROR: unsupported old disk layout %1.1lf\n", f);
33 ret = 0;
34 break;
35 }
36 else
37 if (f < 2.1) {
38 xs *dir = xs_fmt("%s/object", srv_basedir);
39 mkdirx(dir);
40
41 nf = 2.1;
42 }
43 else
44 if (f < 2.2) {
45 xs *users = user_list();
46 char *p;
47 const char *v;
48
49 p = users;
50 while (xs_list_iter(&p, &v)) {
51 snac snac;
52
53 if (user_open(&snac, v)) {
54 xs *spec = xs_fmt("%s/actors/" "*.json", snac.basedir);
55 xs *list = xs_glob(spec, 0, 0);
56 char *g;
57 const char *fn;
58
59 g = list;
60 while (xs_list_iter(&g, &fn)) {
61 xs *l = xs_split(fn, "/");
62 const char *b = xs_list_get(l, -1);
63 xs *dir = xs_fmt("%s/object/%c%c", srv_basedir, b[0], b[1]);
64 xs *nfn = xs_fmt("%s/%s", dir, b);
65
66 mkdirx(dir);
67 rename(fn, nfn);
68 }
69
70 xs *odir = xs_fmt("%s/actors", snac.basedir);
71 rmdir(odir);
72
73 user_free(&snac);
74 }
75 }
76
77 nf = 2.2;
78 }
79 else
80 if (f < 2.3) {
81 xs *users = user_list();
82 char *p;
83 const char *v;
84
85 p = users;
86 while (xs_list_iter(&p, &v)) {
87 snac snac;
88
89 if (user_open(&snac, v)) {
90 char *p;
91 const char *v;
92 xs *dir = xs_fmt("%s/hidden", snac.basedir);
93
94 /* create the hidden directory */
95 mkdirx(dir);
96
97 /* rename all muted files incorrectly named .json */
98 xs *spec = xs_fmt("%s/muted/" "*.json", snac.basedir);
99 xs *fns = xs_glob(spec, 0, 0);
100
101 p = fns;
102 while (xs_list_iter(&p, &v)) {
103 xs *nfn = xs_replace(v, ".json", "");
104 rename(v, nfn);
105 }
106
107 user_free(&snac);
108 }
109 }
110
111 nf = 2.3;
112 }
113 else
114 if (f < 2.4) {
115 xs *users = user_list();
116 char *p;
117 const char *v;
118
119 p = users;
120 while (xs_list_iter(&p, &v)) {
121 snac snac;
122
123 if (user_open(&snac, v)) {
124 xs *dir = xs_fmt("%s/public", snac.basedir);
125 mkdirx(dir);
126
127 dir = xs_replace_i(dir, "public", "private");
128 mkdirx(dir);
129
130 user_free(&snac);
131 }
132 }
133
134 nf = 2.4;
135 }
136 else
137 if (f < 2.5) {
138 /* upgrade followers */
139 xs *users = user_list();
140 char *p;
141 const char *v;
142
143 p = users;
144 while (xs_list_iter(&p, &v)) {
145 snac snac;
146
147 if (user_open(&snac, v)) {
148 xs *spec = xs_fmt("%s/followers/" "*.json", snac.basedir);
149 xs *dir = xs_glob(spec, 0, 0);
150 char *p;
151 const char *v;
152
153 p = dir;
154 while (xs_list_iter(&p, &v)) {
155 FILE *f;
156
157 if ((f = fopen(v, "r")) != NULL) {
158 xs *s = xs_readall(f);
159 xs *o = xs_json_loads(s);
160 fclose(f);
161
162 const char *type = xs_dict_get(o, "type");
163
164 if (!xs_is_null(type) && strcmp(type, "Follow") == 0) {
165 unlink(v);
166
167 const char *actor = xs_dict_get(o, "actor");
168
169 if (!xs_is_null(actor))
170 follower_add(&snac, actor);
171 }
172 }
173 }
174
175 user_free(&snac);
176 }
177 }
178
179 nf = 2.5;
180 }
181 else
182 if (f < 2.6) {
183 /* upgrade local/ to public/ */
184 xs *users = user_list();
185 char *p;
186 const char *v;
187
188 p = users;
189 while (xs_list_iter(&p, &v)) {
190 snac snac;
191
192 if (user_open(&snac, v)) {
193 xs *spec = xs_fmt("%s/local/" "*.json", snac.basedir);
194 xs *dir = xs_glob(spec, 0, 0);
195 char *p;
196 const char *v;
197
198 p = dir;
199 while (xs_list_iter(&p, &v)) {
200 FILE *f;
201
202 if ((f = fopen(v, "r")) != NULL) {
203 xs *s = xs_readall(f);
204 xs *o = xs_json_loads(s);
205 fclose(f);
206
207 xs *meta = xs_dup(xs_dict_get(o, "_snac"));
208 o = xs_dict_del(o, "_snac");
209
210 const char *id = xs_dict_get(o, "id");
211
212 /* store object */
213 object_add_ow(id, o);
214
215 /* if it's from us, add to public */
216 if (xs_startswith(id, snac.actor)) {
217 const xs_list *p;
218 const char *v;
219 int c;
220
221 object_user_cache_add(&snac, id, "public");
222
223 p = xs_dict_get(meta, "announced_by");
224
225 c = 0;
226 while (xs_list_next(p, &v, &c))
227 object_admire(id, v, 0);
228
229 p = xs_dict_get(meta, "liked_by");
230
231 c = 0;
232 while (xs_list_next(p, &v, &c))
233 object_admire(id, v, 1);
234 }
235
236 unlink(v);
237 }
238 }
239
240 xs *od = xs_fmt("%s/local", snac.basedir);
241 rmdir(od);
242
243 user_free(&snac);
244 }
245 }
246
247 nf = 2.6;
248 }
249 else
250 if (f < 2.7) {
251 /* upgrade timeline/ to private/ */
252 xs *users = user_list();
253 char *p;
254 const char *v;
255
256 p = users;
257 while (xs_list_iter(&p, &v)) {
258 snac snac;
259
260 if (user_open(&snac, v)) {
261 xs *spec = xs_fmt("%s/timeline/" "*.json", snac.basedir);
262 xs *dir = xs_glob(spec, 0, 0);
263 char *p;
264 const char *v;
265
266 p = dir;
267 while (xs_list_iter(&p, &v)) {
268 FILE *f;
269
270 if ((f = fopen(v, "r")) != NULL) {
271 xs *s = xs_readall(f);
272 xs *o = xs_json_loads(s);
273 fclose(f);
274
275 xs *meta = xs_dup(xs_dict_get(o, "_snac"));
276 o = xs_dict_del(o, "_snac");
277
278 const char *id = xs_dict_get(o, "id");
279
280 /* store object */
281 object_add_ow(id, o);
282
283 {
284 const xs_list *p;
285 const char *v;
286 int c = 0;
287
288 object_user_cache_add(&snac, id, "private");
289
290 p = xs_dict_get(meta, "announced_by");
291
292 c = 0;
293 while (xs_list_next(p, &v, &c))
294 object_admire(id, v, 0);
295
296 p = xs_dict_get(meta, "liked_by");
297
298 c = 0;
299 while (xs_list_next(p, &v, &c))
300 object_admire(id, v, 1);
301 }
302
303 unlink(v);
304 }
305 }
306
307 xs *od = xs_fmt("%s/timeline", snac.basedir);
308 rmdir(od);
309
310 user_free(&snac);
311 }
312 }
313
314 nf = 2.7;
315 }
316
317 if (f < nf) {
318 f = nf;
319 xs *nv = xs_number_new(f);
320 srv_config = xs_dict_set(srv_config, "layout", nv);
321
322 srv_log(xs_fmt("disk layout upgraded to version %1.1lf", f));
323 changed++;
324 }
325 else
326 break;
327 }
328
329 if (f > disk_layout) {
330 *error = xs_fmt("ERROR: unknown future version %lf\n", f);
331 ret = 0;
332 }
333
334 if (changed) {
335 /* upgrade the configuration file */
336 xs *fn = xs_fmt("%s/server.json", srv_basedir);
337 FILE *f;
338
339 if ((f = fopen(fn, "w")) != NULL) {
340 xs_json_dump(srv_config, 4, f);
341 fclose(f);
342
343 srv_log(xs_fmt("disk layout upgraded %s after %d changes", fn, changed));
344 }
345 else
346 ret = 0;
347 }
348
349 return ret;
350}