UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

565 lines 22.4 kB
/** * Backup Location Manager - Flexible Backup Destination Management * * Features: * - Default local backup location * - User-configurable backup destinations * - Cloud storage folder integration (OneDrive, iCloud, Dropbox, Google Drive) * - Multiple backup destinations with sync * - Path validation and availability checking * - Cross-platform path handling */ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import { logger } from '../../utils/logging.js'; export class BackupLocationManager { config; defaultBackupPath; constructor() { this.defaultBackupPath = path.join(os.homedir(), '.hive-ai', 'backups'); // Initialize asynchronously this.initialize(); } /** * Initialize the backup location manager */ async initialize() { await this.loadConfig(); this.initializeDefaultLocation(); } /** * Load configuration from unified database or create default */ async loadConfig() { try { const { getConfig } = await import('../../storage/unified-database.js'); const configData = await getConfig('backup_locations_config'); if (configData) { this.config = JSON.parse(configData); this.validateConfig(); } else { this.config = this.createDefaultConfig(); await this.saveConfig(); } } catch (error) { logger.warn(`Failed to load backup location config from database, using defaults: ${error instanceof Error ? error.message : 'Unknown error'}`); this.config = this.createDefaultConfig(); } } /** * Create default configuration with smart cloud detection */ createDefaultConfig() { const detectedCloudPaths = this.detectCloudStoragePaths(); const locations = [ { name: 'Default Local', path: this.defaultBackupPath, type: 'local', enabled: true, priority: 1, auto_sync: false, last_verified: new Date().toISOString(), available: true } ]; // Add detected cloud storage locations let priority = 2; for (const cloudPath of detectedCloudPaths) { locations.push({ name: `${cloudPath.provider} Sync`, path: path.join(cloudPath.path, 'hive-ai-backups'), type: 'cloud_sync', cloud_provider: cloudPath.provider, enabled: false, // User must explicitly enable priority: priority++, auto_sync: true, last_verified: new Date().toISOString(), available: cloudPath.available }); } return { default_location: this.defaultBackupPath, locations, sync_enabled: false, verification_interval_hours: 6, max_locations: 5, require_primary_available: true }; } /** * Detect common cloud storage paths across platforms */ detectCloudStoragePaths() { const homeDir = os.homedir(); const platform = os.platform(); const detected = []; // Common cloud storage paths by platform const cloudPaths = { onedrive: [ path.join(homeDir, 'OneDrive'), path.join(homeDir, 'OneDrive - Personal'), path.join(homeDir, 'OneDrive - Business'), ...(platform === 'win32' ? [ path.join(homeDir, 'OneDrive'), 'C:\\Users\\' + os.userInfo().username + '\\OneDrive' ] : []) ], icloud: [ ...(platform === 'darwin' ? [ path.join(homeDir, 'Library', 'Mobile Documents', 'com~apple~CloudDocs'), path.join(homeDir, 'iCloud Drive (Archive)'), path.join(homeDir, 'iCloud') ] : []) ], dropbox: [ path.join(homeDir, 'Dropbox'), path.join(homeDir, 'Dropbox (Personal)'), path.join(homeDir, 'Dropbox (Business)'), ...(platform === 'win32' ? [ 'C:\\Users\\' + os.userInfo().username + '\\Dropbox' ] : []) ], google_drive: [ path.join(homeDir, 'Google Drive'), path.join(homeDir, 'GoogleDrive'), ...(platform === 'win32' ? [ 'C:\\Users\\' + os.userInfo().username + '\\Google Drive' ] : []), ...(platform === 'darwin' ? [ path.join(homeDir, 'Google Drive'), '/Volumes/GoogleDrive' ] : []) ] }; // Check each provider's paths for (const [provider, paths] of Object.entries(cloudPaths)) { for (const cloudPath of paths) { try { if (fs.existsSync(cloudPath) && fs.statSync(cloudPath).isDirectory()) { // Verify it's actually a cloud sync folder by checking for common indicators const isCloudFolder = this.verifyCloudSyncFolder(cloudPath, provider); if (isCloudFolder) { detected.push({ provider: provider, path: cloudPath, available: true }); break; // Only add the first valid path per provider } } } catch (error) { // Path not accessible, skip } } } return detected; } /** * Verify if a path is actually a cloud sync folder */ verifyCloudSyncFolder(folderPath, provider) { try { // Check for provider-specific indicators const indicators = { onedrive: ['.849C9593-D756-4E56-8D6E-42412F2A707B', 'OneDriveTemp'], icloud: ['.com.apple.clouddocs', '.icloud'], dropbox: ['.dropbox', '.dropbox.cache'], google_drive: ['.tmp.drivedownload', '.tmp.driveupload'] }; const providerIndicators = indicators[provider] || []; // Check if any indicator files/folders exist (these are usually hidden) for (const indicator of providerIndicators) { const indicatorPath = path.join(folderPath, indicator); if (fs.existsSync(indicatorPath)) { return true; } } // Additional check: see if we can write to the folder const testFile = path.join(folderPath, '.hive-ai-test-write'); try { fs.writeFileSync(testFile, 'test'); fs.unlinkSync(testFile); return true; } catch { return false; } } catch { return false; } } /** * Initialize default backup location */ initializeDefaultLocation() { try { if (!fs.existsSync(this.defaultBackupPath)) { fs.mkdirSync(this.defaultBackupPath, { recursive: true }); // Create subdirectories const subdirs = ['daily', 'weekly', 'monthly', 'emergency', 'manual']; for (const subdir of subdirs) { const fullPath = path.join(this.defaultBackupPath, subdir); fs.mkdirSync(fullPath, { recursive: true }); } logger.info(`✅ Default backup location initialized: ${this.defaultBackupPath}`); } } catch (error) { logger.error(`❌ Failed to initialize default backup location: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Add a new backup location */ async addBackupLocation(location) { try { // Validate the path exists and is writable const validation = await this.validateLocation(location.path); if (!validation.valid) { return { success: false, error: validation.error }; } // Check if location already exists const existingLocation = this.config.locations.find(loc => loc.path === location.path); if (existingLocation) { return { success: false, error: 'Location already exists' }; } // Check location limits if (this.config.locations.length >= this.config.max_locations) { return { success: false, error: `Maximum ${this.config.max_locations} locations allowed` }; } // Create the backup location with verification const newLocation = { ...location, last_verified: new Date().toISOString(), available: validation.available || false, free_space_gb: validation.free_space_gb }; // Create backup structure in the new location await this.createBackupStructure(newLocation.path); // Add to configuration this.config.locations.push(newLocation); this.config.locations.sort((a, b) => a.priority - b.priority); await this.saveConfig(); logger.info(`✅ Added backup location: ${newLocation.name} at ${newLocation.path}`); return { success: true, location: newLocation }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error adding location' }; } } /** * Remove a backup location */ async removeBackupLocation(locationName) { try { const locationIndex = this.config.locations.findIndex(loc => loc.name === locationName); if (locationIndex === -1) { return { success: false, error: 'Location not found' }; } const location = this.config.locations[locationIndex]; // Prevent removing the primary location if (location.priority === 1) { return { success: false, error: 'Cannot remove primary backup location' }; } // Remove from configuration this.config.locations.splice(locationIndex, 1); await this.saveConfig(); logger.info(`🗑️ Removed backup location: ${locationName}`); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error removing location' }; } } /** * Get all backup locations with current status */ async getBackupLocations() { // Ensure config is loaded first if (!this.config) { await this.loadConfig(); this.initializeDefaultLocation(); } // Verify all locations and update availability for (const location of this.config.locations) { const validation = await this.validateLocation(location.path); location.available = validation.valid && validation.available || false; location.free_space_gb = validation.free_space_gb; location.last_verified = new Date().toISOString(); } await this.saveConfig(); return [...this.config.locations].sort((a, b) => a.priority - b.priority); } /** * Get available backup locations for writing */ async getAvailableLocations() { const locations = await this.getBackupLocations(); return locations.filter(loc => loc.enabled && loc.available); } /** * Get primary backup location */ async getPrimaryLocation() { const locations = await this.getBackupLocations(); return locations.find(loc => loc.priority === 1) || null; } /** * Update backup location settings */ async updateLocationSettings(locationName, updates) { try { const location = this.config.locations.find(loc => loc.name === locationName); if (!location) { return { success: false, error: 'Location not found' }; } // Apply updates Object.assign(location, updates); // Re-validate if path changed if (updates.path) { const validation = await this.validateLocation(updates.path); if (!validation.valid) { return { success: false, error: validation.error }; } location.available = validation.available || false; location.free_space_gb = validation.free_space_gb; } location.last_verified = new Date().toISOString(); // Re-sort by priority this.config.locations.sort((a, b) => a.priority - b.priority); await this.saveConfig(); logger.info(`✅ Updated backup location: ${locationName}`); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error updating location' }; } } /** * Validate a backup location path */ async validateLocation(locationPath) { try { // Check if path exists if (!fs.existsSync(locationPath)) { // Try to create it try { fs.mkdirSync(locationPath, { recursive: true }); } catch (createError) { return { valid: false, error: `Cannot create directory: ${createError instanceof Error ? createError.message : 'Unknown error'}` }; } } // Check if it's a directory const stats = fs.statSync(locationPath); if (!stats.isDirectory()) { return { valid: false, error: 'Path is not a directory' }; } // Check write permissions const testFile = path.join(locationPath, '.hive-ai-write-test'); try { fs.writeFileSync(testFile, 'test'); fs.unlinkSync(testFile); } catch (writeError) { return { valid: false, error: `No write permission: ${writeError instanceof Error ? writeError.message : 'Unknown error'}` }; } // Check available space (simplified - in production you'd use a proper disk space library) let freeSpaceGB = 0; try { // This is a simplified approach - you'd want to use a proper library like 'check-disk-space' const diskUsage = fs.statSync(locationPath); freeSpaceGB = 10; // Placeholder - implement proper disk space checking } catch { freeSpaceGB = 0; } return { valid: true, available: true, free_space_gb: freeSpaceGB }; } catch (error) { return { valid: false, error: error instanceof Error ? error.message : 'Unknown validation error' }; } } /** * Create backup directory structure at location */ async createBackupStructure(locationPath) { const subdirs = ['daily', 'weekly', 'monthly', 'emergency', 'manual']; for (const subdir of subdirs) { const fullPath = path.join(locationPath, subdir); if (!fs.existsSync(fullPath)) { fs.mkdirSync(fullPath, { recursive: true }); } } // Create a marker file to identify this as a Hive.AI backup location const markerFile = path.join(locationPath, '.hive-ai-backup-location'); const markerContent = { created: new Date().toISOString(), version: '1.0.0', description: 'Hive.AI Backup Location - Do not delete this folder' }; fs.writeFileSync(markerFile, JSON.stringify(markerContent, null, 2)); } /** * Save configuration to unified database */ async saveConfig() { try { const { setConfig } = await import('../../storage/unified-database.js'); await setConfig('backup_locations_config', JSON.stringify(this.config, null, 2)); } catch (error) { logger.error(`Failed to save backup location config to database: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Validate configuration integrity */ validateConfig() { // Ensure at least one location exists if (!this.config.locations || this.config.locations.length === 0) { this.config = this.createDefaultConfig(); return; } // Ensure there's a primary location const hasPrimary = this.config.locations.some(loc => loc.priority === 1); if (!hasPrimary && this.config.locations.length > 0) { this.config.locations[0].priority = 1; } // Set default values for missing properties if (!this.config.default_location) { this.config.default_location = this.defaultBackupPath; } if (typeof this.config.sync_enabled !== 'boolean') { this.config.sync_enabled = false; } if (!this.config.verification_interval_hours) { this.config.verification_interval_hours = 6; } if (!this.config.max_locations) { this.config.max_locations = 5; } } /** * Get configuration for external access */ getConfig() { return { ...this.config }; } /** * Sync backups across all enabled locations */ async syncBackupsAcrossLocations(backupFileName) { const availableLocations = await this.getAvailableLocations(); const syncedLocations = []; const failedLocations = []; const errors = []; if (availableLocations.length <= 1) { return { success: true, synced_locations: [], failed_locations: [], errors: ['Only one location available, no sync needed'] }; } // Find the source file in the primary location const primaryLocation = availableLocations.find(loc => loc.priority === 1); if (!primaryLocation) { return { success: false, synced_locations: [], failed_locations: [], errors: ['No primary location available'] }; } // Try to find the backup file in primary location subdirectories const subdirs = ['daily', 'weekly', 'monthly', 'emergency', 'manual']; let sourceFilePath = null; for (const subdir of subdirs) { const candidatePath = path.join(primaryLocation.path, subdir, backupFileName); if (fs.existsSync(candidatePath)) { sourceFilePath = candidatePath; break; } } if (!sourceFilePath) { return { success: false, synced_locations: [], failed_locations: [], errors: ['Source backup file not found in primary location'] }; } // Copy to other locations const secondaryLocations = availableLocations.filter(loc => loc.priority !== 1 && loc.auto_sync); for (const location of secondaryLocations) { try { // Determine which subdirectory to use based on backup filename let targetSubdir = 'manual'; for (const subdir of subdirs) { if (backupFileName.includes(`-${subdir}-`)) { targetSubdir = subdir; break; } } const targetPath = path.join(location.path, targetSubdir, backupFileName); const targetDir = path.dirname(targetPath); // Ensure target directory exists if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } // Copy the file fs.copyFileSync(sourceFilePath, targetPath); // Also copy metadata file if it exists const metadataFileName = backupFileName.replace('.db', '.metadata.json'); const sourceMetadataPath = sourceFilePath.replace('.db', '.metadata.json'); const targetMetadataPath = targetPath.replace('.db', '.metadata.json'); if (fs.existsSync(sourceMetadataPath)) { fs.copyFileSync(sourceMetadataPath, targetMetadataPath); } syncedLocations.push(location.name); logger.info(`📋 Synced backup to: ${location.name}`); } catch (error) { failedLocations.push(location.name); errors.push(`${location.name}: ${error instanceof Error ? error.message : 'Unknown sync error'}`); logger.warn(`⚠️ Failed to sync backup to ${location.name}: ${error instanceof Error ? error.message : 'Unknown sync error'}`); } } return { success: failedLocations.length === 0, synced_locations: syncedLocations, failed_locations: failedLocations, errors }; } } //# sourceMappingURL=backup-location-manager.js.map