yemot-recordings-backup
Version:
Automatic backup tool for Yemot HaMashiach call recordings to Amazon S3
182 lines (150 loc) • 6.04 kB
JavaScript
const path = require('path');
const logger = require('./logger');
const YemotApi = require('./yemotApi');
const S3Client = require('./s3Client');
class BackupService {
constructor(system) {
this.system = system;
this.yemotApi = new YemotApi(system.username, system.password);
this.s3Client = new S3Client();
}
/**
* Run backup for previous day's recordings
* @param {string} customPath - Optional custom path to backup instead of entire system
*/
async runBackup(customPath) {
try {
logger.info(`Starting backup for system ${this.system.id} (${this.system.username})`);
// Get yesterday's date in YYYY-MM-DD format
const yesterday = this.getYesterdayDate();
logger.info(`Backing up recordings from ${yesterday}`);
// Start from custom path or root directory
const startPath = customPath || '/';
await this.processDirectory(startPath, yesterday);
logger.info(`Backup completed for system ${this.system.id}`);
} catch (error) {
logger.error(`Backup failed for system ${this.system.id}: ${error.message}`);
}
}
/**
* Process directory and its subdirectories
* @param {string} dirPath - Directory path
* @param {string} targetDate - Date to filter recordings (YYYY-MM-DD)
*/
async processDirectory(dirPath, targetDate) {
try {
logger.debug(`Processing directory: ${dirPath}`);
const directoryContents = await this.yemotApi.getDirectoryContents(dirPath);
// Process files in this directory
if (directoryContents.files && directoryContents.files.length > 0) {
await this.processFiles(dirPath, directoryContents.files, targetDate);
}
// Process subdirectories recursively
if (directoryContents.dirs && directoryContents.dirs.length > 0) {
for (const dir of directoryContents.dirs) {
// Skip if not a valid directory object
if (!dir.what) continue;
await this.processDirectory(dir.what, targetDate);
}
}
} catch (error) {
logger.error(`Failed to process directory ${dirPath}: ${error.message}`);
}
}
/**
* Process files in a directory
* @param {string} dirPath - Directory path
* @param {Array} files - List of files
* @param {string} targetDate - Date to filter recordings (YYYY-MM-DD)
*/
async processFiles(dirPath, files, targetDate) {
// Filter for audio recordings from the target date
const recordingsToBackup = files.filter(file => {
// Check if it's a recording file
if (!this.yemotApi.isRecording(file.name)) return false;
// Check if the file was created on the target date
if (!file.date) return false;
// Extract date part only (remove time)
const fileDate = file.date.split(' ')[0]; // dd/mm/yyyy
const [day, month, year] = fileDate.split('/');
const fileIsoDate = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
return fileIsoDate === targetDate;
});
// Log found recordings
logger.info(`Found ${recordingsToBackup.length} recordings from ${targetDate} in ${dirPath}`);
// Download and upload each recording
for (const file of recordingsToBackup) {
await this.backupFile(file, dirPath);
}
}
/**
* Download and upload a single file
* @param {object} file - File object
* @param {string} dirPath - Directory path
*/
async backupFile(file, dirPath) {
try {
const filePath = this.joinPaths(dirPath, file.name);
logger.debug(`Processing file: ${filePath}`);
// Create S3 key
const s3Key = this.createS3Key(filePath);
// Check if file already exists in S3
const exists = await this.s3Client.fileExists(s3Key);
if (exists) {
logger.info(`File already exists in S3: ${s3Key}`);
return;
}
// Download file
const fileContent = await this.yemotApi.downloadFile(filePath);
// Upload to S3
await this.s3Client.uploadFile(fileContent, s3Key);
logger.info(`Successfully backed up ${filePath} to ${s3Key}`);
} catch (error) {
logger.error(`Failed to backup file ${file.name}: ${error.message}`);
}
}
/**
* Join paths safely
* @param {string} dirPath - Directory path
* @param {string} fileName - File name
* @returns {string} Joined path
*/
joinPaths(dirPath, fileName) {
// If dirPath is root (/), don't add another slash
if (dirPath === '/') {
return `/${fileName}`;
}
// Otherwise join with slash
return `${dirPath}/${fileName}`;
}
/**
* Create S3 key (path) for a file
* @param {string} filePath - File path on Yemot system
* @returns {string} S3 key
*/
createS3Key(filePath) {
// Remove leading slash if present
const cleanPath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
// Extract date from file path or metadata - for now, use today's date
const date = this.getYesterdayDate();
// Extract filename
const fileName = cleanPath.split('/').pop();
// Extract directory path (without filename)
const dirPath = cleanPath.split('/').slice(0, -1).join('/');
// Format: system ID > yemot path > date > filename
return `${this.system.id}/${dirPath}/${date}/${fileName}`;
}
/**
* Get yesterday's date in YYYY-MM-DD format
* @returns {string} Yesterday's date
*/
getYesterdayDate() {
const date = new Date();
date.setDate(date.getDate() - 1);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
}
module.exports = BackupService;