AmendHub

Download

jcs

/

amend

/

repo.c

 

(View History)

jcs   *: Switch diff views to TextView Latest amendment: 153 on 2024-10-25

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