Skip to content

Commit 59f4fd8

Browse files
committed
[CLI] Implement cp
Summary: implement uploaing a file/folder implement download of a folder add a TODO to redo cp fix the call of c.Upload FindNode must ignore extra slashes fixes T6 Test Plan: Travis-CI must be green. Try `arc help cp` Reviewers: #go_amazon_cloud_drive, kalbasit Reviewed By: #go_amazon_cloud_drive, kalbasit Maniphest Tasks: T6 Differential Revision: http://phabricator.nasreddine.com/D14
1 parent 30d1fb5 commit 59f4fd8

File tree

9 files changed

+215
-18
lines changed

9 files changed

+215
-18
lines changed

cli/app.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import (
88
"gopkg.in/acd.v0/internal/log"
99
)
1010

11+
// TODO(kalbasit): I do not like this API code not even a bit.
12+
// a) I used codegangsta/cli wrong or overthought it.
13+
// b) codegangsta/cli is not the right library for this project.
14+
// This entire package should be re-written and TESTED!.
15+
1116
var (
1217
commands []cli.Command
1318
acdClient *acd.Client

cli/cp.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"path"
8+
"strings"
9+
10+
"gopkg.in/acd.v0/internal/constants"
11+
"gopkg.in/acd.v0/internal/log"
12+
13+
"github.com/codegangsta/cli"
14+
)
15+
16+
var (
17+
cpCommand = cli.Command{
18+
Name: "cp",
19+
Usage: "copy files",
20+
Description: "cp copy files, multiple files can be given. It follows the usage of cp whereas the last entry is the destination and has to be a folder if multiple files were given",
21+
Action: cpAction,
22+
BashComplete: cpBashComplete,
23+
Before: cpBefore,
24+
Flags: []cli.Flag{
25+
cli.BoolFlag{
26+
Name: "recursive, R",
27+
Usage: "cp recursively",
28+
},
29+
},
30+
}
31+
32+
action string
33+
)
34+
35+
func init() {
36+
registerCommand(cpCommand)
37+
}
38+
39+
func cpAction(c *cli.Context) {
40+
if strings.HasPrefix(c.Args()[len(c.Args())-1], "acd://") {
41+
cpUpload(c)
42+
} else {
43+
cpDownload(c)
44+
}
45+
}
46+
47+
func cpUpload(c *cli.Context) {
48+
// make sure the destination is a folder if it exists upstream and more than
49+
// one file is scheduled to be copied.
50+
dest := strings.TrimPrefix(c.Args()[len(c.Args())-1], "acd://")
51+
destNode, err := acdClient.NodeTree.FindNode(dest)
52+
if err == nil {
53+
// make sure if the remote node exists, it is a folder.
54+
if len(c.Args()) > 2 {
55+
if !destNode.IsDir() {
56+
log.Fatalf("cp: target %q is not a directory", dest)
57+
}
58+
}
59+
}
60+
61+
for _, src := range c.Args()[:len(c.Args())-1] {
62+
if strings.HasPrefix(src, "acd://") {
63+
fmt.Printf("cp: target %q is amazon, src cannot be amazon when destination is amazon. Skipping\n", src)
64+
continue
65+
}
66+
stat, err := os.Stat(src)
67+
if err != nil {
68+
if os.IsNotExist(err) {
69+
log.Fatalf("cp: %s: %s", constants.ErrFileNotFound, src)
70+
}
71+
72+
log.Fatalf("cp: %s: %s", constants.ErrStatFile, src)
73+
}
74+
if stat.IsDir() {
75+
if !c.Bool("recursive") {
76+
fmt.Printf("cp: %q is a directory (not copied).", src)
77+
continue
78+
}
79+
destFile := dest
80+
if destNode != nil {
81+
if !destNode.IsDir() {
82+
log.Fatalf("cp: target %q is not a directory", dest)
83+
}
84+
destFile = fmt.Sprintf("%s/%s", dest, path.Base(src))
85+
}
86+
acdClient.UploadFolder(src, destFile, true, true)
87+
continue
88+
}
89+
f, err := os.Open(src)
90+
if err != nil {
91+
log.Fatalf("%s: %s -- %s", constants.ErrOpenFile, err, src)
92+
}
93+
err = acdClient.Upload(dest, true, f)
94+
f.Close()
95+
if err != nil {
96+
log.Fatalf("%s: %s", err, dest)
97+
}
98+
}
99+
}
100+
101+
func cpDownload(c *cli.Context) {
102+
dest := c.Args()[len(c.Args())-1]
103+
destDir := false
104+
destStat, err := os.Stat(dest)
105+
if err == nil && destStat.IsDir() {
106+
destDir = true
107+
}
108+
if len(c.Args()) > 2 {
109+
if err == nil && !destDir {
110+
log.Fatalf("cp: target %q is not a directory", dest)
111+
}
112+
}
113+
114+
for _, src := range c.Args()[:len(c.Args())-1] {
115+
if !strings.HasPrefix(src, "acd://") {
116+
fmt.Printf("cp: source %q is local, src cannot be local when destination is local. Skipping\n", src)
117+
continue
118+
}
119+
srcPath := strings.TrimPrefix(src, "acd://")
120+
destPath := dest
121+
if destDir {
122+
destPath = path.Join(destPath, path.Base(srcPath))
123+
}
124+
srcNode, err := acdClient.GetNodeTree().FindNode(srcPath)
125+
if err != nil {
126+
fmt.Printf("cp: source %q not found. Skipping", src)
127+
continue
128+
}
129+
if srcNode.IsDir() {
130+
acdClient.DownloadFolder(destPath, srcPath, c.Bool("recursive"))
131+
} else {
132+
content, err := acdClient.Download(srcPath)
133+
if err != nil {
134+
fmt.Printf("cp: error downloading source %q. Skipping", src)
135+
}
136+
// TODO: respect umask
137+
if err := os.MkdirAll(path.Dir(destPath), os.FileMode(0755)); err != nil {
138+
fmt.Printf("cp: error creating the parents folders of %q: %s. Skipping", destPath, err)
139+
continue
140+
}
141+
// TODO: respect umask
142+
f, err := os.Create(destPath)
143+
if err != nil {
144+
fmt.Printf("cp: error writing %q: %s. Skipping", destPath, err)
145+
continue
146+
}
147+
io.Copy(f, content)
148+
f.Close()
149+
}
150+
}
151+
}
152+
153+
func cpBashComplete(c *cli.Context) {
154+
}
155+
156+
func cpBefore(c *cli.Context) error {
157+
foundRemote := false
158+
for _, arg := range c.Args() {
159+
if strings.HasPrefix(arg, "acd://") {
160+
foundRemote = true
161+
break
162+
}
163+
}
164+
165+
if !foundRemote {
166+
return fmt.Errorf("cp: at least one path prefixed by acd:// is required. Given: %v", c.Args())
167+
}
168+
169+
return nil
170+
}

client.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,12 @@ func loadConfig(configFile string) (*Config, error) {
127127
func validateFile(file string, checkPerms bool) error {
128128
stat, err := os.Stat(file)
129129
if err != nil {
130-
return err
130+
if os.IsNotExist(err) {
131+
log.Errorf("%s: %s -- %s", constants.ErrFileNotFound, err, file)
132+
return constants.ErrFileNotFound
133+
}
134+
log.Errorf("%s: %s -- %s", constants.ErrStatFile, err, file)
135+
return constants.ErrStatFile
131136
}
132137
if checkPerms && stat.Mode() != os.FileMode(0600) {
133138
log.Errorf("%s: want 0600 got %s", constants.ErrWrongPermissions, stat.Mode())

integrationtest/simple_upload_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestSimpleUpload(t *testing.T) {
4141
t.Fatal(err)
4242
}
4343
in.Seek(0, 0)
44-
if err := c.Upload(remoteReadmeFile, in); err != nil {
44+
if err := c.Upload(remoteReadmeFile, false, in); err != nil {
4545
t.Errorf("error uploading %s to %s: %s", readmeFile, remoteReadmeFile, err)
4646
}
4747

@@ -118,7 +118,7 @@ func Test0ByteUpload(t *testing.T) {
118118
if err := c.FetchNodeTree(); err != nil {
119119
t.Fatal(err)
120120
}
121-
if want, got := constants.ErrNoContentsToUpload, c.Upload(remoteZeroByteFile, in); want != got {
121+
if want, got := constants.ErrNoContentsToUpload, c.Upload(remoteZeroByteFile, false, in); want != got {
122122
t.Errorf("uploading a 0-byte file: want %s got %s", want, got)
123123
}
124124
}

integrationtest/tree_sync_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func TestTreeSync(t *testing.T) {
4545
io.Copy(inhash, in)
4646
inmd5 := hex.EncodeToString(inhash.Sum(nil))
4747
in.Seek(0, 0)
48-
if err := c1.Upload(remoteReadmeFile, in); err != nil {
48+
if err := c1.Upload(remoteReadmeFile, false, in); err != nil {
4949
t.Errorf("error uploading %s to %s: %s", readmeFile, remoteReadmeFile, err)
5050
}
5151

internal/constants/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ var (
100100

101101
// File-related errors
102102

103+
// ErrStatFile is returned if there was an error getting info about the file.
104+
ErrStatFile = errors.New("error stat() the file")
103105
// ErrOpenFile is returned if an error occurred while opening the file for reading
104106
ErrOpenFile = errors.New("error opening the file for reading")
105107
// ErrCreateFile is returned if an error is returned when trying to create a file

node/find.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package node
22

33
import (
4+
"regexp"
45
"strings"
56

67
"gopkg.in/acd.v0/internal/constants"
@@ -11,10 +12,13 @@ import (
1112
// TODO(kalbasit): This does not perform well, this should be cached in a map
1213
// path->node and calculated on load (fresh, cache, refresh).
1314
func (nt *Tree) FindNode(path string) (*Node, error) {
15+
// replace multiple n*/ with /
16+
re := regexp.MustCompile("/[/]*")
17+
path = string(re.ReplaceAll([]byte(path), []byte("/")))
1418
// chop off the first /.
1519
path = strings.TrimPrefix(path, "/")
20+
// did we ask for the root node?
1621
if path == "" {
17-
// we asked for the root node, return it
1822
return nt.Node, nil
1923
}
2024

node/find_test.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import "testing"
55
func TestFindNode(t *testing.T) {
66
// tests are [path -> ID]
77
tests := map[string]string{
8-
"/": "/",
9-
"/README.md": "/README.md",
10-
"/rEaDme.MD": "/README.md",
11-
"/pictuREs": "/pictures",
12-
"/pictures/loGO.png": "/pictures/logo.png",
8+
"/": "/",
9+
"/README.md": "/README.md",
10+
"/rEaDme.MD": "/README.md",
11+
"//rEaDme.MD": "/README.md",
12+
"///REadmE.Md": "/README.md",
13+
"/pictuREs": "/pictures",
14+
"/pictures/loGO.png": "/pictures/logo.png",
15+
"/pictures//loGO.png": "/pictures/logo.png",
1316
}
1417

1518
for path, ID := range tests {

upload.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,33 @@ import (
1616

1717
// Upload uploads io.Reader to the path defined by the filename. It will create
1818
// any non-existing folders.
19-
func (c *Client) Upload(filename string, r io.Reader) error {
19+
func (c *Client) Upload(filename string, overwrite bool, r io.Reader) error {
2020
var (
2121
err error
2222
logLevel = log.GetLevel()
23+
fileNode *node.Node
2324
node *node.Node
2425
)
2526

27+
node, err = c.NodeTree.MkdirAll(path.Dir(filename))
28+
if err != nil {
29+
return err
30+
}
2631
{
2732
log.SetLevel(log.DisableLogLevel)
28-
_, err = c.NodeTree.FindNode(filename)
33+
fileNode, err = c.NodeTree.FindNode(filename)
2934
log.SetLevel(logLevel)
3035
}
3136
if err == nil {
32-
log.Errorf("%s: %s", constants.ErrFileExists, filename)
33-
return constants.ErrFileExists
34-
}
35-
node, err = c.NodeTree.MkdirAll(path.Dir(filename))
36-
if err != nil {
37-
return err
37+
if !overwrite {
38+
log.Errorf("%s: %s", constants.ErrFileExists, filename)
39+
return constants.ErrFileExists
40+
}
41+
if err = fileNode.Overwrite(r); err != nil {
42+
return err
43+
}
44+
45+
return nil
3846
}
3947
if _, err = node.Upload(path.Base(filename), r); err != nil {
4048
return err

0 commit comments

Comments
 (0)