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  }