github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/swarm/api/filesystem.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 12:09:46</date> 10 //</624342668939366400> 11 12 // 13 // 14 // 15 // 16 // 17 // 18 // 19 // 20 // 21 // 22 // 23 // 24 // 25 // 26 // 27 28 package api 29 30 import ( 31 "bufio" 32 "context" 33 "fmt" 34 "io" 35 "net/http" 36 "os" 37 "path" 38 "path/filepath" 39 "sync" 40 41 "github.com/ethereum/go-ethereum/common" 42 "github.com/ethereum/go-ethereum/swarm/log" 43 "github.com/ethereum/go-ethereum/swarm/storage" 44 ) 45 46 const maxParallelFiles = 5 47 48 type FileSystem struct { 49 api *API 50 } 51 52 func NewFileSystem(api *API) *FileSystem { 53 return &FileSystem{api} 54 } 55 56 // 57 // 58 // 59 // 60 // 61 // 62 func (fs *FileSystem) Upload(lpath, index string, toEncrypt bool) (string, error) { 63 var list []*manifestTrieEntry 64 localpath, err := filepath.Abs(filepath.Clean(lpath)) 65 if err != nil { 66 return "", err 67 } 68 69 f, err := os.Open(localpath) 70 if err != nil { 71 return "", err 72 } 73 stat, err := f.Stat() 74 if err != nil { 75 return "", err 76 } 77 78 var start int 79 if stat.IsDir() { 80 start = len(localpath) 81 log.Debug(fmt.Sprintf("uploading '%s'", localpath)) 82 err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { 83 if (err == nil) && !info.IsDir() { 84 if len(path) <= start { 85 return fmt.Errorf("Path is too short") 86 } 87 if path[:start] != localpath { 88 return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) 89 } 90 entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(path)}, nil) 91 list = append(list, entry) 92 } 93 return err 94 }) 95 if err != nil { 96 return "", err 97 } 98 } else { 99 dir := filepath.Dir(localpath) 100 start = len(dir) 101 if len(localpath) <= start { 102 return "", fmt.Errorf("Path is too short") 103 } 104 if localpath[:start] != dir { 105 return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir) 106 } 107 entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(localpath)}, nil) 108 list = append(list, entry) 109 } 110 111 cnt := len(list) 112 errors := make([]error, cnt) 113 done := make(chan bool, maxParallelFiles) 114 dcnt := 0 115 awg := &sync.WaitGroup{} 116 117 for i, entry := range list { 118 if i >= dcnt+maxParallelFiles { 119 <-done 120 dcnt++ 121 } 122 awg.Add(1) 123 go func(i int, entry *manifestTrieEntry, done chan bool) { 124 f, err := os.Open(entry.Path) 125 if err == nil { 126 stat, _ := f.Stat() 127 var hash storage.Address 128 var wait func(context.Context) error 129 ctx := context.TODO() 130 hash, wait, err = fs.api.fileStore.Store(ctx, f, stat.Size(), toEncrypt) 131 if hash != nil { 132 list[i].Hash = hash.Hex() 133 } 134 err = wait(ctx) 135 awg.Done() 136 if err == nil { 137 first512 := make([]byte, 512) 138 fread, _ := f.ReadAt(first512, 0) 139 if fread > 0 { 140 mimeType := http.DetectContentType(first512[:fread]) 141 if filepath.Ext(entry.Path) == ".css" { 142 mimeType = "text/css" 143 } 144 list[i].ContentType = mimeType 145 } 146 } 147 f.Close() 148 } 149 errors[i] = err 150 done <- true 151 }(i, entry, done) 152 } 153 for dcnt < cnt { 154 <-done 155 dcnt++ 156 } 157 158 trie := &manifestTrie{ 159 fileStore: fs.api.fileStore, 160 } 161 quitC := make(chan bool) 162 for i, entry := range list { 163 if errors[i] != nil { 164 return "", errors[i] 165 } 166 entry.Path = RegularSlashes(entry.Path[start:]) 167 if entry.Path == index { 168 ientry := newManifestTrieEntry(&ManifestEntry{ 169 ContentType: entry.ContentType, 170 }, nil) 171 ientry.Hash = entry.Hash 172 trie.addEntry(ientry, quitC) 173 } 174 trie.addEntry(entry, quitC) 175 } 176 177 err2 := trie.recalcAndStore() 178 var hs string 179 if err2 == nil { 180 hs = trie.ref.Hex() 181 } 182 awg.Wait() 183 return hs, err2 184 } 185 186 // 187 // 188 // 189 // 190 func (fs *FileSystem) Download(bzzpath, localpath string) error { 191 lpath, err := filepath.Abs(filepath.Clean(localpath)) 192 if err != nil { 193 return err 194 } 195 err = os.MkdirAll(lpath, os.ModePerm) 196 if err != nil { 197 return err 198 } 199 200 // 201 uri, err := Parse(path.Join("bzz:/", bzzpath)) 202 if err != nil { 203 return err 204 } 205 addr, err := fs.api.Resolve(context.TODO(), uri.Addr) 206 if err != nil { 207 return err 208 } 209 path := uri.Path 210 211 if len(path) > 0 { 212 path += "/" 213 } 214 215 quitC := make(chan bool) 216 trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC, NOOPDecrypt) 217 if err != nil { 218 log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)) 219 return err 220 } 221 222 type downloadListEntry struct { 223 addr storage.Address 224 path string 225 } 226 227 var list []*downloadListEntry 228 var mde error 229 230 prevPath := lpath 231 err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) { 232 log.Trace(fmt.Sprintf("fs.Download: %#v", entry)) 233 234 addr = common.Hex2Bytes(entry.Hash) 235 path := lpath + "/" + suffix 236 dir := filepath.Dir(path) 237 if dir != prevPath { 238 mde = os.MkdirAll(dir, os.ModePerm) 239 prevPath = dir 240 } 241 if (mde == nil) && (path != dir+"/") { 242 list = append(list, &downloadListEntry{addr: addr, path: path}) 243 } 244 }) 245 if err != nil { 246 return err 247 } 248 249 wg := sync.WaitGroup{} 250 errC := make(chan error) 251 done := make(chan bool, maxParallelFiles) 252 for i, entry := range list { 253 select { 254 case done <- true: 255 wg.Add(1) 256 case <-quitC: 257 return fmt.Errorf("aborted") 258 } 259 go func(i int, entry *downloadListEntry) { 260 defer wg.Done() 261 err := retrieveToFile(quitC, fs.api.fileStore, entry.addr, entry.path) 262 if err != nil { 263 select { 264 case errC <- err: 265 case <-quitC: 266 } 267 return 268 } 269 <-done 270 }(i, entry) 271 } 272 go func() { 273 wg.Wait() 274 close(errC) 275 }() 276 select { 277 case err = <-errC: 278 return err 279 case <-quitC: 280 return fmt.Errorf("aborted") 281 } 282 } 283 284 func retrieveToFile(quitC chan bool, fileStore *storage.FileStore, addr storage.Address, path string) error { 285 f, err := os.Create(path) // 286 if err != nil { 287 return err 288 } 289 reader, _ := fileStore.Retrieve(context.TODO(), addr) 290 writer := bufio.NewWriter(f) 291 size, err := reader.Size(context.TODO(), quitC) 292 if err != nil { 293 return err 294 } 295 if _, err = io.CopyN(writer, reader, size); err != nil { 296 return err 297 } 298 if err := writer.Flush(); err != nil { 299 return err 300 } 301 return f.Close() 302 } 303