github.com/code-to-go/safepool.lib@v0.0.0-20221205180519-ee25e63c226e/transport/local.go (about) 1 package transport 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/fs" 8 "os" 9 "path" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "github.com/code-to-go/safepool.lib/core" 15 ) 16 17 type LocalConfig struct { 18 Base string `json:"base" yaml:"base"` 19 } 20 21 type Local struct { 22 base string 23 url string 24 touch map[string]time.Time 25 } 26 27 func NewLocal(config LocalConfig) (Exchanger, error) { 28 base := config.Base 29 if base == "" { 30 base = "/" 31 } 32 return &Local{base, "file://" + base, map[string]time.Time{}}, nil 33 } 34 35 func (l *Local) Touched(name string) bool { 36 touchFile := path.Join(l.base, fmt.Sprintf("%s.touch", name)) 37 stat, err := l.Stat(touchFile) 38 touched := err != nil || stat.ModTime().After(l.touch[name]) 39 if touched { 40 if !core.IsErr(l.Write(touchFile, &bytes.Buffer{}), "cannot write touch file: %v") { 41 l.touch[name] = stat.ModTime() 42 } 43 } 44 return touched 45 } 46 47 func (l *Local) Read(name string, rang *Range, dest io.Writer) error { 48 f, err := os.Open(path.Join(l.base, name)) 49 if core.IsErr(err, "cannot open file on %v:%v", l) { 50 return err 51 } 52 53 if rang == nil { 54 _, err = io.Copy(dest, f) 55 } else { 56 left := rang.To - rang.From 57 f.Seek(rang.From, 0) 58 var b [4096]byte 59 60 for left > 0 && err == nil { 61 var sz int64 62 if rang.From-rang.To > 4096 { 63 sz = 4096 64 } else { 65 sz = rang.From - rang.To 66 } 67 _, err = f.Read(b[0:sz]) 68 dest.Write(b[0:sz]) 69 left -= sz 70 } 71 } 72 if core.IsErr(err, "cannot read from %s/%s:%v", l, name) { 73 return err 74 } 75 76 return nil 77 } 78 79 func createDir(n string) error { 80 return os.MkdirAll(filepath.Dir(n), 0755) 81 } 82 83 func (l *Local) Write(name string, source io.Reader) error { 84 n := filepath.Join(l.base, name) 85 err := createDir(n) 86 if core.IsErr(err, "cannot create parent of %s: %v", n) { 87 return err 88 } 89 90 f, err := os.Create(n) 91 if core.IsErr(err, "cannot create file on %v:%v", l) { 92 return err 93 } 94 defer f.Close() 95 96 _, err = io.Copy(f, source) 97 core.IsErr(err, "cannot copy file on %v:%v", l) 98 return err 99 } 100 101 func (l *Local) ReadDir(name string, opts ListOption) ([]fs.FileInfo, error) { 102 var dir, prefix string 103 if opts&IsPrefix > 0 { 104 dir, prefix = filepath.Split(filepath.Join(l.base, name)) 105 } else { 106 dir = filepath.Join(l.base, name) 107 } 108 result, err := os.ReadDir(dir) 109 if err != nil { 110 return nil, err 111 } 112 113 var infos []fs.FileInfo 114 for _, item := range result { 115 if strings.HasPrefix(item.Name(), prefix) { 116 info, err := item.Info() 117 if err == nil { 118 infos = append(infos, info) 119 } 120 } 121 } 122 123 return infos, nil 124 } 125 126 func (l *Local) Stat(name string) (os.FileInfo, error) { 127 return os.Stat(path.Join(l.base, name)) 128 } 129 130 func (l *Local) Rename(old, new string) error { 131 return os.Rename(path.Join(l.base, old), path.Join(l.base, new)) 132 } 133 134 func (l *Local) Delete(name string) error { 135 return os.Remove(path.Join(l.base, name)) 136 } 137 138 func (l *Local) Close() error { 139 return nil 140 } 141 142 func (l *Local) String() string { 143 return l.url 144 }