@kadi.build/local-remote-file-manager-ability
Version:
Local & Remote File Management System with S3-compatible container registry, HTTP server provider, file streaming, and comprehensive testing suite
494 lines (407 loc) • 16.9 kB
JavaScript
import { LocalProvider } from './providers/localProvider.js';
import { WatchProvider } from './providers/watchProvider.js';
import { CompressionProvider } from './providers/compressionProvider.js';
import { TunnelProvider } from './providers/tunnelProvider.js';
import EventEmitter from 'events';
import path from 'path';
import os from 'os';
class LocalRemoteManager extends EventEmitter {
constructor(config) {
super();
this.config = config;
this.providers = {};
this.initializeProviders();
// Set up event handling after providers are initialized
process.nextTick(() => {
this.setupEventHandling();
});
}
initializeProviders() {
// Initialize Local Provider (always available)
this.providers.local = new LocalProvider(this.config.getLocalConfig());
// Initialize Watch Provider (Bucket 2)
this.providers.watch = new WatchProvider(this.config.getWatchConfig());
// Initialize Compression Provider (Bucket 3)
this.providers.compression = new CompressionProvider(this.config.getCompressionConfig());
// Initialize Tunnel Provider (Bucket 4)
this.providers.tunnel = new TunnelProvider(this.config.getTunnelConfig());
// Future buckets will add more providers:
// this.providers.remote = new RemoteProvider(this.config.getRemoteConfig());
}
getProvider(providerName = 'local') {
const provider = this.providers[providerName.toLowerCase()];
if (!provider) {
throw new Error(`Provider '${providerName}' not available. Available providers: ${Object.keys(this.providers).join(', ')}`);
}
return provider;
}
// Convenience method to get compression provider
getCompressionProvider() {
return this.getProvider('compression');
}
// ============================================================================
// FILE OPERATIONS (mirroring CloudStorageManager)
// ============================================================================
async uploadFile(sourcePath, targetPath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.uploadFile(sourcePath, targetPath);
}
async downloadFile(sourcePath, targetPath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.downloadFile(sourcePath, targetPath);
}
async getFileInfo(filePath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.getFile(filePath);
}
async listFiles(directoryPath = '/', providerName = 'local', options = {}) {
const provider = this.getProvider(providerName);
return await provider.listFiles(directoryPath, options);
}
async deleteFile(filePath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.deleteFile(filePath);
}
async renameFile(oldPath, newName, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.renameFile(oldPath, newName);
}
async copyFile(sourcePath, targetPath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.copyFile(sourcePath, targetPath);
}
async moveFile(sourcePath, targetPath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.moveFile(sourcePath, targetPath);
}
// ============================================================================
// FOLDER OPERATIONS (mirroring CloudStorageManager)
// ============================================================================
async createFolder(folderPath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.createFolder(folderPath);
}
async listFolders(directoryPath = '/', providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.listFolders(directoryPath);
}
async deleteFolder(folderPath, recursive = false, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.deleteFolder(folderPath, recursive);
}
async renameFolder(oldPath, newName, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.renameFolder(oldPath, newName);
}
async getFolderInfo(folderPath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.getFolder(folderPath);
}
async copyFolder(sourcePath, targetPath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.copyFolder(sourcePath, targetPath);
}
async moveFolder(sourcePath, targetPath, providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.moveFolder(sourcePath, targetPath);
}
// ============================================================================
// SEARCH OPERATIONS (mirroring CloudStorageManager)
// ============================================================================
async searchFiles(query, providerName = 'local', options = {}) {
const provider = this.getProvider(providerName);
return await provider.searchFiles(query, options);
}
// ============================================================================
// UTILITY OPERATIONS (mirroring CloudStorageManager)
// ============================================================================
async testConnection(providerName = 'local') {
const provider = this.getProvider(providerName);
return await provider.testConnection();
}
async validateProvider(providerName = 'local') {
const provider = this.getProvider(providerName);
if (provider.validateConfig) {
return await provider.validateConfig();
}
return { isValid: true, errors: [], warnings: [] };
}
getAvailableProviders() {
return Object.keys(this.providers);
}
// ============================================================================
// BATCH OPERATIONS (for future Bucket 5)
// ============================================================================
async uploadMultipleFiles(fileList, targetDirectory = '/', providerName = 'local') {
const results = [];
const errors = [];
for (const filePath of fileList) {
try {
const fileName = path.basename(filePath);
const targetPath = path.join(targetDirectory, fileName);
const result = await this.uploadFile(filePath, targetPath, providerName);
results.push({ sourcePath: filePath, targetPath, result });
} catch (error) {
errors.push({ filePath, error: error.message });
}
}
return { results, errors };
}
async downloadMultipleFiles(fileList, targetDirectory = './', providerName = 'local') {
const results = [];
const errors = [];
for (const filePath of fileList) {
try {
const fileName = path.basename(filePath);
const targetPath = path.join(targetDirectory, fileName);
const result = await this.downloadFile(filePath, targetPath, providerName);
results.push({ sourcePath: filePath, targetPath, result });
} catch (error) {
errors.push({ filePath, error: error.message });
}
}
return { results, errors };
}
// ============================================================================
// FILE WATCHING METHODS (Bucket 2)
// ============================================================================
async startWatching(directoryPath, options = {}) {
const watchProvider = this.getProvider('watch');
return await watchProvider.startWatching(directoryPath, options);
}
async stopWatching(watchIdOrPath) {
const watchProvider = this.getProvider('watch');
return await watchProvider.stopWatching(watchIdOrPath);
}
async stopAllWatching() {
const watchProvider = this.getProvider('watch');
return await watchProvider.stopAllWatching();
}
listActiveWatchers() {
const watchProvider = this.getProvider('watch');
return watchProvider.listActiveWatchers();
}
getWatcherInfo(watchIdOrPath) {
const watchProvider = this.getProvider('watch');
return watchProvider.getWatcherInfo(watchIdOrPath);
}
getWatchingStatus() {
const watchProvider = this.getProvider('watch');
return watchProvider.getWatchingStatus();
}
// ============================================================================
// COMPRESSION METHODS (Bucket 3)
// ============================================================================
async compressFile(inputPath, outputPath, options = {}) {
const compressionProvider = this.getProvider('compression');
return await compressionProvider.compressFile(inputPath, outputPath, options);
}
async decompressFile(archivePath, outputDirectory, options = {}) {
const compressionProvider = this.getProvider('compression');
return await compressionProvider.decompressFile(archivePath, outputDirectory, options);
}
async compressMultipleFiles(fileList, outputDirectory, options = {}) {
const compressionProvider = this.getProvider('compression');
return await compressionProvider.compressMultipleFiles(fileList, outputDirectory, options);
}
async decompressMultipleFiles(archiveList, outputDirectory, options = {}) {
const compressionProvider = this.getProvider('compression');
return await compressionProvider.decompressMultipleFiles(archiveList, outputDirectory, options);
}
listActiveCompressionOperations() {
const compressionProvider = this.getProvider('compression');
return compressionProvider.listActiveOperations();
}
getCompressionOperationInfo(operationId) {
const compressionProvider = this.getProvider('compression');
return compressionProvider.getOperationInfo(operationId);
}
getCompressionStatus() {
const compressionProvider = this.getProvider('compression');
return compressionProvider.getCompressionStatus();
}
// ============================================================================
// FUTURE BUCKET METHODS (stubs for now)
// ============================================================================
// Bucket 4: Tunneling
async createTunnel(options = {}) {
const provider = this.getProvider('tunnel');
return await provider.createTunnel(options);
}
async destroyTunnel(tunnelId) {
const provider = this.getProvider('tunnel');
return await provider.destroyTunnel(tunnelId);
}
async createTemporaryUrl(filePath, options = {}) {
const provider = this.getProvider('tunnel');
return await provider.createTemporaryUrl(filePath, options);
}
async revokeTemporaryUrl(urlId) {
const provider = this.getProvider('tunnel');
return await provider.revokeTemporaryUrl(urlId);
}
getTunnelStatus() {
const provider = this.getProvider('tunnel');
return provider.getTunnelStatus();
}
listActiveTunnels() {
const provider = this.getProvider('tunnel');
return provider.listActiveTunnels();
}
listActiveUrls() {
const provider = this.getProvider('tunnel');
return provider.listActiveUrls();
}
// Convenience method to get tunnel provider
getTunnelProvider() {
return this.getProvider('tunnel');
}
// ============================================================================
// EVENT HANDLING SETUP
// ============================================================================
setupEventHandling() {
try {
// Forward tunnel events
const tunnelProvider = this.getProvider('tunnel');
tunnelProvider.on('tunnelProgress', (progressData) => {
this.emit('tunnelProgress', progressData);
});
tunnelProvider.on('tunnelError', (errorData) => {
this.emit('tunnelError', errorData);
});
tunnelProvider.on('urlCreated', (urlData) => {
this.emit('urlCreated', urlData);
});
tunnelProvider.on('urlRevoked', (revokeData) => {
this.emit('urlRevoked', revokeData);
});
tunnelProvider.on('fileAccessed', (accessData) => {
this.emit('fileAccessed', accessData);
});
} catch (error) {
console.warn('⚠️ Tunnel provider not available for event handling');
}
try {
// Forward file watching events
const watchProvider = this.getProvider('watch');
watchProvider.on('fileEvent', (eventData) => {
this.emit('fileEvent', eventData);
});
watchProvider.on('watcherError', (errorData) => {
this.emit('watcherError', errorData);
});
} catch (error) {
// Watch provider might not be available, ignore silently
console.warn('⚠️ Watch provider not available for event handling');
}
try {
// Forward compression events
const compressionProvider = this.getProvider('compression');
compressionProvider.on('compressionProgress', (progressData) => {
this.emit('compressionProgress', progressData);
});
compressionProvider.on('decompressionProgress', (progressData) => {
this.emit('decompressionProgress', progressData);
});
} catch (error) {
// Compression provider might not be available, ignore silently
console.warn('⚠️ Compression provider not available for event handling');
}
}
// ============================================================================
// UTILITY METHODS
// ============================================================================
formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
getSystemInfo() {
return {
platform: os.platform(),
arch: os.arch(),
nodeVersion: process.version,
availableProviders: this.getAvailableProviders(),
config: {
local: this.config.getLocalConfig(),
performance: this.config.getPerformanceConfig(),
watch: this.config.getWatchConfig(),
compression: this.config.getCompressionConfig()
}
};
}
async getUsageStats() {
const provider = this.getProvider('local');
const localConfig = this.config.getLocalConfig();
try {
const fs = (await import('fs')).promises;
const path = await import('path');
const stats = {
uploadDirectory: {
path: localConfig.uploadDirectory,
exists: false,
fileCount: 0,
totalSize: 0
},
downloadDirectory: {
path: localConfig.downloadDirectory,
exists: false,
fileCount: 0,
totalSize: 0
},
tempDirectory: {
path: localConfig.tempDirectory,
exists: false,
fileCount: 0,
totalSize: 0
},
watchStatus: this.getWatchingStatus(),
compressionStatus: this.getCompressionStatus()
};
// Check each directory
for (const [key, dirInfo] of Object.entries(stats)) {
if (key === 'watchStatus' || key === 'compressionStatus') continue; // Skip status objects
try {
const dirStat = await fs.stat(dirInfo.path);
if (dirStat.isDirectory()) {
dirInfo.exists = true;
const files = await provider.listFiles(dirInfo.path, { recursive: true });
dirInfo.fileCount = files.length;
dirInfo.totalSize = files.reduce((sum, file) => sum + (file.size || 0), 0);
}
} catch (error) {
// Directory doesn't exist or can't be accessed
dirInfo.exists = false;
}
}
return stats;
} catch (error) {
throw new Error(`Failed to get usage stats: ${error.message}`);
}
}
// ============================================================================
// CLEANUP AND SHUTDOWN
// ============================================================================
async shutdown() {
console.log('🔄 Shutting down Local Remote Manager...');
try {
// Stop all watchers first
const watchProvider = this.getProvider('watch');
await watchProvider.shutdown();
// Shutdown compression provider
const compressionProvider = this.getProvider('compression');
await compressionProvider.shutdown();
// Shutdown tunnel provider
const tunnelProvider = this.getProvider('tunnel');
await tunnelProvider.shutdown();
// Future: shutdown other providers
console.log('✅ Local Remote Manager shutdown complete');
} catch (error) {
console.error('❌ Error during shutdown:', error.message);
throw error;
}
}
}
export { LocalRemoteManager };