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