🔨 Extract data from cloud file name

This commit is contained in:
SebClem 2022-11-14 16:12:05 +01:00
parent b12e6f5111
commit 9271437ec6
Signed by: sebclem
GPG Key ID: 5A4308F6A359EA50
7 changed files with 155 additions and 51 deletions

View File

@ -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) => {

View File

@ -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+)?)"
);
} }

View File

@ -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,20 +128,51 @@ 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
.delete(config.url + endpoint + path, {
headers: { headers: {
authorization: authorization:
"Basic " + "Basic " +
Buffer.from(config.username + ":" + config.password).toString("base64") Buffer.from(config.username + ":" + config.password).toString(
} "base64"
}).then( ),
},
})
.then(
(response) => { (response) => {
return response; return response;
}, },
(reason) => { (reason) => {
messageManager.error("Fail to delete backup in webdav", reason?.message); messageManager.error(
"Fail to delete backup in webdav",
reason?.message
);
logger.error(`Fail to delete backup in Cloud`); logger.error(`Fail to delete backup in Cloud`);
logger.error(reason); logger.error(reason);
return Promise.reject(reason); return Promise.reject(reason);
@ -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), ""),
}); });
} }
} }

View File

@ -6,6 +6,8 @@ export interface WebdavBackup {
size: number; size: number;
lastEdit: DateTime; lastEdit: DateTime;
path: string; path: string;
haVersion?: string;
creationDate?: DateTime;
} }

View File

@ -26,8 +26,50 @@
</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-col class="d-flex justify-center">
<v-tooltip text="Creation" 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-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> <v-icon start icon="mdi-pencil"></v-icon>
{{ {{
DateTime.fromISO(item.lastEdit).toLocaleString( DateTime.fromISO(item.lastEdit).toLocaleString(
@ -35,10 +77,18 @@
) )
}} }}
</v-chip> </v-chip>
<v-chip color="success" variant="flat" label> </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> <v-icon start icon="mdi-database"></v-icon>
{{ prettyBytes(item.size) }} {{ prettyBytes(item.size) }}
</v-chip> </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">

View File

@ -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 {