-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Replace coreunix.Add with shell/fsnode's AddFromReader #1136
Conversation
all the text above sgtm, except:
why remove i want every function in the "API" to be in all, {cli, lib, http api}. the reason for mirroring the api exactly is ease of use. having a consistent api across all {cli, http api, and lib} will make using IPFS to build things very easy. we can certainly have more in the lib, and guide users to use other functions, but every part of the api should exist. I'll try to synthesize a generalized API spec next week |
@jbenet I gotta be straight with you, i'm not a huge fan of the whole |
I just want the API to be implemented in the interface, shell or core doesn't really matter. Also, everything in the shell will have to be in the core one way or another. The shell is a wrapper that can RPC over to the daemon. It has to be implemented somewhere. And one shell implementation may just be direct calls to the core, if you DO happen to be running an embedded node in your app. That is very much a use case. Think sqllite, leveldb, apps that build use IPFS as a database and don't want to require installing a node in the OS. |
On Fri, Apr 24, 2015 at 09:24:40PM -0700, Juan Batiz-Benet wrote:
And I'm fine with that. It just seems to me that importer is lib location → use in command-line or HTTP-endpoint implementation not the current: lib location → coreunix wrapper → command wrapper → use in If you want the lib location to be in coreunix, or lib/ipfs/unix or
Sounds good. In the interim, I think removing any extra layers of |
|
errors "github.com/ipfs/go-ipfs/util/debugerror" | ||
) | ||
|
||
func AddCat(adder *core.IpfsNode, catter *core.IpfsNode, data string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The creation of this function is a good addition, let's keep this.
I very much appreciate the effort to remove unnecessary code, and unnecessary symbols. In general, I'm all for that. And thanks for taking the time to look through this case. But in this case, I'm strongly against this change. It increases (not reduces) the complexity for us, and for end users. When i want to add an I'm ok with the |
I know this is not documented anywhere, please bear with me as I make all this clearer through specs. I don't really care whether it's For now (and until the shell interface appears), please look at the
Yeah, that's fine. the ability to embed an IPFS node and use it directly from within an application (as you would an HTTP client or server) is very much a target use case. Not all IPFS nodes will be bulky, os-level services. Many will be ephemeral nodes created inside apps. (and yeah, the shell interface -- when it arrives -- will allow embedding only a proxy object and RPCing out to use it. ideally we will use the shell interface for the directly embedded case too, so the interface is the exact same, but until then, |
On Mon, Apr 27, 2015 at 02:29:16AM -0700, Juan Batiz-Benet wrote:
I agree that having a simple UX is really important. I just think
Folks using a terminal or an HTTP API will have a different interface Also, adding a file from a reader is going to get more complicated as Anyhow, I think we have the same end goal in mind (providing an |
agreed, which is why i prefer a simple:
which will cover most of users' first steps into using this.
changing the signature from: // from
func Add(...) Key
// to
func Add(...) Node is fine with me. what isn't fine is adding lots of knowledge to the user (dagservice, pinning, chunker). We already ask users to understand a lot of stuff in the model. complicating it even further with more implemenetation-specific knowledge is not good.
That's fine, all of those can be more advanced function calls that do ask the user to understand them. What i'm rejecting here is removing the simplest possible use case, which most users will fall into at the very beginning.
Not really, this is what I use, and what other people use when they first try to add data with Go. It's very natural -- both from an idiomatic Go perspective (manipulating io.Readers) and from what we established with the cli (
It takes waaay more time explaining to users on irc what the more complicated things mean, and how to use them. and those are just the users brave enough to ask. most give up on the spot, and i've seen it first hand. At this point i've run multiple "classes" with 30+ people hacking with ipfs. I've observed people trying to grasp the large number of concepts when their goal is a simple "i want to add this data into ipfs". I'm interested in dropping the barriers of entry as much as possible, and this requires having extremely simple interfaces that do the simplest possible thing -- the average use case -- by default. Advanced use should be optional, not forced.
The problem is not 5 sentences of doc. the problem is 5 sentences here, 5 sentences there, and very soon we're at 1000s of sentences of doc. we already ask waaaay too much from users. ipfs must be extremely simple to use, and leveraging the analogues we establish in the cli is critical to this. take a look at Go's net and http packages. they hide away a ton of complexity for the common case. the more advanced use cases can do things by dropping down into lower levels. crystal clear, unencumbered interfaces are really valuable to users new to IPFS, new to Go, new to content addressing in general.... Compare this to asking users to enter:
|
On Mon, Apr 27, 2015 at 01:53:49PM -0700, Juan Batiz-Benet wrote:
No, that's adding a file from a path (where coreunix.AddR is what you $ ipfs add </path/to/file and we don't have a command-line interface to do that.
Ok, and on IRC you suggested to levels of API:
So having high-level wrappers around adding and accessing files in
The command line is a lot easier here, because you can have optional $ ipfs add [options] with options like: --dagservice address of the IPFS node to use. Defaults to
Of course, the command line UI is going to be less flexible about |
sure
what do you mean? we do.
it's ok to have multiple functions. i prefer this. but the simplest, most straightforward case that users will want to try first should be trivially easy to spot and use. hence |
On Mon, Apr 27, 2015 at 03:58:27PM -0700, Juan Batiz-Benet wrote:
Ahh, good stuff. Docs updated in #1151. I guess we can pull the |
yeah, sounds good. i wonder if this should be an option. for example, most cases when publishing to the web, i do not want ipfs to grab my user or permissions, only the content. something like |
(though it doesnt matter much either way) |
On Mon, Apr 27, 2015 at 04:33:23PM -0700, Juan Batiz-Benet wrote:
So “please don't wrap this file with a metadata node”, which makes it Do you want that boolean flag to be part of the high-level func Add(node _core.IpfsNode, reader io.Reader, metadata bool) (_dag.Node, err) where ‘metadata == true’ means “wrap the top-level file node with Or should the high-level signature just assume we don't want to guess |
i think this is right. we could have a set of functions that use metadata. it's an additional concept people need to know and want. its possible it'll be common enough to warrant being the default, but im not convinced of that yet. |
Starting to use the core/... (low-level, all the knobs you need) shell/... (high-level, handle the common case easily) disctinction laid out in [1]. Creating a file node from a reader is hard to do right, because there's a lot of filesystem metadata that we don't have access to (access mode, ownership, etc.). You can guess at those based on the adding process's umask, effective user, etc., but figuring out what you want guessed at or what you want set explicitly, or whether you want wrapping metadata at all is complicated. This function isn't going to do any of that [2], it's just a high-level wrapper to create a minimal file object with the default chunking, pinning, etc. all taken care of in ways that will probably work for you ;). [1]: ipfs#1158 [2]: ipfs#1136 (comment)
Starting to use the core/... (low-level, all the knobs you need) shell/... (high-level, handle the common case easily) disctinction laid out in [1]. Creating a file node from a reader is hard to do right, because there's a lot of filesystem metadata that we don't have access to (access mode, ownership, etc.). You can guess at those based on the adding process's umask, effective user, etc., but figuring out what you want guessed at or what you want set explicitly, or whether you want wrapping metadata at all is complicated. This function isn't going to do any of that [2], it's just a high-level wrapper to create a minimal file object with the default chunking, pinning, etc. all taken care of in ways that will probably work for you ;). [1]: ipfs#1158 [2]: ipfs#1136 (comment)
I'm still running through the local tests, but I think this is ready core/corehttp/gateway_test.go would be a bit simpler if it could use |
|
||
That's a bit more verbose if all you want is the stringified key, but | ||
returning a `dag.Node` makes it easier to perform other operations | ||
like adding the file node to a directory node. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're not currently doing descriptions like this. seems very nice and useful for consumers.
the binary's changelog is not the best place. this changelog is just a tiny summary. maybe we can have a different file for things like this.
we should define clearly what interfaces we commit to as stable. in particular:
- core
- shell
come to mind as the main ones. changes in these are potentially impactful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Wed, Apr 29, 2015 at 01:26:36PM -0700, Juan Batiz-Benet wrote:
the binary's changelog is not the best place. this changelog is just
a tiny summary. maybe we can have a different file for things like
this.
Git uses a directory of per-release notes 1. I'm happy to put this
in docs/release-notes/unreleased.md (to be moved to
docs/release-notes/0.3.4.md when you cut that release) if that sounds
reasonable to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, sounds good.
Starting to use the core/... (low-level, all the knobs you need) shell/... (high-level, handle the common case easily) disctinction laid out in [1]. Creating a file node from a reader is hard to do right, because there's a lot of filesystem metadata that we don't have access to (access mode, ownership, etc.). You can guess at those based on the adding process's umask, effective user, etc., but figuring out what you want guessed at or what you want set explicitly, or whether you want wrapping metadata at all is complicated. This function isn't going to do any of that [2], it's just a high-level wrapper to create a minimal file object with the default chunking, pinning, etc. all taken care of in ways that will probably work for you ;). I'm not clear on where the 'unix' part of the name comes from (these seem like pretty generic filesystem nodes to me, or if anything, they're POSIX filesystem nodes), but: On Wed, Apr 29, 2015 at 01:30:04PM -0700, Juan Batiz-Benet wrote [3]: > > package fsnode > > i think this package should be called unixfs as that's the > abstraction that this is calling to. So that's what we're going with. [1]: ipfs#1158 [2]: ipfs#1136 (comment) [3]: ipfs#1136 (comment)
…FromReader We don't want to create core -> shell -> core import cycles, so we need to move this test out of the corehttp package before we can safely use AddFromReader. Moving the tests into their own _test package means we need to explicitly import the corehttp package, which in turn means we need to *export* makeHandler (now MakeHandler). Pulling in the shell code also increases the surface area for bugs breaking the test, which was previously only testing the low-level code. Not that I expect many bugs in shell/unixfs's AddFromReader, but it's still nice to rule out a whole API when tracking down issues ;). Personally, it seems simpler to stick to keep using importer.BuildDagFromReader directly from this test, but Juan prefers the external approach taken by this commit [1]. [1]: ipfs#1136 (comment)
coreunix.Add is basically a wrapper around BuildDagFromReader that returns a string-ified key instead of a DAG node. Since we'll need the DAG node to add to the directory, converting to a key and then looking that key back up in dirbuilder.AddChild is just extra work.
No need to convert from a path to a reader locally, when AddR will do that for us. This also future-proofs us from AddR enhancements that will pull other information about the added file from the filesystem besides the data (e.g. access mode, owner and group ID, ...).
From the docs for strings.NewReader [1]: It is similar to bytes.NewBufferString but more efficient and read-only. [1]: https://golang.org/pkg/strings/#NewReader
Save some repetitive logic in the integration tests. While we're at it, replace coreunix.Add with a directo call to BuildDagFromReader. coreunix.Add is basically a wrapper around BuildDagFromReader that returns a string-ified key instead of a DAG node. That's what we want here, so it's a few more lines to inline the key extraction (mostly due to Go's explicit error handling [1], which makes method chaining not idomatic [2]). But I think a bit of local error-checking is a reasonable price for a more concise API, where there's only one function for adding file nodes from a reader. Previous versions of this commit used used a string (instead of []byte) for AddCat's data, because we don't need to mutate the value, and Go's strings are just immutable byte arrays. Jeromy pointed out that most of our callers have []byte data that they've read from io.Readers [3], so it's better to use []byte to avoid the extra copy [4]. I'd planned to add the AddCat helper to the testutil package, but that triggered some cyclic imports: $ make test cd cmd/ipfs && go build -i import cycle not allowed package github.com/ipfs/go-ipfs/cmd/ipfs imports github.com/ipfs/go-ipfs/commands imports github.com/ipfs/go-ipfs/core imports github.com/ipfs/go-ipfs/blockservice imports github.com/ipfs/go-ipfs/exchange/bitswap imports github.com/ipfs/go-ipfs/exchange/bitswap/testnet imports github.com/ipfs/go-ipfs/p2p/net/mock imports github.com/ipfs/go-ipfs/p2p/test/util imports github.com/ipfs/go-ipfs/util/testutil imports github.com/ipfs/go-ipfs/core/coreunix imports github.com/ipfs/go-ipfs/importer imports github.com/ipfs/go-ipfs/importer/balanced imports github.com/ipfs/go-ipfs/importer/helpers imports github.com/ipfs/go-ipfs/merkledag imports github.com/ipfs/go-ipfs/blockservice import cycle not allowed package github.com/ipfs/go-ipfs/cmd/ipfs imports github.com/ipfs/go-ipfs/commands imports github.com/ipfs/go-ipfs/core imports github.com/ipfs/go-ipfs/blockservice imports github.com/ipfs/go-ipfs/exchange/bitswap imports github.com/ipfs/go-ipfs/exchange/bitswap/testnet imports github.com/ipfs/go-ipfs/p2p/net/mock imports github.com/ipfs/go-ipfs/p2p/test/util imports github.com/ipfs/go-ipfs/util/testutil imports github.com/ipfs/go-ipfs/core Makefile:27: recipe for target 'build' failed make: *** [build] Error 1 I think the proper workaround is to split tests that import test-only libraries out into their own packages. For example, exchange/bitswap/bitswap_test.go is in the bitswap package, but imports the exchange/bitswap/testnet package, which in turn pulls in mock, testutil, etc. The easiest workaround seems to be using external tests [5] (e.g. bitswap_test for exchange/bitswap/bitswap_test.go). I tried to apply this approach, but the current codebase had too many crosslinks (e.g. internal tests that import test-only packages but also use internal utilities from their current package), so I gave up. I think gradually converting test files into external tests is a good thing, but it's too much to bite off for this feature branch. [1]: https://golang.org/doc/faq#exceptions [2]: http://stackoverflow.com/a/27300387 [3]: https://github.com/ipfs/go-ipfs/pull/1136/files#r29549480 [4]: http://stackoverflow.com/a/9649061 [5]: http://dave.cheney.net/2014/12/01/five-suggestions-for-setting-up-a-go-project
Starting to use the core/... (low-level, all the knobs you need) shell/... (high-level, handle the common case easily) disctinction laid out in [1]. Creating a file node from a reader is hard to do right, because there's a lot of filesystem metadata that we don't have access to (access mode, ownership, etc.). You can guess at those based on the adding process's umask, effective user, etc., but figuring out what you want guessed at or what you want set explicitly, or whether you want wrapping metadata at all is complicated. This function isn't going to do any of that [2], it's just a high-level wrapper to create a minimal file object with the default chunking, pinning, etc. all taken care of in ways that will probably work for you ;). I'm not clear on where the 'unix' part of the name comes from (these seem like pretty generic filesystem nodes to me, or if anything, they're POSIX filesystem nodes), but: On Wed, Apr 29, 2015 at 01:30:04PM -0700, Juan Batiz-Benet wrote [3]: > > package fsnode > > i think this package should be called unixfs as that's the > abstraction that this is calling to. So that's what we're going with. [1]: ipfs#1158 [2]: ipfs#1136 (comment) [3]: ipfs#1136 (comment)
Folks should be using the node-returning AddFromReader defined in shell/unixfs instead, since that creates the same node but returns the dag.Node object itself, instead of the stringified key. It's cheaper to go from Node to stringified key than it is to go the other way around, and you need the Node to do anything besides printing the stringified key, so the shell/unixfs version has a better API and we want to push people in that direction. The shell/unixfs version also uses our intended high-/low-level API distinction (core/... is for low-level stuff) [1]. This commit starts the deprecation/migration using the procedure outlined here [2], with the API-release-notes directory proposed here [3] and accepted here [4]. [1]: ipfs#1158 [2]: ipfs#1159 [3]: https://github.com/ipfs/go-ipfs/pull/1136/files#r29379579 [4]: https://github.com/ipfs/go-ipfs/pull/1136/files#r29498385
…ader The defaults used by the high-level wrapper are fine here, and we're in a test so consuming shell/... is ok.
Using the procedure suggested in the changelog.
…agFromReader coreunix.Add is basically a wrapper around BuildDagFromReader that returns a string-ified key instead of a DAG node. That's what we want here, so it's a few more lines to inline the key extraction (mostly due to Go's explicit error handling [1], which makes method chaining not idomatic [2]). But I think a bit of local error-checking is a reasonable price for a more concise API, where there's only one function for adding file nodes from a reader. [1]: https://golang.org/doc/faq#exceptions [2]: http://stackoverflow.com/a/27300387
…FromReader We don't want to create core -> shell -> core import cycles, so we need to move this test out of the corehttp package before we can safely use AddFromReader. Moving the tests into their own _test package means we need to explicitly import the corehttp package, which in turn means we need to *export* makeHandler (now MakeHandler). Pulling in the shell code also increases the surface area for bugs breaking the test, which was previously only testing the low-level code. Not that I expect many bugs in shell/unixfs's AddFromReader, but it's still nice to rule out a whole API when tracking down issues ;). Personally, it seems simpler to stick to keep using importer.BuildDagFromReader directly from this test, but Juan prefers the external approach taken by this commit [1]. [1]: ipfs#1136 (comment)
@wking whats the status here? |
closing, old PR cleanup time. |
coreunix.Add is basically a wrapper around BuildDagFromReader that
returns a string-ified key instead of a DAG node. Since we'll need
the DAG node to add to the directory, converting to a key and then
looking that key back up in dirbuilder.AddChild is just extra work.
In #1127, @whyrusleeping mentioned that the file- and
directory-addition API needed some polishing, and this WIP PR is my
attempt to grind things down to a more compact API. I'll probably
just work up to removing coreunix.Add completely, and then start a new
PR for whatever function we want to cull next.