// https://developers.google.com/drive/v3/web/quickstart/nodejs // https://developers.google.com/apis-explorer/?hl=en_GB#p/drive/v3/ var google = require('googleapis'), googleAuth = require('google-auth-library'), config = require('../../../config_server'), Stream = require('stream'); var client = google.drive('v3'); function findMimeType(filename){ let ext = filename.split('.').slice(-1)[0]; let list = { xls: 'application/vnd.ms-excel', xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', xml: 'text/xml', ods: 'application/vnd.oasis.opendocument.spreadsheet', csv: 'text/csv', tmpl: 'text/plain', org: 'text/plain', md: 'text/plain', pdf: 'application/pdf', php: 'application/x-httpd-php', jpg: 'image/jpeg', png: 'image/png', gif: 'image/gif', bmp: 'image/bmp', txt: 'text/plain', text: 'text/plain', conf: 'text/plain', log: 'text/plain', doc: 'application/msword', js: 'text/js', swf: 'application/x-shockwave-flash', mp3: 'audio/mpeg', zip: 'application/zip', rar: 'application/rar', tar: 'application/tar', arj: 'application/arj', cab: 'application/cab', html: 'text/html', htm: 'text/html' }; return list[ext] || 'application/octet-stream'; } function decode(path){ let tmp = path.trim().split('/'); let filename = tmp.pop() || null; tmp.shift(); return { name: filename, parents: tmp, full: filename === null ? tmp : [].concat(tmp, [filename]) }; } function findId(auth, _folders, ids = []){ const folders = JSON.parse(JSON.stringify(_folders)); const name = folders.pop(); return search(auth, name, folders) .then((files) => { let solutions = findSolutions(files, ids); let aggregatedSolution = [].concat(solutions, ids); if(solutions.length === 0){ return Promise.reject({message: 'this path doesn\'t exist', code: 'UNKNOWN_PATH'}); }else if(solutions.length === 1){ return Promise.resolve(findFolderId(solutions[0], ids)); }else{ return findId(auth, folders, aggregatedSolution); } }); function search(_auth, _name, _folders){ if(_name === undefined){ return findRoot(_auth); }else{ return findByName(_auth, _name, _folders.length + 1); } } function findRoot(auth){ return new Promise((_done,_err) => { client.files.list({ auth: auth, q: "'root' in parents", pageSize: 1, fields: "files(parents, id, name)" }, function(error, response){ if(error){_err(error);} else{ if(response.files.length > 0){ _done(response.files.map((file) => { return { level: 0, id: file.parents[0], name: 'root' }; })); }else{ _done([{ level: 0, id: 'root', name: 'root' }]); } } }); }); } function findByName(auth, name, _level){ return new Promise((_done,_err) => { client.files.list({ auth: auth, q: "name = '"+name+"' AND trashed = false", pageSize: 500, fields: "files(parents, id, name)" }, function(error, response){ if(error){_err(error);} else{ _done(response.files.map((file) => { file.level = _level; return file; })); } }); }); } function findFolderId(head, cache, result = 'root'){ for(let i=0, l=cache.length; i { if(cache.length === 0){ return true;} for(let i=0, j=cache.length; i { return new Promise((done, err) => { if(params && params.access_token){ auth.credentials = params; done(auth); }else if(params && params.code){ auth.getToken(params.code, function(error, token) { if(error){ err(error); } else{ auth.credentials = token; done(auth); } }); }else{ err({message: 'can\'t connect without auth code or token', code: 'INVALID_CONNECTION'}); } }); return Promise.resolve(auth); }); } module.exports = { auth: function(params){ return authorize() .then((auth) => { return Promise.resolve(auth.generateAuthUrl({ access_type: 'online', scope: [ "https://www.googleapis.com/auth/drive" ] })); }); }, test: function(params){ return connect(params) .then((auth) => { return new Promise((done, err) => { client.files.list({ auth: auth, q: "'root' in parents AND mimeType = 'application/vnd.google-apps.folder'", pageSize: 5, fields: "files(parents)" }, function(error, response) { if(error){ err(error); } else{ done(auth.credentials); } }); }); }); }, cat: function(path, params){ path = decode(path); return connect(params) .then((auth) => { return findId(auth, path.full) .then((id) => fileInfo(auth, id)) .then((file) => { if(/application\/vnd.google-apps/.test(file.mimeType)){ let type = 'text/plain'; if(file.mimeType === 'application/vnd.google-apps.spreadsheet'){ type = 'text/csv'; } return exporter(auth, file.id, type); }else{ return download(auth, file.id); } }); }) .then(function(stream){ stream.on('response', function(response) { delete response.headers; }); return Promise.resolve(stream); }); function fileInfo(auth, id){ return new Promise((done, err) => { client.files.get({ auth: auth, fileId: id },function(error, response){ if(error){ err(error); } else{ done(response); } }); }); } function download(auth, id){ var content = ''; return Promise.resolve(client.files.get({ auth: auth, fileId: id, alt: 'media' })); } function exporter(auth, id, type){ var content = ''; return new Promise((done, err) => { done(client.files.export({ auth: auth, fileId: id, mimeType: type })); }); } }, ls: function(_path, params){ path = decode(_path); return connect(params) .then((auth) => { return findId(auth, path.parents) .then((id) => findDrive(auth, id)) .then(parse); }); function findDrive(auth, id){ return new Promise((done, err) => { client.files.list({ spaces: path.space, auth: auth, q: "'"+id+"' in parents AND trashed = false", pageSize: 500, fields: "files(id,mimeType,modifiedTime,name,size)" }, function(error, response) { if(error){ err(error); } else{ done(response.files); } }); }); } function parse(files){ return Promise.resolve(files.map((file) => { return { type: file.mimeType === 'application/vnd.google-apps.folder'? 'directory' : 'file', name: file.name, size: file.hasOwnProperty('size')? Number(file.size) : 0, time: new Date(file.modifiedTime).getTime() }; })); } }, write: function(path, content, params){ // TODO path = decode(path); return connect(params) .then((auth) => { return fileAlreadyExist(auth, path) .then((obj) => { if(obj.alreadyExist === true){ return updateFile(auth, content, path.name, obj.id); } if(obj.alreadyExist === false){ return createFile(auth, content, path.name, obj.id); } }); }); function fileAlreadyExist(auth, path){ return findId(auth, path.full) .then((id) => Promise.resolve({alreadyExist: true, id: id})) .catch((err) => { return findId(auth, path.parents) .then((id) => Promise.resolve({alreadyExist: false, id: id})) }); } function createFile(_auth, _stream, _filename, _folderId){ return new Promise((done, err) => { client.files.create({ auth: _auth, fields: 'id', media: { mimeType: 'text/plain', body: _stream }, resource: { name: _filename, parents: [_folderId] } }, function(error){ if(error) {err(error); } else{ done('ok'); } }); }); } function updateFile(_auth, _stream, _filename, _folderId){ return new Promise((done, err) => { client.files.update({ auth: _auth, fileId: _folderId, fields: 'id', media: { mimeType: findMimeType(_filename), body: _stream } }, function(error){ if(error) {err(error); } else{ done('ok'); } }) }); } }, rm: function(path, params){ path = decode(path); return connect(params) .then((auth) => { return findId(auth, path.full) .then((id) => { return new Promise((done, err) => { client.files.delete({ fileId: id, auth: auth }, function(error){ if(error){ err(error); } else{ done('ok'); } }) }); }); }); }, mv: function(from, to, params){ from = decode(from); to = decode(to); return connect(params) .then((auth) => { return Promise.all([findId(auth, from.full), findId(auth, from.parents), findId(auth, to.parents)]) .then((res) => process(auth, res)); }); function wait(res){ return new Promise((done) => { setTimeout(function(){ done(res); }, 500); }); } function process(auth, res){ let fileId = res[0], srcId = res[1], destId = res[2]; let fields = 'id'; let params = {fileId, fileId, auth: auth}; if(destId !== srcId){ fields += ', parents'; params.addParents = destId; params.removeParents = srcId; } if(to.name !== null && from.name !== null && from.name !== to.name ){ fields += 'name'; params.resource = { name: to.name }; } return new Promise((done, err) => { client.files.update(params, function(error, response){ if(error){ err(error); } else{ done('ok'); } }); }); } }, mkdir: function(path, params){ path = decode(path); return connect(params) .then((auth) => { return findId(auth, path.parents.slice(0, -1)) .then((folder) => { return new Promise((done, err) => { client.files.create({ fields: 'id', auth: auth, resource: { name: path.parents.slice(-1)[0], parents: [folder], mimeType: 'application/vnd.google-apps.folder' } }, function(error){ if(error) {err(error); } else{ done(auth); } }); }); }); }) .then((auth) => verifyFolderCreation(auth, path.full)); function verifyFolderCreation(_auth, _path, n = 10){ return sleep(Math.abs(10 - n) * 100) .then(() => findId(_auth, _path)) .catch((err) => { if(n > 0 && err && err.code === 'UNKNOWN_PATH'){ return verifyFolderCreation(_auth, _path, n - 1); } return Promise.reject(err); }); function sleep(t=1000, arg){ return new Promise((done) => { setTimeout(function(){ done(arg); }, t); }); } } function copy(obj){ return JSON.parse(JSON.stringify(obj)); } }, touch: function(path, params){ path = decode(path); var readable = new Stream.Readable(); readable.push(''); readable.push(null); return connect(params) .then((auth) => { return findId(auth, path.parents) .then((folder) => { return new Promise((done, err) => { client.files.create({ auth: auth, fields: 'id', media: { mimeType: 'text/plain', body: readable }, resource: { name: path.name, parents: [folder] } }, function(error){ if(error) {err(error); } else{ done('ok'); } }); }); }); }); } };