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

Commit d1e2792

Browse files
committed
FS-rough-draft
1 parent f77aa7e commit d1e2792

File tree

3 files changed

+337
-0
lines changed

3 files changed

+337
-0
lines changed

filesystem/client.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package fs
2+
3+
import (
4+
"context"
5+
"os"
6+
//pretend we're not the same package
7+
//fs "github.com/ipfs/interface-go-ipfs-core/filesystem"
8+
)
9+
10+
// This file represents a 3rd party user of the API
11+
12+
// Client side using pkg defaults
13+
func client() {
14+
f, err := fs.OpenFile("/ipfs/Qm.../file.ext", flags, perm)
15+
if err != nil {
16+
//...
17+
}
18+
defer f.Close()
19+
x, y := f.Read(someByteSlice)
20+
}
21+
22+
// done
23+
24+
// In addition, clients can also implement their own...
25+
// nodes,
26+
type myNode struct {
27+
myData bool
28+
}
29+
30+
func (mn *myNode) YieldIo(ctx context.Context, nodeType fs.FsType) {
31+
if !mn.myData {
32+
return nil, errIOType // we don't have the data needed to perform this request
33+
}
34+
return constructFileIo(mn.MyData), nil //we have what we need to construct a FsFile interface, so return that
35+
}
36+
37+
// namespace parsers
38+
func myParser(ctx context.Context, name string) (FsNode, error) {
39+
if condition(name) {
40+
return myNode{false}, nil
41+
}
42+
return myNode{true}, nil
43+
}
44+
45+
// and filing systems
46+
func aDifferentFileSystem(ctx context.Context) {
47+
// something like this
48+
fsCtx := deriveFrom(ctx)
49+
return &colonDelimitedRegistry{fsCtx}, nil
50+
}
51+
52+
func clientImp() {
53+
myFs := aDifferentFileSystem(ctx)
54+
55+
_, err = myFs.OpenFile(":myName", flags, perm)
56+
err == os.ErrNotExist // or some other standard error
57+
58+
namespaceCloser, err := myFs.Register(":myName", myParser)
59+
60+
myF, err := myFs.OpenFile(":myName", flags, perm)
61+
myF.Read()
62+
63+
namespaceCloser() // requests for `/myName` are no longer valid according to the filesystem interface (`myFs`)
64+
65+
_, err = myFs.OpenFile(":myName", flags, perm)
66+
err == os.ErrNotExist
67+
68+
_, err = myF.Read()
69+
err == os.ErrInvalid // or some other standard error
70+
}

filesystem/index.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package fs
2+
3+
import (
4+
"context"
5+
"io"
6+
"os"
7+
"strings"
8+
"sync"
9+
10+
"github.com/ipfs/go-unixfs"
11+
)
12+
13+
// This file represents a partial and theoretical
14+
// pkg-level implementation of a filesystem{}
15+
16+
var (
17+
pkgRoot FileSystem
18+
closers []io.Closer
19+
)
20+
21+
func init() {
22+
23+
// we depend on data from the coreapi to initalize our API nodes
24+
// so fetch it or something and store it on the FS
25+
daemon := fallbackApi()
26+
ctx := deriveCtx(daemon.ctx) // if the daemon cancels, so should we
27+
28+
pkgRoot = NewFileSystem(ctx)
29+
for _, pair := range [...]struct {
30+
string
31+
ParseFn
32+
}{
33+
{"/", rootParser},
34+
{"/ipfs", pinRootParser}, // requests for "/ipfs" are directed at pinRootParser(ctx, requestString)
35+
{"/ipfs/", coreAPIParser}, // all requests beneath "/ipfs/" are directed at coreAPIParser(ctx, requestString)
36+
{"/ipns", keyRootParser},
37+
{"/ipns/", nameAPIParser},
38+
{filesRootPrefix, filesAPIParser},
39+
} {
40+
closer, err := pkgRoot.Register(pair.string, pair.ParseFn)
41+
if err != nil {
42+
if err == errRegistered {
43+
panic(fmt.Sprtinf("%q is already registered", pair.string))
44+
}
45+
panic(fmt.Sprtinf("cannot register %q: %s", pair.string, err))
46+
}
47+
48+
// store these somewhere important and call them before you exit
49+
// this will release the namespace at the FS level
50+
closers = append(closers, closer) // in our example we do nothing with them :^)
51+
}
52+
}
53+
54+
func NewDefaultFileSystem(parentCtx context.Context) (FileSystem, error) {
55+
// something like this
56+
fsCtx := deriveFrom(parentCtx)
57+
// go onCancel(fsCtx) { callClosers() } ()
58+
return &PathParserRegistry{fsCtx}, nil
59+
}
60+
61+
type PathParserRegistry struct {
62+
sync.Mutex
63+
ctx context.Context
64+
nodeParsers map[string]ParseFn
65+
}
66+
67+
func (rr *PathParserRegistry) Register(subrootPath string, nodeParser ParseFn) (io.Closer, error) {
68+
rr.Lock()
69+
val, ok := rr.nodeParsers[subrootPath]
70+
if ok || val != nil {
71+
return nil, errRegistered
72+
}
73+
74+
rr.nodeParsers[subrootPath] = nodeParsers
75+
return func() {
76+
ri.Lock()
77+
delete(ri.subSystem, subrootPath)
78+
ri.Unlock()
79+
}, nil
80+
}
81+
82+
func (PathParserRegistry) Lookup(ctx context.Context, name string) (FsPath, error) {
83+
//NOTE: we can use a pkg level cache here, and fallback to the parser only when necessary
84+
85+
/* very simple map lookup
86+
path is compared against registered subsystems
87+
the function associated with the most specific matching prefix wins
88+
(see registration in init() for additional context)
89+
*/
90+
var (
91+
subLookup ParseFn
92+
highestPrecision int
93+
)
94+
for subSystem, parser := range ri.nodeParsers {
95+
if strings.HasPrefix(name, subSystem) {
96+
if precision := len(subSystem); precision > highestPrecision {
97+
highestPrecision = precision
98+
subLookup = parser
99+
}
100+
}
101+
102+
if subLookup == nil {
103+
return nil, errNoHandler
104+
}
105+
106+
//TODO: [important] should we pass in relative (to namespace) paths here instead of absolute?
107+
return subLookup(ctx, name)
108+
}
109+
}
110+
111+
// `pkg/log`-like wrappers for pkg level FS
112+
// simply so we can say `pkg.lock` instead of `pkg.default.lock`
113+
// this is more justified in client.go rather than here
114+
func Lock() {
115+
pkgRoot.Lock()
116+
}
117+
118+
func Unlock() {
119+
pkgRoot.Unlock()
120+
}
121+
122+
func Lookup(ctx context.Context, name string) (FsPath, error) {
123+
return pkgRoot.Lookup(ctx, name)
124+
}
125+
126+
// ...
127+
128+
func OpenFile(name string, flags flags, perm os.FileMode) (FsFile, error) {
129+
fs.Lock()
130+
defer fs.Unlock()
131+
132+
fsNode, err := fs.Lookup(ctx, name)
133+
if err != nil {
134+
return nil, err
135+
}
136+
137+
/*
138+
insert logic pertinent to your own file system implementation here
139+
i.e. flag parsing, permission checking, link-resolution, etc.
140+
e.g. If you are implementing a POSIX compliant FS,
141+
you may want to return an error if write permissions where requested, for a name which resides in a read-only API
142+
143+
Descriptor management (if any) is also client defined
144+
For our Go example, we simple use the interface directly, and Close() it when we're done
145+
If we were implementing POSIX, we could wrap that interface inside of PosixOpen(),
146+
assign it an integer value, hold on to the interface, and Close it later when PosixClose(ourInt) is called
147+
Something like this
148+
`wrappedIo, err := posixRoot.YieldOrGetHandle(fsNode, perm, unixfs.TFile)
149+
if err != nil {
150+
//...
151+
}
152+
return wrappedIo, nil`
153+
*/
154+
155+
// YieldIo is expected to handle type/capability checking and return conforming errors
156+
// XXX: see PR discussion; YieldIo should not require type
157+
return fsNode.YieldIo(ctx, unixfs.TFile)
158+
}

filesystem/interface.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package fs
2+
3+
import (
4+
"context"
5+
"io"
6+
"os"
7+
8+
coreiface "github.com/ipfs/interface-go-ipfs-core"
9+
corepath "github.com/ipfs/interface-go-ipfs-core/path"
10+
)
11+
12+
type (
13+
ParseFn func(ctx context.Context, name string) (FsNode, error)
14+
FsType = coreiface.FileType
15+
// TODO: os.FileInfo-like interface
16+
//FileMetadata
17+
)
18+
19+
// Provides standard convention wrappers around an index
20+
type FileSystem interface {
21+
Index // a namespace registry that is used to parse `name` strings into `FsNode`s
22+
23+
// Create node using respective API for its namespace
24+
Create(ctx context.Context, name string, nodeType FsType) error
25+
26+
/* XXX: see PR discussion; we should avoid context.Value if we can; different requests require different arity, which may be a problem
27+
Request specific parameters are passed via context key's and values, at an API level
28+
If a type's creation method relies on values, the FS must provide them,
29+
else the create method shall return `ErrPartialContext`
30+
31+
e.g. Creating a symlink wrapper at a package level would look as such
32+
`CreateLink(ctx, name, target ) {
33+
fs.Create(context.WithValue(ctx, fs.TargetKey, target), name, unixfs.TSymlink)
34+
}`
35+
*/
36+
37+
// Remove node using respective API for its namespace
38+
// caller provides values necessary to complete the request (if any), inside the context
39+
Remove(ctx context.Context, name string) error
40+
41+
OpenFile(name string, flags flags, perm os.FileMode) (FsFile, error)
42+
OpenDirectory(path string) (FsDirectory, error)
43+
OpenReference(string) (FsReference, error)
44+
}
45+
46+
type Index interface {
47+
// register namespace parser with index, returns release/teardown function
48+
Register(namespace string, nodeParser ParseFn) (io.Closer, error)
49+
//NOTE: it may be better to return the equivalent of a "KeepAlive" function rather than a closer
50+
// if the namespace "host" disappears, we'll never release
51+
52+
// returns true if namespace is registered with the index
53+
// i.e. if "/ipfs/" was registered, Provides("/ipfs/") == true
54+
Provides(namespace string) bool
55+
56+
/*
57+
Lookup is implicitly shallow
58+
if the named string resolves to a reference, a reference object shall be returned
59+
60+
If the named string exists within its namespace
61+
a node with its metadata initialized shall be returned
62+
63+
If named string does not exist within its namespace:
64+
returns a node interface, with metadata uninitialized, and os.ErrNotExist for error
65+
(expectation is for node.Create() to be called immediately after)
66+
67+
In any other case:
68+
If the named string is not valid for any reason, nil and an error shall be returned
69+
*/
70+
Lookup(ctx context.Context, name string) (FsNode, error)
71+
}
72+
73+
type FsNode interface {
74+
corepath.Path
75+
InitMetadata(context.Context) (FileMetadata, error)
76+
Metadata() FileMetadata
77+
//RWLocker Maybe?
78+
79+
/* XXX: see PR discussion, this is not ideal; YieldIo should not require type
80+
YieldIo must ensure that the requested nodeType, and the interface returned, are compatible.
81+
e.g. `YieldIo(ctx, unixfs.TFile)`, is expected to return an object that satisfies the `FsFile` interface
82+
or `errIOType` if the implementation
83+
*/
84+
YieldIo(ctx context.Context, nodeType FsType) (io interface{}, err error)
85+
86+
Remove(context.Context) error
87+
Create(context.Context, FsType) error // should this return something? FsNode, io interface of type FsType?
88+
}
89+
90+
type FsFile interface {
91+
io.Reader
92+
io.Seeker
93+
io.Closer
94+
Size() (int64, error)
95+
Write(buff []byte, ofst int64) (int, error)
96+
Sync() error
97+
Truncate(size uint) error
98+
Record() FsNode
99+
}
100+
101+
type FsDirectory interface {
102+
Entries() int // count
103+
Read(ctx context.Context, offset int64) <-chan FsNode
104+
Record() FsNode
105+
}
106+
107+
type FsReference interface {
108+
Target() string
109+
}

0 commit comments

Comments
 (0)