mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
500 lines
17 KiB
JavaScript
500 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_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<l; i++){
|
|
if(head.id === cache[i].parents[0] && head.level + 1 === cache[i].level){
|
|
return findFolderId(cache[i], cache);
|
|
}
|
|
}
|
|
return head.id;
|
|
}
|
|
|
|
function findSolutions(newFiles, cache){
|
|
return newFiles.filter((newFile) => {
|
|
if(cache.length === 0){ return true;}
|
|
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" ]
|
|
}));
|
|
});
|
|
},
|
|
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'); }
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
};
|