github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/swarm/api/api.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 "fmt" 21 "io" 22 "net/http" 23 "regexp" 24 "strings" 25 "sync" 26 27 "bytes" 28 "mime" 29 "path/filepath" 30 "time" 31 32 "github.com/SmartMeshFoundation/Spectrum/common" 33 "github.com/SmartMeshFoundation/Spectrum/log" 34 "github.com/SmartMeshFoundation/Spectrum/swarm/storage" 35 ) 36 37 var hashMatcher = regexp.MustCompile("^[0-9A-Fa-f]{64}") 38 39 type Resolver interface { 40 Resolve(string) (common.Hash, error) 41 } 42 43 /* 44 Api implements webserver/file system related content storage and retrieval 45 on top of the dpa 46 it is the public interface of the dpa which is included in the ethereum stack 47 */ 48 type Api struct { 49 dpa *storage.DPA 50 dns Resolver 51 } 52 53 //the api constructor initialises 54 func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) { 55 self = &Api{ 56 dpa: dpa, 57 dns: dns, 58 } 59 return 60 } 61 62 // to be used only in TEST 63 func (self *Api) Upload(uploadDir, index string) (hash string, err error) { 64 fs := NewFileSystem(self) 65 hash, err = fs.Upload(uploadDir, index) 66 return hash, err 67 } 68 69 // DPA reader API 70 func (self *Api) Retrieve(key storage.Key) storage.LazySectionReader { 71 return self.dpa.Retrieve(key) 72 } 73 74 func (self *Api) Store(data io.Reader, size int64, wg *sync.WaitGroup) (key storage.Key, err error) { 75 return self.dpa.Store(data, size, wg, nil) 76 } 77 78 type ErrResolve error 79 80 // DNS Resolver 81 func (self *Api) Resolve(uri *URI) (storage.Key, error) { 82 log.Trace(fmt.Sprintf("Resolving : %v", uri.Addr)) 83 84 // if the URI is immutable, check if the address is a hash 85 isHash := hashMatcher.MatchString(uri.Addr) 86 if uri.Immutable() || uri.DeprecatedImmutable() { 87 if !isHash { 88 return nil, fmt.Errorf("immutable address not a content hash: %q", uri.Addr) 89 } 90 return common.Hex2Bytes(uri.Addr), nil 91 } 92 93 // if DNS is not configured, check if the address is a hash 94 if self.dns == nil { 95 if !isHash { 96 return nil, fmt.Errorf("no DNS to resolve name: %q", uri.Addr) 97 } 98 return common.Hex2Bytes(uri.Addr), nil 99 } 100 101 // try and resolve the address 102 resolved, err := self.dns.Resolve(uri.Addr) 103 if err == nil { 104 return resolved[:], nil 105 } else if !isHash { 106 return nil, err 107 } 108 return common.Hex2Bytes(uri.Addr), nil 109 } 110 111 // Put provides singleton manifest creation on top of dpa store 112 func (self *Api) Put(content, contentType string) (storage.Key, error) { 113 r := strings.NewReader(content) 114 wg := &sync.WaitGroup{} 115 key, err := self.dpa.Store(r, int64(len(content)), wg, nil) 116 if err != nil { 117 return nil, err 118 } 119 manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType) 120 r = strings.NewReader(manifest) 121 key, err = self.dpa.Store(r, int64(len(manifest)), wg, nil) 122 if err != nil { 123 return nil, err 124 } 125 wg.Wait() 126 return key, nil 127 } 128 129 // Get uses iterative manifest retrieval and prefix matching 130 // to resolve basePath to content using dpa retrieve 131 // it returns a section reader, mimeType, status and an error 132 func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionReader, mimeType string, status int, err error) { 133 trie, err := loadManifest(self.dpa, key, nil) 134 if err != nil { 135 status = http.StatusNotFound 136 log.Warn(fmt.Sprintf("loadManifestTrie error: %v", err)) 137 return 138 } 139 140 log.Trace(fmt.Sprintf("getEntry(%s)", path)) 141 142 entry, _ := trie.getEntry(path) 143 144 if entry != nil { 145 key = common.Hex2Bytes(entry.Hash) 146 status = entry.Status 147 if status == http.StatusMultipleChoices { 148 return 149 } else { 150 mimeType = entry.ContentType 151 log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", key, mimeType)) 152 reader = self.dpa.Retrieve(key) 153 } 154 } else { 155 status = http.StatusNotFound 156 err = fmt.Errorf("manifest entry for '%s' not found", path) 157 log.Warn(fmt.Sprintf("%v", err)) 158 } 159 return 160 } 161 162 func (self *Api) Modify(key storage.Key, path, contentHash, contentType string) (storage.Key, error) { 163 quitC := make(chan bool) 164 trie, err := loadManifest(self.dpa, key, quitC) 165 if err != nil { 166 return nil, err 167 } 168 if contentHash != "" { 169 entry := newManifestTrieEntry(&ManifestEntry{ 170 Path: path, 171 ContentType: contentType, 172 }, nil) 173 entry.Hash = contentHash 174 trie.addEntry(entry, quitC) 175 } else { 176 trie.deleteEntry(path, quitC) 177 } 178 179 if err := trie.recalcAndStore(); err != nil { 180 return nil, err 181 } 182 return trie.hash, nil 183 } 184 185 func (self *Api) AddFile(mhash, path, fname string, content []byte, nameresolver bool) (storage.Key, string, error) { 186 187 uri, err := Parse("bzz:/" + mhash) 188 if err != nil { 189 return nil, "", err 190 } 191 mkey, err := self.Resolve(uri) 192 if err != nil { 193 return nil, "", err 194 } 195 196 // trim the root dir we added 197 if path[:1] == "/" { 198 path = path[1:] 199 } 200 201 entry := &ManifestEntry{ 202 Path: filepath.Join(path, fname), 203 ContentType: mime.TypeByExtension(filepath.Ext(fname)), 204 Mode: 0700, 205 Size: int64(len(content)), 206 ModTime: time.Now(), 207 } 208 209 mw, err := self.NewManifestWriter(mkey, nil) 210 if err != nil { 211 return nil, "", err 212 } 213 214 fkey, err := mw.AddEntry(bytes.NewReader(content), entry) 215 if err != nil { 216 return nil, "", err 217 } 218 219 newMkey, err := mw.Store() 220 if err != nil { 221 return nil, "", err 222 223 } 224 225 return fkey, newMkey.String(), nil 226 227 } 228 229 func (self *Api) RemoveFile(mhash, path, fname string, nameresolver bool) (string, error) { 230 231 uri, err := Parse("bzz:/" + mhash) 232 if err != nil { 233 return "", err 234 } 235 mkey, err := self.Resolve(uri) 236 if err != nil { 237 return "", err 238 } 239 240 // trim the root dir we added 241 if path[:1] == "/" { 242 path = path[1:] 243 } 244 245 mw, err := self.NewManifestWriter(mkey, nil) 246 if err != nil { 247 return "", err 248 } 249 250 err = mw.RemoveEntry(filepath.Join(path, fname)) 251 if err != nil { 252 return "", err 253 } 254 255 newMkey, err := mw.Store() 256 if err != nil { 257 return "", err 258 259 } 260 261 return newMkey.String(), nil 262 } 263 264 func (self *Api) AppendFile(mhash, path, fname string, existingSize int64, content []byte, oldKey storage.Key, offset int64, addSize int64, nameresolver bool) (storage.Key, string, error) { 265 266 buffSize := offset + addSize 267 if buffSize < existingSize { 268 buffSize = existingSize 269 } 270 271 buf := make([]byte, buffSize) 272 273 oldReader := self.Retrieve(oldKey) 274 io.ReadAtLeast(oldReader, buf, int(offset)) 275 276 newReader := bytes.NewReader(content) 277 io.ReadAtLeast(newReader, buf[offset:], int(addSize)) 278 279 if buffSize < existingSize { 280 io.ReadAtLeast(oldReader, buf[addSize:], int(buffSize)) 281 } 282 283 combinedReader := bytes.NewReader(buf) 284 totalSize := int64(len(buf)) 285 286 // TODO(jmozah): to append using pyramid chunker when it is ready 287 //oldReader := self.Retrieve(oldKey) 288 //newReader := bytes.NewReader(content) 289 //combinedReader := io.MultiReader(oldReader, newReader) 290 291 uri, err := Parse("bzz:/" + mhash) 292 if err != nil { 293 return nil, "", err 294 } 295 mkey, err := self.Resolve(uri) 296 if err != nil { 297 return nil, "", err 298 } 299 300 // trim the root dir we added 301 if path[:1] == "/" { 302 path = path[1:] 303 } 304 305 mw, err := self.NewManifestWriter(mkey, nil) 306 if err != nil { 307 return nil, "", err 308 } 309 310 err = mw.RemoveEntry(filepath.Join(path, fname)) 311 if err != nil { 312 return nil, "", err 313 } 314 315 entry := &ManifestEntry{ 316 Path: filepath.Join(path, fname), 317 ContentType: mime.TypeByExtension(filepath.Ext(fname)), 318 Mode: 0700, 319 Size: totalSize, 320 ModTime: time.Now(), 321 } 322 323 fkey, err := mw.AddEntry(io.Reader(combinedReader), entry) 324 if err != nil { 325 return nil, "", err 326 } 327 328 newMkey, err := mw.Store() 329 if err != nil { 330 return nil, "", err 331 332 } 333 334 return fkey, newMkey.String(), nil 335 336 } 337 338 func (self *Api) BuildDirectoryTree(mhash string, nameresolver bool) (key storage.Key, manifestEntryMap map[string]*manifestTrieEntry, err error) { 339 uri, err := Parse("bzz:/" + mhash) 340 if err != nil { 341 return nil, nil, err 342 } 343 key, err = self.Resolve(uri) 344 if err != nil { 345 return nil, nil, err 346 } 347 348 quitC := make(chan bool) 349 rootTrie, err := loadManifest(self.dpa, key, quitC) 350 if err != nil { 351 return nil, nil, fmt.Errorf("can't load manifest %v: %v", key.String(), err) 352 } 353 354 manifestEntryMap = map[string]*manifestTrieEntry{} 355 err = rootTrie.listWithPrefix(uri.Path, quitC, func(entry *manifestTrieEntry, suffix string) { 356 manifestEntryMap[suffix] = entry 357 }) 358 359 if err != nil { 360 return nil, nil, fmt.Errorf("list with prefix failed %v: %v", key.String(), err) 361 } 362 return key, manifestEntryMap, nil 363 }