github.com/devfans/go-ethereum@v1.5.10-0.20170326212234-7419d0c38291/swarm/api/filesystem.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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 go-ethereum 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 go-ethereum 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/filepath" 26 "sync" 27 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/log" 30 "github.com/ethereum/go-ethereum/swarm/storage" 31 ) 32 33 const maxParallelFiles = 5 34 35 type FileSystem struct { 36 api *Api 37 } 38 39 func NewFileSystem(api *Api) *FileSystem { 40 return &FileSystem{api} 41 } 42 43 // Upload replicates a local directory as a manifest file and uploads it 44 // using dpa store 45 // TODO: localpath should point to a manifest 46 func (self *FileSystem) Upload(lpath, index string) (string, error) { 47 var list []*manifestTrieEntry 48 localpath, err := filepath.Abs(filepath.Clean(lpath)) 49 if err != nil { 50 return "", err 51 } 52 53 f, err := os.Open(localpath) 54 if err != nil { 55 return "", err 56 } 57 stat, err := f.Stat() 58 if err != nil { 59 return "", err 60 } 61 62 var start int 63 if stat.IsDir() { 64 start = len(localpath) 65 log.Debug(fmt.Sprintf("uploading '%s'", localpath)) 66 err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { 67 if (err == nil) && !info.IsDir() { 68 //fmt.Printf("lp %s path %s\n", localpath, path) 69 if len(path) <= start { 70 return fmt.Errorf("Path is too short") 71 } 72 if path[:start] != localpath { 73 return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) 74 } 75 entry := &manifestTrieEntry{ 76 Path: filepath.ToSlash(path), 77 } 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 := &manifestTrieEntry{ 95 Path: filepath.ToSlash(localpath), 96 } 97 list = append(list, entry) 98 } 99 100 cnt := len(list) 101 errors := make([]error, cnt) 102 done := make(chan bool, maxParallelFiles) 103 dcnt := 0 104 awg := &sync.WaitGroup{} 105 106 for i, entry := range list { 107 if i >= dcnt+maxParallelFiles { 108 <-done 109 dcnt++ 110 } 111 awg.Add(1) 112 go func(i int, entry *manifestTrieEntry, done chan bool) { 113 f, err := os.Open(entry.Path) 114 if err == nil { 115 stat, _ := f.Stat() 116 var hash storage.Key 117 wg := &sync.WaitGroup{} 118 hash, err = self.api.dpa.Store(f, stat.Size(), wg, nil) 119 if hash != nil { 120 list[i].Hash = hash.String() 121 } 122 wg.Wait() 123 awg.Done() 124 if err == nil { 125 first512 := make([]byte, 512) 126 fread, _ := f.ReadAt(first512, 0) 127 if fread > 0 { 128 mimeType := http.DetectContentType(first512[:fread]) 129 if filepath.Ext(entry.Path) == ".css" { 130 mimeType = "text/css" 131 } 132 list[i].ContentType = mimeType 133 } 134 } 135 f.Close() 136 } 137 errors[i] = err 138 done <- true 139 }(i, entry, done) 140 } 141 for dcnt < cnt { 142 <-done 143 dcnt++ 144 } 145 146 trie := &manifestTrie{ 147 dpa: self.api.dpa, 148 } 149 quitC := make(chan bool) 150 for i, entry := range list { 151 if errors[i] != nil { 152 return "", errors[i] 153 } 154 entry.Path = RegularSlashes(entry.Path[start:]) 155 if entry.Path == index { 156 ientry := &manifestTrieEntry{ 157 Path: "", 158 Hash: entry.Hash, 159 ContentType: entry.ContentType, 160 } 161 trie.addEntry(ientry, quitC) 162 } 163 trie.addEntry(entry, quitC) 164 } 165 166 err2 := trie.recalcAndStore() 167 var hs string 168 if err2 == nil { 169 hs = trie.hash.String() 170 } 171 awg.Wait() 172 return hs, err2 173 } 174 175 // Download replicates the manifest path structure on the local filesystem 176 // under localpath 177 func (self *FileSystem) Download(bzzpath, localpath string) error { 178 lpath, err := filepath.Abs(filepath.Clean(localpath)) 179 if err != nil { 180 return err 181 } 182 err = os.MkdirAll(lpath, os.ModePerm) 183 if err != nil { 184 return err 185 } 186 187 //resolving host and port 188 key, _, path, err := self.api.parseAndResolve(bzzpath, true) 189 if err != nil { 190 return err 191 } 192 193 if len(path) > 0 { 194 path += "/" 195 } 196 197 quitC := make(chan bool) 198 trie, err := loadManifest(self.api.dpa, key, quitC) 199 if err != nil { 200 log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)) 201 return err 202 } 203 204 type downloadListEntry struct { 205 key storage.Key 206 path string 207 } 208 209 var list []*downloadListEntry 210 var mde error 211 212 prevPath := lpath 213 err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) { 214 log.Trace(fmt.Sprintf("fs.Download: %#v", entry)) 215 216 key = common.Hex2Bytes(entry.Hash) 217 path := lpath + "/" + suffix 218 dir := filepath.Dir(path) 219 if dir != prevPath { 220 mde = os.MkdirAll(dir, os.ModePerm) 221 prevPath = dir 222 } 223 if (mde == nil) && (path != dir+"/") { 224 list = append(list, &downloadListEntry{key: key, path: path}) 225 } 226 }) 227 if err != nil { 228 return err 229 } 230 231 wg := sync.WaitGroup{} 232 errC := make(chan error) 233 done := make(chan bool, maxParallelFiles) 234 for i, entry := range list { 235 select { 236 case done <- true: 237 wg.Add(1) 238 case <-quitC: 239 return fmt.Errorf("aborted") 240 } 241 go func(i int, entry *downloadListEntry) { 242 defer wg.Done() 243 err := retrieveToFile(quitC, self.api.dpa, entry.key, entry.path) 244 if err != nil { 245 select { 246 case errC <- err: 247 case <-quitC: 248 } 249 return 250 } 251 <-done 252 }(i, entry) 253 } 254 go func() { 255 wg.Wait() 256 close(errC) 257 }() 258 select { 259 case err = <-errC: 260 return err 261 case <-quitC: 262 return fmt.Errorf("aborted") 263 } 264 } 265 266 func retrieveToFile(quitC chan bool, dpa *storage.DPA, key storage.Key, path string) error { 267 f, err := os.Create(path) // TODO: path separators 268 if err != nil { 269 return err 270 } 271 reader := dpa.Retrieve(key) 272 writer := bufio.NewWriter(f) 273 size, err := reader.Size(quitC) 274 if err != nil { 275 return err 276 } 277 if _, err = io.CopyN(writer, reader, size); err != nil { 278 return err 279 } 280 if err := writer.Flush(); err != nil { 281 return err 282 } 283 return f.Close() 284 }