AmendHub

Download

jcs

/

amend

/

repo.c

 

(View History)

jcs   util: SFGetFile and SFPutFile have different dialog dimensions Latest amendment: 101 on 2022-09-12

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