✏️ Clean code with prettier

This commit is contained in:
Sebastien Clement 2020-11-09 12:42:26 +01:00
parent 1237f9c6ff
commit 8632a3d079
14 changed files with 1528 additions and 1035 deletions

View File

@ -2,19 +2,14 @@ module.exports = {
"env": { "env": {
"browser": true, "browser": true,
"commonjs": true, "commonjs": true,
"es6": true, "es2021": true,
"node": true "node": true
}, },
"extends": "eslint:recommended", "extends": ["eslint:recommended","prettier"],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": { "parserOptions": {
"ecmaVersion": 2018 "ecmaVersion": 12
}, },
"rules": { "rules": {
"no-unused-vars": "warn" "no-unused-vars": "warn"
} }
}; };

View File

@ -1,30 +1,31 @@
const createError = require('http-errors'); const createError = require("http-errors");
const express = require('express'); const express = require("express");
const path = require('path'); const path = require("path");
const cookieParser = require('cookie-parser'); const cookieParser = require("cookie-parser");
const logger = require('morgan'); const logger = require("morgan");
const indexRouter = require("./routes/index");
const indexRouter = require('./routes/index'); const apiRouter = require("./routes/api");
const apiRouter = require('./routes/api');
const app = express(); const app = express();
// view engine setup // view engine setup
app.set('views', path.join(__dirname, 'views')); app.set("views", path.join(__dirname, "views"));
app.set('view engine', 'ejs'); app.set("view engine", "ejs");
app.use(logger('dev', { app.use(
skip: function (req, res) { logger("dev", {
return res.statusCode = 304 skip: function (req, res) {
} return (res.statusCode = 304);
})); },
})
);
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({extended: false})); app.use(express.urlencoded({ extended: false }));
app.use(cookieParser()); app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, "public")));
app.use('/', indexRouter); app.use("/", indexRouter);
app.use('/api', apiRouter); app.use("/api", apiRouter);
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function (req, res, next) { app.use(function (req, res, next) {
@ -35,43 +36,43 @@ app.use(function (req, res, next) {
app.use(function (err, req, res, next) { app.use(function (err, req, res, next) {
// set locals, only providing error in development // set locals, only providing error in development
res.locals.message = err.message; res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {}; res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page // render the error page
res.status(err.status || 500); res.status(err.status || 500);
res.render('error'); res.render("error");
}); });
const fs = require('fs'); const fs = require("fs");
const newlog = require('./config/winston'); const newlog = require("./config/winston");
if (!fs.existsSync('/data')) if (!fs.existsSync("/data")) fs.mkdirSync("/data");
fs.mkdirSync('/data'); const statusTools = require("./tools/status");
const statusTools = require('./tools/status');
statusTools.init(); statusTools.init();
newlog.info("Satus : \x1b[32mGo !\x1b[0m"); newlog.info("Satus : \x1b[32mGo !\x1b[0m");
const hassioApiTools = require('./tools/hassioApiTools'); const hassioApiTools = require("./tools/hassioApiTools");
hassioApiTools.getSnapshots().then( hassioApiTools.getSnapshots().then(
() => { () => {
newlog.info("Hassio API : \x1b[32mGo !\x1b[0m") newlog.info("Hassio API : \x1b[32mGo !\x1b[0m");
},
}, (err) => { (err) => {
newlog.error("Hassio API : \x1b[31;1mFAIL !\x1b[0m") newlog.error("Hassio API : \x1b[31;1mFAIL !\x1b[0m");
newlog.error("... " + err); newlog.error("... " + err);
}); }
);
const WebdavTools = require('./tools/webdavTools'); const WebdavTools = require("./tools/webdavTools");
const webdav = new WebdavTools().getInstance(); const webdav = new WebdavTools().getInstance();
webdav.confIsValid().then( webdav.confIsValid().then(
() => { () => {
newlog.info("Nextcloud connection : \x1b[32mGo !\x1b[0m") newlog.info("Nextcloud connection : \x1b[32mGo !\x1b[0m");
}, (err) => { },
newlog.error("Nextcloud connection : \x1b[31;1mFAIL !\x1b[0m") (err) => {
newlog.error("Nextcloud connection : \x1b[31;1mFAIL !\x1b[0m");
newlog.error("... " + err); newlog.error("... " + err);
} }
) );
const cronTools = require('./tools/cronTools'); const cronTools = require("./tools/cronTools");
cronTools.startCron(); cronTools.startCron();
module.exports = app; module.exports = app;

View File

@ -1,18 +1,17 @@
const appRoot = require('app-root-path'); const appRoot = require("app-root-path");
const winston = require('winston'); const winston = require("winston");
const logger = winston.createLogger({ const logger = winston.createLogger({
level: 'debug', level: "debug",
format: winston.format.combine( format: winston.format.combine(
winston.format.timestamp({ winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss' format: "YYYY-MM-DD HH:mm:ss",
}), }),
// winston.format.errors({ stack: true }), // winston.format.errors({ stack: true }),
// winston.format.splat(), // winston.format.splat(),
winston.format.colorize(), winston.format.colorize(),
winston.format.printf(({level, message, timestamp}) => { winston.format.printf(({ level, message, timestamp }) => {
return `[${timestamp}] [${level}]: ${message}`; return `[${timestamp}] [${level}]: ${message}`;
}) })
), ),
@ -21,10 +20,9 @@ const logger = winston.createLogger({
// - Write to all logs with level `info` and below to `quick-start-combined.log`. // - Write to all logs with level `info` and below to `quick-start-combined.log`.
// - Write all logs error (and below) to `quick-start-error.log`. // - Write all logs error (and below) to `quick-start-error.log`.
// //
new winston.transports.Console({handleExceptions: true}), new winston.transports.Console({ handleExceptions: true }),
// new winston.transports.File({filename: '/data/NCB.log', handleExceptions: true}) // new winston.transports.File({filename: '/data/NCB.log', handleExceptions: true})
] ],
}); });
module.exports = logger; module.exports = logger;

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,10 @@
"winston": "^3.2.1" "winston": "^3.2.1"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^6.7.2", "eslint": "^7.13.0",
"eslint-config-google": "^0.14.0" "eslint-config-eslint": "^6.0.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0"
} }
} }

View File

@ -1,122 +1,106 @@
const express = require('express'); const express = require("express");
const router = express.Router(); const router = express.Router();
const moment = require('moment'); const moment = require("moment");
const statusTools = require('../tools/status'); const statusTools = require("../tools/status");
const WebdavTools = require('../tools/webdavTools') const WebdavTools = require("../tools/webdavTools");
const webdav = new WebdavTools().getInstance(); const webdav = new WebdavTools().getInstance();
const settingsTools = require('../tools/settingsTools'); const settingsTools = require("../tools/settingsTools");
const pathTools = require('../tools/pathTools'); const pathTools = require("../tools/pathTools");
const hassioApiTools = require('../tools/hassioApiTools'); const hassioApiTools = require("../tools/hassioApiTools");
const humanFileSize = require('../tools/toolbox').humanFileSize; const humanFileSize = require("../tools/toolbox").humanFileSize;
const cronTools = require('../tools/cronTools'); const cronTools = require("../tools/cronTools");
const logger = require('../config/winston'); const logger = require("../config/winston");
router.get("/status", (req, res, next) => {
router.get('/status', (req, res, next) => {
cronTools.updatetNextDate(); cronTools.updatetNextDate();
let status = statusTools.getStatus(); let status = statusTools.getStatus();
res.json(status); res.json(status);
}); });
router.get("/formated-local-snap", function (req, res, next) {
router.get('/formated-local-snap', function(req, res, next) {
hassioApiTools.getSnapshots().then( hassioApiTools.getSnapshots().then(
(snaps) => { (snaps) => {
snaps.sort((a, b) => { snaps.sort((a, b) => {
if (moment(a.date).isBefore(moment(b.date))){ if (moment(a.date).isBefore(moment(b.date))) {
return 1; return 1;
} } else {
else
{
return -1; return -1;
} }
});
}) res.render("localSnaps", { snaps: snaps, moment: moment });
res.render('localSnaps', { snaps: snaps, moment: moment });
}, },
(err) => { (err) => {
logger.error(err); logger.error(err);
res.status(500); res.status(500);
res.send(''); res.send("");
}
);
});
router.get("/formated-backup-manual", function (req, res, next) {
webdav
.getFolderContent(webdav.getConf().back_dir + pathTools.manual)
.then((contents) => {
contents.sort((a, b) => {
if (moment(a.lastmod).isBefore(moment(b.lastmod))) return 1;
else return -1;
});
res.render("backupSnaps", { backups: contents, moment: moment, humanFileSize: humanFileSize });
}) })
.catch(() => {
});
router.get('/formated-backup-manual', function(req, res, next) {
webdav.getFolderContent( webdav.getConf().back_dir + pathTools.manual)
.then((contents) => {
contents.sort((a, b) => {
if (moment(a.lastmod).isBefore(moment(b.lastmod)))
return 1;
else
return -1;
})
res.render('backupSnaps',{backups: contents, moment: moment, humanFileSize: humanFileSize});
}).catch(()=>{
res.send();
});
});
router.get('/formated-backup-auto', function(req, res, next) {
let url = webdav.getConf().back_dir + pathTools.auto
webdav.getFolderContent( url )
.then((contents) => {
contents.sort((a, b) => {
if (moment(a.lastmod).isBefore(moment(b.lastmod)))
return 1;
else
return -1;
})
res.render('backupSnaps',{backups: contents, moment: moment, humanFileSize: humanFileSize});
}).catch(()=>{
res.send(); res.send();
}); });
}); });
router.get("/formated-backup-auto", function (req, res, next) {
let url = webdav.getConf().back_dir + pathTools.auto;
webdav
.getFolderContent(url)
.then((contents) => {
contents.sort((a, b) => {
if (moment(a.lastmod).isBefore(moment(b.lastmod))) return 1;
else return -1;
});
res.render("backupSnaps", { backups: contents, moment: moment, humanFileSize: humanFileSize });
})
.catch(() => {
res.send();
});
});
router.post("/nextcloud-settings", function (req, res, next) {
router.post('/nextcloud-settings', function(req, res, next) {
let settings = req.body; let settings = req.body;
if (settings.ssl != null && settings.host != null && settings.host != "" && settings.username != null && settings.password != null) { if (settings.ssl != null && settings.host != null && settings.host != "" && settings.username != null && settings.password != null) {
webdav.setConf(settings); webdav.setConf(settings);
webdav.confIsValid().then(() => { webdav
res.status(201); .confIsValid()
res.send(); .then(() => {
}).catch((err) => { res.status(201);
res.status(406); res.send();
res.json({ message: err }); })
}); .catch((err) => {
res.status(406);
} res.json({ message: err });
else { });
} else {
res.status(400); res.status(400);
res.send(); res.send();
} }
}); });
router.get('/nextcloud-settings', function(req, res, next) { router.get("/nextcloud-settings", function (req, res, next) {
let conf = webdav.getConf(); let conf = webdav.getConf();
if (conf == null) { if (conf == null) {
res.status(404); res.status(404);
res.send(); res.send();
} } else {
else {
res.json(conf); res.json(conf);
} }
}); });
router.post("/manual-backup", function (req, res, next) {
router.post('/manual-backup', function(req, res, next) {
let id = req.query.id; let id = req.query.id;
let name = req.query.name; let name = req.query.name;
let status = statusTools.getStatus(); let status = statusTools.getStatus();
@ -126,84 +110,81 @@ router.post('/manual-backup', function(req, res, next) {
return; return;
} }
hassioApiTools.downloadSnapshot(id) hassioApiTools
.downloadSnapshot(id)
.then(() => { .then(() => {
webdav.uploadFile(id, webdav.getConf().back_dir + pathTools.manual + name + '.tar'); webdav.uploadFile(id, webdav.getConf().back_dir + pathTools.manual + name + ".tar");
res.status(201); res.status(201);
res.send(); res.send();
}) })
.catch(() => { .catch(() => {
res.status(500) res.status(500);
res.send(); res.send();
}) });
}); });
router.post('/new-backup', function(req, res, next) { router.post("/new-backup", function (req, res, next) {
let status = statusTools.getStatus(); let status = statusTools.getStatus();
if (status.status === "creating" && status.status === "upload" && status.status === "download") { if (status.status === "creating" && status.status === "upload" && status.status === "download") {
res.status(503); res.status(503);
res.send(); res.send();
return; return;
} }
let name = 'Manual-' + moment().format('YYYY-MM-DD_HH-mm'); let name = "Manual-" + moment().format("YYYY-MM-DD_HH-mm");
hassioApiTools.createNewBackup(name).then((id) => { hassioApiTools
hassioApiTools.downloadSnapshot(id) .createNewBackup(name)
.then(() => { .then((id) => {
webdav.uploadFile(id, webdav.getConf().back_dir + pathTools.manual + name + '.tar'); hassioApiTools
}).catch(() => { .downloadSnapshot(id)
.then(() => {
}) webdav.uploadFile(id, webdav.getConf().back_dir + pathTools.manual + name + ".tar");
}).catch(() => { })
.catch(() => {});
}) })
.catch(() => {});
res.status(201); res.status(201);
res.send(); res.send();
}); });
router.get("/backup-settings", function (req, res, next) {
router.get('/backup-settings', function(req, res, next) {
res.send(settingsTools.getSettings()); res.send(settingsTools.getSettings());
}); });
router.post('/backup-settings', function(req, res, next) { router.post("/backup-settings", function (req, res, next) {
if (cronTools.checkConfig(req.body)) { if (cronTools.checkConfig(req.body)) {
settingsTools.setSettings(req.body); settingsTools.setSettings(req.body);
cronTools.startCron(); cronTools.startCron();
res.send(); res.send();
} } else {
else {
res.status(400); res.status(400);
res.send(); res.send();
} }
}); });
router.post('/clean-now', function(req, res, next){ router.post("/clean-now", function (req, res, next) {
webdav.clean().then(()=>{ webdav
hassioApiTools.clean().catch(); .clean()
}).catch(()=>{ .then(() => {
hassioApiTools.clean().catch(); hassioApiTools.clean().catch();
}); })
.catch(() => {
hassioApiTools.clean().catch();
});
res.status(201); res.status(201);
res.send() res.send();
}); });
router.post("/restore", function (req, res, next) {
router.post('/restore', function(req, res, next){ if (req.body["path"] != null) {
if(req.body['path'] != null){ webdav.downloadFile(req.body["path"]).then((path) => {
webdav.downloadFile(req.body['path'] ).then((path)=>{
hassioApiTools.uploadSnapshot(path); hassioApiTools.uploadSnapshot(path);
}); });
res.status(200); res.status(200);
res.send() res.send();
} } else {
else{
res.status(400); res.status(400);
res.send() res.send();
} }
}); });
module.exports = router; module.exports = router;

View File

@ -1,11 +1,9 @@
const express = require('express'); const express = require("express");
const router = express.Router(); const router = express.Router();
/* GET home page. */ /* GET home page. */
router.get('/', function(req, res, next) { router.get("/", function (req, res, next) {
res.render('index'); res.render("index");
}); });
module.exports = router; module.exports = router;

View File

@ -1,43 +1,36 @@
const settingsTools = require('./settingsTools'); const settingsTools = require("./settingsTools");
const WebdavTools = require('./webdavTools'); const WebdavTools = require("./webdavTools");
const webdav = new WebdavTools().getInstance(); const webdav = new WebdavTools().getInstance();
const hassioApiTools = require('./hassioApiTools'); const hassioApiTools = require("./hassioApiTools");
const statusTools = require('./status'); const statusTools = require("./status");
const pathTools = require('./pathTools'); const pathTools = require("./pathTools");
const CronJob = require('cron').CronJob;
const moment = require('moment');
const logger = require('../config/winston');
const CronJob = require("cron").CronJob;
const moment = require("moment");
const logger = require("../config/winston");
function checkConfig(conf) { function checkConfig(conf) {
if (conf.cron_base != null) { if (conf.cron_base != null) {
if (conf.cron_base === '1' || conf.cron_base === '2' || conf.cron_base === '3') { if (conf.cron_base === "1" || conf.cron_base === "2" || conf.cron_base === "3") {
if (conf.cron_hour != null && conf.cron_hour.match(/\d\d:\d\d/)) { if (conf.cron_hour != null && conf.cron_hour.match(/\d\d:\d\d/)) {
if (conf.cron_base === '1') if (conf.cron_base === "1") return true;
return true; } else return false;
}
else
return false;
} }
if (conf.cron_base === '2') { if (conf.cron_base === "2") {
return conf.cron_weekday != null && conf.cron_weekday >= 0 && conf.cron_weekday <= 6; return conf.cron_weekday != null && conf.cron_weekday >= 0 && conf.cron_weekday <= 6;
} }
if (conf.cron_base === '3') { if (conf.cron_base === "3") {
return conf.cron_month_day != null && conf.cron_month_day >= 1 && conf.cron_month_day <= 28; return conf.cron_month_day != null && conf.cron_month_day >= 1 && conf.cron_month_day <= 28;
} }
if (conf.cron_base === '0') if (conf.cron_base === "0") return true;
return true } else return false;
}
else
return false;
return false; return false;
} }
@ -52,19 +45,18 @@ function updatetNextDate() {
cronContainer.updatetNextDate(); cronContainer.updatetNextDate();
} }
class CronContainer { class CronContainer {
constructor() { constructor() {
this.cronJob = null; this.cronJob = null;
this.cronClean = null this.cronClean = null;
} }
init() { init() {
let settings = settingsTools.getSettings(); let settings = settingsTools.getSettings();
let cronStr = ""; let cronStr = "";
if (this.cronClean == null) { if (this.cronClean == null) {
logger.info("Starting auto clean cron..."); logger.info("Starting auto clean cron...");
this.cronClean = new CronJob('0 1 * * *', this._clean, null, false, Intl.DateTimeFormat().resolvedOptions().timeZone); this.cronClean = new CronJob("0 1 * * *", this._clean, null, false, Intl.DateTimeFormat().resolvedOptions().timeZone);
this.cronClean.start(); this.cronClean.start();
} }
if (this.cronJob != null) { if (this.cronJob != null) {
@ -78,28 +70,26 @@ class CronContainer {
} }
switch (settings.cron_base) { switch (settings.cron_base) {
case '0': case "0":
logger.warn("No Cron settings available."); logger.warn("No Cron settings available.");
return; return;
case '1': { case "1": {
let splited = settings.cron_hour.split(':'); let splited = settings.cron_hour.split(":");
cronStr = "" + splited[1] + " " + splited[0] + " * * *"; cronStr = "" + splited[1] + " " + splited[0] + " * * *";
break; break;
} }
case '2': { case "2": {
let splited = settings.cron_hour.split(':'); let splited = settings.cron_hour.split(":");
cronStr = "" + splited[1] + " " + splited[0] + " * * " + settings.cron_weekday; cronStr = "" + splited[1] + " " + splited[0] + " * * " + settings.cron_weekday;
break; break;
} }
case '3': { case "3": {
let splited = settings.cron_hour.split(':'); let splited = settings.cron_hour.split(":");
cronStr = "" + splited[1] + " " + splited[0] + " " + settings.cron_month_day + " * *"; cronStr = "" + splited[1] + " " + splited[0] + " " + settings.cron_month_day + " * *";
break; break;
} }
} }
logger.info("Starting Cron..."); logger.info("Starting Cron...");
this.cronJob = new CronJob(cronStr, this._createBackup, null, false, Intl.DateTimeFormat().resolvedOptions().timeZone); this.cronJob = new CronJob(cronStr, this._createBackup, null, false, Intl.DateTimeFormat().resolvedOptions().timeZone);
@ -109,32 +99,30 @@ class CronContainer {
updatetNextDate() { updatetNextDate() {
let date; let date;
if (this.cronJob == null) if (this.cronJob == null) date = "Not configured";
date = "Not configured"; else date = this.cronJob.nextDate().format("MMM D, YYYY HH:mm");
else
date = this.cronJob.nextDate().format('MMM D, YYYY HH:mm');
let status = statusTools.getStatus(); let status = statusTools.getStatus();
status.next_backup = date; status.next_backup = date;
statusTools.setStatus(status); statusTools.setStatus(status);
} }
_createBackup() { _createBackup() {
logger.debug('Cron triggered !'); logger.debug("Cron triggered !");
let status = statusTools.getStatus(); let status = statusTools.getStatus();
if (status.status === "creating" || status.status === "upload" || status.status === "download") if (status.status === "creating" || status.status === "upload" || status.status === "download") return;
return;
let name = 'Auto-' + moment().format('YYYY-MM-DD_HH-mm'); let name = "Auto-" + moment().format("YYYY-MM-DD_HH-mm");
hassioApiTools.createNewBackup(name).then((id) => { hassioApiTools
hassioApiTools.downloadSnapshot(id) .createNewBackup(name)
.then(() => { .then((id) => {
webdav.uploadFile(id, webdav.getConf().back_dir + pathTools.auto + name + '.tar'); hassioApiTools
}).catch(() => { .downloadSnapshot(id)
.then(() => {
}) webdav.uploadFile(id, webdav.getConf().back_dir + pathTools.auto + name + ".tar");
}).catch(() => { })
.catch(() => {});
}) })
.catch(() => {});
} }
_clean() { _clean() {
@ -146,11 +134,9 @@ class CronContainer {
if (autoCleanCloud != null && autoCleanCloud === "true") { if (autoCleanCloud != null && autoCleanCloud === "true") {
webdav.clean().catch(() => {}); webdav.clean().catch(() => {});
} }
} }
} }
class Singleton { class Singleton {
constructor() { constructor() {
if (!Singleton.instance) { if (!Singleton.instance) {

View File

@ -1,25 +1,25 @@
const statusTools = require('./status'); const fs = require("fs");
const fs = require('fs');
const settingsTools = require('./settingsTools'); const moment = require("moment");
const moment = require('moment'); const stream = require("stream");
const logger = require('../config/winston'); const { promisify } = require("util");
const stream = require('stream');
const {promisify} = require('util');
const pipeline = promisify(stream.pipeline); const pipeline = promisify(stream.pipeline);
const got = require ('got'); const got = require("got");
const FormData = require('form-data'); const FormData = require("form-data");
const statusTools = require("./status");
const create_snap_timeout = 90 * 60 * 1000 const settingsTools = require("./settingsTools");
const logger = require("../config/winston");
const create_snap_timeout = 90 * 60 * 1000;
function getSnapshots() { function getSnapshots() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let token = process.env.HASSIO_TOKEN; let token = process.env.HASSIO_TOKEN;
let status = statusTools.getStatus(); let status = statusTools.getStatus();
let option = { let option = {
headers: { 'X-HASSIO-KEY': token }, headers: { "X-HASSIO-KEY": token },
responseType: 'json' responseType: "json",
}; };
got("http://hassio/snapshots", option) got("http://hassio/snapshots", option)
@ -42,41 +42,43 @@ function getSnapshots() {
reject(error.message); reject(error.message);
}); });
}); });
} }
function downloadSnapshot(id) { function downloadSnapshot(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info(`Downloading snapshot ${id}...`); logger.info(`Downloading snapshot ${id}...`);
if (!fs.existsSync('./temp/')) if (!fs.existsSync("./temp/")) fs.mkdirSync("./temp/");
fs.mkdirSync('./temp/'); let tmp_file = `./temp/${id}.tar`;
let tmp_file = `./temp/${id}.tar`
let stream = fs.createWriteStream(tmp_file); let stream = fs.createWriteStream(tmp_file);
let token = process.env.HASSIO_TOKEN; let token = process.env.HASSIO_TOKEN;
let status = statusTools.getStatus(); let status = statusTools.getStatus();
checkSnap(id).then(() => { checkSnap(id)
.then(() => {
status.status = "download"; status.status = "download";
status.progress = 0; status.progress = 0;
statusTools.setStatus(status); statusTools.setStatus(status);
let option = { let option = {
headers: { 'X-HASSIO-KEY': token }, headers: { "X-HASSIO-KEY": token },
}; };
pipeline(got.stream.get(`http://hassio/snapshots/${id}/download`, option) pipeline(
.on('downloadProgress', e => { got.stream.get(`http://hassio/snapshots/${id}/download`, option).on("downloadProgress", (e) => {
let percent = Math.round(e.percent * 100) / 100; let percent = Math.round(e.percent * 100) / 100;
if (status.progress != percent) { if (status.progress != percent) {
status.progress = percent; status.progress = percent;
statusTools.setStatus(status); statusTools.setStatus(status);
} }
}), stream) }),
.then((res)=>{ stream
logger.info('Download success !') )
.then((res) => {
logger.info("Download success !");
status.progress = 1; status.progress = 1;
statusTools.setStatus(status); statusTools.setStatus(status);
logger.debug("Snapshot dl size : " + (fs.statSync(tmp_file).size / 1024 / 1024)); logger.debug("Snapshot dl size : " + fs.statSync(tmp_file).size / 1024 / 1024);
resolve(); resolve();
}).catch((err)=>{ })
.catch((err) => {
fs.unlinkSync(tmp_file); fs.unlinkSync(tmp_file);
status.status = "error"; status.status = "error";
status.message = "Fail to download Hassio snapshot (" + err.message + ")"; status.message = "Fail to download Hassio snapshot (" + err.message + ")";
@ -84,8 +86,9 @@ function downloadSnapshot(id) {
statusTools.setStatus(status); statusTools.setStatus(status);
logger.error(status.message); logger.error(status.message);
reject(err.message); reject(err.message);
}) });
}).catch((err) => { })
.catch((err) => {
status.status = "error"; status.status = "error";
status.message = "Fail to download Hassio snapshot. Not found ?"; status.message = "Fail to download Hassio snapshot. Not found ?";
status.error_code = 7; status.error_code = 7;
@ -93,18 +96,18 @@ function downloadSnapshot(id) {
logger.error(status.message); logger.error(status.message);
reject(); reject();
}); });
}); });
} }
function dellSnap(id) { function dellSnap(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
checkSnap(id).then(() => { checkSnap(id)
.then(() => {
let token = process.env.HASSIO_TOKEN; let token = process.env.HASSIO_TOKEN;
let option = { let option = {
headers: { 'X-HASSIO-KEY': token }, headers: { "X-HASSIO-KEY": token },
responseType: 'json' responseType: "json",
}; };
got.post(`http://hassio/snapshots/${id}/remove`, option) got.post(`http://hassio/snapshots/${id}/remove`, option)
@ -114,48 +117,45 @@ function dellSnap(id) {
.catch((error) => { .catch((error) => {
reject(); reject();
}); });
}).catch(() => {
reject();
}) })
}) .catch(() => {
reject();
});
});
} }
function checkSnap(id) { function checkSnap(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let token = process.env.HASSIO_TOKEN; let token = process.env.HASSIO_TOKEN;
let option = { let option = {
headers: { 'X-HASSIO-KEY': token }, headers: { "X-HASSIO-KEY": token },
responseType: 'json' responseType: "json",
}; };
got(`http://hassio/snapshots/${id}/info`, option) got(`http://hassio/snapshots/${id}/info`, option)
.then((result) => { .then((result) => {
logger.debug(`Snapshot size: ${result.body.data.size}`) logger.debug(`Snapshot size: ${result.body.data.size}`);
resolve(); resolve();
}) })
.catch((error) => { .catch((error) => {
reject(); reject();
}); });
}); });
} }
function createNewBackup(name) { function createNewBackup(name) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let status = statusTools.getStatus(); let status = statusTools.getStatus();
status.status = "creating"; status.status = "creating";
status.progress = -1; status.progress = -1;
statusTools.setStatus(status); statusTools.setStatus(status);
logger.info("Creating new snapshot...") logger.info("Creating new snapshot...");
let token = process.env.HASSIO_TOKEN; let token = process.env.HASSIO_TOKEN;
let option = { let option = {
headers: { 'X-HASSIO-KEY': token }, headers: { "X-HASSIO-KEY": token },
responseType: 'json', responseType: "json",
timeout: create_snap_timeout, timeout: create_snap_timeout,
json: { name: name } json: { name: name },
}; };
got.post(`http://hassio/snapshots/new/full`, option) got.post(`http://hassio/snapshots/new/full`, option)
@ -171,75 +171,71 @@ function createNewBackup(name) {
logger.error(status.message); logger.error(status.message);
reject(status.message); reject(status.message);
}); });
}); });
} }
function clean() { function clean() {
let limit = settingsTools.getSettings().auto_clean_local_keep; let limit = settingsTools.getSettings().auto_clean_local_keep;
if (limit == null) if (limit == null) limit = 5;
limit = 5;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getSnapshots().then(async (snaps) => { getSnapshots()
.then(async (snaps) => {
if (snaps.length < limit) { if (snaps.length < limit) {
resolve(); resolve();
return; return;
} }
snaps.sort((a, b) => { snaps.sort((a, b) => {
if (moment(a.date).isBefore(moment(b.date))) if (moment(a.date).isBefore(moment(b.date))) return 1;
return 1; else return -1;
else
return -1;
}); });
let toDel = snaps.slice(limit); let toDel = snaps.slice(limit);
for (let i in toDel) { for (let i in toDel) {
await dellSnap(toDel[i].slug) await dellSnap(toDel[i].slug);
} }
logger.info('Local clean done.') logger.info("Local clean done.");
resolve(); resolve();
}).catch(() => { })
.catch(() => {
reject(); reject();
}); });
}) });
} }
function uploadSnapshot(path) { function uploadSnapshot(path) {
return new Promise( (resolve, reject) => { return new Promise((resolve, reject) => {
let status = statusTools.getStatus(); let status = statusTools.getStatus();
status.status = "upload-b"; status.status = "upload-b";
status.progress = 0; status.progress = 0;
status.message = null; status.message = null;
status.error_code = null; status.error_code = null;
statusTools.setStatus(status); statusTools.setStatus(status);
logger.info('Uploading backup...'); logger.info("Uploading backup...");
let stream = fs.createReadStream(path); let stream = fs.createReadStream(path);
let token = process.env.HASSIO_TOKEN; let token = process.env.HASSIO_TOKEN;
let form = new FormData(); let form = new FormData();
form.append('file', stream) form.append("file", stream);
let options = { let options = {
body: form, body: form,
username: this.username, username: this.username,
password: this.password, password: this.password,
headers: { 'X-HASSIO-KEY': token }, headers: { "X-HASSIO-KEY": token },
} };
got.stream.post(`http://hassio/snapshots/new/upload`, options).on('uploadProgress', e => { got.stream
.post(`http://hassio/snapshots/new/upload`, options)
.on("uploadProgress", (e) => {
let percent = e.percent; let percent = e.percent;
if (status.progress != percent) { if (status.progress != percent) {
status.progress = percent; status.progress = percent;
statusTools.setStatus(status); statusTools.setStatus(status);
} }
if (percent >= 1) { if (percent >= 1) {
logger.info('Upload done...'); logger.info("Upload done...");
} }
}).on('response', res => { })
.on("response", (res) => {
if (res.statusCode != 200) { if (res.statusCode != 200) {
status.status = "error"; status.status = "error";
status.error_code = 4; status.error_code = 4;
@ -258,8 +254,8 @@ function uploadSnapshot(path) {
fs.unlinkSync(path); fs.unlinkSync(path);
resolve(); resolve();
} }
}).on('error', err => { })
.on("error", (err) => {
fs.unlinkSync(path); fs.unlinkSync(path);
status.status = "error"; status.status = "error";
status.error_code = 4; status.error_code = 4;
@ -268,12 +264,9 @@ function uploadSnapshot(path) {
logger.error(status.message); logger.error(status.message);
reject(status.message); reject(status.message);
}); });
}); });
} }
exports.getSnapshots = getSnapshots; exports.getSnapshots = getSnapshots;
exports.downloadSnapshot = downloadSnapshot; exports.downloadSnapshot = downloadSnapshot;
exports.createNewBackup = createNewBackup; exports.createNewBackup = createNewBackup;

View File

@ -1,5 +1,4 @@
let default_root = "/Hassio Backup/";
let default_root = '/Hassio Backup/';
exports.default_root = default_root; exports.default_root = default_root;
exports.manual = 'Manual/'; exports.manual = "Manual/";
exports.auto = 'Auto/'; exports.auto = "Auto/";

View File

@ -1,26 +1,19 @@
const fs = require('fs'); const fs = require("fs");
const settingsPath = "/data/backup_conf.json"; const settingsPath = "/data/backup_conf.json";
function getSettings() {
function getSettings(){
if (!fs.existsSync(settingsPath)) { if (!fs.existsSync(settingsPath)) {
return {}; return {};
} } else {
else{
let rawSettings = fs.readFileSync(settingsPath); let rawSettings = fs.readFileSync(settingsPath);
return JSON.parse(rawSettings); return JSON.parse(rawSettings);
} }
} }
function setSettings(settings) {
function setSettings(settings){
fs.writeFileSync(settingsPath, JSON.stringify(settings)); fs.writeFileSync(settingsPath, JSON.stringify(settings));
} }
exports.getSettings = getSettings; exports.getSettings = getSettings;
exports.setSettings = setSettings; exports.setSettings = setSettings;

View File

@ -1,31 +1,27 @@
const fs = require('fs'); const fs = require("fs");
const statusPath = '/data/status.json'; const statusPath = "/data/status.json";
let baseStatus = { let baseStatus = {
status: "idle", status: "idle",
last_backup: null, last_backup: null,
next_backup: null next_backup: null,
}; };
function init() { function init() {
if (!fs.existsSync(statusPath)) { if (!fs.existsSync(statusPath)) {
fs.writeFileSync(statusPath, JSON.stringify(baseStatus)); fs.writeFileSync(statusPath, JSON.stringify(baseStatus));
} } else {
else{
let content = getStatus(); let content = getStatus();
if(content.status !== "idle"){ if (content.status !== "idle") {
content.status = "idle"; content.status = "idle";
content.message = null; content.message = null;
setStatus(content) setStatus(content);
} }
} }
} }
function getStatus() {
function getStatus(){
if (!fs.existsSync(statusPath)) { if (!fs.existsSync(statusPath)) {
fs.writeFileSync(statusPath, JSON.stringify(baseStatus)); fs.writeFileSync(statusPath, JSON.stringify(baseStatus));
} }
@ -34,12 +30,10 @@ function getStatus(){
return JSON.parse(content); return JSON.parse(content);
} }
function setStatus(state){ function setStatus(state) {
fs.writeFileSync(statusPath, JSON.stringify(state)); fs.writeFileSync(statusPath, JSON.stringify(state));
} }
exports.init = init; exports.init = init;
exports.getStatus = getStatus; exports.getStatus = getStatus;
exports.setStatus = setStatus; exports.setStatus = setStatus;

View File

@ -1,23 +1,21 @@
// Found on Stackoverflow, perfect code :D https://stackoverflow.com/a/14919494/8654475 // Found on Stackoverflow, perfect code :D https://stackoverflow.com/a/14919494/8654475
function humanFileSize(bytes, si=false, dp=1) { function humanFileSize(bytes, si = false, dp = 1) {
const thresh = si ? 1000 : 1024; const thresh = si ? 1000 : 1024;
if (Math.abs(bytes) < thresh) { if (Math.abs(bytes) < thresh) {
return bytes + ' B'; return bytes + " B";
} }
const units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; const units = si ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
let u = -1; let u = -1;
const r = 10**dp; const r = 10 ** dp;
do { do {
bytes /= thresh; bytes /= thresh;
++u; ++u;
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
return bytes.toFixed(dp) + " " + units[u];
return bytes.toFixed(dp) + ' ' + units[u];
} }
exports.humanFileSize = humanFileSize exports.humanFileSize = humanFileSize;

View File

@ -1,20 +1,20 @@
const { createClient } = require("webdav"); const { createClient } = require("webdav");
const fs = require("fs"); const fs = require("fs");
const moment = require('moment'); const moment = require("moment");
const https = require('https') const https = require("https");
const statusTools = require('./status'); const statusTools = require("./status");
const endpoint = "/remote.php/webdav"; const endpoint = "/remote.php/webdav";
const configPath = "/data/webdav_conf.json"; const configPath = "/data/webdav_conf.json";
const path = require('path'); const path = require("path");
const settingsTools = require('./settingsTools'); const settingsTools = require("./settingsTools");
const pathTools = require('./pathTools'); const pathTools = require("./pathTools");
const hassioApiTools = require('./hassioApiTools'); const hassioApiTools = require("./hassioApiTools");
const logger = require('../config/winston'); const logger = require("../config/winston");
const got = require ('got'); const got = require("got");
const stream = require('stream'); const stream = require("stream");
const {promisify} = require('util'); const { promisify } = require("util");
const pipeline = promisify(stream.pipeline); const pipeline = promisify(stream.pipeline);
class WebdavTools { class WebdavTools {
@ -32,78 +32,85 @@ class WebdavTools {
this.baseUrl = (ssl === "true" ? "https" : "http") + "://" + host + endpoint; this.baseUrl = (ssl === "true" ? "https" : "http") + "://" + host + endpoint;
this.username = username; this.username = username;
this.password = password; this.password = password;
let agent_option = ssl === "true" ? { rejectUnauthorized: accept_selfsigned_cert === "false" } : {} let agent_option = ssl === "true" ? { rejectUnauthorized: accept_selfsigned_cert === "false" } : {};
try { try {
this.client = createClient(this.baseUrl, { username: username, password: password, httpsAgent: new https.Agent(agent_option) }) ; this.client = createClient(this.baseUrl, { username: username, password: password, httpsAgent: new https.Agent(agent_option) });
this.client.getDirectoryContents("/").then(() => { this.client
if (status.error_code == 3) { .getDirectoryContents("/")
status.status = "idle"; .then(() => {
status.message = null; if (status.error_code == 3) {
status.error_code = null; status.status = "idle";
status.message = null;
status.error_code = null;
statusTools.setStatus(status);
}
logger.debug("Nextcloud connection: \x1b[32mSuccess !\x1b[0m");
this.initFolder().then(() => {
resolve();
});
})
.catch((error) => {
status.status = "error";
status.error_code = 3;
status.message = "Can't connect to Nextcloud (" + error + ") !";
statusTools.setStatus(status); statusTools.setStatus(status);
} this.client = null;
logger.debug("Nextcloud connection: \x1b[32mSuccess !\x1b[0m"); logger.error("Can't connect to Nextcloud (" + error + ") !");
this.initFolder().then(() => { reject("Can't connect to Nextcloud (" + error + ") !");
resolve();
}); });
}).catch((error) => {
status.status = "error";
status.error_code = 3;
status.message = "Can't connect to Nextcloud (" + error + ") !"
statusTools.setStatus(status);
this.client = null;
logger.error("Can't connect to Nextcloud (" + error + ") !");
reject("Can't connect to Nextcloud (" + error + ") !");
});
} catch (err) { } catch (err) {
status.status = "error"; status.status = "error";
status.error_code = 3; status.error_code = 3;
status.message = "Can't connect to Nextcloud (" + err + ") !" status.message = "Can't connect to Nextcloud (" + err + ") !";
statusTools.setStatus(status); statusTools.setStatus(status);
this.client = null; this.client = null;
logger.error("Can't connect to Nextcloud (" + err + ") !"); logger.error("Can't connect to Nextcloud (" + err + ") !");
reject("Can't connect to Nextcloud (" + err + ") !"); reject("Can't connect to Nextcloud (" + err + ") !");
} }
}); });
} }
async __createRoot(){ async __createRoot() {
let root_splited = this.getConf().back_dir.split('/').splice(1) let root_splited = this.getConf().back_dir.split("/").splice(1);
let path = '/' let path = "/";
for(let elem of root_splited){ for (let elem of root_splited) {
if(elem != ''){ if (elem != "") {
path = path + elem + '/' path = path + elem + "/";
try { try {
await this.client.createDirectory(path) await this.client.createDirectory(path);
logger.debug(`Path ${path} created.`) logger.debug(`Path ${path} created.`);
} catch (error) { } catch (error) {
if(error.response.status == 405) if (error.response.status == 405) logger.debug(`Path ${path} already exist.`);
logger.debug(`Path ${path} already exist.`) else logger.error(error);
else
logger.error(error)
} }
} }
} }
} }
initFolder() { initFolder() {
return new Promise((resolve) => { return new Promise((resolve) => {
this.__createRoot().catch((err) => { logger.error(err)}).then(() => { this.__createRoot()
this.client.createDirectory(this.getConf().back_dir + pathTools.auto).catch(() => { }).then(() => { .catch((err) => {
this.client.createDirectory(this.getConf().back_dir + pathTools.manual).catch(() => { }).then(() => { logger.error(err);
resolve();
})
}) })
}); .then(() => {
this.client
.createDirectory(this.getConf().back_dir + pathTools.auto)
.catch(() => {})
.then(() => {
this.client
.createDirectory(this.getConf().back_dir + pathTools.manual)
.catch(() => {})
.then(() => {
resolve();
});
});
});
}); });
} }
/** /**
* Check if theh webdav config is valid, if yes, start init of webdav client * Check if theh webdav config is valid, if yes, start init of webdav client
*/ */
confIsValid() { confIsValid() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let status = statusTools.getStatus(); let status = statusTools.getStatus();
@ -117,53 +124,50 @@ class WebdavTools {
statusTools.setStatus(status); statusTools.setStatus(status);
} }
// Check if self_signed option exist // Check if self_signed option exist
if( conf.self_signed == null || conf.self_signed == ''){ if (conf.self_signed == null || conf.self_signed == "") {
conf.self_signed = "false"; conf.self_signed = "false";
this.setConf(conf); this.setConf(conf);
} }
this.init(conf.ssl, conf.host, conf.username, conf.password, conf.self_signed).then(() => { this.init(conf.ssl, conf.host, conf.username, conf.password, conf.self_signed)
resolve(); .then(() => {
}).catch((err) => { resolve();
reject(err); })
}); .catch((err) => {
reject(err);
} });
else { } else {
status.status = "error"; status.status = "error";
status.error_code = 2; status.error_code = 2;
status.message = "Nextcloud config invalid !" status.message = "Nextcloud config invalid !";
statusTools.setStatus(status); statusTools.setStatus(status);
logger.error(status.message); logger.error(status.message);
reject("Nextcloud config invalid !"); reject("Nextcloud config invalid !");
} }
if(conf.back_dir == null || conf.back_dir == ''){ if (conf.back_dir == null || conf.back_dir == "") {
logger.info('Backup dir is null, initializing it.'); logger.info("Backup dir is null, initializing it.");
conf.back_dir = pathTools.default_root; conf.back_dir = pathTools.default_root;
this.setConf(conf); this.setConf(conf);
} } else {
else{ if (!conf.back_dir.startsWith("/")) {
if(!conf.back_dir.startsWith('/')){ logger.warn("Backup dir not starting with '/', fixing this...");
logger.warn('Backup dir not starting with \'/\', fixing this...'); conf.back_dir = `/${conf.back_dir}`;
conf.back_dir=`/${conf.back_dir}`;
this.setConf(conf); this.setConf(conf);
} }
if(!conf.back_dir.endsWith('/')){ if (!conf.back_dir.endsWith("/")) {
logger.warn('Backup dir not ending with \'/\', fixing this...'); logger.warn("Backup dir not ending with '/', fixing this...");
conf.back_dir=`${conf.back_dir}/`; conf.back_dir = `${conf.back_dir}/`;
this.setConf(conf); this.setConf(conf);
} }
} }
} } else {
else {
status.status = "error"; status.status = "error";
status.error_code = 2; status.error_code = 2;
status.message = "Nextcloud config not found !" status.message = "Nextcloud config not found !";
statusTools.setStatus(status); statusTools.setStatus(status);
logger.error(status.message); logger.error(status.message);
reject("Nextcloud config not found !"); reject("Nextcloud config not found !");
} }
}); });
} }
@ -171,122 +175,121 @@ class WebdavTools {
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
let content = JSON.parse(fs.readFileSync(configPath)); let content = JSON.parse(fs.readFileSync(configPath));
return content; return content;
} } else return null;
else
return null;
} }
setConf(conf) { setConf(conf) {
fs.writeFileSync(configPath, JSON.stringify(conf)); fs.writeFileSync(configPath, JSON.stringify(conf));
} }
uploadFile(id, path) { uploadFile(id, path) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.client == null) { if (this.client == null) {
this.confIsValid().then(() => { this.confIsValid()
this._startUpload(id, path); .then(() => {
}).catch((err) => { this._startUpload(id, path);
reject(err); })
}) .catch((err) => {
} reject(err);
else });
this._startUpload(id, path); } else this._startUpload(id, path);
}); });
} }
_startUpload(id, path) { _startUpload(id, path) {
return new Promise( (resolve, reject) => { return new Promise((resolve, reject) => {
let status = statusTools.getStatus(); let status = statusTools.getStatus();
status.status = "upload"; status.status = "upload";
status.progress = 0; status.progress = 0;
status.message = null; status.message = null;
status.error_code = null; status.error_code = null;
statusTools.setStatus(status); statusTools.setStatus(status);
logger.info('Uploading snap...'); logger.info("Uploading snap...");
let tmpFile = `./temp/${id}.tar` let tmpFile = `./temp/${id}.tar`;
let stream = fs.createReadStream(tmpFile); let stream = fs.createReadStream(tmpFile);
let conf = this.getConf() let conf = this.getConf();
let options = { let options = {
body: stream, body: stream,
username: this.username, username: this.username,
password: this.password, password: this.password,
} };
if(conf.ssl === 'true'){ if (conf.ssl === "true") {
options["https"] = { rejectUnauthorized: conf.self_signed === "false" } options["https"] = { rejectUnauthorized: conf.self_signed === "false" };
} }
got.stream.put(this.baseUrl + encodeURI(path), options).on('uploadProgress', e => { got.stream
let percent = e.percent; .put(this.baseUrl + encodeURI(path), options)
if (status.progress != percent) { .on("uploadProgress", (e) => {
status.progress = percent; let percent = e.percent;
statusTools.setStatus(status); if (status.progress != percent) {
} status.progress = percent;
if (percent >= 1) { statusTools.setStatus(status);
logger.info('Upload done...'); }
} if (percent >= 1) {
}).on('response', res => { logger.info("Upload done...");
}
if (res.statusCode != 201 && res.statusCode != 204) { })
.on("response", (res) => {
if (res.statusCode != 201 && res.statusCode != 204) {
status.status = "error";
status.error_code = 4;
status.message = `Fail to upload snapshot to nextcloud (Status code: ${res.statusCode})!`;
statusTools.setStatus(status);
logger.error(status.message);
fs.unlinkSync(tmpFile);
reject(status.message);
} else {
logger.info(`...Upload finish ! (status: ${res.statusCode})`);
status.status = "idle";
status.progress = -1;
status.message = null;
status.error_code = null;
status.last_backup = moment().format("MMM D, YYYY HH:mm");
statusTools.setStatus(status);
cleanTempFolder();
let autoCleanCloud = settingsTools.getSettings().auto_clean_backup;
if (autoCleanCloud != null && autoCleanCloud == "true") {
this.clean().catch();
}
let autoCleanlocal = settingsTools.getSettings().auto_clean_local;
if (autoCleanlocal != null && autoCleanlocal == "true") {
hassioApiTools.clean();
}
resolve();
}
})
.on("error", (err) => {
fs.unlinkSync(tmpFile);
status.status = "error"; status.status = "error";
status.error_code = 4; status.error_code = 4;
status.message = `Fail to upload snapshot to nextcloud (Status code: ${res.statusCode})!`; status.message = `Fail to upload snapshot to nextcloud (${err}) !`;
statusTools.setStatus(status); statusTools.setStatus(status);
logger.error(status.message); logger.error(status.message);
fs.unlinkSync(tmpFile);
reject(status.message); reject(status.message);
} else { });
logger.info(`...Upload finish ! (status: ${res.statusCode})`);
status.status = "idle";
status.progress = -1;
status.message = null;
status.error_code = null;
status.last_backup = moment().format('MMM D, YYYY HH:mm');
statusTools.setStatus(status);
cleanTempFolder();
let autoCleanCloud = settingsTools.getSettings().auto_clean_backup;
if (autoCleanCloud != null && autoCleanCloud == "true") {
this.clean().catch();
}
let autoCleanlocal = settingsTools.getSettings().auto_clean_local;
if (autoCleanlocal != null && autoCleanlocal == "true") {
hassioApiTools.clean();
}
resolve();
}
}).on('error', err => {
fs.unlinkSync(tmpFile);
status.status = "error";
status.error_code = 4;
status.message = `Fail to upload snapshot to nextcloud (${err}) !`;
statusTools.setStatus(status);
logger.error(status.message);
reject(status.message);
});
}); });
} }
downloadFile(path) { downloadFile(path) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.client == null) { if (this.client == null) {
this.confIsValid().then(() => { this.confIsValid()
this._startDownload(path) .then(() => {
.then((path)=> resolve(path)) this._startDownload(path)
.catch(()=> reject()); .then((path) => resolve(path))
}).catch((err) => { .catch(() => reject());
reject(err); })
}) .catch((err) => {
} reject(err);
else });
this._startDownload(path) } else
.then((path)=> resolve(path)) this._startDownload(path)
.catch(()=> reject()); .then((path) => resolve(path))
.catch(() => reject());
}); });
} }
_startDownload(path) { _startDownload(path) {
return new Promise( (resolve, reject) => { return new Promise((resolve, reject) => {
let status = statusTools.getStatus(); let status = statusTools.getStatus();
status.status = "download-b"; status.status = "download-b";
status.progress = 0; status.progress = 0;
@ -294,132 +297,121 @@ class WebdavTools {
status.error_code = null; status.error_code = null;
statusTools.setStatus(status); statusTools.setStatus(status);
logger.info('Downloading backup...'); logger.info("Downloading backup...");
let tmpFile = `./temp/restore_${moment().format('MMM-DD-YYYY_HH_mm')}.tar` let tmpFile = `./temp/restore_${moment().format("MMM-DD-YYYY_HH_mm")}.tar`;
let stream = fs.createWriteStream(tmpFile); let stream = fs.createWriteStream(tmpFile);
let conf = this.getConf() let conf = this.getConf();
let options = { let options = {
username: this.username, username: this.username,
password: this.password, password: this.password,
} };
if(conf.ssl === 'true'){ if (conf.ssl === "true") {
options["https"] = { rejectUnauthorized: conf.self_signed === "false" } options["https"] = { rejectUnauthorized: conf.self_signed === "false" };
} }
pipeline( pipeline(
got.stream.get(this.baseUrl + encodeURI(path), options) got.stream.get(this.baseUrl + encodeURI(path), options).on("downloadProgress", (e) => {
.on('downloadProgress', e => {
let percent = Math.round(e.percent * 100) / 100; let percent = Math.round(e.percent * 100) / 100;
if (status.progress != percent) { if (status.progress != percent) {
status.progress = percent; status.progress = percent;
statusTools.setStatus(status); statusTools.setStatus(status);
} }
}) }),
,
stream stream
) )
.then((res)=>{ .then((res) => {
logger.info('Download success !') logger.info("Download success !");
status.progress = 1; status.progress = 1;
statusTools.setStatus(status); statusTools.setStatus(status);
logger.debug("Backup dl size : " + (fs.statSync(tmpFile).size / 1024 / 1024)); logger.debug("Backup dl size : " + fs.statSync(tmpFile).size / 1024 / 1024);
resolve(tmpFile); resolve(tmpFile);
}).catch((err)=>{ })
if(fs.existsSync(tmpFile)) .catch((err) => {
fs.unlinkSync(tmpFile); if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile);
status.status = "error"; status.status = "error";
status.message = "Fail to download Hassio snapshot (" + err.message + ")"; status.message = "Fail to download Hassio snapshot (" + err.message + ")";
status.error_code = 7; status.error_code = 7;
statusTools.setStatus(status); statusTools.setStatus(status);
logger.error(status.message); logger.error(status.message);
reject(err.message); reject(err.message);
}) });
});
}
}); getFolderContent(path) {
} return new Promise((resolve, reject) => {
if (this.client == null) {
getFolderContent(path) { reject();
return new Promise((resolve, reject) => { return;
if(this.client == null){ }
reject(); this.client
return; .getDirectoryContents(path)
}
this.client.getDirectoryContents(path)
.then((contents) => { .then((contents) => {
resolve(contents); resolve(contents);
}).catch((error) => {
reject(error);
}) })
}); .catch((error) => {
} reject(error);
});
});
}
clean() { clean() {
let limit = settingsTools.getSettings().auto_clean_backup_keep; let limit = settingsTools.getSettings().auto_clean_backup_keep;
if (limit == null) if (limit == null) limit = 5;
limit = 5; return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { this.getFolderContent(this.getConf().back_dir + pathTools.auto)
this.getFolderContent(this.getConf().back_dir + pathTools.auto).then(async (contents) => { .then(async (contents) => {
if (contents.length < limit) { if (contents.length < limit) {
resolve(); resolve();
return; return;
} }
contents.sort((a, b) => { contents.sort((a, b) => {
if (moment(a.lastmod).isBefore(moment(b.lastmod))) if (moment(a.lastmod).isBefore(moment(b.lastmod))) return 1;
return 1; else return -1;
else
return -1;
}); });
let toDel = contents.slice(limit); let toDel = contents.slice(limit);
for (let i in toDel) { for (let i in toDel) {
await this.client.deleteFile(toDel[i].filename); await this.client.deleteFile(toDel[i].filename);
} }
logger.info('Cloud clean done.') logger.info("Cloud clean done.");
resolve(); resolve();
})
}).catch((error) => { .catch((error) => {
status.status = "error"; status.status = "error";
status.error_code = 6; status.error_code = 6;
status.message = "Fail to clean Nexcloud (" + error + ") !" status.message = "Fail to clean Nexcloud (" + error + ") !";
statusTools.setStatus(status); statusTools.setStatus(status);
logger.error(status.message); logger.error(status.message);
reject(status.message); reject(status.message);
}); });
})
}
}
function cleanTempFolder() {
fs.readdir("./temp/", (err, files) => {
if (err) throw err;
for (const file of files) {
fs.unlink(path.join("./temp/", file), err => {
if (err) throw err;
});
}
}); });
} }
}
function cleanTempFolder() {
fs.readdir("./temp/", (err, files) => {
if (err) throw err;
class Singleton { for (const file of files) {
constructor() { fs.unlink(path.join("./temp/", file), (err) => {
if (!Singleton.instance) { if (err) throw err;
Singleton.instance = new WebdavTools(); });
}
} }
});
}
getInstance() { class Singleton {
return Singleton.instance; constructor() {
if (!Singleton.instance) {
Singleton.instance = new WebdavTools();
} }
} }
module.exports = Singleton; getInstance() {
return Singleton.instance;
}
}
module.exports = Singleton;