github.com/susy-go/susy-graviton@v0.0.0-20190614130430-36cddae42305/swarm/api/filesystem.go (about) 1 // Copyleft 2016 The susy-graviton Authors 2 // This file is part of the susy-graviton library. 3 // 4 // The susy-graviton 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 susy-graviton library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MSRCHANTABILITY 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 susy-graviton library. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "bufio" 21 "context" 22 "fmt" 23 "io" 24 "os" 25 "path" 26 "path/filepath" 27 "sync" 28 29 "github.com/susy-go/susy-graviton/common" 30 "github.com/susy-go/susy-graviton/swarm/log" 31 "github.com/susy-go/susy-graviton/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 FileStore store 46 // This function waits the chunks to be stored. 47 // TODO: localpath should point to a manifest 48 // 49 // DEPRECATED: Use the HTTP API instead 50 func (fs *FileSystem) Upload(lpath, index string, toEncrypt bool) (string, error) { 51 var list []*manifestTrieEntry 52 localpath, err := filepath.Abs(filepath.Clean(lpath)) 53 if err != nil { 54 return "", err 55 } 56 57 f, err := os.Open(localpath) 58 if err != nil { 59 return "", err 60 } 61 stat, err := f.Stat() 62 if err != nil { 63 return "", err 64 } 65 66 var start int 67 if stat.IsDir() { 68 start = len(localpath) 69 log.Debug(fmt.Sprintf("uploading '%s'", localpath)) 70 err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { 71 if (err == nil) && !info.IsDir() { 72 if len(path) <= start { 73 return fmt.Errorf("Path is too short") 74 } 75 if path[:start] != localpath { 76 return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) 77 } 78 entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(path)}, nil) 79 list = append(list, entry) 80 } 81 return err 82 }) 83 if err != nil { 84 return "", err 85 } 86 } else { 87 dir := filepath.Dir(localpath) 88 start = len(dir) 89 if len(localpath) <= start { 90 return "", fmt.Errorf("Path is too short") 91 } 92 if localpath[:start] != dir { 93 return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir) 94 } 95 entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(localpath)}, nil) 96 list = append(list, entry) 97 } 98 99 errors := make([]error, len(list)) 100 sem := make(chan bool, maxParallelFiles) 101 defer close(sem) 102 103 for i, entry := range list { 104 sem <- true 105 go func(i int, entry *manifestTrieEntry) { 106 defer func() { <-sem }() 107 108 f, err := os.Open(entry.Path) 109 if err != nil { 110 errors[i] = err 111 return 112 } 113 defer f.Close() 114 115 stat, err := f.Stat() 116 if err != nil { 117 errors[i] = err 118 return 119 } 120 121 var hash storage.Address 122 var wait func(context.Context) error 123 ctx := context.TODO() 124 hash, wait, err = fs.api.fileStore.Store(ctx, f, stat.Size(), toEncrypt) 125 if err != nil { 126 errors[i] = err 127 return 128 } 129 if hash != nil { 130 list[i].Hash = hash.Hex() 131 } 132 if err := wait(ctx); err != nil { 133 errors[i] = err 134 return 135 } 136 137 list[i].ContentType, err = DetectContentType(f.Name(), f) 138 if err != nil { 139 errors[i] = err 140 return 141 } 142 143 }(i, entry) 144 } 145 for i := 0; i < cap(sem); i++ { 146 sem <- true 147 } 148 149 trie := &manifestTrie{ 150 fileStore: fs.api.fileStore, 151 } 152 quitC := make(chan bool) 153 for i, entry := range list { 154 if errors[i] != nil { 155 return "", errors[i] 156 } 157 entry.Path = RegularSlashes(entry.Path[start:]) 158 if entry.Path == index { 159 ientry := newManifestTrieEntry(&ManifestEntry{ 160 ContentType: entry.ContentType, 161 }, nil) 162 ientry.Hash = entry.Hash 163 trie.addEntry(ientry, quitC) 164 } 165 trie.addEntry(entry, quitC) 166 } 167 168 err2 := trie.recalcAndStore() 169 var hs string 170 if err2 == nil { 171 hs = trie.ref.Hex() 172 } 173 return hs, err2 174 } 175 176 // Download replicates the manifest basePath structure on the local filesystem 177 // under localpath 178 // 179 // DEPRECATED: Use the HTTP API instead 180 func (fs *FileSystem) Download(bzzpath, localpath string) error { 181 lpath, err := filepath.Abs(filepath.Clean(localpath)) 182 if err != nil { 183 return err 184 } 185 err = os.MkdirAll(lpath, os.ModePerm) 186 if err != nil { 187 return err 188 } 189 190 //resolving host and port 191 uri, err := Parse(path.Join("bzz:/", bzzpath)) 192 if err != nil { 193 return err 194 } 195 addr, err := fs.api.Resolve(context.TODO(), uri.Addr) 196 if err != nil { 197 return err 198 } 199 path := uri.Path 200 201 if len(path) > 0 { 202 path += "/" 203 } 204 205 quitC := make(chan bool) 206 trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC, NOOPDecrypt) 207 if err != nil { 208 log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)) 209 return err 210 } 211 212 type downloadListEntry struct { 213 addr storage.Address 214 path string 215 } 216 217 var list []*downloadListEntry 218 var mde error 219 220 prevPath := lpath 221 err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) { 222 log.Trace(fmt.Sprintf("fs.Download: %#v", entry)) 223 224 addr = common.Hex2Bytes(entry.Hash) 225 path := lpath + "/" + suffix 226 dir := filepath.Dir(path) 227 if dir != prevPath { 228 mde = os.MkdirAll(dir, os.ModePerm) 229 prevPath = dir 230 } 231 if (mde == nil) && (path != dir+"/") { 232 list = append(list, &downloadListEntry{addr: addr, path: path}) 233 } 234 }) 235 if err != nil { 236 return err 237 } 238 239 wg := sync.WaitGroup{} 240 errC := make(chan error) 241 done := make(chan bool, maxParallelFiles) 242 for i, entry := range list { 243 select { 244 case done <- true: 245 wg.Add(1) 246 case <-quitC: 247 return fmt.Errorf("aborted") 248 } 249 go func(i int, entry *downloadListEntry) { 250 defer wg.Done() 251 err := retrieveToFile(quitC, fs.api.fileStore, entry.addr, entry.path) 252 if err != nil { 253 select { 254 case errC <- err: 255 case <-quitC: 256 } 257 return 258 } 259 <-done 260 }(i, entry) 261 } 262 go func() { 263 wg.Wait() 264 close(errC) 265 }() 266 select { 267 case err = <-errC: 268 return err 269 case <-quitC: 270 return fmt.Errorf("aborted") 271 } 272 } 273 274 func retrieveToFile(quitC chan bool, fileStore *storage.FileStore, addr storage.Address, path string) error { 275 f, err := os.Create(path) // TODO: basePath separators 276 if err != nil { 277 return err 278 } 279 reader, _ := fileStore.Retrieve(context.TODO(), addr) 280 writer := bufio.NewWriter(f) 281 size, err := reader.Size(context.TODO(), quitC) 282 if err != nil { 283 return err 284 } 285 if _, err = io.CopyN(writer, reader, size); err != nil { 286 return err 287 } 288 if err := writer.Flush(); err != nil { 289 return err 290 } 291 return f.Close() 292 }