AmendHub

Download

jcs

/

amend

/

repo.c

 

(View History)

jcs   repo: Set cursor to watch while we're loading a repo Latest amendment: 118 on 2023-04-18

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