filestash/server/model/backend/gdrive.js
2017-06-13 17:44:33 +10:00

486 lines
17 KiB
JavaScript

// 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<l; i++){
if(id === cache[i].parents[0]){
return findLast(cache[i].id, cache);
}
}
return id;
}
function findSolutions(newFiles, cache){
return newFiles.filter((newFile) => {
if(cache.length === 0){ return true;}
else{
for(let i=0, j=cache.length; i<j; i++){
if(newFile.id === cache[i].parents[0] && (newFile.level + 1) === cache[i].level){
return true;
}
}
return false;
}
});
}
}
function authorize(params){
var auth = new googleAuth(),
client_id = config.gdrive.clientID,
client_secret = config.gdrive.clientSecret,
redirect_uri = config.gdrive.redirectURI;
var oauth2Client = new auth.OAuth2(client_id, client_secret, redirect_uri);
return Promise.resolve(oauth2Client);
}
function connect(params){
return authorize(params)
.then((auth) => {
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'); }
})
})
})
});
}
}