1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-06-24 22:38:27 +08:00

Merge pull request #1460 from ipfs/hidden-split

Add hidden file support to add
This commit is contained in:
Juan Batiz-Benet
2015-07-09 05:16:26 -07:00
5 changed files with 187 additions and 29 deletions

View File

@ -0,0 +1,19 @@
// +build !windows
package files
import (
"path/filepath"
"strings"
)
func IsHidden(f File) bool {
fName := filepath.Base(f.FileName())
if strings.HasPrefix(fName, ".") && len(fName) > 1 {
return true
}
return false
}

View File

@ -0,0 +1,29 @@
// +build windows
package files
import (
"path/filepath"
"strings"
"syscall"
)
func IsHidden(f File) bool {
fName := filepath.Base(f.FileName())
if strings.HasPrefix(fName, ".") && len(fName) > 1 {
return true
}
p, e := syscall.UTF16PtrFromString(f.FileName())
if e != nil {
return false
}
attrs, e := syscall.GetFileAttributes(p)
if e != nil {
return false
}
return attrs&syscall.FILE_ATTRIBUTE_HIDDEN != 0
}

View File

@ -3,7 +3,7 @@ package files
import ( import (
"io" "io"
"os" "os"
fp "path" fp "path/filepath"
"sort" "sort"
"syscall" "syscall"
) )

View File

@ -27,9 +27,12 @@ var ErrDepthLimitExceeded = fmt.Errorf("depth limit exceeded")
const progressReaderIncrement = 1024 * 256 const progressReaderIncrement = 1024 * 256
const ( const (
quietOptionName = "quiet"
progressOptionName = "progress" progressOptionName = "progress"
trickleOptionName = "trickle" trickleOptionName = "trickle"
wrapOptionName = "wrap-with-directory" wrapOptionName = "wrap-with-directory"
hiddenOptionName = "hidden"
onlyHashOptionName = "only-hash"
) )
type AddedObject struct { type AddedObject struct {
@ -54,14 +57,15 @@ remains to be implemented.
}, },
Options: []cmds.Option{ Options: []cmds.Option{
cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive) cmds.OptionRecursivePath, // a builtin option that allows recursive paths (-r, --recursive)
cmds.BoolOption("quiet", "q", "Write minimal output"), cmds.BoolOption(quietOptionName, "q", "Write minimal output"),
cmds.BoolOption(progressOptionName, "p", "Stream progress data"), cmds.BoolOption(progressOptionName, "p", "Stream progress data"),
cmds.BoolOption(wrapOptionName, "w", "Wrap files with a directory object"),
cmds.BoolOption(trickleOptionName, "t", "Use trickle-dag format for dag generation"), cmds.BoolOption(trickleOptionName, "t", "Use trickle-dag format for dag generation"),
cmds.BoolOption("only-hash", "n", "Only chunk and hash the specified content, don't write to disk"), cmds.BoolOption(onlyHashOptionName, "n", "Only chunk and hash - do not write to disk"),
cmds.BoolOption(wrapOptionName, "w", "Wrap files with a directory object"),
cmds.BoolOption(hiddenOptionName, "Include files that are hidden"),
}, },
PreRun: func(req cmds.Request) error { PreRun: func(req cmds.Request) error {
if quiet, _, _ := req.Option("quiet").Bool(); quiet { if quiet, _, _ := req.Option(quietOptionName).Bool(); quiet {
return nil return nil
} }
@ -93,7 +97,8 @@ remains to be implemented.
progress, _, _ := req.Option(progressOptionName).Bool() progress, _, _ := req.Option(progressOptionName).Bool()
trickle, _, _ := req.Option(trickleOptionName).Bool() trickle, _, _ := req.Option(trickleOptionName).Bool()
wrap, _, _ := req.Option(wrapOptionName).Bool() wrap, _, _ := req.Option(wrapOptionName).Bool()
hash, _, _ := req.Option("only-hash").Bool() hash, _, _ := req.Option(onlyHashOptionName).Bool()
hidden, _, _ := req.Option(hiddenOptionName).Bool()
if hash { if hash {
nilnode, err := core.NewNodeBuilder().NilRepo().Build(n.Context()) nilnode, err := core.NewNodeBuilder().NilRepo().Build(n.Context())
@ -120,7 +125,15 @@ remains to be implemented.
return return
} }
rootnd, err := addFile(n, file, outChan, progress, wrap, trickle) addParams := adder{
node: n,
out: outChan,
progress: progress,
wrap: wrap,
hidden: hidden,
trickle: trickle,
}
rootnd, err := addParams.addFile(file)
if err != nil { if err != nil {
res.SetError(err, cmds.ErrNormal) res.SetError(err, cmds.ErrNormal)
return return
@ -230,6 +243,17 @@ remains to be implemented.
Type: AddedObject{}, Type: AddedObject{},
} }
// Internal structure for holding the switches passed to the `add` call
type adder struct {
node *core.IpfsNode
out chan interface{}
progress bool
wrap bool
hidden bool
trickle bool
}
// Perform the actual add & pin locally, outputting results to reader
func add(n *core.IpfsNode, reader io.Reader, useTrickle bool) (*dag.Node, error) { func add(n *core.IpfsNode, reader io.Reader, useTrickle bool) (*dag.Node, error) {
var node *dag.Node var node *dag.Node
var err error var err error
@ -256,49 +280,56 @@ func add(n *core.IpfsNode, reader io.Reader, useTrickle bool) (*dag.Node, error)
return node, nil return node, nil
} }
func addFile(n *core.IpfsNode, file files.File, out chan interface{}, progress bool, wrap bool, useTrickle bool) (*dag.Node, error) { // Add the given file while respecting the params.
func (params *adder) addFile(file files.File) (*dag.Node, error) {
// Check if file is hidden
if fileIsHidden := files.IsHidden(file); fileIsHidden && !params.hidden {
log.Debugf("%s is hidden, skipping", file.FileName())
return nil, &hiddenFileError{file.FileName()}
}
// Check if "file" is actually a directory
if file.IsDirectory() { if file.IsDirectory() {
return addDir(n, file, out, progress, useTrickle) return params.addDir(file)
} }
// if the progress flag was specified, wrap the file so that we can send // if the progress flag was specified, wrap the file so that we can send
// progress updates to the client (over the output channel) // progress updates to the client (over the output channel)
var reader io.Reader = file var reader io.Reader = file
if progress { if params.progress {
reader = &progressReader{file: file, out: out} reader = &progressReader{file: file, out: params.out}
} }
if wrap { if params.wrap {
p, dagnode, err := coreunix.AddWrapped(n, reader, path.Base(file.FileName())) p, dagnode, err := coreunix.AddWrapped(params.node, reader, path.Base(file.FileName()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
out <- &AddedObject{ params.out <- &AddedObject{
Hash: p, Hash: p,
Name: file.FileName(), Name: file.FileName(),
} }
return dagnode, nil return dagnode, nil
} }
dagnode, err := add(n, reader, useTrickle) dagnode, err := add(params.node, reader, params.trickle)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Infof("adding file: %s", file.FileName()) log.Infof("adding file: %s", file.FileName())
if err := outputDagnode(out, file.FileName(), dagnode); err != nil { if err := outputDagnode(params.out, file.FileName(), dagnode); err != nil {
return nil, err return nil, err
} }
return dagnode, nil return dagnode, nil
} }
func addDir(n *core.IpfsNode, dir files.File, out chan interface{}, progress bool, useTrickle bool) (*dag.Node, error) { func (params *adder) addDir(file files.File) (*dag.Node, error) {
log.Infof("adding directory: %s", dir.FileName())
tree := &dag.Node{Data: ft.FolderPBData()} tree := &dag.Node{Data: ft.FolderPBData()}
log.Infof("adding directory: %s", file.FileName())
for { for {
file, err := dir.NextFile() file, err := file.NextFile()
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return nil, err return nil, err
} }
@ -306,11 +337,15 @@ func addDir(n *core.IpfsNode, dir files.File, out chan interface{}, progress boo
break break
} }
node, err := addFile(n, file, out, progress, false, useTrickle) node, err := params.addFile(file)
if err != nil { if _, ok := err.(*hiddenFileError); ok {
// hidden file error, set the node to nil for below
node = nil
} else if err != nil {
return nil, err return nil, err
} }
if node != nil {
_, name := path.Split(file.FileName()) _, name := path.Split(file.FileName())
err = tree.AddNodeLink(name, node) err = tree.AddNodeLink(name, node)
@ -318,18 +353,19 @@ func addDir(n *core.IpfsNode, dir files.File, out chan interface{}, progress boo
return nil, err return nil, err
} }
} }
}
err := outputDagnode(out, dir.FileName(), tree) err := outputDagnode(params.out, file.FileName(), tree)
if err != nil { if err != nil {
return nil, err return nil, err
} }
k, err := n.DAG.Add(tree) k, err := params.node.DAG.Add(tree)
if err != nil { if err != nil {
return nil, err return nil, err
} }
n.Pinning.GetManual().PinWithMode(k, pin.Indirect) params.node.Pinning.GetManual().PinWithMode(k, pin.Indirect)
return tree, nil return tree, nil
} }
@ -349,6 +385,22 @@ func outputDagnode(out chan interface{}, name string, dn *dag.Node) error {
return nil return nil
} }
type hiddenFileError struct {
fileName string
}
func (e *hiddenFileError) Error() string {
return fmt.Sprintf("%s is a hidden file", e.fileName)
}
type ignoreFileError struct {
fileName string
}
func (e *ignoreFileError) Error() string {
return fmt.Sprintf("%s is an ignored file", e.fileName)
}
type progressReader struct { type progressReader struct {
file files.File file files.File
out chan interface{} out chan interface{}

58
test/sharness/t0042-add-skip.sh Executable file
View File

@ -0,0 +1,58 @@
#!/bin/sh
#
# Copyright (c) 2014 Christian Couder
# MIT Licensed; see the LICENSE file in this repository.
#
test_description="Test add and cat commands"
. lib/test-lib.sh
test_add_skip() {
test_expect_success "'ipfs add -r' with hidden file succeeds" '
mkdir -p mountdir/planets/.asteroids &&
echo "Hello Mars" >mountdir/planets/mars.txt &&
echo "Hello Venus" >mountdir/planets/venus.txt &&
echo "Hello Pluto" >mountdir/planets/.pluto.txt &&
echo "Hello Charon" >mountdir/planets/.charon.txt &&
echo "Hello Ceres" >mountdir/planets/.asteroids/ceres.txt &&
echo "Hello Pallas" >mountdir/planets/.asteroids/pallas.txt &&
ipfs add -r mountdir/planets >actual
'
test_expect_success "'ipfs add -r' did not include . files" '
echo "added QmZy3khu7qf696i5HtkgL2NotsCZ8wzvNZJ1eUdA5n8KaV mountdir/planets/mars.txt
added QmQnv4m3Q5512zgVtpbJ9z85osQrzZzGRn934AGh6iVEXz mountdir/planets/venus.txt
added QmR8nD1Vzk5twWVC6oShTHvv7mMYkVh6dApCByBJyV2oj3 mountdir/planets" >expected
test_cmp expected actual
'
test_expect_success "'ipfs add -r --hidden' succeeds" '
ipfs add -r --hidden mountdir/planets >actual
'
test_expect_success "'ipfs add -r --hidden' did include . files" '
echo "added QmcAREBcjgnUpKfyFmUGnfajA1NQS5ydqRp7WfqZ6JF8Dx mountdir/planets/.asteroids/ceres.txt
added QmZ5eaLybJ5GUZBNwy24AA9EEDTDpA4B8qXnuN3cGxu2uF mountdir/planets/.asteroids/pallas.txt
added Qmf6rbs5GF85anDuoxpSAdtuZPM9D2Yt3HngzjUVSQ7kDV mountdir/planets/.asteroids
added QmaowqjedBkUrMUXgzt9c2ZnAJncM9jpJtkFfgdFstGr5a mountdir/planets/.charon.txt
added QmU4zFD5eJtRBsWC63AvpozM9Atiadg9kPVTuTrnCYJiNF mountdir/planets/.pluto.txt
added QmZy3khu7qf696i5HtkgL2NotsCZ8wzvNZJ1eUdA5n8KaV mountdir/planets/mars.txt
added QmQnv4m3Q5512zgVtpbJ9z85osQrzZzGRn934AGh6iVEXz mountdir/planets/venus.txt
added QmetajtFdmzhWYodAsZoVZSiqpeJDAiaw2NwbM3xcWcpDj mountdir/planets" >expected &&
test_cmp expected actual
'
}
# should work offline
test_init_ipfs
test_add_skip
# should work online
test_launch_ipfs_daemon
test_add_skip
test_kill_ipfs_daemon
test_done