Skip to content
This repository has been archived by the owner on Jun 19, 2023. It is now read-only.

[Draft] File system interface #30

Closed
wants to merge 3 commits into from
Closed
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
87 changes: 87 additions & 0 deletions filesystem/default/default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package ipfs

import (
"context"
"time"
coreiface "github.com/ipfs/interface-go-ipfs-core"


"github.com/ipfs/interface-go-ipfs-core/filesystem/node/soft"
"github.com/ipfs/go-ipfs-http-client"
)

type defaultFs struct {
// index
PathParserRegistry

//defaultFS specific
ctx context.Context
epoch time.Time

//IPFS parser specifics
//ipfsNode core.IpfsNode
core coreiface.CoreAPI
}
/* From DRAFT
func NewDefaultFileSystem(parentCtx context.Context) (FileSystem, error) {
// something like this
fsCtx := deriveFrom(parentCtx)
// go onCancel(fsCtx) { callClosers() } ()
return &PathParserRegistry{fsCtx}, nil
}
*/

func newDefaultFs() (FileSystem, error) {
root := defaultFs {
ctx:context.TODO(), // cancelable something or other
epoch:time.Now(),
}

// we depend on data from the coreapi to initalize our API nodes
// so fetch it or something and store it on the FS
//daemon := fallbackApi()
//ctx := deriveCtx(daemon.ctx) // if the daemon cancels, so should we

//root.ipfsNode = daemon

// mount base subsystems
epoch := time.Now()

// TODO: connect to daemon or fallback [new constructor]
core, err := httpapi.NewLocalApi()
if err != nil {
return nil, err
}
for _, pair := range [...]struct {
string
ParseFn
}{
{"/", fsnode.RootParser(epoch)},
{"/ipfs", inode.PinParser(core.Pin(), epoch)}, // requests for "/ipfs" are directed at pinRootParser(ctx, requestString)
{"/ipfs/", coreAPIParser}, // all requests beneath "/ipfs/" are directed at coreAPIParser(ctx, requestString)
{"/ipns", keyRootParser},
{"/ipns/", nameAPIParser},
{filesRootPrefix, filesAPIParser},
} {
closer, err := root.Register(pair.string, pair.ParseFn)
if err != nil {
if err == errRegistered {
//TODO: [discuss] consider having Plan9 style unions; Mount() would require flags (union contents go to: front, back, replace)
// doing this complicates our io.Closer consturction, but may be worth having
return nil, fmt.Errorf("%q is already registered", pair.string)
}
return nil, fmt.Errorf("cannot register %q: %s", pair.string, err)
}

// store these somewhere important and call them before you exit
// this will release the namespace at the FS level
root.closers = append(root.closers, closer) // in our example we do nothing with them :^)
}
}

type DescriptorTable interface {
}

func newDescriptorTable() {
}

124 changes: 124 additions & 0 deletions filesystem/default/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package ipfs

import (
"context"
"encoding/binary"
"io"
"os"
"strings"
"sync"

"github.com/ipfs/go-cid"
"github.com/ipfs/go-unixfs"
fs "github.com/ipfs/interface-go-ipfs-core/filesystem/interface"
"github.com/multiformats/go-multihash"
)

func Lookup(ctx context.Context, name string) (fs.FsNode, error) {
return pkgRoot.Lookup(ctx, name)
}

// api's may define Metadata.Cid() however they like
// for the default, we use this QID-like generator
func GenQueryID(path string, md fs.Metadata) (cid.Cid, error) {
mdBuf := make([]byte, 16)
binary.LittleEndian.PutUint64(mdBuf, uint64(md.Size()))
binary.LittleEndian.PutUint64(mdBuf[8:], uint64(md.Type()))

prefix := cid.V1Builder{Codec: cid.DagCBOR, MhType: multihash.BLAKE2B_MIN}
return prefix.Sum(append([]byte(path), mdBuf...))
}

type PathParserRegistry struct {
sync.Mutex
nodeParsers map[string]fs.ParseFn
}

func (rr *PathParserRegistry) Mount(subrootPath string, nodeParser fs.ParseFn) (io.Closer, error) {
rr.Lock()
val, ok := rr.nodeParsers[subrootPath]
if ok || val != nil {
return nil, errRegistered
}

rr.nodeParsers[subrootPath] = nodeParsers
return func() {
ri.Lock()
//TODO: somehow trigger cascading close for open subsystem handles
// or note that open handles are still valid, but new handles will not be made
delete(ri.subSystem, subrootPath)
ri.Unlock()
}, nil
}

func (PathParserRegistry) Lookup(ctx context.Context, name string) (FsNode, error) {
//NOTE: we can use a pkg level cache here, and fallback to the parser only when necessary

/* very simple map lookup
path is compared against registered subsystems
the function associated with the most specific matching prefix wins
(see registration in init() for additional context)
*/
var (
subLookup ParseFn
highestPrecision int
)
for subSystem, parser := range ri.nodeParsers {
if strings.HasPrefix(name, subSystem) {
if precision := len(subSystem); precision > highestPrecision {
highestPrecision = precision
subLookup = parser
}
}

if subLookup == nil {
return nil, errNoHandler
}

//TODO: [important] should we pass in relative (to namespace) paths here instead of absolute?
return subLookup(ctx, name)
}
}

// `pkg/log`-like wrappers for pkg level FS
// simply so we can say `pkg.lock` instead of `pkg.default.lock`
// this is more justified in client.go rather than here
func Lock() {
pkgRoot.Lock()
}

func Unlock() {
pkgRoot.Unlock()
}

func OpenFile(name string, flags OFlags, perm os.FileMode) (FsFile, error) {
fs.Lock()
defer fs.Unlock()

fsNode, err := fs.Lookup(ctx, name)
if err != nil {
return nil, err
}

/*
insert logic pertinent to your own file system implementation here
i.e. flag parsing, permission checking, link-resolution, etc.
e.g. If you are implementing a POSIX compliant FS,
you may want to return an error if write permissions where requested, for a name which resides in a read-only API

Descriptor management (if any) is also client defined
For our Go example, we simple use the interface directly, and Close() it when we're done
If we were implementing POSIX, we could wrap that interface inside of PosixOpen(),
assign it an integer value, hold on to the interface, and Close it later when PosixClose(ourInt) is called
Something like this
`wrappedIo, err := posixRoot.YieldOrGetHandle(fsNode, perm, unixfs.TFile)
if err != nil {
//...
}
return wrappedIo, nil`
*/

// YieldIo is expected to handle type/capability checking and return conforming errors
// XXX: see PR discussion; YieldIo should not require type
return fsNode.YieldIo(ctx, unixfs.TFile)
}
14 changes: 14 additions & 0 deletions filesystem/default/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ipfs

import "github.com/ipfs/interface-go-ipfs-core/filesystem/interface"

//TODO: store these in the daemon/ipfs-node scope, or elsewhere
// have something extract the FS from the daemon (`core.fsFrom(IpfsNode)``)
var pkgRoot fs.FileSystem

func init() {
pkgRoot, err = newDefaultFs()
if err != nil {
panic(err)
}
}
Loading