-
Notifications
You must be signed in to change notification settings - Fork 67
feat: Non MFS Files API tutorial #303
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
Conversation
Review made by @ericronne is in #304. I'll be checking it out today |
76a1e48
to
25ecd17
Compare
* Title renaming for parallelism * Copy suggestions - lesson 1 * Lesson 1 edits * Lesson 2 edits * Lesson 3 edits * Lessons 4-6 edits * chore: update to review * chore: restore section on file upload in lesson 2 * chore: worked on last comment left
25ecd17
to
7979d0b
Compare
This is looking good @dominguesgm! Some outstanding items include: Before I do any text edits, I want to ask some bigger conceptual questions and share areas where I got confused on my first pass through this so you can update the content accordingly. Note that I have only run through this as an end user and have not yet looked at your validation code. Lesson 1: I suspect this section may need a little tweaking if my understanding is correct:
My understanding has been that DAG isn't a more limited use case, it's a broader use case because it can deal with all different kinds of data (including the obscure ones you mention), but that it's just really obnoxious to use for the most common use cases, like just sharing files. You could use the DAG API to share files, but you'd have to do a ton of extra work yourself to manipulate the file objects in the way you'd need to. The File API uses the DAG API under the hood but has lots of extra functionality built in to make it work with files more conveniently. We could have done this on our own, but the File API saves us time. (@mikeal can you please confirm this paragraph is true? ) Lesson 2:
Lesson 3:
let message = await ipfs.cat('QmWCscor6qWPdx53zEQmZvQvuWQYxx1ARRCXwYVE4s9wzJ')
message = message.toString('utf8')
Lesson 4:
/* global ipfs */
const run = async (files) => {
let fileObjects = []
files.forEach(function (file) {
let fileObject = {
path: "/dir/" + file.name,
content: file
}
fileObjects.push(fileObject)
})
let result = await ipfs.add(fileObjects, {wrapWithDirectory: true})
return result
}
return run
Lesson 5:
Lesson 6:
In going back to the original issue and outline for this tutorial, the only suggestion I don't see covered here is to show people how to use the CID to view a file on the gateway. I believe neither the files they're adding themselves nor the ones you've used as examples are living there, so maybe that's not practical anymore? Thoughts? I remember hearing about some MFS pitfalls in the end section of Alan's course at IPFS camp that highlighted important distinctions between MFS and all the rest. Would you mind taking a quick look to see whether there are any bits here worth including when you describe the difference between the regular File API and MFS? Sorry for the novel here, @dominguesgm. I'll be out on Monday so just want to share as much feedback as I can so you can get started on anything that doesn't require more clarification. Happy to schedule some time to chat through any of this feedback if you disagree or if I haven't been clear. Thank you so much for building this tutorial. It's fabulous! |
Thank you for the in depth feedback @terichadbourne , I have some points I'd love to clarify/discuss, which I will structure by lesson: ** Lesson 1: **
** Lesson 2: **
** Lesson 3: **
** Lesson 4: **
** Lesson 5: **
** Lesson 6: **
I'll be honest, the CID explorer went over my head, I can try to look into it. But from what I understand, we may have some issues letting a user upload a file and then asking them to view it on the CID explorer because the page may hang due to not finding the file at all. I'll still look into it. Sorry for the long message, but hopefully this will help us get closer to a final version of the tutorial 😃 |
@dominguesgm Thanks for working on some edits! A few responses to some of your points below. Let me know if I've missed anything you want to be sure to discuss. Lesson 1:
Yeah, apologies if I led you astray with our earlier convo on this. I think it's okay to present some bonus advantages that the File API offers (briefly in passing) even if they won't be covered explicitly in this lesson, if they help to explain why you would choose this over the DAG API. For example, (if it's true 😂) you could mention that the Files API handles the complexity of splitting your file into chunks of appropriate size and provide a link to whatever the best documentation is that we know of about how that works. And then if, for example, there's an option you can send to the Lesson 2:
MFS lesson 3 talks about the Lesson 3:
Lesson 4:
👍 I have a couple of folks in mind who we can ping for a review and improved explanations after your current batch of revisions.
I personally am more comfortable with It would be interesting to offer a toggle feature in the future where you could view solutions in "advanced" or "beginner" JavaScript mode. Lesson 5:
Lesson 6:
Honestly, we're never going to know what people will want to do with their variables. My guess is that folks who are super new to JavaScript will most appreciate them being provided and well named, in which case they'd want the multiple variable names and distinct steps (without fancy overwriting array methods). Folks who are advanced will be able to ignore us as much as they want to and remove unnecessary steps, so if we had to favor one side of this argument I'd favor the total newbie. However, it might also be okay not to provide any blank variable statements, so people can use as many or as few steps as they want, and I don't mind
If I'm understanding you correctly, it sounds like using Option A (if it doesn't print a giant mess to the screen):
Option B
The thing I'm trying to avoid is the JavaScript trick of overwriting all the buffers with strings. I think I would find it an easier JavaScript challenge, for example, to find the content of the text file titled X. But perhaps if you give an example that uses simpler JavaScript methods I'll change my mind. :) Agreed that if we'll only use the files you provided we should skip the file upload.
I wasn't meaning to reference the CID explorer tool specifically, just the general concept that you can find things by CID on the gateway. I believe you just need to go to https://gateway.ipfs.io/ipfs/<your_CID_here>, but I think it does depend on the gateway being fully functional and enough people having pinned a thing. Let's ignore this suggestion for now. |
…rectories and simplifying lesson 7
@terichadbourne I made a commit with changes covering most of the topics we discussed. There are still a few I'd like to discuss more in depth:
By the way, I really like the idea of having simple/difficult proposed solutions the user can see and toggle between the two. Maybe in another PR 😄 |
I opened an issue for the toggle idea, although that is definitely not a priority at the moment. Feel free to add any ideas there: #312 Look forward to chatting later today about those remaining issues. |
@alanshaw A few specific content questions for you as @dominguesgm puts the final touches on this... If one uses We could also use a closer look at lesson 1 where Gil explains both the difference between the Files API and the DAG API and between the Files API and MFS. Want to make sure we're highlighting the right pros/cons/functionality here without getting too deep. Feedback on all aspects of the tutorial are welcome, those are just our biggest newbie questions at the moment. :) |
I wanted to put some more context into the question about adding multiple files into a directory with the I was experimenting with it and tried to add two different files into the same directory, with different file names, in separate This is the code I was trying to run: const IPFS = require('ipfs');
const ipfs = await IPFS.create();
let res = await ipfs.add({ content: Buffer.from('HelloWorld'), path: '/dir/file.txt'}, {wrapWithDirectory: true});
res = await ipfs.add({ content: Buffer.from('hello world 2'), path: '/dir/file2.txt'}, {wrapWithDirectory: true});
console.log(await ipfs.get(res[2].hash)); Which returns an array with the root, [ { hash: 'QmPrjMFMsjwpgn5EVwY7k9Qrm5JJT47Q29TW5XxjTgtMZ9',
path: 'QmPrjMFMsjwpgn5EVwY7k9Qrm5JJT47Q29TW5XxjTgtMZ9',
name: 'QmPrjMFMsjwpgn5EVwY7k9Qrm5JJT47Q29TW5XxjTgtMZ9',
depth: 1,
size: 0,
type: 'dir' },
{ hash: 'QmfXcmsEgF2PeevmbaoS1jGuNMBhYRfm468LW59qGdQGoA',
path: 'QmPrjMFMsjwpgn5EVwY7k9Qrm5JJT47Q29TW5XxjTgtMZ9/dir',
name: 'dir',
depth: 2,
size: 0,
type: 'dir' },
{ hash: 'QmUGTcXwYTqHd5hbkRELDf6uSnBRjYv8CDEjsNDYde6o8h',
path:
'QmPrjMFMsjwpgn5EVwY7k9Qrm5JJT47Q29TW5XxjTgtMZ9/dir/file2.txt',
name: 'file2.txt',
depth: 3,
size: 13,
type: 'file',
content: <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64 20 32> } ] I am aware that if I added multiple files in a single |
The regular files API is non-mutable. The idea of wrapping with directory is to provide you with a single CID from which you can access your files by name. For example: Given:
When you do: ipfs.add([
{ path: 'a.txt', content: fs.createReadStream('./a.txt') },
{ path: 'b.zip', content: fs.createReadStream('./b.zip') },
{ path: 'c.jpg', content: fs.createReadStream('./c.jpg') }
]) You'll end up with 3 CIDs for each of the files which address each of the files: QmA, QmB and QmC. When you do: ipfs.add([
{ path: 'a.txt', content: fs.createReadStream('./a.txt') },
{ path: 'b.zip', content: fs.createReadStream('./b.zip') },
{ path: 'c.jpg', content: fs.createReadStream('./c.jpg') }
], { wrapWithDirectory: true }) You'll end up with 4 CIDs. The last one is the CID for the wrapping directory: QmA, QmB, QmC and QmWrappingDirectory. When you don't wrap with a directory you can only use When you wrap with a directory it enables you to use So ensuring you have a wrapping directory for your files allows you to retain file names/paths, not have to remember every CID for every file you add and allows for easier fetching. Note, you don't have to use the ipfs.add([
{ path: 'wrapper/a.txt', content: fs.createReadStream('./a.txt') },
{ path: 'wrapper/b.zip', content: fs.createReadStream('./b.zip') },
{ path: 'wrapper/c.jpg', content: fs.createReadStream('./c.jpg') }
])
// equivalent to:
ipfs.add([
{ path: 'a.txt', content: fs.createReadStream('./a.txt') },
{ path: 'b.zip', content: fs.createReadStream('./b.zip') },
{ path: 'c.jpg', content: fs.createReadStream('./c.jpg') }
], { wrapWithDirectory: true }) I believe js-ipfs currently doesn't allow you to add multiple root directories though, so don't do this: ipfs.add([
{ path: 'documents/a.txt', content: fs.createReadStream('./a.txt') },
{ path: 'zips/b.zip', content: fs.createReadStream('./b.zip') },
{ path: 'pictures/c.jpg', content: fs.createReadStream('./c.jpg') }
]) Back to the question:
You can re-add files and directories and IPFS will de-dupe anything you add twice. Your old content will still be accessible by the CIDs you recieved when you added it and likewise for the new content. e.g. after two adds with duplicate content and an additional file:
You could Alternatively the You could also use the |
Exactly, you're essentially double-wrapping with this code: |
Thank you @alanshaw for such an in depth explanation of what functionality the wrapWithDirectory option is supposed to provide, I was not interpreting it correctly! |
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.
Overall, I enjoyed the lessons. It gives a good explanation about the non-mutable ipfs files API. Please let me know if you need something else from my side. I'll gladly help.
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.
It was fun. I really like "X vs Y" explanations in this tutorial, addressing common questions.
My only concern is around teaching people to use shortened notation of detached content paths without /ipfs/
prefix. Using direct CID is ok (it is an identifier on its own), but as soon as we start traversing the DAG and operating on paths, we should make sure those paths starts with explicit namespace.
(I don't feel super strong about it, but got feeling it makes things less vague when we start teaching IPNS at some poiint and introduce paths starting with /ipns/
)
Adding casing override and updated readme instructions from previous PRs
Co-Authored-By: Marcin Rataj <[email protected]>
Co-Authored-By: Marcin Rataj <[email protected]>
@dominguesgm The collapsing conversations make it kind of hard to keep track of, but I'm pretty sure I've gotten to all the suggestions you hadn't gotten to yet except for:
I also went ahead and merged my PR and then pulled it into this PR to get API to be properly capitalized in the shortname. @alanshaw @lidel if you have time Monday morning to take a quick look at today's commits, which include me addressing a number of issues you suggested from a text standpoint, that would be awesome. |
Co-Authored-By: Marcin Rataj <[email protected]>
…idation of lesson 10 in the MFS tutorial
…hool.github.io into feat/tutorial-nonmfs
@dominguesgm I've made some tweaks to validation messages. Please see the comments in lessons 5 and 7 in the places where I think the validation or messages associated with it still needs some tweaking. I also replaced the text of your catch-all message with something more action-oriented. This feedback is mostly from me reading your validation code and not from going through and trying to break it by doing things wrong that you didn't anticipate (which I'm generally great at but haven't had time for yet). I also went through and changed all references to "root directory" to "top-level directory" per some earlier feedback from @alanshaw, and I moved your links to array methods to the hints section so people can see them before failing rather than after giving up and peeking at the solution. :) @hacdias if you happen to have any time tomorrow morning to take a quick look at the validation code or try submitting some various predictably-wrong code and seeing if it catches on meaningful errors, that would be awesome. Hoping to get this published by end of US day tomorrow if no one has lingering concerns. |
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.
🏆
Introducing a non-MFS Files API Tutorial. Currently consisting of 6 lessons, 1 simple and 5 code lessons.
Structure:
Contents should still be proof-read and validated before merging.
Closes #203