Skip to content

Commit

Permalink
Merge pull request #7 from ajmas/issue-5-basic-cli
Browse files Browse the repository at this point in the history
Fixes #5 CLI support and deal with paging
  • Loading branch information
ajmas authored Jun 2, 2020
2 parents 3be893e + d0bc46b commit d707827
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 69 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,26 @@ Further reading:
- [googleapis](https://www.npmjs.com/package/googleapis) npm module
- [supported export types](https://developers.google.com/drive/api/v3/ref-export-formats)

## CLI

There is now a basic CLI, so you can use this package without needing to integrate it into
a JS application first. You can install it either globally (assuming a Unix type environment):

```bash
npm install -g sync-gdrive
export GOOGLE_CLIENT_EMAIL="xxxxxx"
export GOOGLE_PRIVATE_KEY="xxxxxx"
sync-gdrive "filefolderid" "dest_folder"
```

or if you already installed it as a dependency of your project:

```bash
export GOOGLE_CLIENT_EMAIL="xxxxxx"
export GOOGLE_PRIVATE_KEY="xxxxxx"
./node_modules/.bin/sync-gdrive "filefolderid" "dest_folder"
```

## Contributions & Feedback

Contributions and feedback is welcome. Please open
Expand Down
72 changes: 41 additions & 31 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
{
"name": "sync-gdrive",
"version": "0.9.2",
"version": "0.9.5",
"description": "Module to synchronise a file or directory in Google Drive with the local file system",
"main": "dist/index.js",
"bin": {
"sync-gdrive": "dist/cli.js"
},
"engines": {
"node": ">=10.0.0"
},
"scripts": {
"sync-gdrive": "ts-node src/cli.ts",
"build": "tsc",
"lint": "eslint -c .eslintrc.js --ext .ts src",
"test": "mocha --exit -r ts-node/register -r esm test/*.ts"
Expand Down
57 changes: 57 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#! /usr/bin/env node
/* eslint-disable no-console */
import fs from 'fs';
import syncGDrive, { IKeyConfig } from '../';

async function main () {
try {
let okay = true;
const clientEmail = process.env.GOOGLE_CLIENT_EMAIL;
if (!clientEmail) {
console.log('No client email specified. Be sure to set GOOGLE_CLIENT_EMAIL env variable.');
okay = false;
}

let privateKey = process.env.GOOGLE_PRIVATE_KEY;
if (!privateKey) {
console.log('No Google API private key specified. Be sure to set GOOGLE_PRIVATE_KEY env variable.');
okay = false;
}

if (!okay) {
process.exit(1);
}

// Unescape new lines
privateKey = privateKey.replace(/\\n/g, '\n');

if (process.argv.length < 4) {
console.log('usage: sync-gdrive <drive_file_folder_id> <dest_path>');
process.exit(1);
}

const fileFolderId = process.argv[2];
const destFolder = process.argv[3];

try {
fs.accessSync(destFolder, fs.constants.R_OK | fs.constants.W_OK);
} catch (error) {
console.log(`Destination folder '${destFolder}' does not exist or is not writable by current user`);
process.exit(1);
}

const keyConfig: IKeyConfig = {
clientEmail: clientEmail,
privateKey: privateKey
};

console.log(`Syncing Google Drive file/folder of id '${fileFolderId}' to '${destFolder}'`);
await syncGDrive(fileFolderId, destFolder, keyConfig);
} catch (error) {
console.log(error);
}
}

if (require.main === module) {
main();
}
79 changes: 46 additions & 33 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ async function isGDriveFileNewer(gDriveFile, filePath: string) {
async function downloadFile (drive, file, destFolder: string, options: IOptions = {}) {
const filePath = path.join(destFolder, file.name);
if (await isGDriveFileNewer(file, filePath)) {
options.logger.debug('downloading newer: ', filePath);
options.logger.debug('creating file: ', filePath);
if (options.verbose) {
options.logger.debug('downloading newer: ', filePath);
options.logger.debug('creating file: ', filePath);
}
const dest = fs.createWriteStream(filePath);

const response = await drive.files.get({
Expand Down Expand Up @@ -229,42 +231,53 @@ async function downloadContent (drive, file, path: string, options: IOptions) {


async function visitDirectory (drive, fileId: string, folderPath: string, options: IOptions, callback?: Function) {
const response = await drive.files.list({
includeRemoved: false,
spaces: 'drive',
fileId: fileId,
fields: 'nextPageToken, files(id, name, parents, mimeType, createdTime, modifiedTime)',
q: `'${fileId}' in parents`
});

const { files } = response.data;
let nextPageToken;
let allSyncStates = [];
let syncState;

for (let i = 0; i < files.length; i++) {
const file = files[i];

if (file.mimeType === 'application/vnd.google-apps.folder') {
const childFolderPath = path.join(folderPath, file.name);

if (options.verbose) {
options.logger.debug('DIR', file.id, childFolderPath, file.name)
}
do {
const response = await drive.files.list({
pageToken: nextPageToken,
includeRemoved: false,
spaces: 'drive',
fileId: fileId,
fields: 'nextPageToken, files(id, name, parents, mimeType, createdTime, modifiedTime)',
q: `'${fileId}' in parents`,
pageSize: 200
});

await fs.mkdirp(childFolderPath);
if (options.sleepTime) {
await sleep(options.sleepTime);
// Needed to get further results
nextPageToken = response.data.nextPageToken;

const files = response.data.files;
let syncState;

for (let i = 0; i < files.length; i++) {
const file = files[i];

if (file.mimeType === 'application/vnd.google-apps.folder') {
const childFolderPath = path.join(folderPath, file.name);

if (options.verbose) {
options.logger.debug('DIR', file.id, childFolderPath, file.name)
}

await fs.mkdirp(childFolderPath);
if (options.sleepTime) {
await sleep(options.sleepTime);
}
syncState = await visitDirectory(drive, file.id, childFolderPath, options);
allSyncStates = allSyncStates.concat(syncState);
} else {
if (options.verbose) {
options.logger.debug('DIR', file.id, folderPath, file.name)
}
syncState = await downloadContent(drive, file, folderPath, options);
allSyncStates.push(syncState);
}
syncState = await visitDirectory(drive, file.id, childFolderPath, options);
allSyncStates = allSyncStates.concat(syncState);
} else {
if (options.verbose) {
options.logger.debug('DIR', file.id, folderPath, file.name)
}
syncState = await downloadContent(drive, file, folderPath, options);
allSyncStates.push(syncState);
}
}
// continue until there is no next page
} while (nextPageToken);

return allSyncStates;
}
Expand Down Expand Up @@ -307,7 +320,7 @@ async function syncGDrive (fileFolderId, destFolder: string, keyConfig: IKeyConf

const drive = google.drive('v3');

return await fetchContents(drive, fileFolderId, destFolder, initIOptions(options));
return fetchContents(drive, fileFolderId, destFolder, initIOptions(options));
} catch (error) {
log(error);
}
Expand Down
7 changes: 4 additions & 3 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ describe('Endpoints', async () => {

filefolderId = process.env.GDRIVE_FILEFOLDER_ID;
if (!filefolderId) {
throw new Error('No Google Drive file or folder id specified. Be sure to set env GDRIVE_FILEFOLDER_ID.');
throw new Error('No Google Drive file or folder id specified. Be sure to set GDRIVE_FILEFOLDER_ID env variable.');
}

clientEmail = process.env.GOOGLE_CLIENT_EMAIL;
if (!clientEmail) {
throw new Error('No client email specified. Be sure to set GOOGLE_CLIENT_EMAIL.');
throw new Error('No client email specified. Be sure to set GOOGLE_CLIENT_EMAIL env variable.');
}

privateKey = process.env.GOOGLE_PRIVATE_KEY;
if (!privateKey) {
throw new Error('No Google API privaye key specified. Be sure to set GOOGLE_PRIVATE_KEY.');
throw new Error('No Google API private key specified. Be sure to set GOOGLE_PRIVATE_KEY env variable.');
}

privateKey = privateKey.replace(/\\n/g, '\n').trim();
Expand All @@ -70,6 +70,7 @@ describe('Endpoints', async () => {
const filefolderByPath = {};

expect(syncedFileFolders).to.be.not.null;
expect(syncedFileFolders).to.be.not.undefined;

syncedFileFolders.forEach(filefolder => {
filefolderByPath[filefolder.file] = filefolder;
Expand Down

0 comments on commit d707827

Please sign in to comment.