package main import ( "fmt" log "github.com/sirupsen/logrus" "os" "os/user" "io" "errors" "strings" "path/filepath" "gopkg.in/yaml.v3" "github.com/studio-b12/gowebdav" "github.com/alexflint/go-arg" "github.com/dustin/go-humanize" ) type NextcloudServers struct { Configs []NextcloudServer `nextcloud` } type NextcloudServer struct { Uuid string Name string Url string Username string Token string } type LsCmd struct { Remote string `arg:"positional" help:"remote folder" default:"/"` Detailed bool `arg:"-d,--details",default:"false"` Recurse bool `arg:"-r,--recurse",default:"false"` } type RmCmd struct { Remote string `arg:"positional,required"` Force bool `arg:"-f,--force",default:"false"` Recurse bool `arg:"-r,--recurse",default:"false"` } type UploadCmd struct { Local string `arg:"positional,required"` Remote string `arg:"positional"` } type DownloadCmd struct { Remote string `arg:"positional"` Local string `arg:"positional"` } type ShareCmd struct { Remote string `arg:"positional"` Name string `arg:"-n,--name"` } type args struct { Servername string `arg:"--server"` Ls *LsCmd `arg:"subcommand:ls"` Rm *RmCmd `arg:"subcommand:rm"` Upload *UploadCmd `arg:"subcommand:upload"` Download *DownloadCmd `arg:"subcommand:download"` Share *ShareCmd `arg:"subcommand:share"` } func (args) Version() string { return "1.0.0" } func check(e error) { if e != nil { log.Fatal(e) } } func loadConfig(name string) (NextcloudServer, error) { var err_msg string var config_err NextcloudServer var filename = os.Args[0] usr, _ := user.Current() dir := usr.HomeDir f, err := os.ReadFile(fmt.Sprintf("%s/%s", dir, ".config/gshare/config.yaml")) check(err) var config NextcloudServers err = yaml.Unmarshal([]byte(f), &config) check(err) switch size := len(config.Configs); size { case 0: err_msg = fmt.Sprintf("No server configured. You should configure one with %s config add", filename) log.Fatal(err_msg) return config_err, errors.New(err_msg) case 1: log.Debug("Only one server configured") return config.Configs[0], nil default: if len(name) == 0 { err_msg = fmt.Sprintf("Multiple servers configured. You should specify a server name with %s --server servername %s", filename) err_msg = "You should specify a server name with --server servername" log.Fatal(err_msg) return config_err, errors.New(err_msg) } } for _, s := range config.Configs { if s.Name == name { return s, nil } } err_msg = fmt.Sprintf("Could not find requested server with name %s", name) log.Fatal(err_msg) return config_err, errors.New(err_msg) } func listDir(c gowebdav.Client, dir string, details bool, recurse bool, recursecount int) { files, err := c.ReadDir(dir) check(err) if recursecount == 0 { fmt.Println(dir) } for i, file := range files { var pfx = strings.Repeat("|", recursecount) var sfx = "|-" if details { var ftype = "[File]" var size = "" if file.IsDir() { ftype = "[Dir]" } else { size = humanize.Bytes(uint64(file.Size())) } if i == len(files) - 1 { sfx = "\\-" } fmt.Println(fmt.Sprintf("%s%s %s %s %s", pfx, sfx, ftype, file.Name(), size)) } else { fmt.Println(fmt.Sprintf("%s%s %s", pfx, sfx, file.Name())) } if recurse { if file.IsDir() { new_dir := fmt.Sprintf("%s/%s", dir, file.Name()) listDir(c, new_dir, details, recurse, recursecount+1) } } } } func Remove(c gowebdav.Client, dir string, force bool, recurse bool) { log.Info(fmt.Sprintf("Removing remote file or directory : %s", dir)) c.Remove(dir) } func Upload(c gowebdav.Client, local_dir string, remote_dir string) { _path, _ := os.Stat(local_dir) _info := os.FileInfo(_path) var l_t_r []string if _info.IsDir() { if local_dir[len(local_dir)-1:] != "/" { local_dir = fmt.Sprintf("%s/", local_dir) } } l_t_r = strings.Split(local_dir, "/") log.Info(fmt.Sprintf("Creating directory : %s", remote_dir)) var err = c.Mkdir(remote_dir, 0644) check(err) err = filepath.Walk(local_dir, func(path string, info os.FileInfo, err error) error { check(err) var l_t_r_2 string if _info.IsDir() { l_t_r_2 = strings.Join(strings.Split(path, "/")[len(l_t_r)-2:], "/") } else { l_t_r_2 = strings.Join(strings.Split(path, "/")[len(l_t_r)-1:], "/") } l_t_r_3 := []string{remote_dir, l_t_r_2} local_to_remote := strings.Join(l_t_r_3, "/") if info.IsDir() { log.Info(fmt.Sprintf("Creating directory : %s", local_to_remote)) c.Mkdir(local_to_remote, 0644) } else { file, err := os.Open(path) defer file.Close() check(err) log.Info(fmt.Sprintf("Uploading file %s into %s", path, local_to_remote)) c.WriteStream(local_to_remote, file, 0644) } return nil }) check(err) } func Download(c gowebdav.Client, remote_dir string, local_dir string, inrecurse bool) { remote_info, err := c.Stat(remote_dir) check(err) if remote_info.IsDir() { if remote_dir[len(remote_dir)-1:] != "/" { remote_dir = fmt.Sprintf("%s/", remote_dir) } } var r_t_l = strings.Split(remote_dir, "/") var r_t_l_2 string if remote_info.IsDir() { r_t_l_2 = strings.Join(strings.Split(remote_dir, "/")[len(r_t_l)-2:], "/") } else { r_t_l_2 = strings.Join(strings.Split(remote_dir, "/")[len(r_t_l)-1:], "/") } r_t_l_3 := []string{local_dir, r_t_l_2} remote_to_local := strings.ReplaceAll(strings.Join(r_t_l_3, "/"), "//", "/") if remote_info.IsDir() { os.MkdirAll(remote_to_local, 0644) files, err := c.ReadDir(remote_dir) check(err) for _, file := range files { Download(c, fmt.Sprintf("%s%s", remote_dir, file.Name()), remote_to_local, true) } } else { log.Info(fmt.Sprintf("Downloading remote file %s to %s", remote_dir, remote_to_local)) reader, err := c.ReadStream(remote_dir) check(err) file, err := os.Create(remote_to_local) check(err) defer file.Close() io.Copy(file, reader) } } func nextcloudConnect(config NextcloudServer) gowebdav.Client { baseUrl := fmt.Sprintf("%s/remote.php/dav/files/%s", config.Url, config.Username) c := gowebdav.NewClient(baseUrl, config.Username, config.Token) return *c } func main() { var args args p := arg.MustParse(&args) if p.Subcommand() == nil { p.Fail("missing subcommand") } config, err := loadConfig(args.Servername) check(err) switch { case args.Ls != nil: c := nextcloudConnect(config) c.Connect() listDir(c, args.Ls.Remote, args.Ls.Detailed, args.Ls.Recurse, 0) case args.Rm != nil: c := nextcloudConnect(config) c.Connect() Remove(c, args.Rm.Remote, args.Rm.Force, args.Rm.Recurse) case args.Upload != nil: c := nextcloudConnect(config) c.Connect() Upload(c, args.Upload.Local, args.Upload.Remote) case args.Download != nil: c := nextcloudConnect(config) c.Connect() Download(c, args.Download.Remote, args.Download.Local, false) case args.Share != nil: fmt.Println("Not yet implemented.") fmt.Println("You should use nextcloud directly to create the share for now.") } }