简体   繁体   中英

Create a tree directory structure in Google Drive with Javascript

I am trying to build a simple directory tree with the Google Drive Javascript API. I suppose I should check for the folder existence and create it then add eventually its children. So I built these functions below:

function createFolder(name, parent) {
    if (!parent) parent = 'root';
    var fileMetadata = {
        'name': name,
        'mimeType': 'application/vnd.google-apps.folder',
        'parent': parent
    };
    gapi.client.drive.files.create({
        'resource': fileMetadata,
        'fields': 'id'
    }).then(function (res) {console.log(res); return res.result['id'];}, function (err) {console.log(err); return null;});
}

function getFolder(name, parent) {
    if (!parent) parent = 'root';
    gapi.client.drive.files.list({
        'pageSize': 1,
        'fields': "nextPageToken, files(id, name, mimeType, parents)",
        'q': "mimeType='application/vnd.google-apps.folder' \
                    and '" + parent + "' in parents \
                    and name = '" + name + "'"
    }).then(
        function (res) {console.log(res); var ret = res.result.files.length > 0 ? res.result.files[0]['id'] : null; return ret;},
        function (err) {console.log(err);return null;}
    );
}

function checkPhrFolder() {
    var phr = getFolder('Personal Health Record');
    console.log('get folder: '+phr);
    if (!phr) {
        console.log('creating ...');
        phr = createFolder('Personal Health Record');
    }
}

This simply checks if my first dir exists and if not, creates it. The problem is that the calls are async (using "then promises") so the functions do not return anything (getFolder for example never returns the id of the folder), so I was wondering what is the correct way of creating recursively many folders given the nature of the async calls. Do I need to put a controller function that is always called and chose what to do next?

Thank you !

In this case I found the new asynchronous javascript syntax easier to understand.

Approach

You are running the function checkPhrFolder synchronously, while the other two functions, whose depend on the asynchronous api call, are waiting the promise to resolve in order to return a value. This will cause phr to be undefined when you evaluate it in the synchronous checkPhrFolder execution.

Basically for this to work you should chain your promises resolution in order to use their return values correctly. The new javascript syntax although enables you to write asynchronous functions with fewer lines of code. With the keywords async and await you can control the promises resolution flow, and basically wait the asynchronous function to return before making the proper assignment.

Example

async function createFolder(name, parent) {
    if (!parent) parent = 'root';
    var fileMetadata = {
        'name': name,
        'mimeType': 'application/vnd.google-apps.folder',
        'parent': parent
    };
    const res = await gapi.client.drive.files.create({
        'resource': fileMetadata,
        'fields': 'id'
    });
    return res.result['id'];
}

async function getFolder(name, parent) {
    if (!parent) parent = 'root';
    const res = await gapi.client.drive.files.list({
        'pageSize': 1,
        'fields': "nextPageToken, files(id, name, mimeType, parents)",
        'q': "mimeType='application/vnd.google-apps.folder' \
                    and '" + parent + "' in parents \
                    and name = '" + name + "'"
    });
    return res.result.files.length > 0 ? res.result.files[0]['id'] : null;
}

async function checkPhrFolder() {
  var phr = await getFolder('Personal Health Record');
  console.log('get folder: '+phr);
  if (!phr) {
      console.log('creating ...');
      phr = createFolder('Personal Health Record');
  }
}

In this way your checPhrFolder function will be almost as easy to read as before. When you will use the checkPhrFolder function in a synchronous context you will be able to wrap its return value in the same then/catch statement.

Reference

Async/Await

I used the traditional way to build my dirs populator this way below:

    var phrFolderManager = {
        currentName: null,
        currentParent: null,
        folders: {
            '/PersonalHealthRecord': null,
            '/PersonalHealthRecord/Some': null,
            '/PersonalHealthRecord/Dirs': null,
            '/PersonalHealthRecord/ForMe': null
        },
        addFolder: function (name, id) {
            this.folders[name] = id;
        },
        getFolderId: function (name) {
            return this.folders[name];
        },
        refresh: function (forced) {
            console.log('next step ' + forced + ' ...');
            console.log(this.folders);
            // find the next null in our folder list
            for (k in this.folders) {
                this.currentName = k;
                if (!this.folders[k]) {
                    var parts = k.split('/');
                    if (parts.length == 2) {
                        // this is our base dir inside root
                        if (forced) {
                            this.createFolder(parts[1], 'root');
                        } else {
                            this.getFolder(parts[1], 'root');
                        }
                    } else {
                        var parent = parts.slice(0, -1).join('/');
                        var name = parts[parts.length - 1];
                        var parent_id = this.folders[parent];
                        if (forced) {
                            this.createFolder(name, parent_id);
                        } else {
                            this.getFolder(name, parent_id);
                        }
                    }
                    break;
                } else {
                    console.log('... defined as ' + this.folders[k]);
                }
            }
        },
        getFolder: function (name, parent) {
            //M.toast({html: 'check da pasta '+name,classes: 'rounded'});
            if (!parent) parent = 'root';
            this.currentParent = parent;
            console.log('getFolder ' + name + ' ' + parent);
            var res = gapi.client.drive.files.list({
                'pageSize': 1,
                'fields': "files(id, name, mimeType, parents)",
                'q': "mimeType='application/vnd.google-apps.folder' \
                        and '" + parent + "' in parents \
                        and name = '" + name + "'"
            }).then(
                function (res) {
                    console.log(res);
                    if (res.result.files.length > 0) {
                        this.folders[this.currentName] = res.result.files[0]['id'];
                        this.refresh(false);
                    } else {
                        this.refresh(true);
                    }
                },
                function (err) {
                    console.log('error in getFolder: ' + err)
                },
                this
            );
        },
        createFolder: function (name, parent) {
            M.toast({
                html: 'criando pasta ' + name,
                classes: 'rounded'
            });
            if (!parent) parent = 'root';
            console.log('createFolder ' + name + ' ' + parent);
            var fileMetadata = {
                'name': name,
                'mimeType': 'application/vnd.google-apps.folder',
                'parents': [parent]
            };
            gapi.client.drive.files.create({
                'resource': fileMetadata,
                'fields': 'id'
            }).then(
                function (res) {
                    console.log(res);
                    this.folders[this.currentName] = res.result['id'];
                    this.refresh();
                },
                function (err) {
                    alert('Problem creating ' + this.currentName + ' PersonalHealthRecord structure');
                },
                this
            );
        }
    };

With a little tweaking, @Alessandro's solution worked for me:

  • I reversed the order of the methods just for readability.
  • I incorporated a FOR loop because my file structure will go many folders deep. The loops is through an array of strings containing the folder names in the file path.
  • I noticed that (,parent) wasn't returning properly because it was "undefined". not "null", Also, expecting "null" when the existing folder wasn't found was vital, so I defaulted parentId to "root" since I know my first folder will always be at root
  • variable newFolderId holds the value of the found/created folder, and is passed onto the parentId for the next iteration of the FOR loop
  • Finally, I updated "'parent': parent" to be "'parents': [parent]". That "s" and those brackets seem necessary, otherwise every created folder is placed at root.
        async function buildFilePath() {

            console.log("buildFilePath()");

            var parentId = "root";

            for (let i = 0; i < filePath.length; i++)  {
                var folderName = filePath[i];

                newFolderId = await getFolder(folderName, parentId);
                console.log(folderName + " id is " + newFolderId + ", parentId is " + parentId);
                if (newFolderId == null) {
                    newFolderId = await createFolder(folderName,parentId);
                    console.log ("created new folder " + folderName + " with Id " + parentId);
                } else {
                    // parentId = newParentId;
                    console.log (folderName + " already exist, move on to next.");
                }
                parentId = newFolderId;
            }
        }
        async function getFolder(name, parent) {

            const res = await gapi.client.drive.files.list({
                'pageSize': 1,
                'fields': "nextPageToken, files(id, name, mimeType, parents)",
                'q': "mimeType='application/vnd.google-apps.folder' \
                            and '" + parent + "' in parents \
                            and name = '" + name + "' \
                            and trashed = false"
            });
            return res.result.files.length > 0 ? res.result.files[0]['id'] : null;
        }
        async function createFolder(name, parent) {

            if (!parent) parent = 'root';

            var fileMetadata = {
                'name': name,
                'mimeType': 'application/vnd.google-apps.folder',
                'parents': [parent]
            };
            const res = await gapi.client.drive.files.create({
                'resource': fileMetadata,
                'fields': 'id'
            });
            return res.result['id'];
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM