Regular Files API | Lesson 5 of 8

Add files in a directory

When we added files to IPFS in Lesson 3, we saw that the add and addAll methods returned path, cid, and size values for each file, and that the path and cid string values were identical. To access each of these files later, we would need to know its CID. Did you notice that there was no filename provided?

The add and addAll methods do offer one slightly more human-friendly way to refer to files, but only if we use a special wrapWithDirectory option to provide a path and filename for the added files. By doing this, we can create both CIDs for individual files, as we saw previously, and a CID for their containing directory, which can be used as part of a path to retrieve those files later by their filenames. This option will allow us to do interesting things with the ls and get methods, which we'll cover in upcoming lessons.

To use the wrapWithDirectory option, you'll need to call the add or addAll method as follows:

ipfs.add(file, { wrapWithDirectory: true }) // add a single file
ipfs.addAll([file1, file2], { wrapWithDirectory: true }) // add multiple files via an array

When we used the add method previously, we were able to pass in just a File object from our browser. However, to wrap files with a directory, we need to also provide a path with the name we want for each file. To do this, we must replace the file argument in the add method with an object, structured like so:

{
    path: 'file.txt', // a string for our desired path, including the filename
    content: file // in our case, a File object
}

To add multiple files at once, we just need to pass an array of objects like the one shown above to the addAll method. For example, we could add two cat pictures into a directory on our IPFS node like so:

ipfs.addAll([
    {
        path: 'adorable-kitty.jpg',
        content: catPic1
    },
    {
        path: 'cat-drinking-milk.jpg',
        content: catPic2
    }
], { wrapWithDirectory: true })

The add or addAll method will return information about each file we've added, as well as details about any directory it had to create in order to add the files with the correct paths. For example, in the case above, the addAll method would return an array of three elements, one for each file added and another for the wrapping directory created.

Note that we could also have used the same addAll method to create one or more subdirectories by adding them to the path of each file. For example:

ipfs.addAll([
    {
        path: 'cats/adorable-kitty.jpg',
        content: catPic1
    },
    {
        path: 'cats/cat-drinking-milk.jpg',
        content: catPic2
    },
    {
        path: 'dogs/dog-on-a-table.jpg',
        content: dogPic1
    }
], { wrapWithDirectory: true })

In this case we'd end up with six objects in our resulting array: one for each of the three files added, one for the new wrapping directory, one for the new cats directory, and one for the new dogs directory.

Directories are immutable

It's important to note that directories we create in this way do not behave as in a regular file system. If we've wrapped some files with a directory with using add or addAll, we can't simply add new files to that directory. The contents of the directory we just created are final and immutable.

This is because of the way in which IPFS uses content addressing: different contents lead to a different cryptograhpic hash (Content Identifier), whether we're working with directories or files themselves.

But what if you forgot to add a file to a directory you just created? You'll have to call the addAll method again with all the files you want wrapped with that directory, resulting in a new directory altogether with a new CID.

However, it's important to note that the file won't actually be stored multiple times if you do this. Unlike on your computer, where you can accidentally download the same file twice, store it in two different directories, and double your storage needs, the IPFS network inherently knows the contents of a file based on its CID, and can therefore keep only a single copy of the data that the CID refers to. It's only the reference to that data (the CID, which you can think of as a link or an addresses), which is stored in each of the directories you create. This efficiency is one of IPFS's key advantages.

Rather than thinking of directories created with { wrapWithDirectory: true } as traditional file folders, it's more useful to think of them as naming shortcuts, as we'll see in upcoming lessons.

An alternative

If you're looking for an experience that better mimics a traditional file system, you should explore the Mutable File System (MFS), a tool built into IPFS that lets you treat files like you normally would in a name-based filesystem — you can add, remove, move, and edit MFS files and have all the work of updating links and hashes taken care of for you. It's an abstraction that lets you deal with immutable data as if it were mutable.

not yet startedTry it!

Add multiple files to your IPFS node with addAll, using { wrapWithDirectory: true } to put them in a directory. Because you're targeting the top-level directory, not a subdirectory, the path of each file should just be its name.

Hints:

  • Be sure to reference the examples above for the object structure needed to indicate the desired path of each file, as well as how to pass in multiple files as an array.
  • Try the map or forEach array methods to loop through each file in the files array and access its name as file.name.
  • You need to concatenate all the results into an array because the ipfs.addAll method returns an Async Iterable. You can use either the for await...of loop or the function all.
Step 1: Upload files
Step 2: Update code
View SolutionReplace with SolutionClear Default Code
Upload file(s) and update the code to complete the challenge.
You must upload a file before submitting.