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:
19
commands/files/is_hidden.go
Normal file
19
commands/files/is_hidden.go
Normal 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
|
||||||
|
}
|
29
commands/files/is_hidden_windows.go
Normal file
29
commands/files/is_hidden_windows.go
Normal 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
|
||||||
|
}
|
@ -3,7 +3,7 @@ package files
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
fp "path"
|
fp "path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
@ -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
58
test/sharness/t0042-add-skip.sh
Executable 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
|
Reference in New Issue
Block a user