Skip to content

Commit

Permalink
New version!
Browse files Browse the repository at this point in the history
· Rewrite the entire damn thing
· Fix many bugs, crashes and improve error detection
· Added `keepStructure` option that controls whether to upload all
files found to `uploadTo` or to keep them in subfolders
  • Loading branch information
BlueTheDuck committed Nov 8, 2020
1 parent 6a1015a commit f06446a
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 70 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ The content of those two files should be uploaded as secrets to the repo, then t

```yml
- name: Upload to Google Drive
uses: PeronTheDuck/glob-to-drive@v3
uses: PeronTheDuck/glob-to-drive@v4
with:
# Required, used to find files
glob: "**/*.pdf"
# Optional, defaults to true. Controls whether to create subfolders or to upload everything to `uploadTo`
keepStructure: true
# Optional, if left empty, the files are uploaded to My Drive
uploadTo: ${{ secrets.DRIVE_FOLDER_ID }}
# Both required, tells Google that you are authorized to use Drive
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ inputs:
glob:
description: 'Glob that will be used to find files to upload'
required: true
keepStructure:
description: 'Keep the folder structure (Defaults to true)'
required: false
uploadTo:
description: 'ID of the folder where the files will be uploaded (Defaults to My Drive)'
required: false
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

36 changes: 23 additions & 13 deletions drive-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ async function login(credentials, token) {
core.info("Auth successful");
return auth;
} catch (e) {
Promise.reject(e);
core.error(`Authentication failed: ${e}`);
throw e;
}
}
function createDriveApi(auth) {
// Create the Drive API and store it in the global var `drive`
return Drive = google.drive({
return google.drive({
version: "v3",
auth,
});
Expand Down Expand Up @@ -115,8 +116,9 @@ async function update(drive, options) {
})
}

// TODO: Do we really need to run `folderLockRelease()` on every error? Code could be simplified if not
/**
* Return the folder info
* Return the ID of the last folder in `options.path`
* @param {import("googleapis").drive_v3.Drive} drive Google Drive context
* @param {object} options
* @param {string} options.path Path in the format 'a/b/c' relative to parent. The folder would be 'c'
Expand All @@ -125,27 +127,35 @@ async function update(drive, options) {
* @returns {String} Folder data
*/
async function folder(drive, options) {
let pathStructure = options.path.split(path.sep);
core.info(`Path structure: ${pathStructure}`);
let parentId = options.parent || `root`;
// If we are actually uploading to `parent` (As in "no subfolder" then just return the parent ID)
if (options.path === "") {
return parentId;
}
let pathStructure = options.path.split(path.sep);
core.info(`Path structure: '${pathStructure}' (${pathStructure.length} element/s)`);
for (let folderName of pathStructure) {
let folderLockRelease = await folderLock.acquire(); // Make sure no one is creating folders
core.info(`Finding folder with name '${folderName}'`);

let q = `'${parentId}' in parents and name = '${folderName}'`;

// TODO: Find a better way to chose what folder to use.
// TODO: Find a better way to choose what folder to use.
let folder = await list(drive, q)
.then(folders => folders[0]) // Take the first, ignore the rest
.catch(e => {
folderLockRelease();
Promise.reject(e.toString());
throw e;
});

// Take the ID of the folder...
let currentFolderId = folder ? folder.id : undefined;

// ...if no folder was found...
if (currentFolderId === undefined) {
if (options.create) // ...we check if we are allowed to create one...
core.info(`No folder with name ${folderName} was found`);
if (options.create) {// ...we check if we are allowed to create one...
core.info(`Creating folder ${folderName}`);
currentFolderId = await drive.files.create({
requestBody: {
name: folderName,
Expand All @@ -157,18 +167,18 @@ async function folder(drive, options) {
.then(res => res.data.id)
.catch(e => {
folderLockRelease(); // We are no longer risking a race condition
Promise.reject(e.toString())
throw e;
});
else {// ...if not, we fail
} else {// ...if not, we fail
folderLockRelease(); // We are no longer risking a race condition
Promise.reject(`The (sub)folder ${folderName} couldn't be located nor created (Full path: ${options.path}. Query: ${q})`);
throw `The (sub)folder '${folderName}' couldn't be located nor created (Full path: ${options.path}. Query: ${q})`;
}
}
core.info(`Folder ${folderName} has id ${currentFolderId}`);
core.info(`Folder '${folderName}' has id '${currentFolderId}'`);
folderLockRelease(); // We are no longer risking a race condition
parentId = currentFolderId;
}
core.info(`Returning ${parentId} for ${pathStructure}`);
core.info(`Returning '${parentId}' for '${pathStructure}'`);
return parentId;
}
//#endregion
Expand Down
113 changes: 58 additions & 55 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ const { login, createDriveApi, list, update, upload, folder } = require("./drive
* */
const SCOPES = require("./scopes.json");

let Drive = null;

//#region Helpers
/**
* @param {import("googleapis").drive_v3.Drive} drive Google Drive context
Expand All @@ -31,12 +29,7 @@ async function getGDriveFiles(Drive) {
return await list(Drive, q);
}
async function getMatchedFiles() {
let pattern;
try {
pattern = core.getInput("glob", { required: true });
} catch (e) {
return nok(e);
}
let pattern = core.getInput("glob", { required: true });
core.info(`Performing search with ${pattern}`);
return new Promise((ok, nok) => {
glob(pattern, {}, (err, matches) => {
Expand All @@ -47,62 +40,72 @@ async function getMatchedFiles() {
})
});
}
/**
*
* @param {String} filePath File to be uploaded
* @param {Array<String>} gfiles Files found on Google Drive
* @param {import("googleapis").drive_v3.Drive} drive Google Drive context
*/
async function processFile(filePath, gfiles, drive) {
filePath = path.normalize(filePath);
core.info(`Processing '${filePath}'`);

// Find if this file already exists on GDrive
// We may have grabbed a file without appProperties, so we should check that it exists
let gfile = gfiles.find(f => f.appProperties && f.appProperties.source == filePath);

// If it does, then we update its content
if (gfile) {
await update(drive, {
fileId: gfile.id,
file: filePath
}).then(_ => core.info(`${filePath} successfully updated`));
} else {
let pathParsed = path.parse(filePath);
// Folder to upload
let folderId;
// Also create subfolders
let keepStructure = core.getInput("keepStructure") == "true" || core.getInput("keepStructure") == "";
if (keepStructure) {
folderId = await folder(drive, {
create: true,
parent: core.getInput("uploadTo"),
path: pathParsed.dir
});
} else {
folderId = core.getInput("uploadTo") || "root";
}

core.info(`Uploading '${pathParsed.dir}/${pathParsed.name}' to '${folderId}'`)

await upload(drive, {
path: filePath,
name: pathParsed.base,
mimeType: core.getInput("mimeType"),
parents: [folderId]
}).then(_ => core.info(`${filePath} successfully uploaded`));
}
}
//#endregion

async function main() {
// Try to get the login info as Action Inputs
let credentials, token;
try {
const credentials = JSON.parse(core.getInput("credentials", { required: true }));
const token = JSON.parse(core.getInput("token", { required: true }));
return [credentials, token];
credentials = JSON.parse(core.getInput("credentials", { required: true }));
token = JSON.parse(core.getInput("token", { required: true }));
} catch (e) {
Promise.reject(e);
core.error(`Failed to gather credentials: ${e}`);
throw e;
}
core.info("Logging in");
let auth = await login(credentials, token);
let drive = createDriveApi(auth);
let [gfiles, matches] = await Promise.all([getGDriveFiles(drive), getMatchedFiles()]);
return await Promise.all(matches.map(filePath => processFile(filePath, gfiles, drive))).then(p => p.length); // Return the amount of files processed
}

main()
// Perform auth
.then(([credentials, token]) => login(credentials, token))
.then(auth => {
// Drive is the API, set as a global var
Drive = createDriveApi(auth);
})
// Find get GDrive info and find files
.then(_ => Promise.all([getGDriveFiles(Drive), getMatchedFiles()]))
// Perform the upload/update
.then(([gfiles, matches]) =>
Promise.all(matches.map(async (filePath) => {
filePath = path.normalize(filePath);
core.info(`Processing ${filePath}`);

// Find if this file already exists on GDrive
// We may have grabbed a file without appProperties, so we should check that it exists
let gfile = gfiles.find(f => f.appProperties && f.appProperties.source == filePath);

// If it does, then we update its content
if (gfile) {
await update(Drive, {
fileId: gfile.id,
file: filePath
}).then(_ => core.info(`${filePath} successfully updated`));
} else {
let pathParsed = path.parse(filePath);
let folderId = await folder(Drive, {
create: true,
parent: core.getInput("uploadTo"),
path: pathParsed.dir
}).catch(core.error);
core.info(`Uploading ${pathParsed.dir}>${pathParsed.name} to ${folderId}`)

await upload(Drive, {
path: filePath,
name: pathParsed.base,
mimeType: core.getInput("mimeType"),
parents: [folderId]
}).then(_ => core.info(`${filePath} successfully uploaded`));
}
})).then(p => p.length)// Return the amount of files processed
)
main()
.then(res => {
core.info(`${res} files uploaded/updated`);
})
Expand Down

0 comments on commit f06446a

Please sign in to comment.