@cryptotaxi247 / kubo / commits / 6faa70ee5

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