master
go 189 lines 4.39 KB
Raw
1 package commands
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "io"
8 "os"
9
10 "github.com/ipfs/kubo/core/commands/cmdenv"
11 "github.com/ipfs/kubo/core/commands/cmdutils"
12
13 "github.com/cheggaaa/pb/v3"
14 "github.com/ipfs/boxo/files"
15 cmds "github.com/ipfs/go-ipfs-cmds"
16 iface "github.com/ipfs/kubo/core/coreiface"
17 )
18
19 const (
20 progressBarMinSize = 1024 * 1024 * 8 // show progress bar for outputs > 8MiB
21 offsetOptionName = "offset"
22 lengthOptionName = "length"
23 )
24
25 var CatCmd = &cmds.Command{
26 Helptext: cmds.HelpText{
27 Tagline: "Show IPFS object data.",
28 ShortDescription: "Displays the data contained by an IPFS or IPNS object(s) at the given path.",
29 },
30
31 Arguments: []cmds.Argument{
32 cmds.StringArg("ipfs-path", true, true, "The path to the IPFS object(s) to be outputted.").EnableStdin(),
33 },
34 Options: []cmds.Option{
35 cmds.Int64Option(offsetOptionName, "o", "Byte offset to begin reading from."),
36 cmds.Int64Option(lengthOptionName, "l", "Maximum number of bytes to read."),
37 cmds.BoolOption(progressOptionName, "p", "Stream progress data. Defaults to true when stderr is a terminal."),
38 },
39 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
40 api, err := cmdenv.GetApi(env, req)
41 if err != nil {
42 return err
43 }
44
45 offset, _ := req.Options[offsetOptionName].(int64)
46 if offset < 0 {
47 return errors.New("cannot specify negative offset")
48 }
49
50 max, found := req.Options[lengthOptionName].(int64)
51
52 if max < 0 {
53 return errors.New("cannot specify negative length")
54 }
55 if !found {
56 max = -1
57 }
58
59 err = req.ParseBodyArgs()
60 if err != nil {
61 return err
62 }
63
64 readers, length, err := cat(req.Context, api, req.Arguments, int64(offset), int64(max))
65 if err != nil {
66 return err
67 }
68
69 /*
70 if err := corerepo.ConditionalGC(req.Context, node, length); err != nil {
71 re.SetError(err, cmds.ErrNormal)
72 return
73 }
74 */
75
76 res.SetLength(length)
77 reader := io.MultiReader(readers...)
78
79 // Since the reader returns the error that a block is missing, and that error is
80 // returned from io.Copy inside Emit, we need to take Emit errors and send
81 // them to the client. Usually we don't do that because it means the connection
82 // is broken or we supplied an illegal argument etc.
83 return res.Emit(reader)
84 },
85 PostRun: cmds.PostRunMap{
86 cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
87 if res.Length() > 0 && res.Length() < progressBarMinSize {
88 return cmds.Copy(re, res)
89 }
90
91 for {
92 v, err := res.Next()
93 if err != nil {
94 if err == io.EOF {
95 return nil
96 }
97 return err
98 }
99
100 switch val := v.(type) {
101 case io.Reader:
102 reader := val
103
104 if cmdenv.ShouldShowProgress(res.Request(), progressOptionName) {
105 var bar *pb.ProgressBar
106 bar, reader = progressBarForReader(os.Stderr, val, int64(res.Length()))
107 bar.Start()
108 defer bar.Finish()
109 }
110
111 err = re.Emit(reader)
112 if err != nil {
113 return err
114 }
115 default:
116 log.Warnf("cat postrun: received unexpected type %T", val)
117 }
118 }
119 },
120 },
121 }
122
123 func cat(ctx context.Context, api iface.CoreAPI, paths []string, offset int64, max int64) ([]io.Reader, uint64, error) {
124 readers := make([]io.Reader, 0, len(paths))
125 length := uint64(0)
126 if max == 0 {
127 return nil, 0, nil
128 }
129 for _, pString := range paths {
130 p, err := cmdutils.PathOrCidPath(pString)
131 if err != nil {
132 return nil, 0, err
133 }
134
135 f, err := api.Unixfs().Get(ctx, p)
136 if err != nil {
137 return nil, 0, err
138 }
139
140 var file files.File
141 switch f := f.(type) {
142 case files.File:
143 file = f
144 case files.Directory:
145 return nil, 0, iface.ErrIsDir
146 default:
147 return nil, 0, iface.ErrNotSupported
148 }
149
150 fsize, err := file.Size()
151 if err != nil {
152 return nil, 0, err
153 }
154
155 if offset > fsize {
156 offset = offset - fsize
157 continue
158 }
159
160 seeker, ok := file.(io.Seeker)
161 if !ok {
162 return nil, 0, fmt.Errorf("file does not support seeking")
163 }
164 count, err := seeker.Seek(offset, io.SeekStart)
165 if err != nil {
166 return nil, 0, err
167 }
168 offset = 0
169
170 fsize, err = file.Size()
171 if err != nil {
172 return nil, 0, err
173 }
174
175 size := uint64(fsize - count)
176 length += size
177 if max > 0 && length >= uint64(max) {
178 var r io.Reader = file
179 if overshoot := int64(length - uint64(max)); overshoot != 0 {
180 r = io.LimitReader(file, int64(size)-overshoot)
181 length = uint64(max)
182 }
183 readers = append(readers, r)
184 break
185 }
186 readers = append(readers, file)
187 }
188 return readers, length, nil
189 }