| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | // Copyright 2016 The go-ethereum Authors | 
					
						
							|  |  |  | // This file is part of the go-ethereum library. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Lesser General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  | // GNU Lesser General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Lesser General Public License | 
					
						
							|  |  |  | // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package api | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bufio" | 
					
						
							| 
									
										
										
										
											2018-07-09 14:11:49 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2017-04-06 23:22:22 +01:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/common" | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 	"github.com/ethereum/go-ethereum/swarm/log" | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	"github.com/ethereum/go-ethereum/swarm/storage" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const maxParallelFiles = 5 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type FileSystem struct { | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 	api *API | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | func NewFileSystem(api *API) *FileSystem { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	return &FileSystem{api} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Upload replicates a local directory as a manifest file and uploads it | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | // using FileStore store | 
					
						
							|  |  |  | // This function waits the chunks to be stored. | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | // TODO: localpath should point to a manifest | 
					
						
							| 
									
										
										
										
											2017-04-06 23:22:22 +01:00
										 |  |  | // | 
					
						
							|  |  |  | // DEPRECATED: Use the HTTP API instead | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | func (fs *FileSystem) Upload(lpath, index string, toEncrypt bool) (string, error) { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	var list []*manifestTrieEntry | 
					
						
							|  |  |  | 	localpath, err := filepath.Abs(filepath.Clean(lpath)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	f, err := os.Open(localpath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	stat, err := f.Stat() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var start int | 
					
						
							|  |  |  | 	if stat.IsDir() { | 
					
						
							|  |  |  | 		start = len(localpath) | 
					
						
							| 
									
										
										
										
											2017-02-22 14:10:07 +02:00
										 |  |  | 		log.Debug(fmt.Sprintf("uploading '%s'", localpath)) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 		err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { | 
					
						
							|  |  |  | 			if (err == nil) && !info.IsDir() { | 
					
						
							|  |  |  | 				if len(path) <= start { | 
					
						
							|  |  |  | 					return fmt.Errorf("Path is too short") | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if path[:start] != localpath { | 
					
						
							|  |  |  | 					return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2017-04-06 23:22:22 +01:00
										 |  |  | 				entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(path)}, nil) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 				list = append(list, entry) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		dir := filepath.Dir(localpath) | 
					
						
							|  |  |  | 		start = len(dir) | 
					
						
							|  |  |  | 		if len(localpath) <= start { | 
					
						
							|  |  |  | 			return "", fmt.Errorf("Path is too short") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if localpath[:start] != dir { | 
					
						
							|  |  |  | 			return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-04-06 23:22:22 +01:00
										 |  |  | 		entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(localpath)}, nil) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 		list = append(list, entry) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cnt := len(list) | 
					
						
							|  |  |  | 	errors := make([]error, cnt) | 
					
						
							|  |  |  | 	done := make(chan bool, maxParallelFiles) | 
					
						
							|  |  |  | 	dcnt := 0 | 
					
						
							|  |  |  | 	awg := &sync.WaitGroup{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, entry := range list { | 
					
						
							|  |  |  | 		if i >= dcnt+maxParallelFiles { | 
					
						
							|  |  |  | 			<-done | 
					
						
							|  |  |  | 			dcnt++ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		awg.Add(1) | 
					
						
							|  |  |  | 		go func(i int, entry *manifestTrieEntry, done chan bool) { | 
					
						
							|  |  |  | 			f, err := os.Open(entry.Path) | 
					
						
							|  |  |  | 			if err == nil { | 
					
						
							|  |  |  | 				stat, _ := f.Stat() | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 				var hash storage.Address | 
					
						
							| 
									
										
										
										
											2018-07-09 14:11:49 +02:00
										 |  |  | 				var wait func(context.Context) error | 
					
						
							|  |  |  | 				ctx := context.TODO() | 
					
						
							|  |  |  | 				hash, wait, err = fs.api.fileStore.Store(ctx, f, stat.Size(), toEncrypt) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 				if hash != nil { | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 					list[i].Hash = hash.Hex() | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-07-09 14:11:49 +02:00
										 |  |  | 				err = wait(ctx) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 				awg.Done() | 
					
						
							|  |  |  | 				if err == nil { | 
					
						
							|  |  |  | 					first512 := make([]byte, 512) | 
					
						
							|  |  |  | 					fread, _ := f.ReadAt(first512, 0) | 
					
						
							|  |  |  | 					if fread > 0 { | 
					
						
							|  |  |  | 						mimeType := http.DetectContentType(first512[:fread]) | 
					
						
							|  |  |  | 						if filepath.Ext(entry.Path) == ".css" { | 
					
						
							|  |  |  | 							mimeType = "text/css" | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						list[i].ContentType = mimeType | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				f.Close() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			errors[i] = err | 
					
						
							|  |  |  | 			done <- true | 
					
						
							|  |  |  | 		}(i, entry, done) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for dcnt < cnt { | 
					
						
							|  |  |  | 		<-done | 
					
						
							|  |  |  | 		dcnt++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	trie := &manifestTrie{ | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 		fileStore: fs.api.fileStore, | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	quitC := make(chan bool) | 
					
						
							|  |  |  | 	for i, entry := range list { | 
					
						
							|  |  |  | 		if errors[i] != nil { | 
					
						
							|  |  |  | 			return "", errors[i] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		entry.Path = RegularSlashes(entry.Path[start:]) | 
					
						
							|  |  |  | 		if entry.Path == index { | 
					
						
							| 
									
										
										
										
											2017-04-06 23:22:22 +01:00
										 |  |  | 			ientry := newManifestTrieEntry(&ManifestEntry{ | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 				ContentType: entry.ContentType, | 
					
						
							| 
									
										
										
										
											2017-04-06 23:22:22 +01:00
										 |  |  | 			}, nil) | 
					
						
							|  |  |  | 			ientry.Hash = entry.Hash | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 			trie.addEntry(ientry, quitC) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		trie.addEntry(entry, quitC) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err2 := trie.recalcAndStore() | 
					
						
							|  |  |  | 	var hs string | 
					
						
							|  |  |  | 	if err2 == nil { | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 		hs = trie.ref.Hex() | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	awg.Wait() | 
					
						
							|  |  |  | 	return hs, err2 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-12 05:36:02 +05:30
										 |  |  | // Download replicates the manifest basePath structure on the local filesystem | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | // under localpath | 
					
						
							| 
									
										
										
										
											2017-04-06 23:22:22 +01:00
										 |  |  | // | 
					
						
							|  |  |  | // DEPRECATED: Use the HTTP API instead | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | func (fs *FileSystem) Download(bzzpath, localpath string) error { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	lpath, err := filepath.Abs(filepath.Clean(localpath)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	err = os.MkdirAll(lpath, os.ModePerm) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	//resolving host and port | 
					
						
							| 
									
										
										
										
											2017-04-06 23:22:22 +01:00
										 |  |  | 	uri, err := Parse(path.Join("bzz:/", bzzpath)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-08-15 17:41:52 +02:00
										 |  |  | 	addr, err := fs.api.Resolve(context.TODO(), uri.Addr) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-06 23:22:22 +01:00
										 |  |  | 	path := uri.Path | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if len(path) > 0 { | 
					
						
							|  |  |  | 		path += "/" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	quitC := make(chan bool) | 
					
						
							| 
									
										
										
										
											2018-08-15 17:41:52 +02:00
										 |  |  | 	trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC, NOOPDecrypt) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-02-22 14:10:07 +02:00
										 |  |  | 		log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	type downloadListEntry struct { | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 		addr storage.Address | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 		path string | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var list []*downloadListEntry | 
					
						
							|  |  |  | 	var mde error | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	prevPath := lpath | 
					
						
							|  |  |  | 	err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) { | 
					
						
							| 
									
										
										
										
											2017-02-22 14:10:07 +02:00
										 |  |  | 		log.Trace(fmt.Sprintf("fs.Download: %#v", entry)) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 		addr = common.Hex2Bytes(entry.Hash) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 		path := lpath + "/" + suffix | 
					
						
							|  |  |  | 		dir := filepath.Dir(path) | 
					
						
							|  |  |  | 		if dir != prevPath { | 
					
						
							|  |  |  | 			mde = os.MkdirAll(dir, os.ModePerm) | 
					
						
							|  |  |  | 			prevPath = dir | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (mde == nil) && (path != dir+"/") { | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 			list = append(list, &downloadListEntry{addr: addr, path: path}) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	wg := sync.WaitGroup{} | 
					
						
							|  |  |  | 	errC := make(chan error) | 
					
						
							|  |  |  | 	done := make(chan bool, maxParallelFiles) | 
					
						
							|  |  |  | 	for i, entry := range list { | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case done <- true: | 
					
						
							|  |  |  | 			wg.Add(1) | 
					
						
							|  |  |  | 		case <-quitC: | 
					
						
							|  |  |  | 			return fmt.Errorf("aborted") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		go func(i int, entry *downloadListEntry) { | 
					
						
							|  |  |  | 			defer wg.Done() | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | 			err := retrieveToFile(quitC, fs.api.fileStore, entry.addr, entry.path) | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				select { | 
					
						
							|  |  |  | 				case errC <- err: | 
					
						
							|  |  |  | 				case <-quitC: | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			<-done | 
					
						
							|  |  |  | 		}(i, entry) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		wg.Wait() | 
					
						
							|  |  |  | 		close(errC) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case err = <-errC: | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	case <-quitC: | 
					
						
							|  |  |  | 		return fmt.Errorf("aborted") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-01-09 11:16:06 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-20 14:06:27 +02:00
										 |  |  | func retrieveToFile(quitC chan bool, fileStore *storage.FileStore, addr storage.Address, path string) error { | 
					
						
							| 
									
										
										
										
											2017-04-12 05:36:02 +05:30
										 |  |  | 	f, err := os.Create(path) // TODO: basePath separators | 
					
						
							| 
									
										
										
										
											2017-01-09 11:16:06 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-07-09 14:11:49 +02:00
										 |  |  | 	reader, _ := fileStore.Retrieve(context.TODO(), addr) | 
					
						
							| 
									
										
										
										
											2017-01-09 11:16:06 +01:00
										 |  |  | 	writer := bufio.NewWriter(f) | 
					
						
							| 
									
										
										
										
											2018-07-13 17:40:28 +02:00
										 |  |  | 	size, err := reader.Size(context.TODO(), quitC) | 
					
						
							| 
									
										
										
										
											2017-01-09 11:16:06 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if _, err = io.CopyN(writer, reader, size); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := writer.Flush(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return f.Close() | 
					
						
							| 
									
										
										
										
											2016-08-29 21:18:00 +02:00
										 |  |  | } |