AmendHub

Download

jcs

/

amend

/

repo.c

 

(View History)

jcs   browser: Use a custom LDEF for file list to cross out deleted files Latest amendment: 253 on 2023-11-01

1 /*
2 * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdio.h>
18 #include <string.h>
19 #include <time.h>
20
21 #include "amend.h"
22 #include "bile.h"
23 #include "diff.h"
24 #include "repo.h"
25 #include "settings.h"
26 #include "strnatcmp.h"
27 #include "util.h"
28
29 struct repo * repo_init(struct bile *bile, bool is_new);
30 void repo_sort_files(struct repo *repo);
31 short repo_get_file_attrs(struct repo *repo, Str255 filename,
32 struct repo_file_attrs *attrs);
33 short repo_file_update(struct repo *repo, struct repo_file *file);
34 unsigned short repo_diff_header(struct repo *repo,
35 struct repo_amendment *amendment, char **ret);
36 short repo_migrate(struct repo *repo, bool is_new);
37 pascal Boolean repo_add_file_filter(struct FileParam *pbp);
38
39 struct repo *
40 repo_open(AppFile *file)
41 {
42 SFReply reply;
43 SFTypeList types;
44 struct bile *bile;
45
46 if (file) {
47 reply.vRefNum = file->vRefNum;
48 memcpy(reply.fName, file->fName, sizeof(reply.fName));
49 } else {
50 types[0] = REPO_TYPE;
51
52 SFGetFile(centered_sfget_dialog(), NULL, NULL, 1, &types,
53 NULL, &reply);
54 if (!reply.good)
55 return NULL;
56 }
57
58 bile = bile_open(reply.fName, reply.vRefNum);
59 if (bile == NULL) {
60 if (bile_error(NULL) == BILE_ERR_NEED_UPGRADE_1)
61 warn("File %s is a version 1 repo and must be upgraded",
62 PtoCstr(reply.fName));
63 else
64 warn("Opening repo %s failed: %d", PtoCstr(reply.fName),
65 bile_error(NULL));
66 return NULL;
67 }
68
69 return repo_init(bile, 0);
70 }
71
72 struct repo *
73 repo_create(void)
74 {
75 SFReply reply;
76 struct bile *bile;
77
78 SFPutFile(centered_sfput_dialog(), "\pCreate new repository:",
79 "\p", NULL, &reply);
80 if (!reply.good)
81 return NULL;
82
83 bile = bile_create(reply.fName, reply.vRefNum, AMEND_CREATOR,
84 REPO_TYPE);
85 if (bile == NULL && bile_error(NULL) == dupFNErr) {
86 FSDelete(reply.fName, reply.vRefNum);
87 bile = bile_create(reply.fName, reply.vRefNum, AMEND_CREATOR,
88 REPO_TYPE);
89 }
90 if (bile == NULL)
91 panic("Failed to create %s: %d", PtoCstr(reply.fName),
92 bile_error(NULL));
93
94 return repo_init(bile, true);
95 }
96
97 struct repo *
98 repo_init(struct bile *bile, bool is_new)
99 {
100 struct bile_object *bob;
101 struct repo *repo;
102 size_t size;
103 char *data;
104 unsigned short i;
105 unsigned long *ids;
106
107 repo = xmalloczero(sizeof(struct repo), "repo");
108 repo->bile = bile;
109 repo->next_file_id = 1;
110 repo->next_amendment_id = 1;
111
112 if (repo_migrate(repo, is_new) != 0) {
113 xfree(&repo);
114 return NULL;
115 }
116
117 SetCursor(*(GetCursor(watchCursor)));
118
119 /* fill in file info */
120 repo->nfiles = bile_sorted_ids_by_type(bile, REPO_FILE_RTYPE, &ids);
121 if (repo->nfiles) {
122 repo->files = xcalloc(repo->nfiles, sizeof(Ptr), "repo files");
123 for (i = 0; i < repo->nfiles; i++) {
124 if (i == 0 || i == repo->nfiles - 1 || ((i + 1) % 10) == 0)
125 progress("Loading file %d/%d...", i + 1, repo->nfiles);
126 size = bile_read_alloc(bile, REPO_FILE_RTYPE, ids[i], &data);
127 if (size == 0)
128 panic("failed fetching file %ld", ids[i]);
129 repo->files[i] = xmalloc(sizeof(struct repo_file),
130 "repo file");
131 repo->files[i] = repo_parse_file(ids[i], (unsigned char *)data,
132 size);
133 if (repo->files[i]->id >= repo->next_file_id)
134 repo->next_file_id = repo->files[i]->id + 1;
135 xfree(&data);
136 }
137 xfree(&ids);
138
139 progress("Organizing files...");
140 repo_sort_files(repo);
141 }
142
143 /* fill in amendment info */
144 repo_load_amendments(repo, false);
145
146 return repo;
147 }
148
149 void
150 repo_load_amendments(struct repo *repo, bool fill_in)
151 {
152 size_t size;
153 char *data;
154 unsigned long *ids;
155 unsigned short i;
156
157 if (fill_in) {
158 progress("Loading first %d/%d amendments...",
159 repo->namendments - settings.max_amendments,
160 repo->namendments);
161 repo->unloaded_amendments = false;
162 }
163
164 repo->namendments = bile_sorted_ids_by_type(repo->bile,
165 REPO_AMENDMENT_RTYPE, &ids);
166 if (!repo->namendments)
167 return;
168
169 if (!fill_in) {
170 repo->amendments = xcalloc(repo->namendments, sizeof(Ptr),
171 "repo amendments");
172
173 repo->unloaded_amendments = (settings.max_amendments > 0 &&
174 repo->namendments > settings.max_amendments);
175 }
176
177 for (i = 0; i < repo->namendments; i++) {
178 if (repo->unloaded_amendments &&
179 i < repo->namendments - settings.max_amendments - 1)
180 continue;
181 if (fill_in && repo->amendments[i] != NULL)
182 continue;
183
184 if (i == 0 || i == repo->namendments - 1 ||
185 ((i + 1) % 10) == 0)
186 progress("Loading amendment %d/%d...", i + 1,
187 repo->namendments);
188
189 size = bile_read_alloc(repo->bile, REPO_AMENDMENT_RTYPE, ids[i],
190 &data);
191 if (size == 0)
192 panic("failed fetching amendment %ld", ids[i]);
193
194 repo->amendments[i] = repo_parse_amendment(ids[i],
195 (unsigned char *)data, size);
196 if (repo->amendments[i]->id >= repo->next_amendment_id)
197 repo->next_amendment_id = repo->amendments[i]->id + 1;
198 xfree(&data);
199 }
200 xfree(&ids);
201 }
202
203 void
204 repo_close(struct repo *repo)
205 {
206 struct repo_file *file;
207 struct repo_amendment *amendment;
208 short i;
209
210 for (i = 0; i < repo->namendments; i++) {
211 amendment = repo->amendments[i];
212 if (amendment == NULL)
213 continue;
214
215 if (amendment->log != NULL)
216 DisposHandle(amendment->log);
217
218 if (amendment->file_ids != NULL)
219 xfree(&amendment->file_ids);
220
221 xfree(&amendment);
222 }
223 if (repo->amendments)
224 xfree(&repo->amendments);
225
226 for (i = 0; i < repo->nfiles; i++) {
227 file = repo->files[i];
228 if (file == NULL)
229 continue;
230
231 xfree(&file);
232 }
233 if (repo->files)
234 xfree(&repo->files);
235
236 bile_close(repo->bile);
237 xfree(&repo);
238 }
239
240 struct repo_file *
241 repo_parse_file(unsigned long id, unsigned char *data, size_t size)
242 {
243 struct repo_file *file;
244 unsigned short len, i;
245 short datapos;
246
247 datapos = 0;
248
249 file = xmalloczero(sizeof(struct repo_file), "repo_parse_file");
250 file->id = id;
251
252 /* filename, pstr */
253 len = data[0];
254 memcpy(file->filename, data + 1, len);
255 file->filename[len] = '\0';
256 datapos += (len + 1);
257
258 /* type fourcc, creator fourcc */
259 memcpy(&file->type, data + datapos, 4);
260 datapos += 4;
261 memcpy(&file->creator, data + datapos, 4);
262 datapos += 4;
263
264 /* creation date, long */
265 file->ctime = ((unsigned long)data[datapos] << 24) |
266 ((unsigned long)data[datapos + 1] << 16) |
267 ((unsigned long)data[datapos + 2] << 8) |
268 ((unsigned long)data[datapos + 3]);
269 datapos += 4;
270
271 /* modification date, long */
272 file->mtime = ((unsigned long)data[datapos] << 24) |
273 ((unsigned long)data[datapos + 1] << 16) |
274 ((unsigned long)data[datapos + 2] << 8) |
275 ((unsigned long)data[datapos + 3]);
276 datapos += 4;
277
278 /* flags, unsigned char */
279 file->flags = data[datapos];
280 datapos += 1;
281
282 if (datapos != size)
283 panic("repo_parse_file object size %lu, data position %d", size,
284 datapos);
285
286 return file;
287 }
288
289 struct repo_amendment *
290 repo_parse_amendment(unsigned long id, unsigned char *data, size_t size)
291 {
292 struct repo_amendment *amendment;
293 unsigned short len, i;
294
295 amendment = xmalloc(sizeof(struct repo_amendment),
296 "repo_parse_amendment");
297 amendment->id = id;
298
299 /* date */
300 amendment->date = ((unsigned long)data[0] << 24) |
301 ((unsigned long)data[1] << 16) |
302 ((unsigned long)data[2] << 8) |
303 ((unsigned long)data[3]);
304 data += 4;
305
306 /* author, pstr */
307 len = data[0];
308 if (len > sizeof(amendment->author) - 1)
309 len = sizeof(amendment->author) - 1;
310 memcpy(amendment->author, data + 1, len);
311 amendment->author[len] = '\0';
312 data += (data[0] + 1);
313
314 /* files, short */
315 amendment->nfiles = (data[0] << 8) | data[1];
316 data += 2;
317
318 if (amendment->nfiles) {
319 amendment->file_ids = xcalloc(amendment->nfiles, sizeof(short),
320 "amendment file_ids");
321 for (i = 0; i < amendment->nfiles; i++) {
322 amendment->file_ids[i] = (data[0] << 8) | data[1];
323 data += 2;
324 }
325 }
326
327 /* additions, short */
328 amendment->adds = (data[0] << 8) | data[1];
329 data += 2;
330
331 /* subs, short */
332 amendment->subs = (data[0] << 8) | data[1];
333 data += 2;
334
335 /* log message, word-length */
336 len = (data[0] << 8) | data[1];
337 data += 2;
338 amendment->log = xNewHandle(len + 1);
339 amendment->log_len = len;
340 HLock(amendment->log);
341 memcpy(*(amendment->log), data, len);
342 (*(amendment->log))[len] = '\0';
343 HUnlock(amendment->log);
344 data += len;
345
346 /* TODO: use datapos and check against size like repo_parse_file */
347
348 return amendment;
349 }
350
351 struct repo_file *
352 repo_file_with_id(struct repo *repo, short id)
353 {
354 short i;
355
356 for (i = 0; i < repo->nfiles; i++)
357 if (repo->files[i]->id == id)
358 return repo->files[i];
359
360 return NULL;
361 }
362
363 unsigned short
364 repo_diff_header(struct repo *repo, struct repo_amendment *amendment,
365 char **ret)
366 {
367 struct tm *ttm = NULL;
368 unsigned short header_len;
369 short i;
370
371 *ret = xmalloc(128 + amendment->log_len, "repo_diff_header");
372 ttm = localtime(&amendment->date);
373 header_len = sprintf(*ret,
374 "Author: %s\r"
375 "Date: %04d-%02d-%02d %02d:%02d:%02d\r"
376 "\r ",
377 amendment->author,
378 ttm->tm_year + 1900, ttm->tm_mon + 1, ttm->tm_mday,
379 ttm->tm_hour, ttm->tm_min, ttm->tm_sec);
380
381 /* copy log, indenting each line */
382 HLock(amendment->log);
383 for (i = 0; i < amendment->log_len; i++) {
384 *(*ret + header_len++) = (*(amendment->log))[i];
385
386 if ((*(amendment->log))[i] == '\r' && i < amendment->log_len - 1) {
387 *(*ret + header_len++) = ' ';
388 *(*ret + header_len++) = ' ';
389 }
390 }
391 HUnlock(amendment->log);
392 *(*ret + header_len++) = '\r';
393 *(*ret + header_len++) = '\r';
394
395 return header_len;
396 }
397
398 void
399 repo_show_diff_text(struct repo *repo, struct repo_amendment *amendment,
400 TEHandle te)
401 {
402 char truncbuf[64];
403 struct bile_object *bob;
404 TextStyle style;
405 size_t size;
406 char *dtext;
407 char *buf = NULL;
408 unsigned long diff_len, all_len;
409 short header_len, blen, height, trunc = 0;
410 unsigned short warn_off;
411
412 TESetText("", 0, te);
413
414 bob = bile_find(repo->bile, REPO_DIFF_RTYPE, amendment->id);
415 if (bob == NULL) {
416 warn("Failed finding DIFF %d, corrupted repo?", amendment->id);
417 return;
418 }
419
420 diff_len = bob->size;
421 if (diff_len == 0)
422 panic("diff zero bytes");
423
424 header_len = repo_diff_header(repo, amendment, &buf);
425
426 all_len = header_len + diff_len;
427 if (all_len >= MAX_TEXTEDIT_SIZE) {
428 all_len = MAX_TEXTEDIT_SIZE;
429 trunc = 1;
430 }
431
432 dtext = xmalloc(all_len, "repo_show_diff_text");
433 memcpy(dtext, buf, header_len);
434 xfree(&buf);
435
436 size = bile_read_object(repo->bile, bob, dtext + header_len,
437 all_len - header_len);
438 if (size == 0)
439 panic("failed reading diff %lu: %d", amendment->id,
440 bile_error(repo->bile));
441
442 if (trunc) {
443 warn_off = MAX_TEXTEDIT_SIZE - header_len -
444 strlen(REPO_DIFF_TOO_BIG);
445 blen = snprintf(truncbuf, sizeof(truncbuf), REPO_DIFF_TOO_BIG,
446 diff_len - warn_off);
447 memcpy(dtext + MAX_TEXTEDIT_SIZE - blen, truncbuf, blen);
448 }
449
450 /* manually reset scroll without TESetSelect(0, 0, te) which redraws */
451 height = (*te)->destRect.bottom - (*te)->destRect.top;
452 (*te)->destRect.top = (*te)->viewRect.top;
453 (*te)->destRect.bottom = (*te)->viewRect.top + height;
454 style.tsFont = monaco;
455 style.tsSize = 9;
456 TESetStyle(doFont | doSize, &style, false, te);
457 TEStylInsert(dtext, all_len, 0, te);
458 xfree(&dtext);
459 }
460
461 static long repo_add_file_filter_repo_dir;
462 static struct repo *repo_add_file_filter_repo;
463
464 pascal Boolean
465 repo_add_file_filter(struct FileParam *pbp)
466 {
467 static Str255 file_filter_fname, file_filter_repo_fname;
468 short n;
469 char *justfilename;
470
471 if (CurDirStore != repo_add_file_filter_repo_dir)
472 /* not in same dir as repo, filter out */
473 return true;
474
475 memcpy(file_filter_fname, pbp->ioNamePtr, pbp->ioNamePtr[0] + 1);
476 PtoCstr(file_filter_fname);
477
478 /* exclude the repo itself */
479 memcpy(file_filter_repo_fname,
480 repo_add_file_filter_repo->bile->filename,
481 sizeof(file_filter_repo_fname));
482 PtoCstr(file_filter_repo_fname);
483 justfilename = strrchr((char *)&file_filter_repo_fname, ':');
484 if (justfilename == NULL)
485 justfilename = (char *)&file_filter_repo_fname;
486 else
487 justfilename++;
488 if (strcmp(justfilename, (char *)&file_filter_fname) == 0)
489 return true;
490
491 for (n = 0; n < repo_add_file_filter_repo->nfiles; n++) {
492 if (strcmp(repo_add_file_filter_repo->files[n]->filename,
493 (char *)file_filter_fname) == 0)
494 /* already in the repo, filter out */
495 return true;
496 }
497
498 return false;
499 }
500
501 struct repo_file *
502 repo_add_file(struct repo *repo)
503 {
504 WDPBRec wdir = { 0 };
505 SFReply reply;
506 Str255 fname, repofname, repopath, newpath;
507 struct repo_file *file;
508 short i;
509
510 /* tell SFGetFile we only want to accept files from this dir */
511 wdir.ioVRefNum = wdir.ioWDVRefNum = repo->bile->vrefnum;
512 memcpy(&fname, repo->bile->filename, sizeof(fname));
513 wdir.ioNamePtr = (StringPtr)&fname;
514 if (PBGetWDInfo(&wdir, 0) != noErr) {
515 warn("Failed looking up repo directory");
516 return NULL;
517 }
518 repo_add_file_filter_repo = repo;
519 repo_add_file_filter_repo_dir = wdir.ioWDDirID;
520
521 SFGetFile(centered_sfget_dialog(), "\p", repo_add_file_filter,
522 -1, 0, NULL, &reply);
523 if (!reply.good)
524 return NULL;
525
526 memcpy(&repofname, repo->bile->filename, sizeof(repofname));
527
528 /* if the file is not in the same dir as the repo, bail */
529 getpath(repo->bile->vrefnum, repofname, &repopath, false);
530 if (repopath[0] == 0)
531 panic("Can't find path to repo");
532 getpath(reply.vRefNum, reply.fName, &newpath, false);
533 if (newpath[0] == 0)
534 panic("Can't find path to new file");
535
536 PtoCstr(repopath);
537 PtoCstr(newpath);
538 if (strcmp((char *)&repopath, (char *)&newpath) != 0) {
539 warn("Can't add files from a directory other than the repo's");
540 return NULL;
541 }
542
543 /* make sure the file isn't already in the repo */
544 PtoCstr(reply.fName);
545 for (i = 0; i < repo->nfiles; i++) {
546 if (strcmp(repo->files[i]->filename, (char *)reply.fName) == 0) {
547 warn("%s already exists in this repo", reply.fName);
548 return NULL;
549 }
550 }
551 CtoPstr(reply.fName);
552
553 SetCursor(*(GetCursor(watchCursor)));
554
555 repo->nfiles++;
556 repo->files = xrealloc(repo->files, repo->nfiles * sizeof(Ptr));
557 file = repo->files[repo->nfiles - 1] =
558 xmalloczero(sizeof(struct repo_file), "repo_add_file");
559
560 file->id = repo->next_file_id;
561 repo->next_file_id++;
562 memcpy(&file->filename, reply.fName + 1, reply.fName[0]);
563 file->filename[reply.fName[0]] = '\0';
564
565 /* fetch type/creator/dates, store resource */
566 repo_file_update(repo, file);
567
568 repo_sort_files(repo);
569
570 SetCursor(&arrow);
571
572 return file;
573 }
574
575 short
576 repo_file_update(struct repo *repo, struct repo_file *file)
577 {
578 struct repo_file_attrs attrs;
579 Str255 filename;
580 size_t size, datapos;
581 short error, len;
582 unsigned char *data;
583
584 strlcpy((char *)filename, file->filename, sizeof(filename));
585 CtoPstr(filename);
586
587 error = repo_get_file_attrs(repo, filename, &attrs);
588 if (error && error != fnfErr) {
589 warn("Failed to get info for %s", file->filename);
590 return -1;
591 }
592
593 if (error == fnfErr)
594 file->flags |= REPO_FILE_DELETED;
595 else {
596 file->flags &= ~REPO_FILE_DELETED;
597
598 memcpy(&file->type, &attrs.type, 4);
599 memcpy(&file->creator, &attrs.creator, 4);
600 memcpy(&file->ctime, &attrs.ctime, 4);
601 memcpy(&file->mtime, &attrs.mtime, 4);
602 }
603
604 /* filename len, filename, type, creator, ctime, mtime, flags */
605 len = 1 + filename[0] + 4 + 4 + 4 + 4 + 1;
606
607 data = xmalloczero(len, "repo_file_update");
608 datapos = 0;
609
610 /* copy filename as pstr */
611 memcpy(data, filename, filename[0] + 1);
612 datapos += filename[0] + 1;
613
614 /* file type, creator, and dates */
615 memcpy(data + datapos, &file->type, 4);
616 datapos += 4;
617 memcpy(data + datapos, &file->creator, 4);
618 datapos += 4;
619 memcpy(data + datapos, &file->ctime, 4);
620 datapos += 4;
621 memcpy(data + datapos, &file->mtime, 4);
622 datapos += 4;
623 memcpy(data + datapos, &file->flags, 1);
624 datapos += 1;
625
626 if (datapos != len)
627 panic("repo_file_update: datapos %lu, expected %d", datapos, len);
628
629 size = bile_write(repo->bile, REPO_FILE_RTYPE, file->id, data, datapos);
630 if (size != datapos)
631 panic("repo_file_update: failed writing file data: %d",
632 bile_error(repo->bile));
633
634 xfree(&data);
635
636 return 0;
637 }
638
639 short
640 repo_get_file_attrs(struct repo *repo, Str255 filename,
641 struct repo_file_attrs *attrs)
642 {
643 FInfo fi;
644 short error;
645 struct stat sb;
646 Str255 filepath;
647
648 /* lookup file type and creator */
649 error = GetFInfo(filename, repo->bile->vrefnum, &fi);
650 if (error != 0)
651 return error;
652
653 memcpy(&attrs->type, &fi.fdType, 4);
654 memcpy(&attrs->creator, &fi.fdCreator, 4);
655
656 getpath(repo->bile->vrefnum, filename, &filepath, true);
657 error = FStat(filepath, &sb);
658 if (error) {
659 warn("Failed to stat %s", filepath);
660 return -1;
661 }
662
663 attrs->ctime = sb.st_ctime;
664 attrs->mtime = sb.st_mtime;
665
666 return 0;
667 }
668
669 short
670 repo_checkout_file(struct repo *repo, struct repo_file *file,
671 short vrefnum, Str255 filename)
672 {
673 struct bile_object *textob;
674 size_t size;
675 short error, frefnum;
676 char *text;
677
678 textob = bile_find(repo->bile, REPO_TEXT_RTYPE, file->id);
679 if (textob == NULL) {
680 warn("No copy of file %s exists in repo", file->filename);
681 return -1;
682 }
683
684 error = Create(filename, vrefnum, file->creator, file->type);
685 if (error && error != dupFNErr) {
686 warn("Failed to create file %s: %d", PtoCstr(filename),
687 error);
688 xfree(&textob);
689 return -1;
690 }
691
692 error = FSOpen(filename, vrefnum, &frefnum);
693 if (error)
694 panic("Failed to open file %s: %d", PtoCstr(filename), error);
695
696 error = SetEOF(frefnum, 0);
697 if (error)
698 panic("Failed to truncate file %s: %d", PtoCstr(filename), error);
699
700 /* TODO: add offset to bile_read to read in chunks */
701 text = xmalloc(textob->size, "repo_checkout_file");
702 size = bile_read_object(repo->bile, textob, text, textob->size);
703 if (size != textob->size)
704 panic("Failed to read text object %ld: %d", textob->id,
705 bile_error(repo->bile));
706
707 error = FSWrite(frefnum, &size, text);
708 if (error)
709 panic("Failed to write file to %s: %d", PtoCstr(filename), error);
710
711 xfree(&text);
712 xfree(&textob);
713 FSClose(frefnum);
714
715 return 0;
716 }
717
718 void
719 repo_sort_files(struct repo *repo)
720 {
721 struct repo_file *file;
722 short i, j;
723
724 for (i = 0; i < repo->nfiles; i++) {
725 repo->files[i]->sort_filename[0] = '0';
726 if (repo->files[i]->flags & REPO_FILE_DELETED)
727 repo->files[i]->sort_filename[1] = 'Z';
728 else
729 repo->files[i]->sort_filename[1] = 'A';
730 }
731
732 for (i = 1; i < repo->nfiles; i++) {
733 for (j = i; j > 0; j--) {
734 file = repo->files[j];
735
736 if (strnatcasecmp((const char *)&file->sort_filename,
737 (const char *)&repo->files[j - 1]->sort_filename) == 1)
738 break;
739
740 repo->files[j] = repo->files[j - 1];
741 repo->files[j - 1] = file;
742 }
743 }
744 }
745
746 short
747 repo_diff_file(struct repo *repo, struct repo_file *file)
748 {
749 Str255 fromfilename, tofilename;
750 Str255 fromfilepath, tofilepath;
751 Str255 label0, label1;
752 struct repo_file_attrs attrs;
753 size_t size;
754 char *text;
755 short error, ret, frefnum, tofile_empty = 0;
756
757 /* write out old file */
758 snprintf((char *)&fromfilename, sizeof(fromfilename), "[%lu] %s",
759 Time, file->filename);
760 CtoPstr(fromfilename);
761
762 error = Create(fromfilename, repo->bile->vrefnum, file->creator,
763 file->type);
764 if (error && error != dupFNErr)
765 panic("Failed to create file %s: %d", PtoCstr(fromfilename),
766 error);
767
768 error = FSOpen(fromfilename, repo->bile->vrefnum, &frefnum);
769 if (error)
770 panic("Failed to open file %s: %d", PtoCstr(fromfilename), error);
771
772 error = SetEOF(frefnum, 0);
773 if (error)
774 panic("Failed to truncate file %s: %d", PtoCstr(fromfilename),
775 error);
776
777 if (file->flags & REPO_FILE_DELETED) {
778 /* don't write any TEXT resource, the from file should be blank */
779 } else {
780 /* if there's no existing TEXT resource, it's a new file */
781
782 size = bile_read_alloc(repo->bile, REPO_TEXT_RTYPE, file->id,
783 &text);
784 if (size > 0) {
785 error = FSWrite(frefnum, &size, text);
786 if (error)
787 panic("Failed to write old file to %s: %d",
788 PtoCstr(fromfilename), error);
789 xfree(&text);
790 }
791 }
792
793 FSClose(frefnum);
794
795 if (getpath(repo->bile->vrefnum, fromfilename, &fromfilepath,
796 true) != 0)
797 panic("getpath(%d, %s) failed", repo->bile->vrefnum,
798 PtoCstr(fromfilename));
799
800 memcpy((char *)&tofilename, file->filename, sizeof(tofilename));
801 CtoPstr(tofilename);
802 if (getpath(repo->bile->vrefnum, tofilename, &tofilepath, true) != 0)
803 panic("getpath(%d, %s) failed", repo->bile->vrefnum,
804 PtoCstr(tofilename));
805
806 error = repo_get_file_attrs(repo, tofilename, &attrs);
807 if (error == fnfErr) {
808 /* file no longer exists, create empty temp file */
809 snprintf((char *)tofilename, sizeof(tofilename),
810 "[deleted] %s", file->filename);
811 CtoPstr(tofilename);
812
813 error = Create(tofilename, repo->bile->vrefnum, file->creator,
814 file->type);
815 if (error && error != dupFNErr)
816 panic("Failed to create file %s: %d", PtoCstr(tofilename),
817 error);
818
819 if (getpath(repo->bile->vrefnum, tofilename, &tofilepath,
820 true) != 0)
821 panic("getpath(%d, %s) failed", repo->bile->vrefnum,
822 PtoCstr(tofilename));
823 tofile_empty = 1;
824 } else if (error)
825 panic("Failed to get info for %s", PtoCstr(tofilename));
826
827 /* specify diff header labels to avoid printing tmp filename */
828 /* (TODO: use paths relative to repo) */
829 label[0] = (char *)&label0;
830 snprintf((char *)&label0, sizeof(label0), "%s\t%s", file->filename,
831 ctime(file->mtime ? &file->mtime : &attrs.mtime));
832 label[1] = (char *)&label1;
833 snprintf((char *)&label1, sizeof(label1), "%s\t%s", file->filename,
834 ctime(&attrs.mtime));
835
836 /* ctime is stupid, remove trailing \n */
837 label0[strlen((char *)label0) - 1] = '\0';
838 label1[strlen((char *)label1) - 1] = '\0';
839
840 PtoCstr(fromfilepath);
841 PtoCstr(tofilepath);
842 ret = diffreg((char *)fromfilepath, (char *)tofilepath, D_PROTOTYPE);
843
844 /* delete temp file */
845 error = FSDelete(fromfilename, repo->bile->vrefnum);
846 if (error)
847 panic("Failed to delete temp file %s: %d", PtoCstr(fromfilename),
848 error);
849
850 if (tofile_empty) {
851 error = FSDelete(tofilename, repo->bile->vrefnum);
852 if (error)
853 panic("Failed to delete temp file %s: %d",
854 PtoCstr(tofilename), error);
855 }
856
857 if (ret == D_SAME)
858 return 0;
859
860 return 1;
861 }
862
863 short
864 repo_file_changed(struct repo *repo, struct repo_file *file)
865 {
866 Str255 filename, filepath;
867 struct bile_object *bob;
868 struct stat sb;
869 long fsize;
870
871 /* if there's no existing TEXT resource, it's a new file */
872 bob = bile_find(repo->bile, REPO_TEXT_RTYPE, file->id);
873 if (bob == NULL)
874 return 1;
875 fsize = bob->size;
876 xfree(&bob);
877
878 memcpy((char *)filename, file->filename, sizeof(file->filename));
879 CtoPstr(filename);
880 getpath(repo->bile->vrefnum, filename, &filepath, true);
881
882 if (FStat(filepath, &sb) == 0) {
883 if (file->flags & REPO_FILE_DELETED)
884 return 1;
885 } else {
886 if (file->flags & REPO_FILE_DELETED)
887 return 0;
888 return 1;
889 }
890
891 if (sb.st_size != fsize)
892 return 1;
893 if (sb.st_ctime != file->ctime)
894 return 1;
895 if (sb.st_mtime != file->mtime)
896 return 1;
897
898 return 0;
899 }
900
901 void
902 repo_export_amendment(struct repo *repo, struct repo_amendment *amendment,
903 short vrefnum, Str255 filename)
904 {
905 struct bile_object *bob;
906 size_t size;
907 char *buf = NULL;
908 short error, frefnum;
909
910 bob = bile_find(repo->bile, REPO_DIFF_RTYPE, amendment->id);
911 if (bob == NULL)
912 panic("failed finding DIFF %d", amendment->id);
913
914 /*
915 * Don't use our creator here because we don't want finder opening us
916 * to view diffs, they are plain text
917 */
918 error = Create(filename, vrefnum, DIFF_FILE_TYPE, DIFF_FILE_TYPE);
919 if (error && error != dupFNErr) {
920 warn("Failed to create file %s: %d", PtoCstr(filename),
921 error);
922 return;
923 }
924
925 error = FSOpen(filename, vrefnum, &frefnum);
926 if (error)
927 panic("Failed to open file %s: %d", PtoCstr(filename), error);
928
929 error = SetEOF(frefnum, 0);
930 if (error)
931 panic("Failed to truncate file %s: %d", PtoCstr(filename),
932 error);
933
934 size = repo_diff_header(repo, amendment, &buf);
935 error = FSWrite(frefnum, &size, buf);
936 if (error)
937 panic("Failed to write diff header to %s: %d", PtoCstr(filename),
938 error);
939 xfree(&buf);
940
941 buf = xmalloc(bob->size, "repo_export_amendment");
942 size = bile_read_object(repo->bile, bob, buf, bob->size);
943 error = FSWrite(frefnum, &size, buf);
944 if (error)
945 panic("Failed to write diff to %s: %d", PtoCstr(filename), error);
946
947 xfree(&buf);
948 xfree(&bob);
949 FSClose(frefnum);
950 }
951
952 void
953 repo_amend(struct repo *repo, struct diffed_file *diffed_files,
954 short nfiles, short adds, short subs, char *author, Handle log,
955 short loglen, Handle diff, unsigned long difflen)
956 {
957 Str255 tfilename;
958 struct repo_amendment *amendment;
959 unsigned long datalen, fsize;
960 unsigned char *tdata;
961 char *amendment_data;
962 FInfo finfo;
963 size_t size;
964 short i, error, frefnum;
965
966 amendment = xmalloczero(sizeof(struct repo_amendment),
967 "repo_amend amendment");
968 amendment->id = repo->next_amendment_id;
969 amendment->date = Time;
970
971 /* find files with actual data changes */
972 amendment->nfiles = 0;
973 amendment->file_ids = xcalloc(sizeof(short), nfiles,
974 "repo_amend file_ids");
975 for (i = 0; i < nfiles; i++) {
976 if (diffed_files[i].flags & DIFFED_FILE_TEXT) {
977 amendment->file_ids[amendment->nfiles] =
978 diffed_files[i].file->id;
979 amendment->nfiles++;
980 }
981 }
982 if (amendment->nfiles == 0)
983 panic("repo_amendment passed nfiles %d but actual files is 0",
984 nfiles);
985
986 strlcpy(amendment->author, author, sizeof(amendment->author));
987 amendment->adds = adds;
988 amendment->subs = subs;
989
990 /* caller expects to be able to free their version, so make our own */
991 amendment->log_len = loglen;
992 amendment->log = xNewHandle(loglen);
993 HLock(log);
994 HLock(amendment->log);
995 memcpy(*(amendment->log), *log, loglen);
996 HUnlock(amendment->log);
997 HUnlock(log);
998
999 repo_marshall_amendment(amendment, &amendment_data, &datalen);
1000
1001 /* store diff */
1002 HLock(diff);
1003 progress("Storing diff...");
1004 size = bile_write(repo->bile, REPO_DIFF_RTYPE, amendment->id, *diff,
1005 difflen);
1006 if (size != difflen)
1007 panic("Failed storing diff in repo file: %d",
1008 bile_error(repo->bile));
1009 HUnlock(diff);
1010
1011 /* store amendment */
1012 progress("Storing amendment metadata...");
1013 size = bile_write(repo->bile, REPO_AMENDMENT_RTYPE, amendment->id,
1014 amendment_data, datalen);
1015 if (size != datalen)
1016 panic("Failed storing amendment in repo file: %d",
1017 bile_error(repo->bile));
1018 xfree(&amendment_data);
1019
1020 /* store new versions of each file */
1021 for (i = 0; i < nfiles; i++) {
1022 if (diffed_files[i].flags & DIFFED_FILE_TEXT) {
1023 strlcpy((char *)tfilename, diffed_files[i].file->filename,
1024 sizeof(tfilename));
1025 progress("Storing updated %s...", tfilename);
1026 CtoPstr(tfilename);
1027
1028 /* update file contents if file wasn't deleted */
1029 error = GetFInfo(tfilename, repo->bile->vrefnum, &finfo);
1030 if (error && error != fnfErr)
1031 panic("Error getting file info for %s",
1032 PtoCstr(tfilename));
1033
1034 if (error != fnfErr) {
1035 error = FSOpen(tfilename, repo->bile->vrefnum, &frefnum);
1036 if (error)
1037 panic("Failed to open file %s: %d", PtoCstr(tfilename),
1038 error);
1039
1040 error = GetEOF(frefnum, &fsize);
1041 if (error)
1042 panic("Failed to get size of file %s: %d",
1043 PtoCstr(tfilename), error);
1044
1045 tdata = xmalloc(fsize, "repo_amend data");
1046 error = FSRead(frefnum, &fsize, tdata);
1047 if (error)
1048 panic("Failed to read %ul of file %s: %d", fsize,
1049 PtoCstr(tfilename), error);
1050
1051 FSClose(frefnum);
1052
1053 size = bile_write(repo->bile, REPO_TEXT_RTYPE,
1054 diffed_files[i].file->id, tdata, fsize);
1055 if (size != fsize)
1056 panic("Failed to write new text file at %s: %d",
1057 PtoCstr(tfilename), bile_error(repo->bile));
1058 xfree(&tdata);
1059 }
1060 }
1061
1062 if (diffed_files[i].flags & DIFFED_FILE_METADATA)
1063 repo_file_update(repo, diffed_files[i].file);
1064 }
1065
1066 /* flush volume */
1067 bile_flush(repo->bile, 1);
1068
1069 repo->next_amendment_id = amendment->id + 1;
1070
1071 /* update amendment list */
1072 repo->namendments++;
1073 repo->amendments = xreallocarray(repo->amendments, repo->namendments,
1074 sizeof(Ptr));
1075 repo->amendments[repo->namendments - 1] = amendment;
1076 }
1077
1078 void
1079 repo_marshall_amendment(struct repo_amendment *amendment, char **retdata,
1080 unsigned long *retlen)
1081 {
1082 unsigned short len, pos = 0;
1083 char *data;
1084 short i;
1085 char clen;
1086
1087 /* date (long) */
1088 len = sizeof(long);
1089
1090 /* author (pstr) */
1091 len += 1 + strlen(amendment->author);
1092
1093 /* nfiles (short) */
1094 len += sizeof(short) + (amendment->nfiles * sizeof(short));
1095
1096 /* adds (short) */
1097 len += sizeof(short);
1098
1099 /* deletions (short) */
1100 len += sizeof(short);
1101
1102 /* log (wstr) */
1103 len += sizeof(short) + amendment->log_len;
1104
1105 *retdata = xmalloc(len, "repo_marshall_amendment");
1106 data = *retdata;
1107
1108 data[pos++] = (amendment->date >> 24) & 0xff;
1109 data[pos++] = (amendment->date >> 16) & 0xff;
1110 data[pos++] = (amendment->date >> 8) & 0xff;
1111 data[pos++] = amendment->date & 0xff;
1112
1113 clen = strlen(amendment->author);
1114 data[pos++] = clen;
1115 for (i = 0; i < clen; i++)
1116 data[pos++] = amendment->author[i];
1117
1118 data[pos++] = (amendment->nfiles >> 8) & 0xff;
1119 data[pos++] = amendment->nfiles & 0xff;
1120 for (i = 0; i < amendment->nfiles; i++) {
1121 data[pos++] = (amendment->file_ids[i] >> 8) & 0xff;
1122 data[pos++] = amendment->file_ids[i] & 0xff;
1123 }
1124
1125 data[pos++] = (amendment->adds >> 8) & 0xff;
1126 data[pos++] = amendment->adds & 0xff;
1127
1128 data[pos++] = (amendment->subs >> 8) & 0xff;
1129 data[pos++] = amendment->subs & 0xff;
1130
1131 HLock(amendment->log);
1132 data[pos++] = (amendment->log_len >> 8) & 0xff;
1133 data[pos++] = amendment->log_len & 0xff;
1134 memcpy(data + pos, *(amendment->log), amendment->log_len);
1135 pos += amendment->log_len;
1136 HUnlock(amendment->log);
1137
1138 if (pos != len)
1139 panic("repo_marshall_amendment: accumulated len %d != expected %d",
1140 pos, len);
1141
1142 *retlen = len;
1143 }
1144
1145 short
1146 repo_migrate(struct repo *repo, bool is_new)
1147 {
1148 unsigned long id;
1149 char ver;
1150
1151 if (is_new)
1152 ver = REPO_CUR_VERS;
1153 else {
1154 if (bile_read(repo->bile, REPO_VERS_RTYPE, 1, &ver, 1) != 1)
1155 ver = 1;
1156
1157 if (ver == REPO_CUR_VERS)
1158 return 0;
1159
1160 if (ask("Migrate this repo from version %d to %d to open it?",
1161 ver, REPO_CUR_VERS) != ASK_YES)
1162 return -1;
1163 }
1164
1165 if (!is_new) {
1166 progress("Backing up repo...");
1167 repo_backup(repo);
1168 }
1169
1170 /* per-version migrations */
1171 /* 1 had no version number :( */
1172 while (ver < REPO_CUR_VERS) {
1173 switch (ver) {
1174 case 1:
1175 /* 1->2 added a version */
1176 break;
1177 case 2:
1178 /* 2->3 was switching from resource forks to bile */
1179 break;
1180 }
1181
1182 ver++;
1183 }
1184
1185 /* store new version */
1186 ver = REPO_CUR_VERS;
1187 if (bile_write(repo->bile, REPO_VERS_RTYPE, 1, &ver, 1) != 1)
1188 panic("Failed writing new version: %d", bile_error(repo->bile));
1189
1190 progress("Doing a full check of the new repo...");
1191 bile_verify(repo->bile);
1192
1193 progress(NULL);
1194
1195 return 0;
1196 }
1197
1198 void
1199 repo_backup(struct repo *repo)
1200 {
1201 Str255 repo_filename, source_filename, dest_filename, path;
1202 short error;
1203
1204 if (getpath(repo->bile->vrefnum, repo->bile->filename, &path,
1205 false) != 0)
1206 panic("Failed resolving path of open repo");
1207 PtoCstr(path);
1208
1209 memcpy(repo_filename, repo->bile->filename, sizeof(repo_filename));
1210 PtoCstr(repo_filename);
1211 snprintf((char *)source_filename, sizeof(source_filename), "%s:%s",
1212 path, repo_filename);
1213 snprintf((char *)dest_filename, sizeof(dest_filename), "%s (backup)",
1214 source_filename);
1215 CtoPstr(source_filename);
1216 CtoPstr(dest_filename);
1217
1218 error = copy_file(source_filename, dest_filename, true);
1219 if (error)
1220 panic("Failed backing up repo: %d", error);
1221 }