diff --git a/README.md b/README.md index b882579d..47de8ae3 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ ## Installation - go get github.com/rakyll/drive + go get github.com/rakyll/drive/cmd/drive Use `drive help` for further reference. - $ drive init [path] + $ drive init [path] $ drive pull [-r -no-prompt path] # pulls from remote $ drive push [-r -no-prompt path] # pushes to the remote $ drive diff [path] # outputs a diff of local and remote @@ -27,7 +27,7 @@ Background sync is not just hard, it's stupid. My technical and philosophical ra `drive` is not a sync deamon, it provides: -* Upstreaming and downstreaming. Unlike a sync command, we provide pull and push actions. User has opportunity to decide what to do with their local copy and when. Do some changes, either push it to remote or revert it to the remote version. Perform these actions with user prompt. +* Upstreaming and downstreaming. Unlike a sync command, we provide pull and push actions. User has opportunity to decide what to do with their local copy and when. Do some changes, either push it to remote or revert it to the remote version. Perform these actions with user prompt. $ echo "hello" > hello.txt $ drive push # pushes hello.txt to Google Drive diff --git a/commands/changes.go b/changes.go similarity index 78% rename from commands/changes.go rename to changes.go index 2798b23c..240b5c30 100644 --- a/commands/changes.go +++ b/changes.go @@ -12,20 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -package commands +package drive import ( "fmt" "path" - "sync" "strings" - - "github.com/rakyll/drive/types" + "sync" ) type dirList struct { - remote *types.File - local *types.File + remote *File + local *File } func (d *dirList) Name() string { @@ -36,14 +34,14 @@ func (d *dirList) Name() string { } func (g *Commands) resolveChangeListRecv( - isPush bool, p string, r *types.File, l *types.File) (cl []*types.Change, err error) { - var change *types.Change + isPush bool, p string, r *File, l *File) (cl []*Change, err error) { + var change *Change if isPush { - change = &types.Change{Path: p, Src: l, Dest: r} + change = &Change{Path: p, Src: l, Dest: r} } else { - change = &types.Change{Path: p, Src: r, Dest: l} + change = &Change{Path: p, Src: r, Dest: l} } - if change.Op() != types.OpNone { + if change.Op() != OpNone { cl = append(cl, change) } if !g.opts.IsRecursive { @@ -59,7 +57,7 @@ func (g *Commands) resolveChangeListRecv( } // look-up for children - var localChildren []*types.File + var localChildren []*File if l != nil { localChildren, err = list(g.context, p) if err != nil { @@ -67,7 +65,7 @@ func (g *Commands) resolveChangeListRecv( } } - var remoteChildren []*types.File + var remoteChildren []*File if r != nil { if remoteChildren, err = g.rem.FindByParentId(r.Id); err != nil { return @@ -79,7 +77,7 @@ func (g *Commands) resolveChangeListRecv( var wg sync.WaitGroup wg.Add(len(dirlist)) for _, l := range dirlist { - go func(wg *sync.WaitGroup, isPush bool, cl *[]*types.Change, p string, l *dirList) { + go func(wg *sync.WaitGroup, isPush bool, cl *[]*Change, p string, l *dirList) { defer wg.Done() childChanges, _ := g.resolveChangeListRecv(isPush, path.Join(p, l.Name()), l.remote, l.local) *cl = append(*cl, childChanges...) @@ -89,7 +87,7 @@ func (g *Commands) resolveChangeListRecv( return cl, nil } -func merge(remotes, locals []*types.File) (merged []*dirList) { +func merge(remotes, locals []*File) (merged []*dirList) { for _, r := range remotes { list := &dirList{remote: r} // look for local @@ -109,9 +107,9 @@ func merge(remotes, locals []*types.File) (merged []*dirList) { return } -func printChangeList(changes []*types.Change, isNoPrompt bool) bool { +func printChangeList(changes []*Change, isNoPrompt bool) bool { for _, c := range changes { - if c.Op() != types.OpNone { + if c.Op() != OpNone { fmt.Println(c.Symbol(), c.Path) } } diff --git a/main.go b/cmd/drive/main.go similarity index 92% rename from main.go rename to cmd/drive/main.go index 0f8e7390..dce77e16 100644 --- a/main.go +++ b/cmd/drive/main.go @@ -22,7 +22,7 @@ import ( "path/filepath" "github.com/rakyll/command" - "github.com/rakyll/drive/commands" + "github.com/rakyll/drive" "github.com/rakyll/drive/config" ) @@ -52,7 +52,7 @@ func (cmd *initCmd) Flags(fs *flag.FlagSet) *flag.FlagSet { } func (cmd *initCmd) Run(args []string) { - exitWithError(commands.New(initContext(args), nil).Init()) + exitWithError(drive.New(initContext(args), nil).Init()) } type pullCmd struct { @@ -68,7 +68,7 @@ func (cmd *pullCmd) Flags(fs *flag.FlagSet) *flag.FlagSet { func (cmd *pullCmd) Run(args []string) { context, path := discoverContext(args) - exitWithError(commands.New(context, &commands.Options{ + exitWithError(drive.New(context, &drive.Options{ Path: path, IsRecursive: *cmd.isRecursive, IsNoPrompt: *cmd.isNoPrompt, @@ -88,7 +88,7 @@ func (cmd *pushCmd) Flags(fs *flag.FlagSet) *flag.FlagSet { func (cmd *pushCmd) Run(args []string) { context, path := discoverContext(args) - exitWithError(commands.New(context, &commands.Options{ + exitWithError(drive.New(context, &drive.Options{ Path: path, IsRecursive: *cmd.isRecursive, IsNoPrompt: *cmd.isNoPrompt, @@ -103,7 +103,7 @@ func (cmd *diffCmd) Flags(fs *flag.FlagSet) *flag.FlagSet { func (cmd *diffCmd) Run(args []string) { context, path := discoverContext(args) - exitWithError(commands.New(context, &commands.Options{ + exitWithError(drive.New(context, &drive.Options{ Path: path, }).Diff()) } @@ -116,7 +116,7 @@ func (cmd *publishCmd) Flags(fs *flag.FlagSet) *flag.FlagSet { func (cmd *publishCmd) Run(args []string) { context, path := discoverContext(args) - exitWithError(commands.New(context, &commands.Options{ + exitWithError(drive.New(context, &drive.Options{ Path: path, }).Publish()) } diff --git a/commands/commands.go b/commands.go similarity index 92% rename from commands/commands.go rename to commands.go index 15adb5a6..490a6794 100644 --- a/commands/commands.go +++ b/commands.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package commands +package drive import ( "errors" @@ -20,7 +20,6 @@ import ( "github.com/cheggaaa/pb" "github.com/rakyll/drive/config" - "github.com/rakyll/drive/remote" ) var ( @@ -36,16 +35,16 @@ type Options struct { type Commands struct { context *config.Context - rem *remote.Remote + rem *Remote opts *Options progress *pb.ProgressBar } func New(context *config.Context, opts *Options) *Commands { - var r *remote.Remote + var r *Remote if context != nil { - r = remote.New(context) + r = NewRemoteContext(context) } if opts != nil { // should always start with / diff --git a/commands/diff.go b/diff.go similarity index 97% rename from commands/diff.go rename to diff.go index d9719efa..f45af463 100644 --- a/commands/diff.go +++ b/diff.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package commands +package drive func (g *Commands) Diff() error { panic("not implemented") diff --git a/commands/init.go b/init.go similarity index 87% rename from commands/init.go rename to init.go index e0fcf3f6..c254fb66 100644 --- a/commands/init.go +++ b/init.go @@ -12,18 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package commands - -import ( - "github.com/rakyll/drive/remote" -) +package drive func (g *Commands) Init() (err error) { var refresh string // TODO: read from env variable. g.context.ClientId = "354790962074-7rrlnuanmamgg1i4feed12dpuq871bvd.apps.googleusercontent.com" g.context.ClientSecret = "RHjKdah8RrHFwu6fcc0uEVCw" - if refresh, err = remote.RetrieveRefreshToken(g.context); err != nil { + if refresh, err = RetrieveRefreshToken(g.context); err != nil { return } g.context.RefreshToken = refresh diff --git a/commands/publish.go b/publish.go similarity index 92% rename from commands/publish.go rename to publish.go index c92416f0..ada2d0c7 100644 --- a/commands/publish.go +++ b/publish.go @@ -12,16 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package commands +package drive import ( "fmt" - - "github.com/rakyll/drive/types" ) func (c *Commands) Publish() (err error) { - var file *types.File + var file *File var link string if file, err = c.rem.FindByPath(c.opts.Path); err != nil { return diff --git a/commands/pull.go b/pull.go similarity index 82% rename from commands/pull.go rename to pull.go index b0976839..2785d2b1 100644 --- a/commands/pull.go +++ b/pull.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package commands +package drive import ( "fmt" @@ -20,8 +20,6 @@ import ( "os" "path/filepath" "sync" - - "github.com/rakyll/drive/types" ) const ( @@ -32,17 +30,17 @@ const ( // directory, it recursively pulls from the remote if there are remote changes. // It doesn't check if there are remote changes if isForce is set. func (g *Commands) Pull() (err error) { - var r, l *types.File + var r, l *File if r, err = g.rem.FindByPath(g.opts.Path); err != nil { return } absPath := g.context.AbsPathOf(g.opts.Path) localinfo, _ := os.Stat(absPath) if localinfo != nil { - l = types.NewLocalFile(absPath, localinfo) + l = NewLocalFile(absPath, localinfo) } - var cl []*types.Change + var cl []*Change fmt.Println("Resolving...") if cl, err = g.resolveChangeListRecv(false, g.opts.Path, r, l); err != nil { return @@ -54,15 +52,15 @@ func (g *Commands) Pull() (err error) { return } -func (g *Commands) playPullChangeList(cl []*types.Change) (err error) { - var next []*types.Change +func (g *Commands) playPullChangeList(cl []*Change) (err error) { + var next []*Change g.taskStart(len(cl)) for { if len(cl) > maxNumOfConcPullTasks { next, cl = cl[:maxNumOfConcPullTasks], cl[maxNumOfConcPullTasks:len(cl)] } else { - next, cl = cl, []*types.Change{} + next, cl = cl, []*Change{} } if len(next) == 0 { break @@ -73,11 +71,11 @@ func (g *Commands) playPullChangeList(cl []*types.Change) (err error) { // TODO: add timeouts for _, c := range next { switch c.Op() { - case types.OpMod: + case OpMod: go g.localMod(&wg, c) - case types.OpAdd: + case OpAdd: go g.localAdd(&wg, c) - case types.OpDelete: + case OpDelete: go g.localDelete(&wg, c) } } @@ -88,7 +86,7 @@ func (g *Commands) playPullChangeList(cl []*types.Change) (err error) { return err } -func (g *Commands) localMod(wg *sync.WaitGroup, change *types.Change) (err error) { +func (g *Commands) localMod(wg *sync.WaitGroup, change *Change) (err error) { defer g.taskDone() defer wg.Done() destAbsPath := g.context.AbsPathOf(change.Path) @@ -101,7 +99,7 @@ func (g *Commands) localMod(wg *sync.WaitGroup, change *types.Change) (err error return os.Chtimes(destAbsPath, change.Src.ModTime, change.Src.ModTime) } -func (g *Commands) localAdd(wg *sync.WaitGroup, change *types.Change) (err error) { +func (g *Commands) localAdd(wg *sync.WaitGroup, change *Change) (err error) { defer g.taskDone() defer wg.Done() destAbsPath := g.context.AbsPathOf(change.Path) @@ -119,13 +117,13 @@ func (g *Commands) localAdd(wg *sync.WaitGroup, change *types.Change) (err error return os.Chtimes(destAbsPath, change.Src.ModTime, change.Src.ModTime) } -func (g *Commands) localDelete(wg *sync.WaitGroup, change *types.Change) (err error) { +func (g *Commands) localDelete(wg *sync.WaitGroup, change *Change) (err error) { defer g.taskDone() defer wg.Done() return os.RemoveAll(change.Dest.BlobAt) } -func (g *Commands) download(change *types.Change) (err error) { +func (g *Commands) download(change *Change) (err error) { destAbsPath := g.context.AbsPathOf(change.Path) var fo *os.File fo, err = os.Create(destAbsPath) diff --git a/commands/push.go b/push.go similarity index 76% rename from commands/push.go rename to push.go index 492d06af..3b7c094d 100644 --- a/commands/push.go +++ b/push.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package commands +package drive import ( "fmt" @@ -22,8 +22,6 @@ import ( "strings" "github.com/rakyll/drive/config" - "github.com/rakyll/drive/remote" - "github.com/rakyll/drive/types" ) // Pushes to remote if local path exists and in a god context. If path is a @@ -32,18 +30,18 @@ import ( func (g *Commands) Push() (err error) { absPath := g.context.AbsPathOf(g.opts.Path) r, err := g.rem.FindByPath(g.opts.Path) - if err != nil && err != remote.ErrPathNotExists { + if err != nil && err != ErrPathNotExists { return err } - var l *types.File + var l *File localinfo, _ := os.Stat(absPath) if localinfo != nil { - l = types.NewLocalFile(absPath, localinfo) + l = NewLocalFile(absPath, localinfo) } fmt.Println("Resolving...") - var cl []*types.Change + var cl []*Change if cl, err = g.resolveChangeListRecv(true, g.opts.Path, r, l); err != nil { return err } @@ -54,15 +52,15 @@ func (g *Commands) Push() (err error) { return } -func (g *Commands) playPushChangeList(cl []*types.Change) (err error) { +func (g *Commands) playPushChangeList(cl []*Change) (err error) { g.taskStart(len(cl)) for _, c := range cl { switch c.Op() { - case types.OpMod: + case OpMod: g.remoteMod(c) - case types.OpAdd: + case OpAdd: g.remoteAdd(c) - case types.OpDelete: + case OpDelete: g.remoteDelete(c) } } @@ -70,10 +68,10 @@ func (g *Commands) playPushChangeList(cl []*types.Change) (err error) { return err } -func (g *Commands) remoteMod(change *types.Change) (err error) { +func (g *Commands) remoteMod(change *Change) (err error) { defer g.taskDone() absPath := g.context.AbsPathOf(change.Path) - var updated, parent *types.File + var updated, parent *File if change.Dest != nil { change.Src.Id = change.Dest.Id // TODO: bad hack } @@ -95,16 +93,16 @@ func (g *Commands) remoteMod(change *types.Change) (err error) { return os.Chtimes(absPath, updated.ModTime, updated.ModTime) } -func (g *Commands) remoteAdd(change *types.Change) (err error) { +func (g *Commands) remoteAdd(change *Change) (err error) { return g.remoteMod(change) } -func (g *Commands) remoteDelete(change *types.Change) (err error) { +func (g *Commands) remoteDelete(change *Change) (err error) { defer g.taskDone() return g.rem.Trash(change.Dest.Id) } -func list(context *config.Context, path string) (files []*types.File, err error) { +func list(context *config.Context, path string) (files []*File, err error) { absPath := context.AbsPathOf(path) var f []os.FileInfo if f, err = ioutil.ReadDir(absPath); err != nil { @@ -113,7 +111,7 @@ func list(context *config.Context, path string) (files []*types.File, err error) for _, file := range f { // ignore hidden files if !strings.HasPrefix(file.Name(), ".") { - files = append(files, types.NewLocalFile(gopath.Join(absPath, file.Name()), file)) + files = append(files, NewLocalFile(gopath.Join(absPath, file.Name()), file)) } } return diff --git a/remote/remote.go b/remote.go similarity index 87% rename from remote/remote.go rename to remote.go index b1ebe242..8b4d6ce9 100644 --- a/remote/remote.go +++ b/remote.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package remote +package drive import ( "errors" @@ -25,7 +25,6 @@ import ( "code.google.com/p/goauth2/oauth" drive "code.google.com/p/google-api-go-client/drive/v2" "github.com/rakyll/drive/config" - "github.com/rakyll/drive/types" ) const ( @@ -52,7 +51,7 @@ type Remote struct { service *drive.Service } -func New(context *config.Context) *Remote { +func NewRemoteContext(context *config.Context) *Remote { transport := newTransport(context) service, _ := drive.New(transport.Client()) return &Remote{service: service, transport: transport} @@ -73,16 +72,16 @@ func RetrieveRefreshToken(context *config.Context) (string, error) { return token.RefreshToken, nil } -func (r *Remote) FindById(id string) (file *types.File, err error) { +func (r *Remote) FindById(id string) (file *File, err error) { req := r.service.Files.Get(id) var f *drive.File if f, err = req.Do(); err != nil { return } - return types.NewRemoteFile(f), nil + return NewRemoteFile(f), nil } -func (r *Remote) FindByPath(p string) (file *types.File, err error) { +func (r *Remote) FindByPath(p string) (file *File, err error) { if p == "/" { return r.FindById("root") } @@ -90,7 +89,7 @@ func (r *Remote) FindByPath(p string) (file *types.File, err error) { return r.findByPathRecv("root", parts[1:]) } -func (r *Remote) FindByParentId(parentId string) (files []*types.File, err error) { +func (r *Remote) FindByParentId(parentId string) (files []*File, err error) { req := r.service.Files.List() // TODO: use field selectors req.Q(fmt.Sprintf("'%s' in parents and trashed=false", parentId)) @@ -101,7 +100,7 @@ func (r *Remote) FindByParentId(parentId string) (files []*types.File, err error } for _, f := range results.Items { if !strings.HasPrefix(f.Title, ".") { // ignore hidden files - files = append(files, types.NewRemoteFile(f)) + files = append(files, NewRemoteFile(f)) } } return @@ -129,7 +128,7 @@ func (r *Remote) Download(id string) (io.ReadCloser, error) { return resp.Body, nil } -func (r *Remote) Upsert(parentId string, file *types.File, body io.Reader) (f *types.File, err error) { +func (r *Remote) Upsert(parentId string, file *File, body io.Reader) (f *File, err error) { uploaded := &drive.File{ Title: file.Name, Parents: []*drive.ParentReference{&drive.ParentReference{Id: parentId}}, @@ -146,7 +145,7 @@ func (r *Remote) Upsert(parentId string, file *types.File, body io.Reader) (f *t if uploaded, err = req.Do(); err != nil { return } - return types.NewRemoteFile(uploaded), nil + return NewRemoteFile(uploaded), nil } // update the existing req := r.service.Files.Update(file.Id, uploaded) @@ -156,10 +155,10 @@ func (r *Remote) Upsert(parentId string, file *types.File, body io.Reader) (f *t if uploaded, err = req.Do(); err != nil { return } - return types.NewRemoteFile(uploaded), nil + return NewRemoteFile(uploaded), nil } -func (r *Remote) findByPathRecv(parentId string, p []string) (file *types.File, err error) { +func (r *Remote) findByPathRecv(parentId string, p []string) (file *File, err error) { // find the file or directory under parentId and titled with p[0] req := r.service.Files.List() // TODO: use field selectors @@ -169,7 +168,7 @@ func (r *Remote) findByPathRecv(parentId string, p []string) (file *types.File, // TODO: make sure only 404s are handled here return nil, ErrPathNotExists } - file = types.NewRemoteFile(files.Items[0]) + file = NewRemoteFile(files.Items[0]) if len(p) == 1 { return file, nil } diff --git a/types/types.go b/types.go similarity index 91% rename from types/types.go rename to types.go index 6ed61112..2acad24c 100644 --- a/types/types.go +++ b/types.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package types +package drive import ( + "crypto/md5" + "fmt" "io" "os" - "fmt" "time" - "crypto/md5" drive "code.google.com/p/google-api-go-client/drive/v2" ) @@ -45,13 +45,13 @@ func NewRemoteFile(f *drive.File) *File { mtime, _ := time.Parse("2006-01-02T15:04:05.000Z", f.ModifiedDate) mtime = mtime.Round(time.Second) return &File{ - Id: f.Id, - Name: f.Title, - IsDir: f.MimeType == "application/vnd.google-apps.folder", - ModTime: mtime, - Size: f.FileSize, - BlobAt: f.DownloadUrl, - Md5Checksum: f.Md5Checksum, + Id: f.Id, + Name: f.Title, + IsDir: f.MimeType == "application/vnd.google-apps.folder", + ModTime: mtime, + Size: f.FileSize, + BlobAt: f.DownloadUrl, + Md5Checksum: f.Md5Checksum, } }