github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/swarm/api/filesystem.go (about) 1 // Copyright 2016 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The Spectrum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the Spectrum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "bufio" 21 "fmt" 22 "io" 23 "net/http" 24 "os" 25 "path" 26 "path/filepath" 27 "sync" 28 29 "github.com/SmartMeshFoundation/Spectrum/common" 30 "github.com/SmartMeshFoundation/Spectrum/log" 31 "github.com/SmartMeshFoundation/Spectrum/swarm/storage" 32 ) 33 34 const maxParallelFiles = 5 35 36 type FileSystem struct { 37 api *Api 38 } 39 40 func NewFileSystem(api *Api) *FileSystem { 41 return &FileSystem{api} 42 } 43 44 // Upload replicates a local directory as a manifest file and uploads it 45 // using dpa store 46 // TODO: localpath should point to a manifest 47 // 48 // DEPRECATED: Use the HTTP API instead 49 func (self *FileSystem) Upload(lpath, index string) (string, error) { 50 var list []*manifestTrieEntry 51 localpath, err := filepath.Abs(filepath.Clean(lpath)) 52 if err != nil { 53 return "", err 54 } 55 56 f, err := os.Open(localpath) 57 if err != nil { 58 return "", err 59 } 60 stat, err := f.Stat() 61 if err != nil { 62 return "", err 63 } 64 65 var start int 66 if stat.IsDir() { 67 start = len(localpath) 68 log.Debug(fmt.Sprintf("uploading '%s'", localpath)) 69 err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { 70 if (err == nil) && !info.IsDir() { 71 if len(path) <= start { 72 return fmt.Errorf("Path is too short") 73 } 74 if path[:start] != localpath { 75 return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) 76 } 77 entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(path)}, nil) 78 list = append(list, entry) 79 } 80 return err 81 }) 82 if err != nil { 83 return "", err 84 } 85 } else { 86 dir := filepath.Dir(localpath) 87 start = len(dir) 88 if len(localpath) <= start { 89 return "", fmt.Errorf("Path is too short") 90 } 91 if localpath[:start] != dir { 92 return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir) 93 } 94 entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(localpath)}, nil) 95 list = append(list, entry) 96 } 97 98 cnt := len(list) 99 errors := make([]error, cnt) 100 done := make(chan bool, maxParallelFiles) 101 dcnt := 0 102 awg := &sync.WaitGroup{} 103 104 for i, entry := range list { 105 if i >= dcnt+maxParallelFiles { 106 <-done 107 dcnt++ 108 } 109 awg.Add(1) 110 go func(i int, entry *manifestTrieEntry, done chan bool) { 111 f, err := os.Open(entry.Path) 112 if err == nil { 113 stat, _ := f.Stat() 114 var hash storage.Key 115 wg := &sync.WaitGroup{} 116 hash, err = self.api.dpa.Store(f, stat.Size(), wg, nil) 117 if hash != nil { 118 list[i].Hash = hash.String() 119 } 120 wg.Wait() 121 awg.Done() 122 if err == nil { 123 first512 := make([]byte, 512) 124 fread, _ := f.ReadAt(first512, 0) 125 if fread > 0 { 126 mimeType := http.DetectContentType(first512[:fread]) 127 if filepath.Ext(entry.Path) == ".css" { 128 mimeType = "text/css" 129 } 130 list[i].ContentType = mimeType 131 } 132 } 133 f.Close() 134 } 135 errors[i] = err 136 done <- true 137 }(i, entry, done) 138 } 139 for dcnt < cnt { 140 <-done 141 dcnt++ 142 } 143 144 trie := &manifestTrie{ 145 dpa: self.api.dpa, 146 } 147 quitC := make(chan bool) 148 for i, entry := range list { 149 if errors[i] != nil { 150 return "", errors[i] 151 } 152 entry.Path = RegularSlashes(entry.Path[start:]) 153 if entry.Path == index { 154 ientry := newManifestTrieEntry(&ManifestEntry{ 155 ContentType: entry.ContentType, 156 }, nil) 157 ientry.Hash = entry.Hash 158 trie.addEntry(ientry, quitC) 159 } 160 trie.addEntry(entry, quitC) 161 } 162 163 err2 := trie.recalcAndStore() 164 var hs string 165 if err2 == nil { 166 hs = trie.hash.String() 167 } 168 awg.Wait() 169 return hs, err2 170 } 171 172 // Download replicates the manifest basePath structure on the local filesystem 173 // under localpath 174 // 175 // DEPRECATED: Use the HTTP API instead 176 func (self *FileSystem) Download(bzzpath, localpath string) error { 177 lpath, err := filepath.Abs(filepath.Clean(localpath)) 178 if err != nil { 179 return err 180 } 181 err = os.MkdirAll(lpath, os.ModePerm) 182 if err != nil { 183 return err 184 } 185 186 //resolving host and port 187 uri, err := Parse(path.Join("bzz:/", bzzpath)) 188 if err != nil { 189 return err 190 } 191 key, err := self.api.Resolve(uri) 192 if err != nil { 193 return err 194 } 195 path := uri.Path 196 197 if len(path) > 0 { 198 path += "/" 199 } 200 201 quitC := make(chan bool) 202 trie, err := loadManifest(self.api.dpa, key, quitC) 203 if err != nil { 204 log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)) 205 return err 206 } 207 208 type downloadListEntry struct { 209 key storage.Key 210 path string 211 } 212 213 var list []*downloadListEntry 214 var mde error 215 216 prevPath := lpath 217 err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) { 218 log.Trace(fmt.Sprintf("fs.Download: %#v", entry)) 219 220 key = common.Hex2Bytes(entry.Hash) 221 path := lpath + "/" + suffix 222 dir := filepath.Dir(path) 223 if dir != prevPath { 224 mde = os.MkdirAll(dir, os.ModePerm) 225 prevPath = dir 226 } 227 if (mde == nil) && (path != dir+"/") { 228 list = append(list, &downloadListEntry{key: key, path: path}) 229 } 230 }) 231 if err != nil { 232 return err 233 } 234 235 wg := sync.WaitGroup{} 236 errC := make(chan error) 237 done := make(chan bool, maxParallelFiles) 238 for i, entry := range list { 239 select { 240 case done <- true: 241 wg.Add(1) 242 case <-quitC: 243 return fmt.Errorf("aborted") 244 } 245 go func(i int, entry *downloadListEntry) { 246 defer wg.Done() 247 err := retrieveToFile(quitC, self.api.dpa, entry.key, entry.path) 248 if err != nil { 249 select { 250 case errC <- err: 251 case <-quitC: 252 } 253 return 254 } 255 <-done 256 }(i, entry) 257 } 258 go func() { 259 wg.Wait() 260 close(errC) 261 }() 262 select { 263 case err = <-errC: 264 return err 265 case <-quitC: 266 return fmt.Errorf("aborted") 267 } 268 } 269 270 func retrieveToFile(quitC chan bool, dpa *storage.DPA, key storage.Key, path string) error { 271 f, err := os.Create(path) // TODO: basePath separators 272 if err != nil { 273 return err 274 } 275 reader := dpa.Retrieve(key) 276 writer := bufio.NewWriter(f) 277 size, err := reader.Size(quitC) 278 if err != nil { 279 return err 280 } 281 if _, err = io.CopyN(writer, reader, size); err != nil { 282 return err 283 } 284 if err := writer.Flush(); err != nil { 285 return err 286 } 287 return f.Close() 288 }