- commit
- 61caf14
- parent
- a8ab7f3
- author
- Antonio Mika
- date
- 2023-11-10 01:29:09 +0000 UTC
Working on adding more features to rsync
29 files changed,
+281,
-133
M
go.mod
+16,
-0
1@@ -0,0 +1,16 @@
2+{
3+ // Use IntelliSense to learn about possible attributes.
4+ // Hover to view descriptions of existing attributes.
5+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
6+ "version": "0.2.0",
7+ "configurations": [
8+ {
9+ "name": "Launch Package",
10+ "type": "go",
11+ "request": "launch",
12+ "mode": "auto",
13+ "program": "${workspaceFolder}/cmd/pgs/ssh/main.go",
14+ "envFile": "${workspaceFolder}/.env"
15+ }
16+ ]
17+}
+3,
-2
1@@ -9,6 +9,7 @@ import (
2 "github.com/picosh/pico/imgs"
3 "github.com/picosh/pico/shared"
4 "github.com/picosh/pico/shared/storage"
5+ "github.com/picosh/pico/wish/send/utils"
6 )
7
8 func main() {
9@@ -42,7 +43,7 @@ func main() {
10 continue
11 }
12
13- reader, err := st.GetFile(bucket, post.Filename)
14+ reader, _, err := st.GetFile(bucket, post.Filename)
15 if err != nil {
16 cfg.Logger.Infof("file not found %s/%s", post.UserID, post.Filename)
17 continue
18@@ -67,7 +68,7 @@ func main() {
19 _, err = st.PutFile(
20 bucket,
21 fmt.Sprintf("%s.webp", shared.SanitizeFileExt(post.Filename)),
22- storage.NopReaderAtCloser(webpReader),
23+ utils.NopReaderAtCloser(webpReader),
24 )
25 if err != nil {
26 cfg.Logger.Error(err)
+2,
-1
1@@ -9,11 +9,12 @@ import (
2 "strings"
3 "time"
4
5+ "slices"
6+
7 _ "github.com/lib/pq"
8 "github.com/picosh/pico/db"
9 "github.com/picosh/pico/shared"
10 "go.uber.org/zap"
11- "golang.org/x/exp/slices"
12 )
13
14 var PAGER_SIZE = 15
+2,
-1
1@@ -5,11 +5,12 @@ import (
2 "strings"
3 "time"
4
5+ "slices"
6+
7 "github.com/picosh/pico/db"
8 "github.com/picosh/pico/filehandlers"
9 "github.com/picosh/pico/imgs"
10 "github.com/picosh/pico/shared"
11- "golang.org/x/exp/slices"
12 )
13
14 type FeedHooks struct {
+2,
-2
1@@ -7,7 +7,7 @@ import (
2 "strings"
3
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/shared/storage"
6+ "github.com/picosh/pico/wish/send/utils"
7 )
8
9 func (h *UploadAssetHandler) validateAsset(data *FileData) (bool, error) {
10@@ -81,7 +81,7 @@ func (h *UploadAssetHandler) writeAsset(data *FileData) error {
11 _, err := h.Storage.PutFile(
12 data.Bucket,
13 assetFilename,
14- storage.NopReaderAtCloser(reader),
15+ utils.NopReaderAtCloser(reader),
16 )
17 if err != nil {
18 return err
+6,
-4
1@@ -73,7 +73,7 @@ func NewUploadAssetHandler(dbpool db.DB, cfg *shared.ConfigSite, storage storage
2 }
3 }
4
5-func (h *UploadAssetHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileInfo, io.ReaderAt, error) {
6+func (h *UploadAssetHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileInfo, utils.ReaderAtCloser, error) {
7 user, err := getUser(s)
8 if err != nil {
9 return nil, nil, err
10@@ -92,17 +92,19 @@ func (h *UploadAssetHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.Fil
11 }
12
13 fname := shared.GetAssetFileName(entry)
14- contents, err := h.Storage.GetFile(bucket, fname)
15+ contents, size, err := h.Storage.GetFile(bucket, fname)
16 if err != nil {
17 return nil, nil, err
18 }
19
20+ fileInfo.FSize = size
21+
22 reader := utils.NewAllReaderAt(contents)
23
24 return fileInfo, reader, nil
25 }
26
27-func (h *UploadAssetHandler) List(s ssh.Session, fpath string, isDir bool) ([]os.FileInfo, error) {
28+func (h *UploadAssetHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
29 var fileList []os.FileInfo
30
31 user, err := getUser(s)
32@@ -135,7 +137,7 @@ func (h *UploadAssetHandler) List(s ssh.Session, fpath string, isDir bool) ([]os
33 cleanFilename += "/"
34 }
35
36- foundList, err := h.Storage.ListFiles(bucket, cleanFilename, false)
37+ foundList, err := h.Storage.ListFiles(bucket, cleanFilename, recursive)
38 if err != nil {
39 return fileList, err
40 }
+5,
-4
1@@ -9,6 +9,8 @@ import (
2 "path/filepath"
3 "time"
4
5+ "slices"
6+
7 "github.com/charmbracelet/ssh"
8 exifremove "github.com/neurosnap/go-exif-remove"
9 "github.com/picosh/pico/db"
10@@ -16,7 +18,6 @@ import (
11 "github.com/picosh/pico/shared/storage"
12 "github.com/picosh/pico/wish/cms/util"
13 "github.com/picosh/pico/wish/send/utils"
14- "golang.org/x/exp/slices"
15 )
16
17 var maxSize = 1 * shared.GB
18@@ -72,7 +73,7 @@ func (h *UploadImgHandler) removePost(data *PostMetaData) error {
19 return nil
20 }
21
22-func (h *UploadImgHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileInfo, io.ReaderAt, error) {
23+func (h *UploadImgHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileInfo, utils.ReaderAtCloser, error) {
24 user, err := getUser(s)
25 if err != nil {
26 return nil, nil, err
27@@ -101,7 +102,7 @@ func (h *UploadImgHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileI
28 return nil, nil, err
29 }
30
31- contents, err := h.Storage.GetFile(bucket, post.Filename)
32+ contents, _, err := h.Storage.GetFile(bucket, post.Filename)
33 if err != nil {
34 return nil, nil, err
35 }
36@@ -111,7 +112,7 @@ func (h *UploadImgHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileI
37 return fileInfo, reader, nil
38 }
39
40-func (h *UploadImgHandler) List(s ssh.Session, fpath string, isDir bool) ([]os.FileInfo, error) {
41+func (h *UploadImgHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
42 var fileList []os.FileInfo
43 user, err := getUser(s)
44 if err != nil {
+3,
-3
1@@ -9,7 +9,7 @@ import (
2 "github.com/charmbracelet/ssh"
3 "github.com/picosh/pico/db"
4 "github.com/picosh/pico/shared"
5- "github.com/picosh/pico/shared/storage"
6+ "github.com/picosh/pico/wish/send/utils"
7 )
8
9 func (h *UploadImgHandler) validateImg(data *PostMetaData) (bool, error) {
10@@ -59,7 +59,7 @@ func (h *UploadImgHandler) metaImg(data *PostMetaData) error {
11 fname, err := h.Storage.PutFile(
12 bucket,
13 data.Filename,
14- storage.NopReaderAtCloser(reader),
15+ utils.NopReaderAtCloser(reader),
16 )
17 if err != nil {
18 return err
19@@ -105,7 +105,7 @@ func (h *UploadImgHandler) metaImg(data *PostMetaData) error {
20 _, err = h.Storage.PutFile(
21 bucket,
22 finalName,
23- storage.NopReaderAtCloser(webpReader),
24+ utils.NopReaderAtCloser(webpReader),
25 )
26 if err != nil {
27 return err
+3,
-3
1@@ -61,7 +61,7 @@ func NewScpPostHandler(dbpool db.DB, cfg *shared.ConfigSite, hooks ScpFileHooks,
2 }
3 }
4
5-func (h *ScpUploadHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileInfo, io.ReaderAt, error) {
6+func (h *ScpUploadHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileInfo, utils.ReaderAtCloser, error) {
7 user, err := getUser(s)
8 if err != nil {
9 return nil, nil, err
10@@ -84,12 +84,12 @@ func (h *ScpUploadHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileI
11 FModTime: *post.UpdatedAt,
12 }
13
14- reader := strings.NewReader(post.Text)
15+ reader := utils.NopReaderAtCloser(strings.NewReader(post.Text))
16
17 return fileInfo, reader, nil
18 }
19
20-func (h *ScpUploadHandler) List(s ssh.Session, fpath string, isDir bool) ([]os.FileInfo, error) {
21+func (h *ScpUploadHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
22 var fileList []os.FileInfo
23 user, err := getUser(s)
24 if err != nil {
M
go.mod
+2,
-0
1@@ -2,6 +2,8 @@ module github.com/picosh/pico
2
3 go 1.21
4
5+replace github.com/antoniomika/go-rsync-receiver => /workspaces/go-rsync-receiver
6+
7 require (
8 github.com/alecthomas/chroma v0.10.0
9 github.com/antoniomika/go-rsync-receiver v0.0.0-20220901010427-e6494124f0c8
+5,
-3
1@@ -11,14 +11,16 @@ import (
2
3 _ "net/http/pprof"
4
5+ "slices"
6+
7 "github.com/gorilla/feeds"
8 gocache "github.com/patrickmn/go-cache"
9 "github.com/picosh/pico/db"
10 "github.com/picosh/pico/db/postgres"
11 "github.com/picosh/pico/shared"
12 "github.com/picosh/pico/shared/storage"
13+ "github.com/picosh/pico/wish/send/utils"
14 "go.uber.org/zap"
15- "golang.org/x/exp/slices"
16 )
17
18 type PageData struct {
19@@ -199,7 +201,7 @@ type ImgHandler struct {
20
21 type ImgResizer struct {
22 Key string
23- contents storage.ReaderAtCloser
24+ contents utils.ReaderAtCloser
25 writer io.Writer
26 Img *shared.ImgOptimizer
27 Cache *gocache.Cache
28@@ -282,7 +284,7 @@ func imgHandler(w http.ResponseWriter, h *ImgHandler) {
29 fname = fmt.Sprintf("%s.webp", shared.SanitizeFileExt(post.Filename))
30 }
31
32- contents, err := h.Storage.GetFile(bucket, fname)
33+ contents, _, err := h.Storage.GetFile(bucket, fname)
34 if err != nil {
35 h.Logger.Infof(
36 "file not found %s/%s in storage (bucket: %s, name: %s)",
+2,
-1
1@@ -10,6 +10,8 @@ import (
2 "strconv"
3 "time"
4
5+ "slices"
6+
7 "github.com/gorilla/feeds"
8 gocache "github.com/patrickmn/go-cache"
9 "github.com/picosh/pico/db"
10@@ -17,7 +19,6 @@ import (
11 "github.com/picosh/pico/imgs"
12 "github.com/picosh/pico/shared"
13 "github.com/picosh/pico/shared/storage"
14- "golang.org/x/exp/slices"
15 )
16
17 type PostItemData struct {
+2,
-1
1@@ -4,11 +4,12 @@ import (
2 "fmt"
3 "strings"
4
5+ "slices"
6+
7 "github.com/picosh/pico/db"
8 "github.com/picosh/pico/filehandlers"
9 "github.com/picosh/pico/imgs"
10 "github.com/picosh/pico/shared"
11- "golang.org/x/exp/slices"
12 )
13
14 type ListHooks struct {
+3,
-3
1@@ -210,7 +210,7 @@ func assetHandler(w http.ResponseWriter, h *AssetHandler) {
2 }
3
4 var redirects []*RedirectRule
5- redirectFp, err := h.Storage.GetFile(bucket, filepath.Join(h.ProjectDir, "_redirects"))
6+ redirectFp, _, err := h.Storage.GetFile(bucket, filepath.Join(h.ProjectDir, "_redirects"))
7 if err == nil {
8 defer redirectFp.Close()
9 buf := new(strings.Builder)
10@@ -228,11 +228,11 @@ func assetHandler(w http.ResponseWriter, h *AssetHandler) {
11 }
12
13 routes := calcPossibleRoutes(h.ProjectDir, h.Filepath, redirects)
14- var contents storage.ReaderAtCloser
15+ var contents utils.ReaderAtCloser
16 assetFilepath := ""
17 status := 200
18 for _, fp := range routes {
19- c, err := h.Storage.GetFile(bucket, fp.Filepath)
20+ c, _, err := h.Storage.GetFile(bucket, fp.Filepath)
21 if err == nil {
22 contents = c
23 assetFilepath = fp.Filepath
+2,
-2
1@@ -93,7 +93,7 @@ func parseRedirectText(text string) ([]*RedirectRule, error) {
2
3 parts := reSplitWhitespace.Split(trimmed, -1)
4 if len(parts) < 2 {
5- return rules, fmt.Errorf("Missing destination path/URL")
6+ return rules, fmt.Errorf("missing destination path/URL")
7 }
8
9 from := parts[0]
10@@ -114,7 +114,7 @@ func parseRedirectText(text string) ([]*RedirectRule, error) {
11 }
12
13 if toIndex == -1 {
14- return rules, fmt.Errorf("The destination path/URL must start with '/', 'http:' or 'https:'")
15+ return rules, fmt.Errorf("the destination path/URL must start with '/', 'http:' or 'https:'")
16 }
17
18 queryParts := parts[:toIndex]
+2,
-1
1@@ -10,6 +10,8 @@ import (
2 "strconv"
3 "time"
4
5+ "slices"
6+
7 "github.com/gorilla/feeds"
8 gocache "github.com/patrickmn/go-cache"
9 "github.com/picosh/pico/db"
10@@ -17,7 +19,6 @@ import (
11 "github.com/picosh/pico/imgs"
12 "github.com/picosh/pico/shared"
13 "github.com/picosh/pico/shared/storage"
14- "golang.org/x/exp/slices"
15 )
16
17 type PageData struct {
+2,
-1
1@@ -4,11 +4,12 @@ import (
2 "fmt"
3 "strings"
4
5+ "slices"
6+
7 "github.com/picosh/pico/db"
8 "github.com/picosh/pico/filehandlers"
9 "github.com/picosh/pico/imgs"
10 "github.com/picosh/pico/shared"
11- "golang.org/x/exp/slices"
12 )
13
14 type MarkdownHooks struct {
1@@ -9,8 +9,9 @@ import (
2 "strings"
3 "time"
4
5+ "slices"
6+
7 "github.com/araddon/dateparse"
8- "golang.org/x/exp/slices"
9 )
10
11 var reIndent = regexp.MustCompile(`^[[:blank:]]+`)
1@@ -3,6 +3,7 @@ package storage
2 import (
3 "fmt"
4 "io"
5+ "io/fs"
6 "os"
7 "path"
8 "path/filepath"
9@@ -82,16 +83,21 @@ func (s *StorageFS) DeleteBucket(bucket Bucket) error {
10 return os.RemoveAll(bucket.Path)
11 }
12
13-func (s *StorageFS) GetFile(bucket Bucket, fpath string) (ReaderAtCloser, error) {
14+func (s *StorageFS) GetFile(bucket Bucket, fpath string) (utils.ReaderAtCloser, int64, error) {
15 dat, err := os.Open(filepath.Join(bucket.Path, fpath))
16 if err != nil {
17- return nil, err
18+ return nil, 0, err
19 }
20
21- return dat, nil
22+ info, err := dat.Stat()
23+ if err != nil {
24+ return nil, 0, err
25+ }
26+
27+ return dat, info.Size(), nil
28 }
29
30-func (s *StorageFS) PutFile(bucket Bucket, fpath string, contents ReaderAtCloser) (string, error) {
31+func (s *StorageFS) PutFile(bucket Bucket, fpath string, contents utils.ReaderAtCloser) (string, error) {
32 loc := filepath.Join(bucket.Path, fpath)
33 err := os.MkdirAll(filepath.Dir(loc), os.ModePerm)
34 if err != nil {
35@@ -142,10 +148,26 @@ func (s *StorageFS) ListFiles(bucket Bucket, dir string, recursive bool) ([]os.F
36 return fileList, err
37 }
38
39- files, err := os.ReadDir(fpath)
40- if err != nil {
41- fileList = append(fileList, info)
42- return fileList, nil
43+ var files []fs.DirEntry
44+
45+ if recursive {
46+ err = filepath.WalkDir(fpath, func(s string, d fs.DirEntry, err error) error {
47+ if err != nil {
48+ return err
49+ }
50+ files = append(files, d)
51+ return nil
52+ })
53+ if err != nil {
54+ fileList = append(fileList, info)
55+ return fileList, nil
56+ }
57+ } else {
58+ files, err = os.ReadDir(fpath)
59+ if err != nil {
60+ fileList = append(fileList, info)
61+ return fileList, nil
62+ }
63 }
64
65 for _, f := range files {
1@@ -126,23 +126,23 @@ func (s *StorageMinio) DeleteBucket(bucket Bucket) error {
2 return s.Client.RemoveBucket(context.TODO(), bucket.Name)
3 }
4
5-func (s *StorageMinio) GetFile(bucket Bucket, fpath string) (ReaderAtCloser, error) {
6+func (s *StorageMinio) GetFile(bucket Bucket, fpath string) (utils.ReaderAtCloser, int64, error) {
7 // we have to stat the object first to see if it exists
8 // https://github.com/minio/minio-go/issues/654
9- _, err := s.Client.StatObject(context.Background(), bucket.Name, fpath, minio.StatObjectOptions{})
10+ info, err := s.Client.StatObject(context.Background(), bucket.Name, fpath, minio.StatObjectOptions{})
11 if err != nil {
12- return nil, err
13+ return nil, 0, err
14 }
15
16 obj, err := s.Client.GetObject(context.Background(), bucket.Name, fpath, minio.GetObjectOptions{})
17 if err != nil {
18- return nil, err
19+ return nil, 0, err
20 }
21
22- return obj, nil
23+ return obj, info.Size, nil
24 }
25
26-func (s *StorageMinio) PutFile(bucket Bucket, fpath string, contents ReaderAtCloser) (string, error) {
27+func (s *StorageMinio) PutFile(bucket Bucket, fpath string, contents utils.ReaderAtCloser) (string, error) {
28 info, err := s.Client.PutObject(context.TODO(), bucket.Name, fpath, contents, -1, minio.PutObjectOptions{})
29 if err != nil {
30 return "", err
1@@ -1,8 +1,9 @@
2 package storage
3
4 import (
5- "io"
6 "os"
7+
8+ "github.com/picosh/pico/wish/send/utils"
9 )
10
11 type Bucket struct {
12@@ -10,34 +11,14 @@ type Bucket struct {
13 Path string
14 }
15
16-type ReadAndReaderAt interface {
17- io.ReaderAt
18- io.Reader
19-}
20-
21-type ReaderAtCloser interface {
22- io.ReaderAt
23- io.ReadCloser
24-}
25-
26-func NopReaderAtCloser(r ReadAndReaderAt) ReaderAtCloser {
27- return nopReaderAtCloser{r}
28-}
29-
30-type nopReaderAtCloser struct {
31- ReadAndReaderAt
32-}
33-
34-func (nopReaderAtCloser) Close() error { return nil }
35-
36 type ObjectStorage interface {
37 GetBucket(name string) (Bucket, error)
38 UpsertBucket(name string) (Bucket, error)
39
40 DeleteBucket(bucket Bucket) error
41 GetBucketQuota(bucket Bucket) (uint64, error)
42- GetFile(bucket Bucket, fpath string) (ReaderAtCloser, error)
43- PutFile(bucket Bucket, fpath string, contents ReaderAtCloser) (string, error)
44+ GetFile(bucket Bucket, fpath string) (utils.ReaderAtCloser, int64, error)
45+ PutFile(bucket Bucket, fpath string, contents utils.ReaderAtCloser) (string, error)
46 DeleteFile(bucket Bucket, fpath string) error
47 ListFiles(bucket Bucket, dir string, recursive bool) ([]os.FileInfo, error)
48 }
1@@ -15,8 +15,9 @@ import (
2 "unicode"
3 "unicode/utf8"
4
5+ "slices"
6+
7 "github.com/charmbracelet/ssh"
8- "golang.org/x/exp/slices"
9 )
10
11 var fnameRe = regexp.MustCompile(`[-_]+`)
+3,
-4
1@@ -2,7 +2,6 @@ package main
2
3 import (
4 "fmt"
5- "io"
6 "log"
7 "os"
8 "strings"
9@@ -29,7 +28,7 @@ func (h *handler) Validate(session ssh.Session) error {
10 return nil
11 }
12
13-func (h *handler) Read(session ssh.Session, entry *utils.FileEntry) (os.FileInfo, io.ReaderAt, error) {
14+func (h *handler) Read(session ssh.Session, entry *utils.FileEntry) (os.FileInfo, utils.ReaderAtCloser, error) {
15 log.Printf("Received validate from session: %+v", session)
16
17 data := strings.NewReader("lorem ipsum dolor")
18@@ -39,10 +38,10 @@ func (h *handler) Read(session ssh.Session, entry *utils.FileEntry) (os.FileInfo
19 FIsDir: false,
20 FSize: data.Size(),
21 FModTime: time.Now(),
22- }, data, nil
23+ }, utils.NopReaderAtCloser(data), nil
24 }
25
26-func (h *handler) List(session ssh.Session, fpath string, isDir bool) ([]os.FileInfo, error) {
27+func (h *handler) List(session ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
28 return nil, nil
29 }
30
+1,
-1
1@@ -18,7 +18,7 @@ func Middleware(writeHandler utils.CopyFromClientHandler) wish.Middleware {
2 return
3 }
4
5- fileList, err := writeHandler.List(session, "/", true)
6+ fileList, err := writeHandler.List(session, "/", true, false)
7 if err != nil {
8 utils.ErrorHandler(session, err)
9 return
+105,
-20
1@@ -1,12 +1,15 @@
2 package rsync
3
4 import (
5+ "errors"
6 "fmt"
7 "io"
8 "io/fs"
9 "log"
10 "os"
11+ "path"
12 "path/filepath"
13+ "strings"
14
15 "github.com/antoniomika/go-rsync-receiver/rsyncreceiver"
16 "github.com/antoniomika/go-rsync-receiver/rsyncsender"
17@@ -19,41 +22,114 @@ import (
18 type handler struct {
19 session ssh.Session
20 writeHandler utils.CopyFromClientHandler
21+ root string
22 }
23
24 func (h *handler) Skip(file *rsyncutils.ReceiverFile) bool {
25- return false
26+ log.Printf("SKIP %+v", file)
27+ return file.FileMode().IsDir()
28 }
29
30-func (h *handler) List(path string) ([]fs.FileInfo, error) {
31- list, err := h.writeHandler.List(h.session, path, true)
32+func (h *handler) List(rPath string) ([]fs.FileInfo, error) {
33+ log.Println("LIST", rPath)
34+ isDir := false
35+ if rPath == "." {
36+ rPath = "/"
37+ isDir = true
38+ }
39+
40+ list, err := h.writeHandler.List(h.session, rPath, isDir, true)
41 if err != nil {
42 return nil, err
43 }
44
45- newList := list
46- if list[0].IsDir() {
47- newList = list[1:]
48+ for _, f := range list {
49+ log.Printf("first %+v", f)
50+ }
51+
52+ var dirs []string
53+
54+ var newList []fs.FileInfo
55+
56+ for _, f := range list {
57+ fname := f.Name()
58+ if strings.HasPrefix(f.Name(), "/") {
59+ fname = path.Join(rPath, f.Name())
60+ }
61+
62+ if fname == "" && !f.IsDir() {
63+ fname = path.Base(rPath)
64+ }
65+
66+ newFile := &utils.VirtualFile{
67+ FName: fname,
68+ FIsDir: f.IsDir(),
69+ FSize: f.Size(),
70+ FModTime: f.ModTime(),
71+ FSys: f.Sys(),
72+ }
73+
74+ newList = append(newList, newFile)
75+
76+ parts := strings.Split(newFile.Name(), string(os.PathSeparator))
77+ lastDir := newFile.Name()
78+ for i := 0; i < len(parts); i++ {
79+ lastDir, _ = path.Split(lastDir)
80+ if lastDir == "" {
81+ continue
82+ }
83+
84+ lastDir = lastDir[:len(lastDir)-1]
85+ dirs = append(dirs, lastDir)
86+ }
87+ }
88+
89+ for _, dir := range dirs {
90+ newList = append(newList, &utils.VirtualFile{
91+ FName: dir,
92+ FIsDir: true,
93+ })
94+ }
95+
96+ for _, f := range newList {
97+ log.Printf("%+v", f)
98+ }
99+
100+ if len(newList) == 0 {
101+ return nil, errors.New("no files to process")
102 }
103
104 return newList, nil
105 }
106
107-func (h *handler) Read(path string) (os.FileInfo, io.ReaderAt, error) {
108- return h.writeHandler.Read(h.session, &utils.FileEntry{Filepath: path})
109+func (h *handler) Read(file *rsyncutils.SenderFile) (os.FileInfo, io.ReaderAt, error) {
110+ log.Printf("READ %+v %s", file, h.root)
111+
112+ filePath := file.WPath
113+
114+ if strings.HasSuffix(h.root, file.WPath) {
115+ filePath = h.root
116+ } else if !strings.HasPrefix(filePath, h.root) {
117+ filePath = path.Join(h.root, file.Path, file.WPath)
118+ }
119+
120+ log.Printf("READ %+v %s", file, filePath)
121+
122+ return h.writeHandler.Read(h.session, &utils.FileEntry{Filepath: filePath})
123 }
124
125-func (h *handler) Put(fileName string, content io.Reader, fileSize int64, mTime int64, aTime int64) (int64, error) {
126- cleanName := filepath.Base(fileName)
127- fpath := "/"
128+func (h *handler) Put(file *rsyncutils.ReceiverFile) (int64, error) {
129+ log.Printf("PUT %+v", file)
130+ fpath := path.Join("/", h.root)
131 fileEntry := &utils.FileEntry{
132- Filepath: filepath.Join(fpath, cleanName),
133+ Filepath: filepath.Join(fpath, file.Name),
134 Mode: fs.FileMode(0600),
135- Size: fileSize,
136- Mtime: mTime,
137- Atime: aTime,
138+ Size: file.Length,
139+ Mtime: file.ModTime.Unix(),
140+ Atime: file.ModTime.Unix(),
141 }
142- fileEntry.Reader = content
143+ log.Printf("%+v", fileEntry)
144+ fileEntry.Reader = file.Buf
145
146 msg, err := h.writeHandler.Write(h.session, fileEntry)
147 if err != nil {
148@@ -79,19 +155,28 @@ func Middleware(writeHandler utils.CopyFromClientHandler) wish.Middleware {
149 fileHandler := &handler{
150 session: session,
151 writeHandler: writeHandler,
152+ root: strings.TrimPrefix(cmd[len(cmd)-1], "/"),
153 }
154
155+ cmdFlags := session.Command()
156+
157 for _, arg := range cmd {
158 if arg == "--sender" {
159- if err := rsyncsender.ClientRun(nil, session, fileHandler, cmd[len(cmd)-1], true); err != nil {
160- log.Println("error running rsync:", err)
161+ opts, parser := rsyncsender.NewGetOpt()
162+ _, _ = parser.Parse(cmdFlags)
163+
164+ if err := rsyncsender.ClientRun(opts, session, fileHandler, fileHandler.root, true); err != nil {
165+ log.Println("error running rsync sender:", err)
166 }
167 return
168 }
169 }
170
171- if _, err := rsyncreceiver.ClientRun(nil, session, fileHandler, true); err != nil {
172- log.Println("error running rsync:", err)
173+ opts, parser := rsyncreceiver.NewGetOpt()
174+ _, _ = parser.Parse(cmdFlags)
175+
176+ if _, err := rsyncreceiver.ClientRun(opts, session, fileHandler, true); err != nil {
177+ log.Println("error running rsync receiver:", err)
178 }
179 }
180 }
+3,
-2
1@@ -6,10 +6,11 @@ import (
2 "io"
3 "os"
4
5+ "slices"
6+
7 "github.com/charmbracelet/ssh"
8 "github.com/picosh/pico/wish/send/utils"
9 "github.com/pkg/sftp"
10- "golang.org/x/exp/slices"
11 )
12
13 type listerat []os.FileInfo
14@@ -51,7 +52,7 @@ func (f *handler) Filelist(r *sftp.Request) (sftp.ListerAt, error) {
15 case "List", "Stat":
16 list := r.Method == "List"
17
18- listData, err := f.writeHandler.List(f.session, r.Filepath, list)
19+ listData, err := f.writeHandler.List(f.session, r.Filepath, list, false)
20 if err != nil {
21 return nil, err
22 }
+0,
-33
1@@ -1,33 +0,0 @@
2-package utils
3-
4-import (
5- "errors"
6- "io"
7- "net/http"
8-
9- "github.com/minio/minio-go/v7"
10-)
11-
12-type AllReaderAt struct {
13- Reader io.ReaderAt
14-}
15-
16-func NewAllReaderAt(reader io.ReaderAt) *AllReaderAt {
17- return &AllReaderAt{reader}
18-}
19-
20-func (a *AllReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
21- n, err = a.Reader.ReadAt(p, off)
22-
23- if errors.Is(err, io.EOF) {
24- return
25- }
26-
27- resp := minio.ToErrorResponse(err)
28-
29- if resp.StatusCode == http.StatusRequestedRangeNotSatisfiable {
30- err = io.EOF
31- }
32-
33- return
34-}
+61,
-0
1@@ -0,0 +1,61 @@
2+package utils
3+
4+import (
5+ "errors"
6+ "io"
7+ "net/http"
8+
9+ "github.com/minio/minio-go/v7"
10+)
11+
12+type ReadAndReaderAt interface {
13+ io.ReaderAt
14+ io.Reader
15+}
16+
17+type ReaderAtCloser interface {
18+ io.ReaderAt
19+ io.ReadCloser
20+}
21+
22+func NopReaderAtCloser(r ReadAndReaderAt) ReaderAtCloser {
23+ return nopReaderAtCloser{r}
24+}
25+
26+type nopReaderAtCloser struct {
27+ ReadAndReaderAt
28+}
29+
30+func (nopReaderAtCloser) Close() error { return nil }
31+
32+type AllReaderAt struct {
33+ Reader ReaderAtCloser
34+}
35+
36+func NewAllReaderAt(reader ReaderAtCloser) *AllReaderAt {
37+ return &AllReaderAt{reader}
38+}
39+
40+func (a *AllReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
41+ n, err = a.Reader.ReadAt(p, off)
42+
43+ if errors.Is(err, io.EOF) {
44+ return
45+ }
46+
47+ resp := minio.ToErrorResponse(err)
48+
49+ if resp.StatusCode == http.StatusRequestedRangeNotSatisfiable {
50+ err = io.EOF
51+ }
52+
53+ return
54+}
55+
56+func (a *AllReaderAt) Read(p []byte) (int, error) {
57+ return a.Reader.Read(p)
58+}
59+
60+func (a *AllReaderAt) Close() error {
61+ return a.Reader.Close()
62+}
+2,
-2
1@@ -57,8 +57,8 @@ func octalPerms(info fs.FileMode) string {
2 type CopyFromClientHandler interface {
3 // Write should write the given file.
4 Write(ssh.Session, *FileEntry) (string, error)
5- Read(ssh.Session, *FileEntry) (os.FileInfo, io.ReaderAt, error)
6- List(ssh.Session, string, bool) ([]os.FileInfo, error)
7+ Read(ssh.Session, *FileEntry) (os.FileInfo, ReaderAtCloser, error)
8+ List(ssh ssh.Session, path string, isDir bool, recursive bool) ([]os.FileInfo, error)
9 Validate(ssh.Session) error
10 }
11