iobroker.backitup
Version:
ioBroker.backitup allows you to backup and restore your ioBroker installation and other systems, such as databases, Zigbee, scripts and many more.
336 lines (311 loc) • 12.3 kB
JavaScript
// const google = require('googleapis');
const google = require('@googleapis/drive');
const { OAuth2Client } = require('google-auth-library');
const fs = require('node:fs');
const axios = require('axios');
const OAUTH_URL = 'https://googleauth.iobroker.in/googleDriveOAuth';
const NOT_FOUND = 'Not found';
class GoogleDrive {
constructor(accessJson, newToken) {
this.oAuth2Client = new OAuth2Client();
this.newToken = newToken;
if (accessJson && typeof accessJson !== 'object') {
if (accessJson[0] === '{') {
accessJson = JSON.parse(accessJson);
} else {
accessJson = JSON.parse(Buffer.from(accessJson, 'base64').toString('utf8'));
}
}
accessJson && this.oAuth2Client.setCredentials(accessJson);
this.drive = null;
}
_authorize() {
if (this.oAuth2Client.credentials.access_token && Date.now() > this.oAuth2Client.credentials.expiry_date) {
let url = OAUTH_URL;
if (!this.newToken) {
url = OAUTH_URL.replace('googleDriveOAuth', '');
}
return axios.post(url, this.oAuth2Client.credentials, { headers: { 'content-type': 'application/json' } })
.then(response => {
if (response.data) {
// BF: TODO - this token should be saved in the state variable and used by the next token request
this.oAuth2Client.setCredentials(response.data);
return this.oAuth2Client;
}
});
} else {
return Promise.resolve(this.oAuth2Client);
}
}
getAuthorizeUrl() {
return axios(OAUTH_URL)
.then(response => {
if (response.data) {
return response.data.authURL;
}
throw new Error('Cannot get authorize URL');
});
}
_getDrive() {
this.drive = this.drive || new google.drive_v3.Drive({
version: 'v3',
auth: this.oAuth2Client,
});
// update access_token
return this._authorize()
.then(client => this.drive)
.catch(() => console.log('Error Google Drive _getDrive'));
}
_createFolder(parts, folderId, callback) {
if (typeof parts !== 'object') {
parts = parts.replace(/\\/g, '/').split('/').filter(p => !!p);
}
if (!parts.length) {
callback && callback(NOT_FOUND)
} else {
const dir = parts.shift();
this._getFileOrFolderId([dir], folderId, (err, _folderId) => {
if (!_folderId) {
const fileMetadata = {
// eslint-disable-next-line quote-props
'name': dir,
// eslint-disable-next-line quote-props
'mimeType': 'application/vnd.google-apps.folder'
};
if (folderId) {
fileMetadata.parents = [folderId];
}
this._getDrive().then(drive =>
drive.files.create({
resource: fileMetadata,
fields: 'id'
}, (err, file) => {
if (err) {
callback && callback(err);
} else {
const __folderId = file.data.id;
if (!parts.length || !__folderId) {
callback && callback(null, __folderId);
} else {
setTimeout(() => this._createFolder(parts, __folderId, callback), 150);
}
}
})).catch(err => {
console.log(`Error Google Drive _getDrive: ${err}`);
callback && callback(err);
});
} else {
if (!parts.length || !_folderId) {
callback && callback(null, _folderId);
} else {
setTimeout(() => this._createFolder(parts, _folderId, callback), 150);
}
}
});
}
}
createFolder(path, log) {
return new Promise((resolve, reject) => {
this.getFileOrFolderId(path)
.then(id => {
if (id) {
resolve(id);
} else {
this._createFolder(path, null, (err, folderId) => {
if (err) {
log.error(`Error Google Drive create folder: ${err}`);
reject(err);
} else {
resolve(folderId);
}
});
}
}).catch(err => {
log.error(`Error Google Drive create folder: ${err}`);
reject(err);
});
});
}
writeFile(folderId, fileName, dataStream, log) {
const fileMetadata = {
name: fileName,
parents: [folderId],
};
const media = {
mimeType: 'application/gzip',
body: dataStream,
};
return new Promise((resolve, reject) => {
this._getDrive().then(drive =>
drive.files.create({
resource: fileMetadata,
media: media,
fields: 'id',
}, (err, file) => {
if (err) {
log.error(`Error Google Drive write file: ${err}`);
// Handle error
reject(err);
} else {
resolve(file.data.id);
}
})).catch(err => {
log.error(`Error Google Drive write file: ${err}`);
reject(err);
});
});
}
/*
deleteFile(folderOrFileId, fileName) {
return new Promise((resolve, reject) => {
if (folderOrFileId && !fileName) {
this._getDrive()
.then(drive => drive.files.delete({fileId: folderOrFileId}, err => {
if (err) {
// Handle error
reject(err);
} else {
resolve();
}
}));
} else {
this.getFileOrFolderId(fileName, folderOrFileId)
.then(fileId => {
this._getDrive()
.then(drive => drive.files.delete({fileId}, err => {
if (err) {
// Handle error
reject(err);
} else {
resolve();
}
}));
});
}
});
}
*/
async deleteFile(folderOrFileId, fileName) {
try {
const drive = await this._getDrive()
if (folderOrFileId && !fileName) {
await drive.files.delete({ fileId: folderOrFileId });
} else {
const fileId = await this.getFileOrFolderId(fileName, folderOrFileId);
await drive.files.delete({ fileId });
}
} catch (e) {
console.log(`error delete files on GoogleDrive: ${e}`);
}
}
_getFileOrFolderId(parts, folderId, callback) {
if (typeof parts === 'string') {
parts = parts.replace(/\\/g, '/').split('/').filter(part => part);
}
if (!parts.length) {
return callback && callback(NOT_FOUND);
}
const dir = parts.shift();
const q = folderId ? `"${folderId}" in parents and name="${dir}" and trashed=false` : `name="${dir}" and trashed=false`;
try {
this._getDrive()
.then(drive => drive.files.list({
q,
fields: 'files(id)',
spaces: 'drive',
pageToken: null
}, (err, res) => {
if (err) {
// Handle error
callback(err);
} else {
folderId = res.data.files[0] ? res.data.files[0].id : null;
if (!parts.length || !folderId) {
callback && callback(!folderId ? NOT_FOUND : null, folderId);
} else {
setTimeout(() => this._getFileOrFolderId(parts, folderId, callback), 150);
}
}
})).catch(() => console.log('Error Google Drive getFileOrFolderId'));
} catch (e) {
console.log(`error get File or FolderId on GoogleDrive: ${e}`);
}
}
getFileOrFolderId(path, folderId) {
return new Promise((resolve, reject) => {
this._getFileOrFolderId(path, folderId, (err, folderId) => {
if (err && err !== NOT_FOUND) {
reject(err);
} else {
resolve(folderId);
}
})
});
}
_listFilesInFolder(folderId, cb, pageToken, _list) {
pageToken = pageToken || null;
_list = _list || [];
this._getDrive()
.then(drive => drive.files.list({
q: `"${folderId}" in parents and trashed=false`,
fields: 'nextPageToken, files(name, id, modifiedTime, size)',
spaces: 'drive',
pageToken: pageToken
}, (err, res) => {
if (err) {
// Handle error
cb(err);
} else {
res.data.files.forEach(file => _list.push(file));
if (res.nextPageToken) {
setTimeout(() => this._listFilesInFolder(folderId, cb, res.nextPageToken, _list), 150);
} else {
cb(null, _list);
}
}
})).catch(() => console.log('Error Google Drive _listFilesInFolder'));
}
listFilesInFolder(folderId) {
return new Promise((resolve, reject) =>
this._listFilesInFolder(folderId, (err, list) => err ? reject(err) : resolve(list)));
}
readFile(fileId, localFileName) {
return new Promise((resolve, reject) => {
let dest;
try {
dest = fs.createWriteStream(localFileName);
dest.on('error', err => reject(err));
} catch (e) {
return reject(e);
}
this._getDrive()
.then(drive =>
drive.files.get({
fileId,
alt: 'media'
}, { responseType: 'stream' }, (err, res) => {
if (err) {
console.error(err);
return reject(err);
}
res.data
.on('end', () => resolve())
.on('error', err => reject(err))
.pipe(dest);
}))
.catch(e => {
return reject(e);
});
});
}
}
if (module.parent) {
module.exports = GoogleDrive;
} else {
const token = require('./test');
const gDrive = new GoogleDrive(token);
gDrive.createFolder('/iobroker-backup/broker/one')
.then(id => console.log(`ID=${id}`))
.catch(() => console.log('Error Google Drive createFolder'));
}
;