3

So I'm trying to create a file dropper web application. Right now, a user can drop files on the screen and I can read them, including all the files in a directory that was dropped. But, I don't know when the script is done reading the files.

Some code:

This first function handles a 'drop' event and will loop through each file and send it to another function that will read its contents.

function readDrop( evt )
{
    for( var i = 0; i < evt.dataTransfer.files.length; i++)
    {
        var entry = evt.dataTransfer.items[i].webkitGetAsEntry();

        if(entry)
            readContents(entry, "");
    }

    //Do stuff after all files and directories have been read.
}

This function is a recursive FileEntry reader. If it is a file, I will read the FileEntry. If it is a directory, it will loop through the contents and pass it through this function.

function readContents(entry, path)
{
    if( entry.isFile )
    {
        readFileData( entry, path, function(fileData)
        {
            _MyFiles.push( fileData );
        });
    }
    else if( entry.isDirectory )
    {
        var directoryReader = entry.createReader();
        var path = path + entry.name;

        directoryReader.readEntries(function(results)
        {
            for( var j = 0; j < results.length; j++ )
            {
                readContents(entry, path);
            }

        }, errorHandler)
    }
}

And here is my function for reading the files. The callback just pushes the fileData object to a global array

function readFileData(entry, path, callback)
{
    var fileData = {"name": entry.name, "size": 0, "path": path, "file": entry};

    entry.file(function(file)
    {
        fileData["size"] = file.size;
        callback( fileData );
    }
}

I'm not sure where to go from here so that I can have a callback when all files and directories have been read.

2
  • The interesting bit is in the code you left out where you have the comment //read the file - show us that code and we might be able to help you Commented Sep 15, 2013 at 17:30
  • Ok I guess I didn't think that part of the code was that important, but I added it Commented Sep 15, 2013 at 17:48

2 Answers 2

11

The FileSystem API doesn't seem well suited for the task of a full recursive traversal, perhaps that's part of the reason why other vendors are not adopting it. Anyway, with an arcane combination of Promises I think I was able to accomplish this goal:

    function traverse_directory(entry) {
        let reader = entry.createReader();
        // Resolved when the entire directory is traversed
        return new Promise((resolve_directory) => {
            var iteration_attempts = [];
            (function read_entries() {
                // According to the FileSystem API spec, readEntries() must be called until
                // it calls the callback with an empty array.  Seriously??
                reader.readEntries((entries) => {
                    if (!entries.length) {
                        // Done iterating this particular directory
                        resolve_directory(Promise.all(iteration_attempts));
                    } else {
                        // Add a list of promises for each directory entry.  If the entry is itself 
                        // a directory, then that promise won't resolve until it is fully traversed.
                        iteration_attempts.push(Promise.all(entries.map((entry) => {
                            if (entry.isFile) {
                                // DO SOMETHING WITH FILES
                                return entry;
                            } else {
                                // DO SOMETHING WITH DIRECTORIES
                                return traverse_directory(entry);
                            }
                        })));
                        // Try calling readEntries() again for the same dir, according to spec
                        read_entries();
                    }
                }, errorHandler );
            })();
        });
    }

    traverse_directory(my_directory_entry).then(()=> {
        // AT THIS POINT THE DIRECTORY SHOULD BE FULLY TRAVERSED.
    });
Sign up to request clarification or add additional context in comments.

1 Comment

you are a modern-day hero
5

Following on from the answer by drarmstr, I modified the function to be compliant with the Airbnb ESLint standards, and wanted to make some further comments on its usage and results

Here's the new function:

function traverseDirectory(entry) {
  const reader = entry.createReader();
  // Resolved when the entire directory is traversed
  return new Promise((resolve, reject) => {
    const iterationAttempts = [];
    function readEntries() {
      // According to the FileSystem API spec, readEntries() must be called until
      // it calls the callback with an empty array.  Seriously??
      reader.readEntries((entries) => {
        if (!entries.length) {
          // Done iterating this particular directory
          resolve(Promise.all(iterationAttempts));
        } else {
          // Add a list of promises for each directory entry.  If the entry is itself
          // a directory, then that promise won't resolve until it is fully traversed.
          iterationAttempts.push(Promise.all(entries.map((ientry) => {
            if (ientry.isFile) {
              // DO SOMETHING WITH FILES
              return ientry;
            }
            // DO SOMETHING WITH DIRECTORIES
            return traverseDirectory(ientry);
          })));
          // Try calling readEntries() again for the same dir, according to spec
          readEntries();
        }
      }, error => reject(error));
    }
    readEntries();
  });
}

here's a drop event handler:

function dropHandler(evt) {
  evt.preventDefault();
  const data = evt.dataTransfer.items;
  for (let i = 0; i < data.length; i += 1) {
    const item = data[i];
    const entry = item.webkitGetAsEntry();
    traverseDirectory(entry).then(result => console.log(result));
  }
}

The result variable at the end contains an array, mirroring the tree structure of the folder you dragged and dropped.

For example, here is a git repo for my own site, ran through the above code:

enter image description here

Here's the Git repo for comparison https://github.com/tomjn/tomjn.com

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.