@cryptotaxi247 / kubo / commits / a5db13f3a

original commands to original dir

Brian Tiger Chow committed Oct 29, 2014 at 00:05 UTC a5db13f3ae75da38b3145258f77696f22572fe01
13 files changed +734
core/commands/add.go new
+122
@@ -0,0 +1,122 @@
1 +package commands
2 +
3 +import (
4 + "errors"
5 + "fmt"
6 + "io"
7 + "io/ioutil"
8 + "os"
9 + "path/filepath"
10 +
11 + "github.com/jbenet/go-ipfs/core"
12 + "github.com/jbenet/go-ipfs/importer"
13 + dag "github.com/jbenet/go-ipfs/merkledag"
14 + ft "github.com/jbenet/go-ipfs/unixfs"
15 +)
16 +
17 +// Error indicating the max depth has been exceded.
18 +var ErrDepthLimitExceeded = fmt.Errorf("depth limit exceeded")
19 +
20 +// Add is a command that imports files and directories -- given as arguments -- into ipfs.
21 +func Add(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
22 + depth := 1
23 +
24 + // if recursive, set depth to reflect so
25 + if r, ok := opts["r"].(bool); r && ok {
26 + depth = -1
27 + }
28 +
29 + // add every path in args
30 + for _, path := range args {
31 +
32 + // Add the file
33 + _, err := AddPath(n, path, depth, out)
34 + if err != nil {
35 + if err == ErrDepthLimitExceeded && depth == 1 {
36 + err = errors.New("use -r to recursively add directories")
37 + }
38 + return fmt.Errorf("addFile error: %v", err)
39 + }
40 +
41 + }
42 + return nil
43 +}
44 +
45 +// AddPath adds a particular path to ipfs.
46 +func AddPath(n *core.IpfsNode, fpath string, depth int, out io.Writer) (*dag.Node, error) {
47 + if depth == 0 {
48 + return nil, ErrDepthLimitExceeded
49 + }
50 +
51 + fi, err := os.Stat(fpath)
52 + if err != nil {
53 + return nil, err
54 + }
55 +
56 + if fi.IsDir() {
57 + return addDir(n, fpath, depth, out)
58 + }
59 +
60 + return addFile(n, fpath, depth, out)
61 +}
62 +
63 +func addDir(n *core.IpfsNode, fpath string, depth int, out io.Writer) (*dag.Node, error) {
64 + tree := &dag.Node{Data: ft.FolderPBData()}
65 +
66 + files, err := ioutil.ReadDir(fpath)
67 + if err != nil {
68 + return nil, err
69 + }
70 +
71 + // construct nodes for containing files.
72 + for _, f := range files {
73 + fp := filepath.Join(fpath, f.Name())
74 + nd, err := AddPath(n, fp, depth-1, out)
75 + if err != nil {
76 + return nil, err
77 + }
78 +
79 + if err = tree.AddNodeLink(f.Name(), nd); err != nil {
80 + return nil, err
81 + }
82 + }
83 +
84 + log.Info("adding dir: %s", fpath)
85 +
86 + return tree, addNode(n, tree, fpath, out)
87 +}
88 +
89 +func addFile(n *core.IpfsNode, fpath string, depth int, out io.Writer) (*dag.Node, error) {
90 + root, err := importer.NewDagFromFile(fpath)
91 + if err != nil {
92 + return nil, err
93 + }
94 +
95 + log.Info("adding file: %s", fpath)
96 +
97 + for _, l := range root.Links {
98 + log.Info("adding subblock: %s %s", l.Name, l.Hash.B58String())
99 + }
100 +
101 + return root, addNode(n, root, fpath, out)
102 +}
103 +
104 +// addNode adds the node to the graph + local storage
105 +func addNode(n *core.IpfsNode, nd *dag.Node, fpath string, out io.Writer) error {
106 + // add the file to the graph + local storage
107 + err := n.DAG.AddRecursive(nd)
108 + if err != nil {
109 + return err
110 + }
111 +
112 + k, err := nd.Key()
113 + if err != nil {
114 + return err
115 + }
116 +
117 + // output that we've added this node
118 + fmt.Fprintf(out, "added %s %s\n", k, fpath)
119 +
120 + // ensure we keep it
121 + return n.Pinning.Pin(nd, true)
122 +}
core/commands/block.go new
+60
@@ -0,0 +1,60 @@
1 +package commands
2 +
3 +import (
4 + "fmt"
5 + "io"
6 + "io/ioutil"
7 + "os"
8 + "time"
9 +
10 + "code.google.com/p/go.net/context"
11 +
12 + mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
13 + "github.com/jbenet/go-ipfs/blocks"
14 + "github.com/jbenet/go-ipfs/core"
15 + u "github.com/jbenet/go-ipfs/util"
16 +)
17 +
18 +// BlockGet retrives a raw ipfs block from the node's BlockService
19 +func BlockGet(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
20 +
21 + if !u.IsValidHash(args[0]) {
22 + return fmt.Errorf("block get: not a valid hash")
23 + }
24 +
25 + h, err := mh.FromB58String(args[0])
26 + if err != nil {
27 + return fmt.Errorf("block get: %v", err)
28 + }
29 +
30 + k := u.Key(h)
31 + log.Debug("BlockGet key: '%q'", k)
32 + ctx, _ := context.WithTimeout(context.TODO(), time.Second*5)
33 + b, err := n.Blocks.GetBlock(ctx, k)
34 + if err != nil {
35 + return fmt.Errorf("block get: %v", err)
36 + }
37 +
38 + _, err = out.Write(b.Data)
39 + return err
40 +}
41 +
42 +// BlockPut reads everything from conn and saves the data to the nodes BlockService
43 +func BlockPut(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
44 + // TODO: this should read from an io.Reader arg
45 + data, err := ioutil.ReadAll(os.Stdin)
46 + if err != nil {
47 + return err
48 + }
49 +
50 + b := blocks.NewBlock(data)
51 + log.Debug("BlockPut key: '%q'", b.Key())
52 +
53 + k, err := n.Blocks.AddBlock(b)
54 + if err != nil {
55 + return err
56 + }
57 + fmt.Fprintf(out, "added as '%s'\n", k)
58 +
59 + return nil
60 +}
core/commands/cat.go new
+29
@@ -0,0 +1,29 @@
1 +package commands
2 +
3 +import (
4 + "fmt"
5 + "io"
6 +
7 + "github.com/jbenet/go-ipfs/core"
8 + uio "github.com/jbenet/go-ipfs/unixfs/io"
9 +)
10 +
11 +func Cat(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
12 + for _, fn := range args {
13 + dagnode, err := n.Resolver.ResolvePath(fn)
14 + if err != nil {
15 + return fmt.Errorf("catFile error: %v", err)
16 + }
17 +
18 + read, err := uio.NewDagReader(dagnode, n.DAG)
19 + if err != nil {
20 + return fmt.Errorf("cat error: %v", err)
21 + }
22 +
23 + _, err = io.Copy(out, read)
24 + if err != nil {
25 + return fmt.Errorf("cat error: %v", err)
26 + }
27 + }
28 + return nil
29 +}
core/commands/commands.go new
+12
@@ -0,0 +1,12 @@
1 +package commands
2 +
3 +import (
4 + "io"
5 +
6 + "github.com/jbenet/go-ipfs/core"
7 + u "github.com/jbenet/go-ipfs/util"
8 +)
9 +
10 +var log = u.Logger("commands")
11 +
12 +type CmdFunc func(*core.IpfsNode, []string, map[string]interface{}, io.Writer) error
core/commands/diag.go new
+49
@@ -0,0 +1,49 @@
1 +package commands
2 +
3 +import (
4 + "encoding/json"
5 + "errors"
6 + "fmt"
7 + "io"
8 + "time"
9 +
10 + "github.com/jbenet/go-ipfs/core"
11 + diagn "github.com/jbenet/go-ipfs/diagnostics"
12 +)
13 +
14 +func PrintDiagnostics(info []*diagn.DiagInfo, out io.Writer) {
15 + for _, i := range info {
16 + fmt.Fprintf(out, "Peer: %s\n", i.ID)
17 + fmt.Fprintf(out, "\tUp for: %s\n", i.LifeSpan.String())
18 + fmt.Fprintf(out, "\tConnected To:\n")
19 + for _, c := range i.Connections {
20 + fmt.Fprintf(out, "\t%s\n\t\tLatency = %s\n", c.ID, c.Latency.String())
21 + }
22 + fmt.Fprintln(out)
23 + }
24 +
25 +}
26 +
27 +func Diag(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
28 + if n.Diagnostics == nil {
29 + return errors.New("Cannot run diagnostic in offline mode!")
30 + }
31 + info, err := n.Diagnostics.GetDiagnostic(time.Second * 20)
32 + if err != nil {
33 + return err
34 + }
35 + raw, ok := opts["raw"].(bool)
36 + if !ok {
37 + return errors.New("incorrect value to parameter 'raw'")
38 + }
39 + if raw {
40 + enc := json.NewEncoder(out)
41 + err = enc.Encode(info)
42 + if err != nil {
43 + return err
44 + }
45 + } else {
46 + PrintDiagnostics(info, out)
47 + }
48 + return nil
49 +}
core/commands/log.go new
+18
@@ -0,0 +1,18 @@
1 +package commands
2 +
3 +import (
4 + "io"
5 +
6 + "github.com/jbenet/go-ipfs/core"
7 + u "github.com/jbenet/go-ipfs/util"
8 +)
9 +
10 +// Log changes the log level of a subsystem
11 +func Log(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
12 + if err := u.SetLogLevel(args[0], args[1]); err != nil {
13 + return err
14 + }
15 +
16 + log.Info("Changed LogLevel of %q to %q", args[0], args[1])
17 + return nil
18 +}
core/commands/ls.go new
+22
@@ -0,0 +1,22 @@
1 +package commands
2 +
3 +import (
4 + "fmt"
5 + "io"
6 +
7 + "github.com/jbenet/go-ipfs/core"
8 +)
9 +
10 +func Ls(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
11 + for _, fn := range args {
12 + dagnode, err := n.Resolver.ResolvePath(fn)
13 + if err != nil {
14 + return fmt.Errorf("ls error: %v", err)
15 + }
16 +
17 + for _, link := range dagnode.Links {
18 + fmt.Fprintf(out, "%s %d %s\n", link.Hash.B58String(), link.Size, link.Name)
19 + }
20 + }
21 + return nil
22 +}
core/commands/object.go new
+135
@@ -0,0 +1,135 @@
1 +package commands
2 +
3 +import (
4 + "bytes"
5 + "encoding/json"
6 + "errors"
7 + "fmt"
8 + "io"
9 + "io/ioutil"
10 + "os"
11 +
12 + "github.com/jbenet/go-ipfs/core"
13 + dag "github.com/jbenet/go-ipfs/merkledag"
14 +)
15 +
16 +// ObjectData takes a key string from args and writes out the raw bytes of that node (if there is one)
17 +func ObjectData(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
18 + dagnode, err := n.Resolver.ResolvePath(args[0])
19 + if err != nil {
20 + return fmt.Errorf("objectData error: %v", err)
21 + }
22 + log.Debug("objectData: found dagnode %q (# of bytes: %d - # links: %d)", args[0], len(dagnode.Data), len(dagnode.Links))
23 +
24 + _, err = io.Copy(out, bytes.NewReader(dagnode.Data))
25 + return err
26 +}
27 +
28 +// ObjectLinks takes a key string from args and lists the links it points to
29 +func ObjectLinks(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
30 + dagnode, err := n.Resolver.ResolvePath(args[0])
31 + if err != nil {
32 + return fmt.Errorf("objectLinks error: %v", err)
33 + }
34 + log.Debug("ObjectLinks: found dagnode %q (# of bytes: %d - # links: %d)", args[0], len(dagnode.Data), len(dagnode.Links))
35 +
36 + for _, link := range dagnode.Links {
37 + _, err = fmt.Fprintf(out, "%s %d %q\n", link.Hash.B58String(), link.Size, link.Name)
38 + if err != nil {
39 + break
40 + }
41 + }
42 +
43 + return err
44 +}
45 +
46 +// ErrUnknownObjectEnc is returned if a invalid encoding is supplied
47 +var ErrUnknownObjectEnc = errors.New("unknown object encoding")
48 +
49 +type objectEncoding string
50 +
51 +const (
52 + objectEncodingJSON objectEncoding = "json"
53 + objectEncodingProtobuf = "protobuf"
54 +)
55 +
56 +func getObjectEnc(o interface{}) objectEncoding {
57 + v, ok := o.(string)
58 + if !ok {
59 + // chosen as default because it's human readable
60 + log.Warning("option is not a string - falling back to json")
61 + return objectEncodingJSON
62 + }
63 +
64 + return objectEncoding(v)
65 +}
66 +
67 +// ObjectGet takes a key string from args and a format option and serializes the dagnode to that format
68 +func ObjectGet(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
69 + dagnode, err := n.Resolver.ResolvePath(args[0])
70 + if err != nil {
71 + return fmt.Errorf("ObjectGet error: %v", err)
72 + }
73 + log.Debug("objectGet: found dagnode %q (# of bytes: %d - # links: %d)", args[0], len(dagnode.Data), len(dagnode.Links))
74 +
75 + // sadly all encodings dont implement a common interface
76 + var data []byte
77 + switch getObjectEnc(opts["encoding"]) {
78 + case objectEncodingJSON:
79 + data, err = json.MarshalIndent(dagnode, "", " ")
80 +
81 + case objectEncodingProtobuf:
82 + data, err = dagnode.Marshal()
83 +
84 + default:
85 + return ErrUnknownObjectEnc
86 + }
87 +
88 + if err != nil {
89 + return fmt.Errorf("ObjectGet error: %v", err)
90 + }
91 +
92 + _, err = io.Copy(out, bytes.NewReader(data))
93 + return err
94 +}
95 +
96 +// ErrObjectTooLarge is returned when too much data was read from stdin. current limit 512k
97 +var ErrObjectTooLarge = errors.New("input object was too large. limit is 512kbytes")
98 +
99 +const inputLimit = 512 * 1024
100 +
101 +// ObjectPut takes a format option, serilizes bytes from stdin and updates the dag with that data
102 +func ObjectPut(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
103 + var (
104 + dagnode *dag.Node
105 + data []byte
106 + err error
107 + )
108 +
109 + data, err = ioutil.ReadAll(io.LimitReader(os.Stdin, inputLimit+10))
110 + if err != nil {
111 + return fmt.Errorf("ObjectPut error: %v", err)
112 + }
113 +
114 + if len(data) >= inputLimit {
115 + return ErrObjectTooLarge
116 + }
117 +
118 + switch getObjectEnc(opts["encoding"]) {
119 + case objectEncodingJSON:
120 + dagnode = new(dag.Node)
121 + err = json.Unmarshal(data, dagnode)
122 +
123 + case objectEncodingProtobuf:
124 + dagnode, err = dag.Decoded(data)
125 +
126 + default:
127 + return ErrUnknownObjectEnc
128 + }
129 +
130 + if err != nil {
131 + return fmt.Errorf("ObjectPut error: %v", err)
132 + }
133 +
134 + return addNode(n, dagnode, "stdin", out)
135 +}
core/commands/pin.go new
+58
@@ -0,0 +1,58 @@
1 +package commands
2 +
3 +import (
4 + "fmt"
5 + "io"
6 +
7 + "github.com/jbenet/go-ipfs/core"
8 +)
9 +
10 +func Pin(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
11 +
12 + // set recursive flag
13 + recursive, _ := opts["r"].(bool) // false if cast fails.
14 +
15 + // if recursive, set depth flag
16 + depth := 1 // default (non recursive)
17 + if d, ok := opts["d"].(int); recursive && ok {
18 + depth = d
19 + }
20 + if depth < -1 {
21 + return fmt.Errorf("ipfs pin: called with invalid depth: %v", depth)
22 + }
23 +
24 + fmt.Printf("recursive, depth: %v, %v\n", recursive, depth)
25 +
26 + for _, fn := range args {
27 + dagnode, err := n.Resolver.ResolvePath(fn)
28 + if err != nil {
29 + return fmt.Errorf("pin error: %v", err)
30 + }
31 +
32 + err = n.Pinning.Pin(dagnode, recursive)
33 + if err != nil {
34 + return fmt.Errorf("pin: %v", err)
35 + }
36 + }
37 + return n.Pinning.Flush()
38 +}
39 +
40 +func Unpin(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
41 +
42 + // set recursive flag
43 + recursive, _ := opts["r"].(bool) // false if cast fails.
44 +
45 + for _, fn := range args {
46 + dagnode, err := n.Resolver.ResolvePath(fn)
47 + if err != nil {
48 + return fmt.Errorf("pin error: %v", err)
49 + }
50 +
51 + k, _ := dagnode.Key()
52 + err = n.Pinning.Unpin(k, recursive)
53 + if err != nil {
54 + return fmt.Errorf("pin: %v", err)
55 + }
56 + }
57 + return n.Pinning.Flush()
58 +}
core/commands/publish.go new
+53
@@ -0,0 +1,53 @@
1 +package commands
2 +
3 +import (
4 + "errors"
5 + "fmt"
6 + "io"
7 +
8 + "github.com/jbenet/go-ipfs/core"
9 + u "github.com/jbenet/go-ipfs/util"
10 +
11 + nsys "github.com/jbenet/go-ipfs/namesys"
12 +)
13 +
14 +func Publish(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
15 + log.Debug("Begin Publish")
16 +
17 + if n.Identity == nil {
18 + return errors.New("Identity not loaded!")
19 + }
20 +
21 + // name := ""
22 + ref := ""
23 +
24 + switch len(args) {
25 + case 2:
26 + // name = args[0]
27 + ref = args[1]
28 + return errors.New("keychains not yet implemented")
29 + case 1:
30 + // name = n.Identity.ID.String()
31 + ref = args[0]
32 +
33 + default:
34 + return fmt.Errorf("Publish expects 1 or 2 args; got %d.", len(args))
35 + }
36 +
37 + // later, n.Keychain.Get(name).PrivKey
38 + k := n.Identity.PrivKey()
39 +
40 + pub := nsys.NewRoutingPublisher(n.Routing)
41 + err := pub.Publish(k, ref)
42 + if err != nil {
43 + return err
44 + }
45 +
46 + hash, err := k.GetPublic().Hash()
47 + if err != nil {
48 + return err
49 + }
50 + fmt.Fprintf(out, "published name %s to %s\n", u.Key(hash), ref)
51 +
52 + return nil
53 +}
core/commands/refs.go new
+65
@@ -0,0 +1,65 @@
1 +package commands
2 +
3 +import (
4 + "io"
5 +
6 + mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
7 + "github.com/jbenet/go-ipfs/core"
8 + mdag "github.com/jbenet/go-ipfs/merkledag"
9 + u "github.com/jbenet/go-ipfs/util"
10 +)
11 +
12 +func Refs(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
13 + unique, ok := opts["u"].(bool)
14 + if !ok {
15 + unique = false
16 + }
17 +
18 + recursive, ok := opts["r"].(bool)
19 + if !ok {
20 + recursive = false
21 + }
22 +
23 + var refsSeen map[u.Key]bool
24 + if unique {
25 + refsSeen = make(map[u.Key]bool)
26 + }
27 +
28 + for _, fn := range args {
29 + nd, err := n.Resolver.ResolvePath(fn)
30 + if err != nil {
31 + return err
32 + }
33 +
34 + printRefs(n, nd, refsSeen, recursive)
35 + }
36 + return nil
37 +}
38 +
39 +func printRefs(n *core.IpfsNode, nd *mdag.Node, refSeen map[u.Key]bool, recursive bool) {
40 + for _, link := range nd.Links {
41 + printRef(link.Hash, refSeen)
42 +
43 + if recursive {
44 + nd, err := n.DAG.Get(u.Key(link.Hash))
45 + if err != nil {
46 + log.Errorf("error: cannot retrieve %s (%s)", link.Hash.B58String(), err)
47 + return
48 + }
49 +
50 + printRefs(n, nd, refSeen, recursive)
51 + }
52 + }
53 +}
54 +
55 +func printRef(h mh.Multihash, refsSeen map[u.Key]bool) {
56 + if refsSeen != nil {
57 + _, found := refsSeen[u.Key(h)]
58 + if found {
59 + return
60 + }
61 + refsSeen[u.Key(h)] = true
62 + }
63 +
64 + u.POut("%s\n", h.B58String())
65 +}
core/commands/resolve.go new
+35
@@ -0,0 +1,35 @@
1 +package commands
2 +
3 +import (
4 + "errors"
5 + "fmt"
6 + "io"
7 +
8 + "github.com/jbenet/go-ipfs/core"
9 +)
10 +
11 +func Resolve(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
12 +
13 + name := ""
14 +
15 + switch len(args) {
16 + case 1:
17 + name = args[0]
18 + case 0:
19 + if n.Identity == nil {
20 + return errors.New("Identity not loaded!")
21 + }
22 + name = n.Identity.ID().String()
23 +
24 + default:
25 + return fmt.Errorf("Publish expects 1 or 2 args; got %d.", len(args))
26 + }
27 +
28 + res, err := n.Namesys.Resolve(name)
29 + if err != nil {
30 + return err
31 + }
32 +
33 + fmt.Fprintf(out, "%s\n", res)
34 + return nil
35 +}
core/commands/update.go new
+76
@@ -0,0 +1,76 @@
1 +package commands
2 +
3 +import (
4 + "errors"
5 + "fmt"
6 + "io"
7 + "os"
8 +
9 + "github.com/jbenet/go-ipfs/core"
10 + "github.com/jbenet/go-ipfs/updates"
11 +)
12 +
13 +// UpdateApply applys an update of the ipfs binary and shuts down the node if successful
14 +func UpdateApply(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
15 + fmt.Fprintln(out, "Current Version:", updates.Version)
16 + u, err := updates.CheckForUpdate()
17 + if err != nil {
18 + return err
19 + }
20 +
21 + if u == nil {
22 + fmt.Fprintln(out, "No update available")
23 + return nil
24 + }
25 + fmt.Fprintln(out, "New Version:", u.Version)
26 +
27 + _, onDaemon := opts["onDaemon"]
28 + force := opts["force"].(bool)
29 + if onDaemon && !force {
30 + return fmt.Errorf(`Error: update must stop running ipfs service.
31 +You may want to abort the update, or shut the service down manually.
32 +To shut it down automatically, run:
33 +
34 + ipfs update --force
35 +`)
36 + }
37 +
38 + if err = updates.Apply(u); err != nil {
39 + fmt.Fprint(out, err.Error())
40 + return fmt.Errorf("Couldn't apply update: %v", err)
41 + }
42 +
43 + fmt.Fprintln(out, "Updated applied!")
44 + if onDaemon {
45 + if force {
46 + fmt.Fprintln(out, "Shutting down ipfs service.")
47 + os.Exit(1) // is there a cleaner shutdown routine?
48 + } else {
49 + fmt.Fprintln(out, "You can now restart the ipfs service.")
50 + }
51 + }
52 +
53 + return nil
54 +}
55 +
56 +// UpdateCheck checks wether there is an update available
57 +func UpdateCheck(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
58 + fmt.Fprintln(out, "Current Version:", updates.Version)
59 + u, err := updates.CheckForUpdate()
60 + if err != nil {
61 + return err
62 + }
63 +
64 + if u == nil {
65 + fmt.Fprintln(out, "No update available")
66 + return nil
67 + }
68 +
69 + fmt.Fprintln(out, "New Version:", u.Version)
70 + return nil
71 +}
72 +
73 +// UpdateLog lists the version available online
74 +func UpdateLog(n *core.IpfsNode, args []string, opts map[string]interface{}, out io.Writer) error {
75 + return errors.New("Not yet implemented")
76 +}