implement ipfs files command
License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>
Jeromy committed
Sep 29, 2015 at 21:31 UTC
6faa70ee5967d743cef5e87086c3b5a2d3648eec
8 files changed
+955
-8
commands/http/handler.go
+5
-1
@@ -285,7 +285,11 @@ func flushCopy(w io.Writer, r io.Reader) error {
285
n, err := r.Read(buf)
286
switch err {
287
case io.EOF:
288
- return nil
288
+ if n <= 0 {
289
+ return nil
290
+ }
291
+ // if data was returned alongside the EOF, pretend we didnt
292
+ // get an EOF. The next read call should also EOF.
293
case nil:
294
// continue
295
default:
core/builder.go
+5
@@ -159,5 +159,10 @@ func setupNode(ctx context.Context, n *IpfsNode, cfg *BuildCfg) error {
159
}
160
n.Resolver = &path.Resolver{DAG: n.DAG}
161
162
+ err = n.loadFilesRoot()
163
+ if err != nil {
164
+ return err
165
+ }
166
+
167
return nil
168
}
core/commands/files/files.go
new
+556
@@ -0,0 +1,556 @@
1
+package commands
2
+
3
+import (
4
+ "bytes"
5
+ "errors"
6
+ "fmt"
7
+ "io"
8
+ "os"
9
+ gopath "path"
10
+ "strings"
11
+
12
+ cmds "github.com/ipfs/go-ipfs/commands"
13
+ core "github.com/ipfs/go-ipfs/core"
14
+ dag "github.com/ipfs/go-ipfs/merkledag"
15
+ mfs "github.com/ipfs/go-ipfs/mfs"
16
+ path "github.com/ipfs/go-ipfs/path"
17
+ ft "github.com/ipfs/go-ipfs/unixfs"
18
+
19
+ logging "github.com/ipfs/go-ipfs/vendor/QmQg1J6vikuXF9oDvm4wpdeAUvvkVEKW1EYDw9HhTMnP2b/go-log"
20
+)
21
+
22
+var log = logging.Logger("cmds/files")
23
+
24
+var FilesCmd = &cmds.Command{
25
+ Helptext: cmds.HelpText{
26
+ Tagline: "Manipulate unixfs files",
27
+ ShortDescription: `
28
+Files is an API for manipulating ipfs objects as if they were a unix filesystem.
29
+`,
30
+ },
31
+ Subcommands: map[string]*cmds.Command{
32
+ "read": FilesReadCmd,
33
+ "write": FilesWriteCmd,
34
+ "mv": FilesMvCmd,
35
+ "cp": FilesCpCmd,
36
+ "ls": FilesLsCmd,
37
+ "mkdir": FilesMkdirCmd,
38
+ "stat": FilesStatCmd,
39
+ "rm": FilesRmCmd,
40
+ },
41
+}
42
+
43
+var FilesStatCmd = &cmds.Command{
44
+ Helptext: cmds.HelpText{
45
+ Tagline: "display file status",
46
+ },
47
+
48
+ Arguments: []cmds.Argument{
49
+ cmds.StringArg("path", true, false, "path to node to stat"),
50
+ },
51
+ Run: func(req cmds.Request, res cmds.Response) {
52
+ node, err := req.InvocContext().GetNode()
53
+ if err != nil {
54
+ res.SetError(err, cmds.ErrNormal)
55
+ return
56
+ }
57
+
58
+ path := req.Arguments()[0]
59
+ fsn, err := mfs.Lookup(node.FilesRoot, path)
60
+ if err != nil {
61
+ res.SetError(err, cmds.ErrNormal)
62
+ return
63
+ }
64
+
65
+ nd, err := fsn.GetNode()
66
+ if err != nil {
67
+ res.SetError(err, cmds.ErrNormal)
68
+ return
69
+ }
70
+
71
+ k, err := nd.Key()
72
+ if err != nil {
73
+ res.SetError(err, cmds.ErrNormal)
74
+ return
75
+ }
76
+
77
+ res.SetOutput(&Object{
78
+ Hash: k.B58String(),
79
+ })
80
+ },
81
+ Marshalers: cmds.MarshalerMap{
82
+ cmds.Text: func(res cmds.Response) (io.Reader, error) {
83
+ out := res.Output().(*Object)
84
+ return strings.NewReader(out.Hash), nil
85
+ },
86
+ },
87
+ Type: Object{},
88
+}
89
+
90
+var FilesCpCmd = &cmds.Command{
91
+ Helptext: cmds.HelpText{
92
+ Tagline: "copy files into mfs",
93
+ },
94
+ Arguments: []cmds.Argument{
95
+ cmds.StringArg("src", true, false, "source object to copy"),
96
+ cmds.StringArg("dest", true, false, "destination to copy object to"),
97
+ },
98
+ Run: func(req cmds.Request, res cmds.Response) {
99
+ node, err := req.InvocContext().GetNode()
100
+ if err != nil {
101
+ res.SetError(err, cmds.ErrNormal)
102
+ return
103
+ }
104
+
105
+ src := req.Arguments()[0]
106
+ dst := req.Arguments()[1]
107
+
108
+ var nd *dag.Node
109
+ switch {
110
+ case strings.HasPrefix(src, "/ipfs/"):
111
+ p, err := path.ParsePath(src)
112
+ if err != nil {
113
+ res.SetError(err, cmds.ErrNormal)
114
+ return
115
+ }
116
+
117
+ obj, err := core.Resolve(req.Context(), node, p)
118
+ if err != nil {
119
+ res.SetError(err, cmds.ErrNormal)
120
+ return
121
+ }
122
+
123
+ nd = obj
124
+ default:
125
+ fsn, err := mfs.Lookup(node.FilesRoot, src)
126
+ if err != nil {
127
+ res.SetError(err, cmds.ErrNormal)
128
+ return
129
+ }
130
+
131
+ obj, err := fsn.GetNode()
132
+ if err != nil {
133
+ res.SetError(err, cmds.ErrNormal)
134
+ return
135
+ }
136
+
137
+ nd = obj
138
+ }
139
+
140
+ err = mfs.PutNode(node.FilesRoot, dst, nd)
141
+ if err != nil {
142
+ res.SetError(err, cmds.ErrNormal)
143
+ return
144
+ }
145
+ },
146
+}
147
+
148
+type Object struct {
149
+ Hash string
150
+}
151
+
152
+type FilesLsOutput struct {
153
+ Entries []mfs.NodeListing
154
+}
155
+
156
+var FilesLsCmd = &cmds.Command{
157
+ Helptext: cmds.HelpText{
158
+ Tagline: "List directories",
159
+ ShortDescription: `
160
+List directories.
161
+
162
+Examples:
163
+
164
+ $ ipfs files ls /welcome/docs/
165
+ about
166
+ contact
167
+ help
168
+ quick-start
169
+ readme
170
+ security-notes
171
+
172
+ $ ipfs files ls /myfiles/a/b/c/d
173
+ foo
174
+ bar
175
+`,
176
+ },
177
+ Arguments: []cmds.Argument{
178
+ cmds.StringArg("path", true, false, "path to show listing for"),
179
+ },
180
+ Options: []cmds.Option{
181
+ cmds.BoolOption("l", "use long listing format"),
182
+ },
183
+ Run: func(req cmds.Request, res cmds.Response) {
184
+ path := req.Arguments()[0]
185
+ nd, err := req.InvocContext().GetNode()
186
+ if err != nil {
187
+ res.SetError(err, cmds.ErrNormal)
188
+ return
189
+ }
190
+
191
+ fsn, err := mfs.Lookup(nd.FilesRoot, path)
192
+ if err != nil {
193
+ res.SetError(err, cmds.ErrNormal)
194
+ return
195
+ }
196
+
197
+ switch fsn := fsn.(type) {
198
+ case *mfs.Directory:
199
+ listing, err := fsn.List()
200
+ if err != nil {
201
+ res.SetError(err, cmds.ErrNormal)
202
+ return
203
+ }
204
+ res.SetOutput(&FilesLsOutput{listing})
205
+ return
206
+ case *mfs.File:
207
+ parts := strings.Split(path, "/")
208
+ name := parts[len(parts)-1]
209
+ out := &FilesLsOutput{[]mfs.NodeListing{mfs.NodeListing{Name: name, Type: 1}}}
210
+ res.SetOutput(out)
211
+ return
212
+ default:
213
+ res.SetError(errors.New("unrecognized type"), cmds.ErrNormal)
214
+ }
215
+ },
216
+ Marshalers: cmds.MarshalerMap{
217
+ cmds.Text: func(res cmds.Response) (io.Reader, error) {
218
+ out := res.Output().(*FilesLsOutput)
219
+ buf := new(bytes.Buffer)
220
+ long, _, _ := res.Request().Option("l").Bool()
221
+
222
+ for _, o := range out.Entries {
223
+ if long {
224
+ fmt.Fprintf(buf, "%s\t%s\t%d\n", o.Name, o.Hash, o.Size)
225
+ } else {
226
+ fmt.Fprintf(buf, "%s\n", o.Name)
227
+ }
228
+ }
229
+ return buf, nil
230
+ },
231
+ },
232
+ Type: FilesLsOutput{},
233
+}
234
+
235
+var FilesReadCmd = &cmds.Command{
236
+ Helptext: cmds.HelpText{
237
+ Tagline: "Read a file in a given mfs",
238
+ ShortDescription: `
239
+Read a specified number of bytes from a file at a given offset. By default, will
240
+read the entire file similar to unix cat.
241
+
242
+Examples:
243
+
244
+ $ ipfs files read /test/hello
245
+ hello
246
+ `,
247
+ },
248
+
249
+ Arguments: []cmds.Argument{
250
+ cmds.StringArg("path", true, false, "path to file to be read"),
251
+ },
252
+ Options: []cmds.Option{
253
+ cmds.IntOption("o", "offset", "offset to read from"),
254
+ cmds.IntOption("n", "count", "maximum number of bytes to read"),
255
+ },
256
+ Run: func(req cmds.Request, res cmds.Response) {
257
+ n, err := req.InvocContext().GetNode()
258
+ if err != nil {
259
+ res.SetError(err, cmds.ErrNormal)
260
+ return
261
+ }
262
+
263
+ path := req.Arguments()[0]
264
+ fsn, err := mfs.Lookup(n.FilesRoot, path)
265
+ if err != nil {
266
+ res.SetError(err, cmds.ErrNormal)
267
+ return
268
+ }
269
+
270
+ fi, ok := fsn.(*mfs.File)
271
+ if !ok {
272
+ res.SetError(fmt.Errorf("%s was not a file", path), cmds.ErrNormal)
273
+ return
274
+ }
275
+
276
+ offset, _, _ := req.Option("offset").Int()
277
+
278
+ _, err = fi.Seek(int64(offset), os.SEEK_SET)
279
+ if err != nil {
280
+ res.SetError(err, cmds.ErrNormal)
281
+ return
282
+ }
283
+ var r io.Reader = fi
284
+ count, found, err := req.Option("count").Int()
285
+ if err == nil && found {
286
+ r = io.LimitReader(fi, int64(count))
287
+ }
288
+
289
+ res.SetOutput(r)
290
+ },
291
+}
292
+
293
+var FilesMvCmd = &cmds.Command{
294
+ Helptext: cmds.HelpText{
295
+ Tagline: "Move files",
296
+ ShortDescription: `
297
+Move files around. Just like traditional unix mv.
298
+
299
+Example:
300
+
301
+ $ ipfs files mv /myfs/a/b/c /myfs/foo/newc
302
+
303
+ `,
304
+ },
305
+
306
+ Arguments: []cmds.Argument{
307
+ cmds.StringArg("source", true, false, "source file to move"),
308
+ cmds.StringArg("dest", true, false, "target path for file to be moved to"),
309
+ },
310
+ Run: func(req cmds.Request, res cmds.Response) {
311
+ n, err := req.InvocContext().GetNode()
312
+ if err != nil {
313
+ res.SetError(err, cmds.ErrNormal)
314
+ return
315
+ }
316
+
317
+ src := req.Arguments()[0]
318
+ dst := req.Arguments()[1]
319
+
320
+ err = mfs.Mv(n.FilesRoot, src, dst)
321
+ if err != nil {
322
+ res.SetError(err, cmds.ErrNormal)
323
+ return
324
+ }
325
+ },
326
+}
327
+
328
+var FilesWriteCmd = &cmds.Command{
329
+ Helptext: cmds.HelpText{
330
+ Tagline: "Write to a mutable file in a given filesystem",
331
+ ShortDescription: `
332
+Write data to a file in a given filesystem. This command allows you to specify
333
+a beginning offset to write to. The entire length of the input will be written.
334
+
335
+If the '--create' option is specified, the file will be create if it does not
336
+exist. Nonexistant intermediate directories will not be created.
337
+
338
+Example:
339
+
340
+ echo "hello world" | ipfs files write --create /myfs/a/b/file
341
+ echo "hello world" | ipfs files write --truncate /myfs/a/b/file
342
+ `,
343
+ },
344
+ Arguments: []cmds.Argument{
345
+ cmds.StringArg("path", true, false, "path to write to"),
346
+ cmds.FileArg("data", true, false, "data to write").EnableStdin(),
347
+ },
348
+ Options: []cmds.Option{
349
+ cmds.IntOption("o", "offset", "offset to write to"),
350
+ cmds.BoolOption("n", "create", "create the file if it does not exist"),
351
+ cmds.BoolOption("t", "truncate", "truncate the file before writing"),
352
+ },
353
+ Run: func(req cmds.Request, res cmds.Response) {
354
+ path := req.Arguments()[0]
355
+ create, _, _ := req.Option("create").Bool()
356
+ trunc, _, _ := req.Option("truncate").Bool()
357
+
358
+ nd, err := req.InvocContext().GetNode()
359
+ if err != nil {
360
+ res.SetError(err, cmds.ErrNormal)
361
+ return
362
+ }
363
+
364
+ fi, err := getFileHandle(nd.FilesRoot, path, create)
365
+ if err != nil {
366
+ res.SetError(err, cmds.ErrNormal)
367
+ return
368
+ }
369
+ defer fi.Close()
370
+
371
+ if trunc {
372
+ if err := fi.Truncate(0); err != nil {
373
+ res.SetError(err, cmds.ErrNormal)
374
+ return
375
+ }
376
+ }
377
+
378
+ offset, _, _ := req.Option("offset").Int()
379
+
380
+ _, err = fi.Seek(int64(offset), os.SEEK_SET)
381
+ if err != nil {
382
+ log.Error("seekfail: ", err)
383
+ res.SetError(err, cmds.ErrNormal)
384
+ return
385
+ }
386
+
387
+ input, err := req.Files().NextFile()
388
+ if err != nil {
389
+ res.SetError(err, cmds.ErrNormal)
390
+ return
391
+ }
392
+
393
+ n, err := io.Copy(fi, input)
394
+ if err != nil {
395
+ res.SetError(err, cmds.ErrNormal)
396
+ return
397
+ }
398
+
399
+ log.Debugf("wrote %d bytes to %s", n, path)
400
+ },
401
+}
402
+
403
+var FilesMkdirCmd = &cmds.Command{
404
+ Helptext: cmds.HelpText{
405
+ Tagline: "make directories",
406
+ ShortDescription: `
407
+Create the directory if it does not already exist.
408
+
409
+Note: all paths must be absolute.
410
+
411
+Examples:
412
+
413
+ $ ipfs mfs mkdir /test/newdir
414
+ $ ipfs mfs mkdir -p /test/does/not/exist/yet
415
+`,
416
+ },
417
+
418
+ Arguments: []cmds.Argument{
419
+ cmds.StringArg("path", true, false, "path to dir to make"),
420
+ },
421
+ Options: []cmds.Option{
422
+ cmds.BoolOption("p", "parents", "no error if existing, make parent directories as needed"),
423
+ },
424
+ Run: func(req cmds.Request, res cmds.Response) {
425
+ n, err := req.InvocContext().GetNode()
426
+ if err != nil {
427
+ res.SetError(err, cmds.ErrNormal)
428
+ return
429
+ }
430
+
431
+ dashp, _, _ := req.Option("parents").Bool()
432
+ dirtomake := req.Arguments()[0]
433
+
434
+ if dirtomake[0] != '/' {
435
+ res.SetError(errors.New("paths must be absolute"), cmds.ErrNormal)
436
+ return
437
+ }
438
+
439
+ err = mfs.Mkdir(n.FilesRoot, dirtomake, dashp)
440
+ if err != nil {
441
+ res.SetError(err, cmds.ErrNormal)
442
+ return
443
+ }
444
+ },
445
+}
446
+
447
+var FilesRmCmd = &cmds.Command{
448
+ Helptext: cmds.HelpText{
449
+ Tagline: "remove a file",
450
+ ShortDescription: ``,
451
+ },
452
+
453
+ Arguments: []cmds.Argument{
454
+ cmds.StringArg("path", true, true, "file to remove"),
455
+ },
456
+ Options: []cmds.Option{
457
+ cmds.BoolOption("r", "recursive", "recursively remove directories"),
458
+ },
459
+ Run: func(req cmds.Request, res cmds.Response) {
460
+ nd, err := req.InvocContext().GetNode()
461
+ if err != nil {
462
+ res.SetError(err, cmds.ErrNormal)
463
+ return
464
+ }
465
+
466
+ path := req.Arguments()[0]
467
+ dir, name := gopath.Split(path)
468
+ parent, err := mfs.Lookup(nd.FilesRoot, dir)
469
+ if err != nil {
470
+ res.SetError(err, cmds.ErrNormal)
471
+ return
472
+ }
473
+
474
+ pdir, ok := parent.(*mfs.Directory)
475
+ if !ok {
476
+ res.SetError(fmt.Errorf("no such file or directory: %s", path), cmds.ErrNormal)
477
+ return
478
+ }
479
+
480
+ childi, err := pdir.Child(name)
481
+ if err != nil {
482
+ res.SetError(err, cmds.ErrNormal)
483
+ return
484
+ }
485
+
486
+ dashr, _, _ := req.Option("r").Bool()
487
+
488
+ switch childi.(type) {
489
+ case *mfs.Directory:
490
+ if dashr {
491
+ err := pdir.Unlink(name)
492
+ if err != nil {
493
+ res.SetError(err, cmds.ErrNormal)
494
+ return
495
+ }
496
+ } else {
497
+ res.SetError(fmt.Errorf("%s is a directory, use -r to remove directories", path), cmds.ErrNormal)
498
+ return
499
+ }
500
+ default:
501
+ err := pdir.Unlink(name)
502
+ if err != nil {
503
+ res.SetError(err, cmds.ErrNormal)
504
+ return
505
+ }
506
+ }
507
+ },
508
+}
509
+
510
+func getFileHandle(r *mfs.Root, path string, create bool) (*mfs.File, error) {
511
+
512
+ target, err := mfs.Lookup(r, path)
513
+ switch err {
514
+ case nil:
515
+ fi, ok := target.(*mfs.File)
516
+ if !ok {
517
+ return nil, fmt.Errorf("%s was not a file", path)
518
+ }
519
+ return fi, nil
520
+
521
+ case os.ErrNotExist:
522
+ if !create {
523
+ return nil, err
524
+ }
525
+
526
+ // if create is specified and the file doesnt exist, we create the file
527
+ dirname, fname := gopath.Split(path)
528
+ pdiri, err := mfs.Lookup(r, dirname)
529
+ if err != nil {
530
+ log.Error("lookupfail ", dirname)
531
+ return nil, err
532
+ }
533
+ pdir, ok := pdiri.(*mfs.Directory)
534
+ if !ok {
535
+ return nil, fmt.Errorf("%s was not a directory", dirname)
536
+ }
537
+
538
+ nd := &dag.Node{Data: ft.FilePBData(nil, 0)}
539
+ err = pdir.AddChild(fname, nd)
540
+ if err != nil {
541
+ return nil, err
542
+ }
543
+
544
+ fsn, err := pdir.Child(fname)
545
+ if err != nil {
546
+ return nil, err
547
+ }
548
+
549
+ // can unsafely cast, if it fails, that means programmer error
550
+ return fsn.(*mfs.File), nil
551
+
552
+ default:
553
+ log.Error("GFH default")
554
+ return nil, err
555
+ }
556
+}
core/commands/root.go
+2
@@ -5,6 +5,7 @@ import (
5
"strings"
6
7
cmds "github.com/ipfs/go-ipfs/commands"
8
+ files "github.com/ipfs/go-ipfs/core/commands/files"
9
unixfs "github.com/ipfs/go-ipfs/core/commands/unixfs"
10
logging "github.com/ipfs/go-ipfs/vendor/QmQg1J6vikuXF9oDvm4wpdeAUvvkVEKW1EYDw9HhTMnP2b/go-log"
11
)
@@ -99,6 +100,7 @@ var rootSubcommands = map[string]*cmds.Command{
100
"dht": DhtCmd,
101
"diag": DiagCmd,
102
"dns": DNSCmd,
103
+ "files": files.FilesCmd,
104
"get": GetCmd,
105
"id": IDCmd,
106
"log": LogCmd,
core/core.go
+55
-6
@@ -17,6 +17,7 @@ import (
17
"time"
18
19
b58 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-base58"
20
+ ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
21
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
22
goprocess "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
23
mamask "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter"
@@ -40,11 +41,13 @@ import (
41
offroute "github.com/ipfs/go-ipfs/routing/offline"
42
43
bstore "github.com/ipfs/go-ipfs/blocks/blockstore"
44
+ key "github.com/ipfs/go-ipfs/blocks/key"
45
bserv "github.com/ipfs/go-ipfs/blockservice"
46
exchange "github.com/ipfs/go-ipfs/exchange"
47
bitswap "github.com/ipfs/go-ipfs/exchange/bitswap"
48
bsnet "github.com/ipfs/go-ipfs/exchange/bitswap/network"
49
rp "github.com/ipfs/go-ipfs/exchange/reprovide"
50
+ mfs "github.com/ipfs/go-ipfs/mfs"
51
52
mount "github.com/ipfs/go-ipfs/fuse/mount"
53
merkledag "github.com/ipfs/go-ipfs/merkledag"
@@ -54,6 +57,7 @@ import (
57
pin "github.com/ipfs/go-ipfs/pin"
58
repo "github.com/ipfs/go-ipfs/repo"
59
config "github.com/ipfs/go-ipfs/repo/config"
60
+ unixfs "github.com/ipfs/go-ipfs/unixfs"
61
u "github.com/ipfs/go-ipfs/util"
62
)
63
@@ -94,6 +98,7 @@ type IpfsNode struct {
98
Resolver *path.Resolver // the path resolution system
99
Reporter metrics.Reporter
100
Discovery discovery.Service
101
+ FilesRoot *mfs.Root
102
103
// Online
104
PeerHost p2phost.Host // the network host (server+client)
@@ -316,8 +321,14 @@ func (n *IpfsNode) teardown() error {
321
log.Debug("core is shutting down...")
322
// owned objects are closed in this teardown to ensure that they're closed
323
// regardless of which constructor was used to add them to the node.
319
- closers := []io.Closer{
320
- n.Repo,
324
+ var closers []io.Closer
325
+
326
+ // NOTE: the order that objects are added(closed) matters, if an object
327
+ // needs to use another during its shutdown/cleanup process, it should be
328
+ // closed before that other object
329
+
330
+ if n.FilesRoot != nil {
331
+ closers = append(closers, n.FilesRoot)
332
}
333
334
if n.Exchange != nil {
@@ -331,6 +342,10 @@ func (n *IpfsNode) teardown() error {
342
closers = append(closers, mount.Closer(n.Mounts.Ipns))
343
}
344
345
+ if dht, ok := n.Routing.(*dht.IpfsDHT); ok {
346
+ closers = append(closers, dht.Process())
347
+ }
348
+
349
if n.Blocks != nil {
350
closers = append(closers, n.Blocks)
351
}
@@ -339,14 +354,13 @@ func (n *IpfsNode) teardown() error {
354
closers = append(closers, n.Bootstrapper)
355
}
356
342
- if dht, ok := n.Routing.(*dht.IpfsDHT); ok {
343
- closers = append(closers, dht.Process())
344
- }
345
-
357
if n.PeerHost != nil {
358
closers = append(closers, n.PeerHost)
359
}
360
361
+ // Repo closed last, most things need to preserve state here
362
+ closers = append(closers, n.Repo)
363
+
364
var errs []error
365
for _, closer := range closers {
366
if err := closer.Close(); err != nil {
@@ -457,6 +471,41 @@ func (n *IpfsNode) loadBootstrapPeers() ([]peer.PeerInfo, error) {
471
return toPeerInfos(parsed), nil
472
}
473
474
+func (n *IpfsNode) loadFilesRoot() error {
475
+ dsk := ds.NewKey("/filesroot")
476
+ pf := func(ctx context.Context, k key.Key) error {
477
+ return n.Repo.Datastore().Put(dsk, []byte(k))
478
+ }
479
+
480
+ var nd *merkledag.Node
481
+ val, err := n.Repo.Datastore().Get(dsk)
482
+
483
+ switch {
484
+ case err == ds.ErrNotFound || val == nil:
485
+ nd = &merkledag.Node{Data: unixfs.FolderPBData()}
486
+ _, err := n.DAG.Add(nd)
487
+ if err != nil {
488
+ return fmt.Errorf("failure writing to dagstore: %s", err)
489
+ }
490
+ case err == nil:
491
+ k := key.Key(val.([]byte))
492
+ nd, err = n.DAG.Get(n.Context(), k)
493
+ if err != nil {
494
+ return fmt.Errorf("error loading filesroot from DAG: %s", err)
495
+ }
496
+ default:
497
+ return err
498
+ }
499
+
500
+ mr, err := mfs.NewRoot(n.Context(), n.DAG, nd, pf)
501
+ if err != nil {
502
+ return err
503
+ }
504
+
505
+ n.FilesRoot = mr
506
+ return nil
507
+}
508
+
509
// SetupOfflineRouting loads the local nodes private key and
510
// uses it to instantiate a routing system in offline mode.
511
// This is primarily used for offline ipns modifications.
mfs/dir.go
+5
@@ -280,6 +280,11 @@ func (d *Directory) AddChild(name string, nd *dag.Node) error {
280
return ErrDirExists
281
}
282
283
+ _, err = d.dserv.Add(nd)
284
+ if err != nil {
285
+ return err
286
+ }
287
+
288
err = d.node.AddNodeLinkClean(name, nd)
289
if err != nil {
290
return err
mfs/ops.go
+108
-1
@@ -3,10 +3,117 @@ package mfs
3
import (
4
"errors"
5
"fmt"
6
+ "os"
7
+ gopath "path"
8
"strings"
9
+
10
+ dag "github.com/ipfs/go-ipfs/merkledag"
11
)
12
9
-func rootLookup(r *Root, path string) (FSNode, error) {
13
+// Mv moves the file or directory at 'src' to 'dst'
14
+func Mv(r *Root, src, dst string) error {
15
+ srcDir, srcFname := gopath.Split(src)
16
+
17
+ srcObj, err := Lookup(r, src)
18
+ if err != nil {
19
+ return err
20
+ }
21
+
22
+ var dstDirStr string
23
+ var filename string
24
+ if dst[len(dst)-1] == '/' {
25
+ dstDirStr = dst
26
+ filename = srcFname
27
+ } else {
28
+ dstDirStr, filename = gopath.Split(dst)
29
+ }
30
+
31
+ dstDiri, err := Lookup(r, dstDirStr)
32
+ if err != nil {
33
+ return err
34
+ }
35
+
36
+ dstDir := dstDiri.(*Directory)
37
+ nd, err := srcObj.GetNode()
38
+ if err != nil {
39
+ return err
40
+ }
41
+
42
+ err = dstDir.AddChild(filename, nd)
43
+ if err != nil {
44
+ return err
45
+ }
46
+
47
+ srcDirObji, err := Lookup(r, srcDir)
48
+ if err != nil {
49
+ return err
50
+ }
51
+
52
+ srcDirObj := srcDirObji.(*Directory)
53
+ err = srcDirObj.Unlink(srcFname)
54
+ if err != nil {
55
+ return err
56
+ }
57
+
58
+ return nil
59
+}
60
+
61
+// PutNode inserts 'nd' at 'path' in the given mfs
62
+func PutNode(r *Root, path string, nd *dag.Node) error {
63
+ dirp, filename := gopath.Split(path)
64
+
65
+ parent, err := Lookup(r, dirp)
66
+ if err != nil {
67
+ return fmt.Errorf("lookup '%s' failed: %s", dirp, err)
68
+ }
69
+
70
+ pdir, ok := parent.(*Directory)
71
+ if !ok {
72
+ return fmt.Errorf("%s did not point to directory", dirp)
73
+ }
74
+
75
+ return pdir.AddChild(filename, nd)
76
+}
77
+
78
+// Mkdir creates a directory at 'path' under the directory 'd', creating
79
+// intermediary directories as needed if 'parents' is set to true
80
+func Mkdir(r *Root, path string, parents bool) error {
81
+ parts := strings.Split(path, "/")
82
+ if parts[0] == "" {
83
+ parts = parts[1:]
84
+ }
85
+
86
+ cur := r.GetValue().(*Directory)
87
+ for i, d := range parts[:len(parts)-1] {
88
+ fsn, err := cur.Child(d)
89
+ if err != nil {
90
+ if err == os.ErrNotExist && parents {
91
+ mkd, err := cur.Mkdir(d)
92
+ if err != nil {
93
+ return err
94
+ }
95
+ fsn = mkd
96
+ }
97
+ }
98
+
99
+ next, ok := fsn.(*Directory)
100
+ if !ok {
101
+ return fmt.Errorf("%s was not a directory", strings.Join(parts[:i], "/"))
102
+ }
103
+ cur = next
104
+ }
105
+
106
+ _, err := cur.Mkdir(parts[len(parts)-1])
107
+ if err != nil {
108
+ if !parents || err != os.ErrExist {
109
+ return err
110
+ }
111
+ }
112
+
113
+ return nil
114
+}
115
+
116
+func Lookup(r *Root, path string) (FSNode, error) {
117
dir, ok := r.GetValue().(*Directory)
118
if !ok {
119
return nil, errors.New("root was not a directory")
test/sharness/t0250-files-api.sh
new
+219
@@ -0,0 +1,219 @@
1
+#!/bin/sh
2
+#
3
+# Copyright (c) 2015 Jeromy Johnson
4
+# MIT Licensed; see the LICENSE file in this repository.
5
+#
6
+
7
+test_description="test the unix files api"
8
+
9
+. lib/test-lib.sh
10
+
11
+test_init_ipfs
12
+
13
+# setup files for testing
14
+test_expect_success "can create some files for testing" '
15
+ FILE1=$(echo foo | ipfs add -q) &&
16
+ FILE2=$(echo bar | ipfs add -q) &&
17
+ FILE3=$(echo baz | ipfs add -q) &&
18
+ mkdir stuff_test &&
19
+ echo cats > stuff_test/a &&
20
+ echo dogs > stuff_test/b &&
21
+ echo giraffes > stuff_test/c &&
22
+ DIR1=$(ipfs add -q stuff_test | tail -n1)
23
+'
24
+
25
+verify_path_exists() {
26
+ # simply running ls on a file should be a good 'check'
27
+ ipfs files ls $1
28
+}
29
+
30
+verify_dir_contents() {
31
+ dir=$1
32
+ shift
33
+ rm -f expected
34
+ touch expected
35
+ for e in $@
36
+ do
37
+ echo $e >> expected
38
+ done
39
+
40
+ test_expect_success "can list dir" '
41
+ ipfs files ls $dir > output
42
+ '
43
+
44
+ test_expect_success "dir entries look good" '
45
+ test_sort_cmp output expected
46
+ '
47
+}
48
+
49
+test_files_api() {
50
+ test_expect_success "can mkdir in root" '
51
+ ipfs files mkdir /cats
52
+ '
53
+
54
+ test_expect_success "directory was created" '
55
+ verify_path_exists /cats
56
+ '
57
+
58
+ test_expect_success "directory is empty" '
59
+ verify_dir_contents /cats
60
+ '
61
+
62
+ test_expect_success "can put files into directory" '
63
+ ipfs files cp /ipfs/$FILE1 /cats/file1
64
+ '
65
+
66
+ test_expect_success "file shows up in directory" '
67
+ verify_dir_contents /cats file1
68
+ '
69
+
70
+ test_expect_success "can read file" '
71
+ ipfs files read /cats/file1 > file1out
72
+ '
73
+
74
+ test_expect_success "output looks good" '
75
+ echo foo > expected &&
76
+ test_cmp file1out expected
77
+ '
78
+
79
+ test_expect_success "can put another file into root" '
80
+ ipfs files cp /ipfs/$FILE2 /file2
81
+ '
82
+
83
+ test_expect_success "file shows up in root" '
84
+ verify_dir_contents / file2 cats
85
+ '
86
+
87
+ test_expect_success "can read file" '
88
+ ipfs files read /file2 > file2out
89
+ '
90
+
91
+ test_expect_success "output looks good" '
92
+ echo bar > expected &&
93
+ test_cmp file2out expected
94
+ '
95
+
96
+ test_expect_success "can make deep directory" '
97
+ ipfs files mkdir -p /cats/this/is/a/dir
98
+ '
99
+
100
+ test_expect_success "directory was created correctly" '
101
+ verify_path_exists /cats/this/is/a/dir &&
102
+ verify_dir_contents /cats this file1 &&
103
+ verify_dir_contents /cats/this is &&
104
+ verify_dir_contents /cats/this/is a &&
105
+ verify_dir_contents /cats/this/is/a dir &&
106
+ verify_dir_contents /cats/this/is/a/dir
107
+ '
108
+
109
+ test_expect_success "can copy file into new dir" '
110
+ ipfs files cp /ipfs/$FILE3 /cats/this/is/a/dir/file3
111
+ '
112
+
113
+ test_expect_success "can read file" '
114
+ ipfs files read /cats/this/is/a/dir/file3 > output
115
+ '
116
+
117
+ test_expect_success "output looks good" '
118
+ echo baz > expected &&
119
+ test_cmp output expected
120
+ '
121
+
122
+ test_expect_success "file shows up in dir" '
123
+ verify_dir_contents /cats/this/is/a/dir file3
124
+ '
125
+
126
+ test_expect_success "can remove file" '
127
+ ipfs files rm /cats/this/is/a/dir/file3
128
+ '
129
+
130
+ test_expect_success "file no longer appears" '
131
+ verify_dir_contents /cats/this/is/a/dir
132
+ '
133
+
134
+ test_expect_success "can remove dir" '
135
+ ipfs files rm -r /cats/this/is/a/dir
136
+ '
137
+
138
+ test_expect_success "dir no longer appears" '
139
+ verify_dir_contents /cats/this/is/a
140
+ '
141
+
142
+ test_expect_success "can remove file from root" '
143
+ ipfs files rm /file2
144
+ '
145
+
146
+ test_expect_success "file no longer appears" '
147
+ verify_dir_contents / cats
148
+ '
149
+
150
+ # test read options
151
+
152
+ test_expect_success "read from offset works" '
153
+ ipfs files read -o 1 /cats/file1 > output
154
+ '
155
+
156
+ test_expect_success "output looks good" '
157
+ echo oo > expected &&
158
+ test_cmp output expected
159
+ '
160
+
161
+ test_expect_success "read with size works" '
162
+ ipfs files read -n 2 /cats/file1 > output
163
+ '
164
+
165
+ test_expect_success "output looks good" '
166
+ printf fo > expected &&
167
+ test_cmp output expected
168
+ '
169
+
170
+ # test write
171
+
172
+ test_expect_success "can write file" '
173
+ echo "ipfs rocks" > tmpfile &&
174
+ cat tmpfile | ipfs files write --create /cats/ipfs
175
+ '
176
+
177
+ test_expect_success "file was created" '
178
+ verify_dir_contents /cats ipfs file1 this
179
+ '
180
+
181
+ test_expect_success "can read file we just wrote" '
182
+ ipfs files read /cats/ipfs > output
183
+ '
184
+
185
+ test_expect_success "can write to offset" '
186
+ echo "is super cool" | ipfs files write -o 5 /cats/ipfs
187
+ '
188
+
189
+ test_expect_success "file looks correct" '
190
+ echo "ipfs is super cool" > expected &&
191
+ ipfs files read /cats/ipfs > output &&
192
+ test_cmp output expected
193
+ '
194
+
195
+ # test mv
196
+ test_expect_success "can mv dir" '
197
+ ipfs files mv /cats/this/is /cats/
198
+ '
199
+
200
+ test_expect_success "mv worked" '
201
+ verify_dir_contents /cats file1 ipfs this is &&
202
+ verify_dir_contents /cats/this
203
+ '
204
+
205
+ test_expect_success "cleanup, remove 'cats'" '
206
+ ipfs files rm -r /cats
207
+ '
208
+
209
+ test_expect_success "cleanup looks good" '
210
+ verify_dir_contents /
211
+ '
212
+}
213
+
214
+# test offline and online
215
+test_files_api
216
+test_launch_ipfs_daemon
217
+test_files_api
218
+test_kill_ipfs_daemon
219
+test_done