1package main
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "os"
9 "path/filepath"
10
11 "github.com/bluesky-social/indigo/atproto/syntax"
12 "github.com/bluesky-social/indigo/repo"
13
14 "github.com/ipfs/go-cid"
15 cli "github.com/urfave/cli/v2"
16)
17
18var carCmd = &cli.Command{
19 Name: "car",
20 Usage: "sub-commands to work with CAR files on local disk",
21 Subcommands: []*cli.Command{
22 carUnpackCmd,
23 },
24}
25
26var carUnpackCmd = &cli.Command{
27 Name: "unpack",
28 Usage: "read all records from repo export CAR file, write as JSON files in directories",
29 Flags: []cli.Flag{
30 &cli.BoolFlag{
31 Name: "cbor",
32 Usage: "output CBOR files instead of JSON",
33 },
34 &cli.StringFlag{
35 Name: "out-dir",
36 Usage: "directory to write files to",
37 },
38 },
39 ArgsUsage: `<car-file>`,
40 Action: func(cctx *cli.Context) error {
41 ctx := context.Background()
42 arg := cctx.Args().First()
43 if arg == "" {
44 return fmt.Errorf("CAR file path arg is required")
45 }
46
47 fi, err := os.Open(arg)
48 if err != nil {
49 return err
50 }
51
52 r, err := repo.ReadRepoFromCar(ctx, fi)
53 if err != nil {
54 return err
55 }
56
57 sc := r.SignedCommit()
58 did, err := syntax.ParseDID(sc.Did)
59 if err != nil {
60 return err
61 }
62
63 topDir := cctx.String("out-dir")
64 if topDir == "" {
65 topDir = did.String()
66 }
67 log.Info("writing output", "topDir", topDir)
68
69 commitPath := topDir + "/_commit"
70 os.MkdirAll(filepath.Dir(commitPath), os.ModePerm)
71 if cctx.Bool("cbor") {
72 cborBytes := new(bytes.Buffer)
73 err = sc.MarshalCBOR(cborBytes)
74 if err := os.WriteFile(commitPath+".cbor", cborBytes.Bytes(), 0666); err != nil {
75 return err
76 }
77 } else {
78 recJson, err := json.MarshalIndent(sc, "", " ")
79 if err != nil {
80 return err
81 }
82 if err := os.WriteFile(commitPath+".json", recJson, 0666); err != nil {
83 return err
84 }
85 }
86
87 err = r.ForEach(ctx, "", func(k string, v cid.Cid) error {
88
89 _, rec, err := r.GetRecord(ctx, k)
90 if err != nil {
91 return err
92 }
93 log.Debug("processing record", "rec", k)
94
95 // TODO: check if path is safe more carefully
96 recPath := topDir + "/" + k
97 os.MkdirAll(filepath.Dir(recPath), os.ModePerm)
98 if err != nil {
99 return err
100 }
101 if cctx.Bool("cbor") {
102 cborBytes := new(bytes.Buffer)
103 err = rec.MarshalCBOR(cborBytes)
104 if err := os.WriteFile(recPath+".cbor", cborBytes.Bytes(), 0666); err != nil {
105 return err
106 }
107 } else {
108 recJson, err := json.MarshalIndent(rec, "", " ")
109 if err != nil {
110 return err
111 }
112 if err := os.WriteFile(recPath+".json", recJson, 0666); err != nil {
113 return err
114 }
115 }
116
117 return nil
118 })
119 if err != nil {
120 return err
121 }
122
123 return nil
124 },
125}