Add hidden file support to add
License: MIT Signed-off-by: Gaetan Voyer-Perrault <gatesvp@gmail.com>
gatesvp committed
Jul 6, 2015 at 02:38 UTC
e55a130b4909448f01ae547dd49cbfd754f4b7af
5 files changed
+187
-29
commands/files/is_hidden.go
new
+19
@@ -0,0 +1,19 @@
1
+// +build !windows
2
+
3
+package files
4
+
5
+import (
6
+ "path/filepath"
7
+ "strings"
8
+)
9
+
10
+func IsHidden(f File) bool {
11
+
12
+ fName := filepath.Base(f.FileName())
13
+
14
+ if strings.HasPrefix(fName, ".") && len(fName) > 1 {
15
+ return true
16
+ }
17
+
18
+ return false
19
+}
commands/files/is_hidden_windows.go
new
+29
@@ -0,0 +1,29 @@
1
+// +build windows
2
+
3
+package files
4
+
5
+import (
6
+ "path/filepath"
7
+ "strings"
8
+ "syscall"
9
+)
10
+
11
+func IsHidden(f File) bool {
12
+
13
+ fName := filepath.Base(f.FileName())
14
+
15
+ if strings.HasPrefix(fName, ".") && len(fName) > 1 {
16
+ return true
17
+ }
18
+
19
+ p, e := syscall.UTF16PtrFromString(f.FileName())
20
+ if e != nil {
21
+ return false
22
+ }
23
+
24
+ attrs, e := syscall.GetFileAttributes(p)
25
+ if e != nil {
26
+ return false
27
+ }
28
+ return attrs&syscall.FILE_ATTRIBUTE_HIDDEN != 0
29
+}
commands/files/serialfile.go
+1
-1
@@ -3,7 +3,7 @@ package files
3
import (
4
"io"
5
"os"
6
- fp "path"
6
+ fp "path/filepath"
7
"sort"
8
"syscall"
9
)
core/commands/add.go
+80
-28
@@ -27,9 +27,12 @@ var ErrDepthLimitExceeded = fmt.Errorf("depth limit exceeded")
27
const progressReaderIncrement = 1024 * 256
28
29
const (
30
+ quietOptionName = "quiet"
31
progressOptionName = "progress"
32
trickleOptionName = "trickle"
33
wrapOptionName = "wrap-with-directory"
34
+ hiddenOptionName = "hidden"
35
+ onlyHashOptionName = "only-hash"
36
)
37
38
type AddedObject struct {
@@ -54,14 +57,15 @@ remains to be implemented.
57
},
58
Options: []cmds.Option{
59
cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
57
- cmds.BoolOption("quiet", "q", "Write minimal output"),
60
+ cmds.BoolOption(quietOptionName, "q", "Write minimal output"),
61
cmds.BoolOption(progressOptionName, "p", "Stream progress data"),
59
- cmds.BoolOption(wrapOptionName, "w", "Wrap files with a directory object"),
62
cmds.BoolOption(trickleOptionName, "t", "Use trickle-dag format for dag generation"),
61
- cmds.BoolOption("only-hash", "n", "Only chunk and hash the specified content, don't write to disk"),
63
+ cmds.BoolOption(onlyHashOptionName, "n", "Only chunk and hash - do not write to disk"),
64
+ cmds.BoolOption(wrapOptionName, "w", "Wrap files with a directory object"),
65
+ cmds.BoolOption(hiddenOptionName, "Include files that are hidden"),
66
},
67
PreRun: func(req cmds.Request) error {
64
- if quiet, _, _ := req.Option("quiet").Bool(); quiet {
68
+ if quiet, _, _ := req.Option(quietOptionName).Bool(); quiet {
69
return nil
70
}
71
@@ -93,7 +97,8 @@ remains to be implemented.
97
progress, _, _ := req.Option(progressOptionName).Bool()
98
trickle, _, _ := req.Option(trickleOptionName).Bool()
99
wrap, _, _ := req.Option(wrapOptionName).Bool()
96
- hash, _, _ := req.Option("only-hash").Bool()
100
+ hash, _, _ := req.Option(onlyHashOptionName).Bool()
101
+ hidden, _, _ := req.Option(hiddenOptionName).Bool()
102
103
if hash {
104
nilnode, err := core.NewNodeBuilder().NilRepo().Build(n.Context())
@@ -120,7 +125,15 @@ remains to be implemented.
125
return
126
}
127
123
- rootnd, err := addFile(n, file, outChan, progress, wrap, trickle)
128
+ addParams := adder{
129
+ node: n,
130
+ out: outChan,
131
+ progress: progress,
132
+ wrap: wrap,
133
+ hidden: hidden,
134
+ trickle: trickle,
135
+ }
136
+ rootnd, err := addParams.addFile(file)
137
if err != nil {
138
res.SetError(err, cmds.ErrNormal)
139
return
@@ -230,6 +243,17 @@ remains to be implemented.
243
Type: AddedObject{},
244
}
245
246
+// Internal structure for holding the switches passed to the `add` call
247
+type adder struct {
248
+ node *core.IpfsNode
249
+ out chan interface{}
250
+ progress bool
251
+ wrap bool
252
+ hidden bool
253
+ trickle bool
254
+}
255
+
256
+// Perform the actual add & pin locally, outputting results to reader
257
func add(n *core.IpfsNode, reader io.Reader, useTrickle bool) (*dag.Node, error) {
258
var node *dag.Node
259
var err error
@@ -256,49 +280,56 @@ func add(n *core.IpfsNode, reader io.Reader, useTrickle bool) (*dag.Node, error)
280
return node, nil
281
}
282
259
-func addFile(n *core.IpfsNode, file files.File, out chan interface{}, progress bool, wrap bool, useTrickle bool) (*dag.Node, error) {
283
+// Add the given file while respecting the params.
284
+func (params *adder) addFile(file files.File) (*dag.Node, error) {
285
+ // Check if file is hidden
286
+ if fileIsHidden := files.IsHidden(file); fileIsHidden && !params.hidden {
287
+ log.Debugf("%s is hidden, skipping", file.FileName())
288
+ return nil, &hiddenFileError{file.FileName()}
289
+ }
290
+
291
+ // Check if "file" is actually a directory
292
if file.IsDirectory() {
261
- return addDir(n, file, out, progress, useTrickle)
293
+ return params.addDir(file)
294
}
295
296
// if the progress flag was specified, wrap the file so that we can send
297
// progress updates to the client (over the output channel)
298
var reader io.Reader = file
267
- if progress {
268
- reader = &progressReader{file: file, out: out}
299
+ if params.progress {
300
+ reader = &progressReader{file: file, out: params.out}
301
}
302
271
- if wrap {
272
- p, dagnode, err := coreunix.AddWrapped(n, reader, path.Base(file.FileName()))
303
+ if params.wrap {
304
+ p, dagnode, err := coreunix.AddWrapped(params.node, reader, path.Base(file.FileName()))
305
if err != nil {
306
return nil, err
307
}
276
- out <- &AddedObject{
308
+ params.out <- &AddedObject{
309
Hash: p,
310
Name: file.FileName(),
311
}
312
return dagnode, nil
313
}
314
283
- dagnode, err := add(n, reader, useTrickle)
315
+ dagnode, err := add(params.node, reader, params.trickle)
316
if err != nil {
317
return nil, err
318
}
319
320
log.Infof("adding file: %s", file.FileName())
289
- if err := outputDagnode(out, file.FileName(), dagnode); err != nil {
321
+ if err := outputDagnode(params.out, file.FileName(), dagnode); err != nil {
322
return nil, err
323
}
324
return dagnode, nil
325
}
326
295
-func addDir(n *core.IpfsNode, dir files.File, out chan interface{}, progress bool, useTrickle bool) (*dag.Node, error) {
296
- log.Infof("adding directory: %s", dir.FileName())
297
-
327
+func (params *adder) addDir(file files.File) (*dag.Node, error) {
328
tree := &dag.Node{Data: ft.FolderPBData()}
329
+ log.Infof("adding directory: %s", file.FileName())
330
331
for {
301
- file, err := dir.NextFile()
332
+ file, err := file.NextFile()
333
if err != nil && err != io.EOF {
334
return nil, err
335
}
@@ -306,30 +337,35 @@ func addDir(n *core.IpfsNode, dir files.File, out chan interface{}, progress boo
337
break
338
}
339
309
- node, err := addFile(n, file, out, progress, false, useTrickle)
310
- if err != nil {
340
+ node, err := params.addFile(file)
341
+ if _, ok := err.(*hiddenFileError); ok {
342
+ // hidden file error, set the node to nil for below
343
+ node = nil
344
+ } else if err != nil {
345
return nil, err
346
}
347
314
- _, name := path.Split(file.FileName())
348
+ if node != nil {
349
+ _, name := path.Split(file.FileName())
350
316
- err = tree.AddNodeLink(name, node)
317
- if err != nil {
318
- return nil, err
351
+ err = tree.AddNodeLink(name, node)
352
+ if err != nil {
353
+ return nil, err
354
+ }
355
}
356
}
357
322
- err := outputDagnode(out, dir.FileName(), tree)
358
+ err := outputDagnode(params.out, file.FileName(), tree)
359
if err != nil {
360
return nil, err
361
}
362
327
- k, err := n.DAG.Add(tree)
363
+ k, err := params.node.DAG.Add(tree)
364
if err != nil {
365
return nil, err
366
}
367
332
- n.Pinning.GetManual().PinWithMode(k, pin.Indirect)
368
+ params.node.Pinning.GetManual().PinWithMode(k, pin.Indirect)
369
370
return tree, nil
371
}
@@ -349,6 +385,22 @@ func outputDagnode(out chan interface{}, name string, dn *dag.Node) error {
385
return nil
386
}
387
388
+type hiddenFileError struct {
389
+ fileName string
390
+}
391
+
392
+func (e *hiddenFileError) Error() string {
393
+ return fmt.Sprintf("%s is a hidden file", e.fileName)
394
+}
395
+
396
+type ignoreFileError struct {
397
+ fileName string
398
+}
399
+
400
+func (e *ignoreFileError) Error() string {
401
+ return fmt.Sprintf("%s is an ignored file", e.fileName)
402
+}
403
+
404
type progressReader struct {
405
file files.File
406
out chan interface{}
test/sharness/t0042-add-skip.sh
new
+58
@@ -0,0 +1,58 @@
1
+#!/bin/sh
2
+#
3
+# Copyright (c) 2014 Christian Couder
4
+# MIT Licensed; see the LICENSE file in this repository.
5
+#
6
+
7
+test_description="Test add and cat commands"
8
+
9
+. lib/test-lib.sh
10
+
11
+test_add_skip() {
12
+
13
+ test_expect_success "'ipfs add -r' with hidden file succeeds" '
14
+ mkdir -p mountdir/planets/.asteroids &&
15
+ echo "Hello Mars" >mountdir/planets/mars.txt &&
16
+ echo "Hello Venus" >mountdir/planets/venus.txt &&
17
+ echo "Hello Pluto" >mountdir/planets/.pluto.txt &&
18
+ echo "Hello Charon" >mountdir/planets/.charon.txt &&
19
+ echo "Hello Ceres" >mountdir/planets/.asteroids/ceres.txt &&
20
+ echo "Hello Pallas" >mountdir/planets/.asteroids/pallas.txt &&
21
+ ipfs add -r mountdir/planets >actual
22
+ '
23
+
24
+ test_expect_success "'ipfs add -r' did not include . files" '
25
+ echo "added QmZy3khu7qf696i5HtkgL2NotsCZ8wzvNZJ1eUdA5n8KaV mountdir/planets/mars.txt
26
+added QmQnv4m3Q5512zgVtpbJ9z85osQrzZzGRn934AGh6iVEXz mountdir/planets/venus.txt
27
+added QmR8nD1Vzk5twWVC6oShTHvv7mMYkVh6dApCByBJyV2oj3 mountdir/planets" >expected
28
+ test_cmp expected actual
29
+ '
30
+
31
+ test_expect_success "'ipfs add -r --hidden' succeeds" '
32
+ ipfs add -r --hidden mountdir/planets >actual
33
+ '
34
+
35
+ test_expect_success "'ipfs add -r --hidden' did include . files" '
36
+ echo "added QmcAREBcjgnUpKfyFmUGnfajA1NQS5ydqRp7WfqZ6JF8Dx mountdir/planets/.asteroids/ceres.txt
37
+added QmZ5eaLybJ5GUZBNwy24AA9EEDTDpA4B8qXnuN3cGxu2uF mountdir/planets/.asteroids/pallas.txt
38
+added Qmf6rbs5GF85anDuoxpSAdtuZPM9D2Yt3HngzjUVSQ7kDV mountdir/planets/.asteroids
39
+added QmaowqjedBkUrMUXgzt9c2ZnAJncM9jpJtkFfgdFstGr5a mountdir/planets/.charon.txt
40
+added QmU4zFD5eJtRBsWC63AvpozM9Atiadg9kPVTuTrnCYJiNF mountdir/planets/.pluto.txt
41
+added QmZy3khu7qf696i5HtkgL2NotsCZ8wzvNZJ1eUdA5n8KaV mountdir/planets/mars.txt
42
+added QmQnv4m3Q5512zgVtpbJ9z85osQrzZzGRn934AGh6iVEXz mountdir/planets/venus.txt
43
+added QmetajtFdmzhWYodAsZoVZSiqpeJDAiaw2NwbM3xcWcpDj mountdir/planets" >expected &&
44
+ test_cmp expected actual
45
+ '
46
+
47
+}
48
+
49
+# should work offline
50
+test_init_ipfs
51
+test_add_skip
52
+
53
+# should work online
54
+test_launch_ipfs_daemon
55
+test_add_skip
56
+test_kill_ipfs_daemon
57
+
58
+test_done