mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-11-22 09:12:58 +01:00
🔨 Extract data from cloud file name
This commit is contained in:
parent
b12e6f5111
commit
9271437ec6
@ -1,5 +1,6 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
|
import { getBackupConfig } from "../services/backupConfigService.js";
|
||||||
import {
|
import {
|
||||||
getWebdavConfig,
|
getWebdavConfig,
|
||||||
validateWebdavConfig,
|
validateWebdavConfig,
|
||||||
@ -13,10 +14,11 @@ const webdavRouter = express.Router();
|
|||||||
|
|
||||||
webdavRouter.get("/backup/auto", (req, res, next) => {
|
webdavRouter.get("/backup/auto", (req, res, next) => {
|
||||||
const config = getWebdavConfig();
|
const config = getWebdavConfig();
|
||||||
|
const backupConf = getBackupConfig();
|
||||||
validateWebdavConfig(config)
|
validateWebdavConfig(config)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const value = await webdavService
|
const value = await webdavService
|
||||||
.getBackups(pathTools.auto, config);
|
.getBackups(pathTools.auto, config, backupConf.nameTemplate);
|
||||||
res.json(value);
|
res.json(value);
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
@ -27,10 +29,11 @@ webdavRouter.get("/backup/auto", (req, res, next) => {
|
|||||||
|
|
||||||
webdavRouter.get("/backup/manual", (req, res, next) => {
|
webdavRouter.get("/backup/manual", (req, res, next) => {
|
||||||
const config = getWebdavConfig();
|
const config = getWebdavConfig();
|
||||||
|
const backupConf = getBackupConfig();
|
||||||
validateWebdavConfig(config)
|
validateWebdavConfig(config)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const value = await webdavService
|
const value = await webdavService
|
||||||
.getBackups(pathTools.manual, config);
|
.getBackups(pathTools.manual, config, backupConf.nameTemplate);
|
||||||
res.json(value);
|
res.json(value);
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
|
@ -1,27 +1,25 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import Joi from "joi"
|
import Joi from "joi";
|
||||||
import logger from "../config/winston.js";
|
import logger from "../config/winston.js";
|
||||||
import type { BackupConfig } from "../types/services/backupConfig.js"
|
import type { BackupConfig } from "../types/services/backupConfig.js";
|
||||||
import backupConfigValidation from "../types/services/backupConfigValidation.js";
|
import backupConfigValidation from "../types/services/backupConfigValidation.js";
|
||||||
|
|
||||||
|
|
||||||
const backupConfigPath = "/data/backupConfigV2.json";
|
const backupConfigPath = "/data/backupConfigV2.json";
|
||||||
|
|
||||||
|
export function validateBackupConfig(config: BackupConfig) {
|
||||||
export function validateBackupConfig(config: BackupConfig){
|
|
||||||
const validator = Joi.object(backupConfigValidation);
|
const validator = Joi.object(backupConfigValidation);
|
||||||
return validator.validateAsync(config, {
|
return validator.validateAsync(config, {
|
||||||
abortEarly: false
|
abortEarly: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveBackupConfig(config: BackupConfig){
|
export function saveBackupConfig(config: BackupConfig) {
|
||||||
fs.writeFileSync(backupConfigPath, JSON.stringify(config, undefined, 2));
|
fs.writeFileSync(backupConfigPath, JSON.stringify(config, undefined, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBackupConfig(): BackupConfig {
|
export function getBackupConfig(): BackupConfig {
|
||||||
if (!fs.existsSync(backupConfigPath)) {
|
if (!fs.existsSync(backupConfigPath)) {
|
||||||
logger.warn("Config file not found, creating default one !")
|
logger.warn("Config file not found, creating default one !");
|
||||||
const defaultConfig = getBackupDefaultConfig();
|
const defaultConfig = getBackupDefaultConfig();
|
||||||
saveBackupConfig(defaultConfig);
|
saveBackupConfig(defaultConfig);
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
@ -39,7 +37,7 @@ export function getBackupDefaultConfig(): BackupConfig {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
webdav: {
|
webdav: {
|
||||||
enabled: false
|
enabled: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
exclude: {
|
exclude: {
|
||||||
@ -49,6 +47,18 @@ export function getBackupDefaultConfig(): BackupConfig {
|
|||||||
autoStopAddon: [],
|
autoStopAddon: [],
|
||||||
password: {
|
password: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function templateToRegexp(template: string) {
|
||||||
|
let regexp = template.replace("{date}", "(?<date>\\d{4}-\\d{2}-\\d{2})");
|
||||||
|
regexp = regexp.replace("{hour}", "(?<hour>\\d{4})");
|
||||||
|
regexp = regexp.replace("{hour_12}", "(?<hour12>\\d{4}(AM|PM))");
|
||||||
|
regexp = regexp.replace("{type}", "(?<type>Auto|Manual|)")
|
||||||
|
regexp = regexp.replace("{type_low}", "(?<type>auto|manual|)")
|
||||||
|
return regexp.replace(
|
||||||
|
"{ha_version}",
|
||||||
|
"(?<version>\\d+\\.\\d+\\.\\d+(b\\d+)?)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ import * as pathTools from "../tools/pathTools.js";
|
|||||||
import * as statusTools from "../tools/status.js";
|
import * as statusTools from "../tools/status.js";
|
||||||
import type { WebdavBackup } from "../types/services/webdav.js";
|
import type { WebdavBackup } from "../types/services/webdav.js";
|
||||||
import type { WebdavConfig } from "../types/services/webdavConfig.js";
|
import type { WebdavConfig } from "../types/services/webdavConfig.js";
|
||||||
|
import { templateToRegexp } from "./backupConfigService.js";
|
||||||
import { getEndpoint } from "./webdavConfigService.js";
|
import { getEndpoint } from "./webdavConfigService.js";
|
||||||
|
|
||||||
const PROPFIND_BODY =
|
const PROPFIND_BODY =
|
||||||
@ -94,7 +95,11 @@ function createDirectory(path: string, config: WebdavConfig) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBackups(folder: string, config: WebdavConfig) {
|
export function getBackups(
|
||||||
|
folder: string,
|
||||||
|
config: WebdavConfig,
|
||||||
|
nameTemplate: string
|
||||||
|
) {
|
||||||
const endpoint = getEndpoint(config);
|
const endpoint = getEndpoint(config);
|
||||||
return got(config.url + endpoint + config.backupDir + folder, {
|
return got(config.url + endpoint + config.backupDir + folder, {
|
||||||
method: "PROPFIND" as Method,
|
method: "PROPFIND" as Method,
|
||||||
@ -107,7 +112,10 @@ export function getBackups(folder: string, config: WebdavConfig) {
|
|||||||
body: PROPFIND_BODY,
|
body: PROPFIND_BODY,
|
||||||
}).then(
|
}).then(
|
||||||
(value) => {
|
(value) => {
|
||||||
return parseXmlBackupData(value.body, config);
|
const data = parseXmlBackupData(value.body, config).sort(
|
||||||
|
(a, b) => b.lastEdit.toMillis() - a.lastEdit.toMillis()
|
||||||
|
);
|
||||||
|
return extractBackupInfo(data, nameTemplate);
|
||||||
},
|
},
|
||||||
(reason) => {
|
(reason) => {
|
||||||
messageManager.error(
|
messageManager.error(
|
||||||
@ -120,25 +128,56 @@ export function getBackups(folder: string, config: WebdavConfig) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteBackup(path: string, config: WebdavConfig){
|
function extractBackupInfo(backups: WebdavBackup[], template: string) {
|
||||||
|
const regex = new RegExp(templateToRegexp(template));
|
||||||
|
for (const elem of backups) {
|
||||||
|
const match = elem.name.match(regex);
|
||||||
|
if (match?.groups?.date) {
|
||||||
|
let format = "yyyy-LL-dd";
|
||||||
|
let date = match.groups.date;
|
||||||
|
if (match.groups.hour) {
|
||||||
|
format += "+HHmm";
|
||||||
|
date += `+${match.groups.hour}`;
|
||||||
|
} else if (match.groups.hour_12) {
|
||||||
|
format += "+hhmma";
|
||||||
|
date += `+${match.groups.hour_12}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
elem.creationDate = DateTime.fromFormat(date, format);
|
||||||
|
}
|
||||||
|
if(match?.groups?.version){
|
||||||
|
elem.haVersion = match.groups.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return backups;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteBackup(path: string, config: WebdavConfig) {
|
||||||
const endpoint = getEndpoint(config);
|
const endpoint = getEndpoint(config);
|
||||||
return got.delete(config.url + endpoint + path, {
|
return got
|
||||||
headers: {
|
.delete(config.url + endpoint + path, {
|
||||||
authorization:
|
headers: {
|
||||||
"Basic " +
|
authorization:
|
||||||
Buffer.from(config.username + ":" + config.password).toString("base64")
|
"Basic " +
|
||||||
}
|
Buffer.from(config.username + ":" + config.password).toString(
|
||||||
}).then(
|
"base64"
|
||||||
(response) => {
|
),
|
||||||
return response;
|
},
|
||||||
},
|
})
|
||||||
(reason) => {
|
.then(
|
||||||
messageManager.error("Fail to delete backup in webdav", reason?.message);
|
(response) => {
|
||||||
logger.error(`Fail to delete backup in Cloud`);
|
return response;
|
||||||
logger.error(reason);
|
},
|
||||||
return Promise.reject(reason);
|
(reason) => {
|
||||||
}
|
messageManager.error(
|
||||||
);
|
"Fail to delete backup in webdav",
|
||||||
|
reason?.message
|
||||||
|
);
|
||||||
|
logger.error(`Fail to delete backup in Cloud`);
|
||||||
|
logger.error(reason);
|
||||||
|
return Promise.reject(reason);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseXmlBackupData(body: string, config: WebdavConfig) {
|
function parseXmlBackupData(body: string, config: WebdavConfig) {
|
||||||
@ -162,7 +201,7 @@ function parseXmlBackupData(body: string, config: WebdavConfig) {
|
|||||||
lastEdit: lastEdit,
|
lastEdit: lastEdit,
|
||||||
size: propstat["d:prop"]["d:getcontentlength"],
|
size: propstat["d:prop"]["d:getcontentlength"],
|
||||||
name: name,
|
name: name,
|
||||||
path: href.replace(getEndpoint(config), "")
|
path: href.replace(getEndpoint(config), ""),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,4 +48,4 @@ export interface CronConfig {
|
|||||||
export interface AutoCleanConfig {
|
export interface AutoCleanConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
nbrToKeep?: number;
|
nbrToKeep?: number;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ export interface WebdavBackup {
|
|||||||
size: number;
|
size: number;
|
||||||
lastEdit: DateTime;
|
lastEdit: DateTime;
|
||||||
path: string;
|
path: string;
|
||||||
|
haVersion?: string;
|
||||||
|
creationDate?: DateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,19 +26,69 @@
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-expand-transition>
|
<v-expand-transition>
|
||||||
<v-card v-show="detail" variant="tonal" color="secondary" rounded="0">
|
<v-card v-show="detail" variant="tonal" color="secondary" rounded="0">
|
||||||
<v-card-text class="d-flex justify-center">
|
<v-card-text>
|
||||||
<v-chip color="primary" variant="flat" class="mr-2" label>
|
<v-row>
|
||||||
<v-icon start icon="mdi-pencil"></v-icon>
|
<v-col class="d-flex justify-center">
|
||||||
{{
|
<v-tooltip text="Creation" location="top">
|
||||||
DateTime.fromISO(item.lastEdit).toLocaleString(
|
<template v-slot:activator="{ props }">
|
||||||
DateTime.DATETIME_MED
|
<v-chip
|
||||||
)
|
color="primary"
|
||||||
}}
|
variant="flat"
|
||||||
</v-chip>
|
class="mr-2"
|
||||||
<v-chip color="success" variant="flat" label>
|
label
|
||||||
<v-icon start icon="mdi-database"></v-icon>
|
v-bind="props"
|
||||||
{{ prettyBytes(item.size) }}
|
>
|
||||||
</v-chip>
|
<v-icon start icon="mdi-folder-plus"></v-icon>
|
||||||
|
{{
|
||||||
|
item.creationDate
|
||||||
|
? DateTime.fromISO(item.creationDate).toLocaleString(
|
||||||
|
DateTime.DATETIME_MED
|
||||||
|
)
|
||||||
|
: "UNKNOWN"
|
||||||
|
}}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="Home Assistant Version" location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-chip color="success" variant="flat" label v-bind="props">
|
||||||
|
<v-icon start icon="mdi-home-assistant"></v-icon>
|
||||||
|
{{ item.haVersion ? item.haVersion : "UNKNOWN" }}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col class="d-flex justify-center">
|
||||||
|
<v-tooltip text="Last edit" location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-chip
|
||||||
|
color="primary"
|
||||||
|
variant="flat"
|
||||||
|
class="mr-2"
|
||||||
|
label
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<v-icon start icon="mdi-pencil"></v-icon>
|
||||||
|
{{
|
||||||
|
DateTime.fromISO(item.lastEdit).toLocaleString(
|
||||||
|
DateTime.DATETIME_MED
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip text="Size" location="top">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-chip color="success" variant="flat" label v-bind="props">
|
||||||
|
<v-icon start icon="mdi-database"></v-icon>
|
||||||
|
{{ prettyBytes(item.size) }}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider class="mx-4"></v-divider>
|
<v-divider class="mx-4"></v-divider>
|
||||||
<v-card-actions class="justify-center">
|
<v-card-actions class="justify-center">
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import type { DateTime } from "luxon";
|
|
||||||
|
|
||||||
export interface WebdavBackup {
|
export interface WebdavBackup {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
size: number;
|
size: number;
|
||||||
lastEdit: string;
|
lastEdit: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
haVersion?: string;
|
||||||
|
creationDate?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebdavDeletePayload {
|
export interface WebdavDeletePayload {
|
||||||
|
Loading…
Reference in New Issue
Block a user