// 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'), Stream = require('stream'), Readable = require('stream').Readable; 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.split('/'); let filename = tmp.pop() || null; let space = tmp.splice(0,2)[1]; space = space? space.toLowerCase() : null; return { space: space || null, name: filename, parents: tmp, full: filename === null ? tmp : [].concat(tmp, [filename]) } } function findId(auth, folders, ids = []){ let name = folders.pop(); return search(auth, name, folders) .then((files) => { let solutions = findSolutions(files, ids); let newCache = [].concat(solutions, ids); // console.log("=====") // console.log("SEARCH ON", name) // console.log("FILES", files.map((file) => file.id)) // console.log("CACHE", ids) // console.log("> SOLUTIONS => ",solutions) if(solutions.length === 0){ return Promise.reject({message: 'this path doesn\'t exist', code: 'UNKNOWN_PATH'}) }else if(solutions.length === 1){ return Promise.resolve(findLast(solutions[0].id, ids)); }else{ return findId(auth, folders, newCache); } }); 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+"'", 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 findLast(id, cache){ if(id === 'root'){ return id; } for(let i=0, l=cache.length; i { if(cache.length === 0){ return true;} else{ 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", "https://www.googleapis.com/auth/drive.photos.readonly" ] })); }) }, 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{ console.log("DOWNLOAD",file) return download(auth, file.id); } }); }); 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) => { if(path.space === null){ return Promise.resolve([ {type: 'metadata', name: './', can_create_file: false, can_create_directory: false}, {type: 'directory', name: 'Drive', can_read: true, can_write: false, can_delete: false, can_move: false}, {type: 'directory', name: 'Photos', can_read: true, can_write: false, can_delete: false, can_move: false} ]); }else{ if(path.space === 'photos'){ return findPhotos(auth) .then(parse) .then((files) => { files.push({type: 'metadata', name: '.', can_create_file: false, can_create_directory: false}) return Promise.resolve(files) }) }else{ return findId(auth, JSON.parse(JSON.stringify(path.parents))) .then((id) => { return findDrive(auth, id) .then(parse) }); } } }); function findPhotos(auth){ return new Promise((done, err) => { client.files.list({ spaces: path.space, auth: auth, q: "trashed = false", pageSize: 500, fields: "files(id,mimeType,modifiedTime,name,size)" }, function(error, response) { if(error){ err(error); } else{ done(response.files); } }); }); } 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){ path = decode(path); var readable = new Stream.Readable(); readable.push(content); readable.push(null); return connect(params) .then((auth) => { return findId(auth, path.full) .then((id) => { return new Promise((done, err) => { client.files.update({ auth: auth, fileId: id, fields: 'id', media: { mimeType: findMimeType(path.name), body: readable } }, 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)) //.then(wait) }); 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); let name = path.parents.pop(); return connect(params) .then((auth) => { return findId(auth, path.parents) .then((folder) => { return new Promise((done, err) => { done('ok'); client.files.create({ fields: 'id', auth: auth, resource: { name: name, parents: [folder], mimeType: 'application/vnd.google-apps.folder' } }, function(error){ if(error) {err(error) } else{ done('ok'); } }) }) }); }) }, 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'); } }) }) }) }); } }