Commit 298a5386 authored by Nemanja Zbiljić's avatar Nemanja Zbiljić Committed by GitHub

Add support for manifest metadata (#713)

parent 3c321e9d
......@@ -9,7 +9,7 @@ require (
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/ethereum/go-ethereum v1.9.20
github.com/ethersphere/bmt v0.1.2
github.com/ethersphere/manifest v0.2.0
github.com/ethersphere/manifest v0.3.0
github.com/ethersphere/sw3-bindings/v2 v2.0.0
github.com/gogo/protobuf v1.3.1
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
......
......@@ -165,8 +165,8 @@ github.com/ethereum/go-ethereum v1.9.20 h1:kk/J5OIoaoz3DRrCXznz3RGi212mHHXwzXlY/
github.com/ethereum/go-ethereum v1.9.20/go.mod h1:JSSTypSMTkGZtAdAChH2wP5dZEvPGh3nUTuDpH+hNrg=
github.com/ethersphere/bmt v0.1.2 h1:FEuvQY9xuK+rDp3VwDVyde8T396Matv/u9PdtKa2r9Q=
github.com/ethersphere/bmt v0.1.2/go.mod h1:fqRBDmYwn3lX2MH4lkImXQgFWeNP8ikLkS/hgi/HRws=
github.com/ethersphere/manifest v0.2.0 h1:HD2ufiIaw/5Vgrl4XyeGduDJ5tn50wIhqMQoWdT2GME=
github.com/ethersphere/manifest v0.2.0/go.mod h1:ygAx0KLhXYmKqsjUab95RCbXf8UcO7yMDjyfP0lY76Y=
github.com/ethersphere/manifest v0.3.0 h1:+QRXY/AQ17mg0x3e20gvn4aAOHsZpm3rzi930bsOlro=
github.com/ethersphere/manifest v0.3.0/go.mod h1:ygAx0KLhXYmKqsjUab95RCbXf8UcO7yMDjyfP0lY76Y=
github.com/ethersphere/sw3-bindings/v2 v2.0.0 h1:uc+wBqEMMq7c4NWj+MSkKkkpObgrUYxfAxz6FYJWkI4=
github.com/ethersphere/sw3-bindings/v2 v2.0.0/go.mod h1:OA34yk7ludjNag+yBDY9Gp3czWoFUVMsiK7gUXnZ26U=
github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
......
......@@ -25,10 +25,11 @@ import (
)
const (
SwarmPinHeader = "Swarm-Pin"
SwarmTagUidHeader = "Swarm-Tag-Uid"
SwarmEncryptHeader = "Swarm-Encrypt"
SwarmIndextHeader = "Swarm-Index"
SwarmPinHeader = "Swarm-Pin"
SwarmTagUidHeader = "Swarm-Tag-Uid"
SwarmEncryptHeader = "Swarm-Encrypt"
SwarmIndexDocumentHeader = "Swarm-Index-Document"
SwarmErrorDocumentHeader = "Swarm-Error-Document"
)
var (
......
......@@ -26,14 +26,15 @@ import (
)
type testServerOptions struct {
Storer storage.Storer
Resolver resolver.Interface
Pss pss.Interface
WsPath string
Tags *tags.Tags
GatewayMode bool
WsPingPeriod time.Duration
Logger logging.Logger
Storer storage.Storer
Resolver resolver.Interface
Pss pss.Interface
WsPath string
Tags *tags.Tags
GatewayMode bool
WsPingPeriod time.Duration
Logger logging.Logger
PreventRedirect bool
}
func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.Conn, string) {
......@@ -74,8 +75,14 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket.
if err != nil {
t.Fatalf("dial: %v. url %v", err, u.String())
}
}
if o.PreventRedirect {
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
return httpClient, conn, ts.Listener.Addr().String()
}
......
......@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"net/http"
"path"
"strings"
"github.com/gorilla/mux"
......@@ -20,6 +21,7 @@ import (
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/manifest"
"github.com/ethersphere/bee/pkg/sctx"
"github.com/ethersphere/bee/pkg/swarm"
"github.com/ethersphere/bee/pkg/tracing"
)
......@@ -30,8 +32,12 @@ func (s *server) bzzDownloadHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
nameOrHex := mux.Vars(r)["address"]
path := mux.Vars(r)["path"]
path = strings.TrimRight(path, "/")
pathVar := mux.Vars(r)["path"]
if strings.HasSuffix(pathVar, "/") {
pathVar = strings.TrimRight(pathVar, "/")
// NOTE: leave one slash if there was some
pathVar += "/"
}
address, err := s.resolveNameOrAddress(nameOrHex)
if err != nil {
......@@ -96,12 +102,76 @@ func (s *server) bzzDownloadHandler(w http.ResponseWriter, r *http.Request) {
return
}
me, err := m.Lookup(path)
if pathVar == "" {
logger.Tracef("bzz download: handle empty path %s", address)
if indexDocumentSuffixKey, ok := manifestMetadataLoad(m, manifestRootPath, manifestWebsiteIndexDocumentSuffixKey); ok {
pathWithIndex := path.Join(pathVar, indexDocumentSuffixKey)
indexDocumentManifestEntry, err := m.Lookup(pathWithIndex)
if err == nil {
// index document exists
logger.Debugf("bzz download: serving path: %s", pathWithIndex)
s.serveManifestEntry(w, r, j, address, indexDocumentManifestEntry.Reference())
return
}
}
}
me, err := m.Lookup(pathVar)
if err != nil {
logger.Debugf("bzz download: invalid path %s/%s: %v", address, path, err)
logger.Debugf("bzz download: invalid path %s/%s: %v", address, pathVar, err)
logger.Error("bzz download: invalid path")
if errors.Is(err, manifest.ErrNotFound) {
if !strings.HasPrefix(pathVar, "/") {
// check for directory
dirPath := pathVar + "/"
exists, err := m.HasPrefix(dirPath)
if err == nil && exists {
// redirect to directory
u := r.URL
u.Path += "/"
redirectURL := u.String()
logger.Debugf("bzz download: redirecting to %s: %v", redirectURL, err)
http.Redirect(w, r, redirectURL, http.StatusPermanentRedirect)
return
}
}
// check index suffix path
if indexDocumentSuffixKey, ok := manifestMetadataLoad(m, manifestRootPath, manifestWebsiteIndexDocumentSuffixKey); ok {
if !strings.HasSuffix(pathVar, indexDocumentSuffixKey) {
// check if path is directory with index
pathWithIndex := path.Join(pathVar, indexDocumentSuffixKey)
indexDocumentManifestEntry, err := m.Lookup(pathWithIndex)
if err == nil {
// index document exists
logger.Debugf("bzz download: serving path: %s", pathWithIndex)
s.serveManifestEntry(w, r, j, address, indexDocumentManifestEntry.Reference())
return
}
}
}
// check if error document is to be shown
if errorDocumentPath, ok := manifestMetadataLoad(m, manifestRootPath, manifestWebsiteErrorDocumentPathKey); ok {
if pathVar != errorDocumentPath {
errorDocumentManifestEntry, err := m.Lookup(errorDocumentPath)
if err == nil {
// error document exists
logger.Debugf("bzz download: serving path: %s", errorDocumentPath)
s.serveManifestEntry(w, r, j, address, errorDocumentManifestEntry.Reference())
return
}
}
}
jsonhttp.NotFound(w, "path address not found")
} else {
jsonhttp.BadRequest(w, "invalid path address")
......@@ -109,11 +179,17 @@ func (s *server) bzzDownloadHandler(w http.ResponseWriter, r *http.Request) {
return
}
manifestEntryAddress := me.Reference()
// serve requested path
s.serveManifestEntry(w, r, j, address, me.Reference())
}
func (s *server) serveManifestEntry(w http.ResponseWriter, r *http.Request, j file.JoinSeeker, address, manifestEntryAddress swarm.Address) {
logger := tracing.NewLoggerWithTraceID(r.Context(), s.Logger)
ctx := r.Context()
// read file entry
buf = bytes.NewBuffer(nil)
_, err = file.JoinReadAll(ctx, j, manifestEntryAddress, buf)
buf := bytes.NewBuffer(nil)
_, err := file.JoinReadAll(ctx, j, manifestEntryAddress, buf)
if err != nil {
logger.Debugf("bzz download: read file entry %s: %v", address, err)
logger.Errorf("bzz download: read file entry %s", address)
......@@ -156,3 +232,20 @@ func (s *server) bzzDownloadHandler(w http.ResponseWriter, r *http.Request) {
s.downloadHandler(w, r, fileEntryAddress, additionalHeaders)
}
// manifestMetadataLoad returns the value for a key stored in the metadata of
// manifest path, or empty string if no value is present.
// The ok result indicates whether value was found in the metadata.
func manifestMetadataLoad(manifest manifest.Interface, path, metadataKey string) (string, bool) {
me, err := manifest.Lookup(path)
if err != nil {
return "", false
}
manifestRootMetadata := me.Metadata()
if val, ok := manifestRootMetadata[metadataKey]; ok {
return val, ok
}
return "", false
}
......@@ -16,14 +16,13 @@ import (
"strings"
"testing"
"github.com/ethersphere/bee/pkg/file/pipeline/builder"
statestore "github.com/ethersphere/bee/pkg/statestore/mock"
"github.com/ethersphere/bee/pkg/collection/entry"
"github.com/ethersphere/bee/pkg/file/pipeline/builder"
"github.com/ethersphere/bee/pkg/jsonhttp"
"github.com/ethersphere/bee/pkg/jsonhttp/jsonhttptest"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/manifest"
statestore "github.com/ethersphere/bee/pkg/statestore/mock"
"github.com/ethersphere/bee/pkg/storage"
smock "github.com/ethersphere/bee/pkg/storage/mock"
"github.com/ethersphere/bee/pkg/swarm"
......@@ -104,7 +103,7 @@ func TestBzz(t *testing.T) {
t.Fatal(err)
}
e := manifest.NewEntry(fileReference)
e := manifest.NewEntry(fileReference, nil)
err = m.Add(filePath, e)
if err != nil {
......
......@@ -33,6 +33,12 @@ const (
contentTypeTar = "application/x-tar"
)
const (
manifestRootPath = "/"
manifestWebsiteIndexDocumentSuffixKey = "website-index-document"
manifestWebsiteErrorDocumentPathKey = "website-error-document"
)
// dirUploadHandler uploads a directory supplied as a tar in an HTTP request
func (s *server) dirUploadHandler(w http.ResponseWriter, r *http.Request) {
logger := tracing.NewLoggerWithTraceID(r.Context(), s.Logger)
......@@ -55,7 +61,7 @@ func (s *server) dirUploadHandler(w http.ResponseWriter, r *http.Request) {
// Add the tag to the context
ctx := sctx.SetTag(r.Context(), tag)
reference, err := storeDir(ctx, r.Body, s.Storer, requestModePut(r), s.Logger, requestEncrypt(r), r.Header.Get(SwarmIndextHeader))
reference, err := storeDir(ctx, r.Body, s.Storer, requestModePut(r), s.Logger, requestEncrypt(r), r.Header.Get(SwarmIndexDocumentHeader), r.Header.Get(SwarmErrorDocumentHeader))
if err != nil {
logger.Debugf("dir upload: store dir err: %v", err)
logger.Errorf("dir upload: store dir")
......@@ -95,7 +101,7 @@ func validateRequest(r *http.Request) error {
// storeDir stores all files recursively contained in the directory given as a tar
// it returns the hash for the uploaded manifest corresponding to the uploaded dir
func storeDir(ctx context.Context, reader io.ReadCloser, s storage.Storer, mode storage.ModePut, log logging.Logger, encrypt bool, indexFilename string) (swarm.Address, error) {
func storeDir(ctx context.Context, reader io.ReadCloser, s storage.Storer, mode storage.ModePut, log logging.Logger, encrypt bool, indexFilename string, errorFilename string) (swarm.Address, error) {
logger := tracing.NewLoggerWithTraceID(ctx, log)
dirManifest, err := manifest.NewDefaultManifest(encrypt, s)
......@@ -103,6 +109,10 @@ func storeDir(ctx context.Context, reader io.ReadCloser, s storage.Storer, mode
return swarm.ZeroAddress, err
}
if indexFilename != "" && strings.ContainsRune(indexFilename, '/') {
return swarm.ZeroAddress, fmt.Errorf("index document suffix must not include slash character")
}
// set up HTTP body reader
tarReader := tar.NewReader(reader)
defer reader.Close()
......@@ -143,23 +153,11 @@ func storeDir(ctx context.Context, reader io.ReadCloser, s storage.Storer, mode
logger.Tracef("uploaded dir file %v with reference %v", filePath, fileReference)
// add file entry to dir manifest
err = dirManifest.Add(filePath, manifest.NewEntry(fileReference))
err = dirManifest.Add(filePath, manifest.NewEntry(fileReference, nil))
if err != nil {
return swarm.ZeroAddress, fmt.Errorf("add to manifest: %w", err)
}
if indexFilename != "" && filePath == indexFilename || strings.HasSuffix(filePath, "/"+indexFilename) {
filePath := strings.TrimSuffix(filePath, indexFilename)
filePath = strings.TrimRight(filePath, "/")
// add file entry to dir manifest
err = dirManifest.Add(filePath, manifest.NewEntry(fileReference))
if err != nil {
return swarm.ZeroAddress, fmt.Errorf("add to manifest: %w", err)
}
}
filesAdded++
}
......@@ -168,6 +166,22 @@ func storeDir(ctx context.Context, reader io.ReadCloser, s storage.Storer, mode
return swarm.ZeroAddress, fmt.Errorf("no files in tar")
}
// store website information
if indexFilename != "" || errorFilename != "" {
metadata := map[string]string{}
if indexFilename != "" {
metadata[manifestWebsiteIndexDocumentSuffixKey] = indexFilename
}
if errorFilename != "" {
metadata[manifestWebsiteErrorDocumentPathKey] = errorFilename
}
rootManifestEntry := manifest.NewEntry(swarm.ZeroAddress, metadata)
err = dirManifest.Add(manifestRootPath, rootManifestEntry)
if err != nil {
return swarm.ZeroAddress, fmt.Errorf("add to manifest: %w", err)
}
}
// save manifest
manifestBytesReference, err := dirManifest.Store(ctx, mode)
if err != nil {
......
......@@ -9,6 +9,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path"
......@@ -32,13 +33,15 @@ func TestDirs(t *testing.T) {
var (
dirUploadResource = "/dirs"
fileDownloadResource = func(addr string) string { return "/files/" + addr }
bzzDownloadResource = func(addr, path string) string { return "/bzz/" + addr + "/" + path }
storer = mock.NewStorer()
mockStatestore = statestore.NewStateStore()
logger = logging.New(ioutil.Discard, 0)
client, _, _ = newTestServer(t, testServerOptions{
Storer: storer,
Tags: tags.NewTags(mockStatestore, logger),
Logger: logging.New(ioutil.Discard, 5),
Storer: storer,
Tags: tags.NewTags(mockStatestore, logger),
Logger: logging.New(ioutil.Discard, 5),
PreventRedirect: true,
})
)
......@@ -87,7 +90,9 @@ func TestDirs(t *testing.T) {
for _, tc := range []struct {
name string
wantIndexFilename string
wantErrorFilename string
indexFilenameOption jsonhttptest.Option
errorFilenameOption jsonhttptest.Option
files []f // files in dir for test case
}{
{
......@@ -162,7 +167,7 @@ func TestDirs(t *testing.T) {
{
name: "explicit index filename",
wantIndexFilename: "index.html",
indexFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmIndextHeader, "index.html"),
indexFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmIndexDocumentHeader, "index.html"),
files: []f{
{
data: []byte("<h1>Swarm"),
......@@ -178,7 +183,7 @@ func TestDirs(t *testing.T) {
{
name: "nested index filename",
wantIndexFilename: "index.html",
indexFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmIndextHeader, "index.html"),
indexFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmIndexDocumentHeader, "index.html"),
files: []f{
{
data: []byte("<h1>Swarm"),
......@@ -191,6 +196,33 @@ func TestDirs(t *testing.T) {
},
},
},
{
name: "explicit index and error filename",
wantIndexFilename: "index.html",
wantErrorFilename: "error.html",
indexFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmIndexDocumentHeader, "index.html"),
errorFilenameOption: jsonhttptest.WithRequestHeader(api.SwarmErrorDocumentHeader, "error.html"),
files: []f{
{
data: []byte("<h1>Swarm"),
name: "index.html",
dir: "",
reference: swarm.MustParseHexAddress("bcb1bfe15c36f1a529a241f4d0c593e5648aa6d40859790894c6facb41a6ef28"),
header: http.Header{
"Content-Type": {"text/html; charset=utf-8"},
},
},
{
data: []byte("<h2>404"),
name: "error.html",
dir: "",
reference: swarm.MustParseHexAddress("b1f309c095d650521b75760b23122a9c59c2b581af28fc6daaf9c58da86a204d"),
header: http.Header{
"Content-Type": {"text/html; charset=utf-8"},
},
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
// tar all the test case files
......@@ -206,6 +238,9 @@ func TestDirs(t *testing.T) {
if tc.indexFilenameOption != nil {
options = append(options, tc.indexFilenameOption)
}
if tc.errorFilenameOption != nil {
options = append(options, tc.errorFilenameOption)
}
// verify directory tar upload response
jsonhttptest.Request(t, client, http.MethodPost, dirUploadResource, http.StatusOK, options...)
......@@ -271,15 +306,77 @@ func TestDirs(t *testing.T) {
)
}
validateIsPermanentRedirect := func(t *testing.T, fromPath, toPath string) {
t.Helper()
expectedResponse := fmt.Sprintf("<a href=\"%s\">Permanent Redirect</a>.\n\n", bzzDownloadResource(resp.Reference.String(), toPath))
jsonhttptest.Request(t, client, http.MethodGet, bzzDownloadResource(resp.Reference.String(), fromPath), http.StatusPermanentRedirect,
jsonhttptest.WithExpectedResponse([]byte(expectedResponse)),
)
}
validateBzzPath := func(t *testing.T, fromPath, toPath string) {
t.Helper()
toEntry, err := verifyManifest.Lookup(toPath)
if err != nil {
t.Fatal(err)
}
var respBytes []byte
jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(toEntry.Reference().String()), http.StatusOK,
jsonhttptest.WithPutResponseBody(&respBytes),
)
jsonhttptest.Request(t, client, http.MethodGet, bzzDownloadResource(resp.Reference.String(), fromPath), http.StatusOK,
jsonhttptest.WithExpectedResponse(respBytes),
)
}
// check if each file can be located and read
for _, file := range tc.files {
validateFile(t, file, path.Join(file.dir, file.name))
}
// if there is an index filename to be tested
// try to download it using only the directory as the path
if file.name == tc.wantIndexFilename {
validateFile(t, file, file.dir)
// check index filename
if tc.wantIndexFilename != "" {
entry, err := verifyManifest.Lookup(api.ManifestRootPath)
if err != nil {
t.Fatal(err)
}
manifestRootMetadata := entry.Metadata()
indexDocumentSuffixPath, ok := manifestRootMetadata[api.ManifestWebsiteIndexDocumentSuffixKey]
if !ok {
t.Fatalf("expected index filename '%s', did not find any", tc.wantIndexFilename)
}
// check index suffix for each dir
for _, file := range tc.files {
if file.dir != "" {
validateIsPermanentRedirect(t, file.dir, file.dir+"/")
validateBzzPath(t, file.dir+"/", path.Join(file.dir, indexDocumentSuffixPath))
}
}
}
// check error filename
if tc.wantErrorFilename != "" {
entry, err := verifyManifest.Lookup(api.ManifestRootPath)
if err != nil {
t.Fatal(err)
}
manifestRootMetadata := entry.Metadata()
errorDocumentPath, ok := manifestRootMetadata[api.ManifestWebsiteErrorDocumentPathKey]
if !ok {
t.Fatalf("expected error filename '%s', did not find any", tc.wantErrorFilename)
}
// check error document
validateBzzPath(t, "_non_existent_file_path_", errorDocumentPath)
}
})
......
......@@ -21,6 +21,12 @@ var (
ContentTypeTar = contentTypeTar
)
var (
ManifestRootPath = manifestRootPath
ManifestWebsiteIndexDocumentSuffixKey = manifestWebsiteIndexDocumentSuffixKey
ManifestWebsiteErrorDocumentPathKey = manifestWebsiteErrorDocumentPathKey
)
var (
ErrNoResolver = errNoResolver
ErrInvalidNameOrAddress = errInvalidNameOrAddress
......
......@@ -33,6 +33,8 @@ type Interface interface {
Remove(string) error
// Lookup returns a manifest entry if one is found in the specified path.
Lookup(string) (Entry, error)
// HasPrefix tests whether the specified prefix path exists.
HasPrefix(string) (bool, error)
// Store stores the manifest, returning the resulting address.
Store(context.Context, storage.ModePut) (swarm.Address, error)
}
......@@ -41,6 +43,8 @@ type Interface interface {
type Entry interface {
// Reference returns the address of the file.
Reference() swarm.Address
// Metadata returns the metadata of the file.
Metadata() map[string]string
}
// NewDefaultManifest creates a new manifest with default type.
......@@ -87,15 +91,21 @@ func NewManifestReference(
type manifestEntry struct {
reference swarm.Address
metadata map[string]string
}
// NewEntry creates a new manifest entry.
func NewEntry(reference swarm.Address) Entry {
func NewEntry(reference swarm.Address, metadata map[string]string) Entry {
return &manifestEntry{
reference: reference,
metadata: metadata,
}
}
func (e *manifestEntry) Reference() swarm.Address {
return e.reference
}
func (e *manifestEntry) Metadata() map[string]string {
return e.metadata
}
......@@ -68,7 +68,7 @@ func (m *mantarayManifest) Add(path string, entry Entry) error {
p := []byte(path)
e := entry.Reference().Bytes()
return m.trie.Add(p, e, m.loader)
return m.trie.Add(p, e, entry.Metadata(), m.loader)
}
func (m *mantarayManifest) Remove(path string) error {
......@@ -88,18 +88,28 @@ func (m *mantarayManifest) Remove(path string) error {
func (m *mantarayManifest) Lookup(path string) (Entry, error) {
p := []byte(path)
ref, err := m.trie.Lookup(p, m.loader)
node, err := m.trie.LookupNode(p, m.loader)
if err != nil {
return nil, ErrNotFound
}
address := swarm.NewAddress(ref)
if !node.IsValueType() {
return nil, ErrNotFound
}
address := swarm.NewAddress(node.Entry())
entry := NewEntry(address)
entry := NewEntry(address, node.Metadata())
return entry, nil
}
func (m *mantarayManifest) HasPrefix(prefix string) (bool, error) {
p := []byte(prefix)
return m.trie.HasPrefix(p, m.loader)
}
func (m *mantarayManifest) Store(ctx context.Context, mode storage.ModePut) (swarm.Address, error) {
saver := newMantaraySaver(ctx, m.encrypted, m.storer, mode)
......
......@@ -66,7 +66,7 @@ func (m *simpleManifest) Type() string {
func (m *simpleManifest) Add(path string, entry Entry) error {
e := entry.Reference().String()
return m.manifest.Add(path, e)
return m.manifest.Add(path, e, entry.Metadata())
}
func (m *simpleManifest) Remove(path string) error {
......@@ -94,11 +94,15 @@ func (m *simpleManifest) Lookup(path string) (Entry, error) {
return nil, fmt.Errorf("parse swarm address: %w", err)
}
entry := NewEntry(address)
entry := NewEntry(address, n.Metadata())
return entry, nil
}
func (m *simpleManifest) HasPrefix(prefix string) (bool, error) {
return m.manifest.HasPrefix(prefix), nil
}
func (m *simpleManifest) Store(ctx context.Context, mode storage.ModePut) (swarm.Address, error) {
data, err := m.manifest.MarshalBinary()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment