Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support HAMT #4

Merged
merged 7 commits into from
Apr 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions data/builder/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package builder

import (
"errors"
"strconv"
"time"

"github.com/ipfs/go-unixfsnode/data"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/fluent/qp"
)

// BuildUnixFS provides a clean, validated interface to building data structures
// that match the UnixFS protobuf encoded in the Data member of a ProtoNode
// with sensible defaults
//
// smallFileData, err := BuildUnixFS(func(b *Builder) {
// Data(b, []byte{"hello world"})
// Mtime(b, func(tb TimeBuilder) {
// Time(tb, time.Now())
// })
// })
//
func BuildUnixFS(fn func(*Builder)) (data.UnixFSData, error) {
nd, err := qp.BuildMap(data.Type.UnixFSData, -1, func(ma ipld.MapAssembler) {
b := &Builder{MapAssembler: ma}
fn(b)
if !b.hasBlockSizes {
qp.MapEntry(ma, data.Field__BlockSizes, qp.List(0, func(ipld.ListAssembler) {}))
}
if !b.hasDataType {
qp.MapEntry(ma, data.Field__DataType, qp.Int(data.Data_File))
}
})
if err != nil {
return nil, err
}
return nd.(data.UnixFSData), nil
}

// Builder is an interface for making UnixFS data nodes
type Builder struct {
ipld.MapAssembler
hasDataType bool
hasBlockSizes bool
}

// DataType sets the default on a builder for a UnixFS node - default is File
func DataType(b *Builder, dataType int64) {
_, ok := data.DataTypeNames[dataType]
if !ok {
panic(data.ErrInvalidDataType{dataType})
}
qp.MapEntry(b.MapAssembler, data.Field__DataType, qp.Int(dataType))
b.hasDataType = true
}

// Data sets the data member inside the UnixFS data
func Data(b *Builder, dataBytes []byte) {
qp.MapEntry(b.MapAssembler, data.Field__Data, qp.Bytes(dataBytes))
}

// FileSize sets the file size which should be the size of actual bytes underneath
// this node for large files, w/o additional bytes to encode intermediate nodes
func FileSize(b *Builder, fileSize uint64) {
qp.MapEntry(b.MapAssembler, data.Field__FileSize, qp.Int(int64(fileSize)))
}

// BlockSizes encodes block sizes for each child node
func BlockSizes(b *Builder, blockSizes []uint64) {
qp.MapEntry(b.MapAssembler, data.Field__BlockSizes, qp.List(int64(len(blockSizes)), func(la ipld.ListAssembler) {
for _, bs := range blockSizes {
qp.ListEntry(la, qp.Int(int64(bs)))
}
}))
b.hasBlockSizes = true
}

// HashType sets the hash function for this node -- only applicable to HAMT
func HashType(b *Builder, hashType uint64) {
qp.MapEntry(b.MapAssembler, data.Field__HashType, qp.Int(int64(hashType)))
}

// Fanout sets the fanout in a HAMT tree
func Fanout(b *Builder, fanout uint64) {
qp.MapEntry(b.MapAssembler, data.Field__Fanout, qp.Int(int64(fanout)))
}

// Permissions sets file permissions for the Mode member of the UnixFS node
func Permissions(b *Builder, mode int) {
mode = mode & 0xFFF
qp.MapEntry(b.MapAssembler, data.Field__Mode, qp.Int(int64(mode)))
}

func parseModeString(modeString string) (uint64, error) {
if len(modeString) > 0 && modeString[0] == '0' {
return strconv.ParseUint(modeString, 8, 32)
}
return strconv.ParseUint(modeString, 10, 32)
}

// PermissionsString sets file permissions for the Mode member of the UnixFS node,
// parsed from a typical octect encoded permission string (eg '0755')
func PermissionsString(b *Builder, modeString string) {
mode64, err := parseModeString(modeString)
if err != nil {
panic(err)
}
mode64 = mode64 & 0xFFF
qp.MapEntry(b.MapAssembler, data.Field__Mode, qp.Int(int64(mode64)))
}

// Mtime sets the modification time for this node using the time builder interface
// and associated methods
func Mtime(b *Builder, fn func(tb TimeBuilder)) {
qp.MapEntry(b.MapAssembler, data.Field__Mtime, qp.Map(-1, func(ma ipld.MapAssembler) {
fn(ma)
}))
}

// TimeBuilder is a simple interface for constructing the time member of UnixFS data
type TimeBuilder ipld.MapAssembler

// Time sets the modification time from a golang time value
func Time(ma TimeBuilder, t time.Time) {
Seconds(ma, t.Unix())
FractionalNanoseconds(ma, int32(t.Nanosecond()))
}

// Seconds sets the seconds for a modification time
func Seconds(ma TimeBuilder, seconds int64) {
qp.MapEntry(ma, data.Field__Seconds, qp.Int(seconds))

}

// FractionalNanoseconds sets the nanoseconds for a modification time (must
// be between 0 & a billion)
func FractionalNanoseconds(ma TimeBuilder, nanoseconds int32) {
if nanoseconds < 0 || nanoseconds > 999999999 {
panic(errors.New("mtime-nsecs must be within the range [0,999999999]"))
}
qp.MapEntry(ma, data.Field__Nanoseconds, qp.Int(int64(nanoseconds)))
}
40 changes: 40 additions & 0 deletions data/datatypes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package data

const (
Data_Raw int64 = 0
Data_Directory int64 = 1
Data_File int64 = 2
Data_Metadata int64 = 3
Data_Symlink int64 = 4
Data_HAMTShard int64 = 5
gammazero marked this conversation as resolved.
Show resolved Hide resolved
)

var DataTypeNames = map[int64]string{
Data_Raw: "Raw",
Data_Directory: "Directory",
Data_File: "File",
Data_Metadata: "Metadata",
Data_Symlink: "Symlink",
Data_HAMTShard: "HAMTShard",
}

var DataTypeValues = map[string]int64{
"Raw": Data_Raw,
"Directory": Data_Directory,
"File": Data_File,
"Metadata": Data_Metadata,
"Symlink": Data_Symlink,
"HAMTShard": Data_HAMTShard,
}

const Field__DataType = "DataType"
const Field__Data = "Data"
const Field__FileSize = "FileSize"
const Field__BlockSizes = "BlockSizes"
const Field__HashType = "HashType"
const Field__Fanout = "Fanout"
const Field__Mode = "Mode"
const Field__Mtime = "Mtime"
const Field__Seconds = "Seconds"
const Field__Nanoseconds = "FractionalNanoseconds"
const Field__MimeType = "MimeType"
14 changes: 14 additions & 0 deletions data/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
Package data provides tools for working with the UnixFS data structure that
is encoded in the "Data" field of the larger a DagPB encoded IPLD node.

See https://github.com/ipfs/specs/blob/master/UNIXFS.md for more information
about this data structure.

This package provides an IPLD Prime compatible node interface for this data
structure, as well as methods for serializing and deserializing the data
structure to protobuf
*/
package data

//go:generate go run ./gen
43 changes: 43 additions & 0 deletions data/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package data

import (
"fmt"

"google.golang.org/protobuf/encoding/protowire"
)

type ErrWrongNodeType struct {
Expected int64
Actual int64
}

func (e ErrWrongNodeType) Error() string {
expectedName, ok := DataTypeNames[e.Expected]
if !ok {
expectedName = "Unknown Type"
}
actualName, ok := DataTypeNames[e.Actual]
if !ok {
actualName = "Unknown Type"
}
return fmt.Sprintf("incorrect Node Type: (UnixFSData) expected type: %s, actual type: %s", expectedName, actualName)
}

type ErrWrongWireType struct {
Module string
Field string
Expected protowire.Type
Actual protowire.Type
}

func (e ErrWrongWireType) Error() string {
return fmt.Sprintf("protobuf: (%s) invalid wireType, field: %s, expected %d, got %d", e.Module, e.Field, e.Expected, e.Actual)
}

type ErrInvalidDataType struct {
DataType int64
}

func (e ErrInvalidDataType) Error() string {
return fmt.Sprintf("type: %d is not valid", e.DataType)
}
1 change: 1 addition & 0 deletions data/fixtures/directory.unixfs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions data/fixtures/directory/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello UnixFS
1 change: 1 addition & 0 deletions data/fixtures/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello UnixFS
2 changes: 2 additions & 0 deletions data/fixtures/file.txt.unixfs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hello UnixFS

Expand Down
2 changes: 2 additions & 0 deletions data/fixtures/raw.unixfs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hello UnixFS

Expand Down
1 change: 1 addition & 0 deletions data/fixtures/symlink.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello UnixFS
1 change: 1 addition & 0 deletions data/fixtures/symlink.txt.unixfs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
file.txt
Loading