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