real-time-backup
Version:
Node.js module for real time backup of files and directories. This is a simple script to backup a directory to a sd card. Only works on Linux.
189 lines (160 loc) • 5.1 kB
JavaScript
const { execSync } = require("child_process");
const { watch } = require("fs");
const chokidar = require("chokidar");
const EventEmitter = require('events');
const RealtimeBackup = {
MEDIA_NAME: "Samsung_T5",
PARTITION_NAME: "/dev/sdb1",
DIR_POINT: "/media/laurent",
ORIGIN_PROJECT_DIR: "/home/laurent/projet-perso",
APP_DIR: `${this.ORIGIN_PROJECT_DIR}/test-listener-media`,
MOUNT_POINT: `${this.DIR_POINT}/${this.MEDIA_NAME}`,
mediaIsMount: false,
copyToBackup: false,
firstCopyIsOk: false,
activeLog: false,
event: new EventEmitter(),
callBackListener: undefined,
excludeFiles: [],
/**
* init config for realtime backup
* @param {{
* sdCardName: string;
* partitionName:string;
* dirMount:string;
* projectDir:string;
* excludeFiles?:string[];
* activeLog?:boolean;
* }} realTimeBackupParams
*/
init(realTimeBackupParams) {
if (!realTimeBackupParams) throw new Error('Expected realTimeBackupParams!');
if (Object.keys(realTimeBackupParams).length === 0) throw new Error('Expected all keys in realTimeBackupParams!');
[
"sdCardName",
"partitionName",
"dirMount",
"projectDir"
].forEach((key) => {
if ([undefined, null,].includes(realTimeBackupParams[key])
|| (realTimeBackupParams[key] && realTimeBackupParams[key].trim().length === 0)) throw new Error(`Expected ${key} argument!`);
})
this.activeLog = realTimeBackupParams.activeLog;
if (Array.isArray(realTimeBackupParams.excludeFiles)) {
this.excludeFiles = realTimeBackupParams.excludeFiles;
}
this.MEDIA_NAME = realTimeBackupParams.sdCardName;
this.PARTITION_NAME = realTimeBackupParams.partitionName;
this.DIR_POINT = realTimeBackupParams.dirMount;
this.APP_DIR = realTimeBackupParams.projectDir;
this.ORIGIN_PROJECT_DIR = this.APP_DIR.split("/").reverse().slice(1).reverse().join("/");
this.MOUNT_POINT = `${this.DIR_POINT}/${this.MEDIA_NAME}`;
this.log(this.ORIGIN_PROJECT_DIR);
return {
start: this.start.bind(this),
setCopyToBackup: (arg) => (this.copyToBackup = arg),
isMounted: this.checkMediaIsMounted.bind(this),
subscribe: this.subscribeIsMounted.bind(this),
unSubscribe: this.unSubscribeIsMounted.bind(this)
};
},
subscribeIsMounted(callback) {
this.callBackListener = callback;
const event = this.event.on('isMounted', callback);
},
unSubscribeIsMounted() {
if (!this.callBackListener) {
throw new Error('Not listener to destroy...');
}
this.event.removeAllListeners('isMounted');
this.callBackListener = undefined;
},
log(msg) { if (this.activeLog) console.log(msg) },
checkMediaIsMounted() {
try {
execSync(`cat /etc/mtab | grep -c "${this.MOUNT_POINT}"`);
this.log("MOUNTED");
this.mediaIsMount = true;
this.event.emit('isMounted', true);
return true;
} catch (e) {
this.log("NOT MOUNTED");
this.mediaIsMount = false;
this.event.emit('isMounted', false);
return false;
}
},
appWatcher() {
chokidar.watch(this.APP_DIR, {
ignoreInitial: true,
persistent: true,
awaitWriteFinish: {
pollInterval: 100,
stabilityThreshold: 2000
}
}).on("all", (event, path) => {
if (this.excludeFiles.length && !!this.excludeFiles.find(e => path.includes(e))) {
return;
}
if (this.copyToBackup && this.mediaIsMount) {
this.log("WATCHER APP...");
this.log(event, path);
if (!this.firstCopyIsOk) {
execSync(`cp -R -f "${this.APP_DIR}" "${this.MOUNT_POINT}"`);
this.log("FIRST COPY OK");
this.firstCopyIsOk = true;
} else {
switch (event) {
case "add":
case "addDir":
case "change":
execSync(`cp -R -f "${path}" "${path.replace(this.ORIGIN_PROJECT_DIR, this.MOUNT_POINT)}"`);
break;
case "unlink":
case "unlinkDir":
execSync(`rm -R -f "${path.replace(this.ORIGIN_PROJECT_DIR, this.MOUNT_POINT)}"`);
break;
}
}
} else {
this.firstCopyIsOk = false;
}
});
},
sdWatcher() {
this.checkMediaIsMounted();
watch(this.DIR_POINT, { recursive: true, encoding: 'utf8' }, (event, path) => {
this.log("WATCHER SD");
if (path === this.MEDIA_NAME) {
setTimeout(() => this.checkMediaIsMounted(), 2000);
}
});
},
start() {
this.appWatcher();
this.sdWatcher();
}
}
/**
*
* @returns {{create: (realTimeBackupParams:
* {
* sdCardName: string;
* partitionName:string;
* dirMount:string;
* projectDir:string;
* activeLog?:boolean
* }
* )=>({
* start:()=>void,
* setCopyToBackup:(arg:boolean)=>void,
* isMounted:()=>boolean,
* subscribe:(callback:(isMounted:boolean)=>void)=>void,
* unSubscribe:()=>void
* })}} init
*/
module.exports.RealTimeBackup = () => {
return {
create: RealtimeBackup.init.bind(RealtimeBackup),
};
}