@kadi.build/file-manager 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,526 @@
1
+ /**
2
+ * FileManager - Main orchestrator for file operations
3
+ *
4
+ * Provides unified interface for local and remote file management.
5
+ * API-compatible with the original LocalRemoteManager for test migration.
6
+ */
7
+
8
+ import { EventEmitter } from 'events';
9
+ import path from 'path';
10
+ import os from 'os';
11
+ import { LocalProvider } from './providers/LocalProvider.js';
12
+ import { WatchProvider } from './providers/WatchProvider.js';
13
+ import { CompressionProvider } from './providers/CompressionProvider.js';
14
+ import { RemoteProvider } from './providers/RemoteProvider.js';
15
+
16
+ class FileManager extends EventEmitter {
17
+ constructor(config) {
18
+ super();
19
+ this.config = config;
20
+ this.providers = {};
21
+ this._initializeProviders();
22
+
23
+ // Set up event handling after providers are initialized
24
+ process.nextTick(() => {
25
+ this._setupEventHandling();
26
+ });
27
+ }
28
+
29
+ _initializeProviders() {
30
+ // Always initialize local provider
31
+ this.providers.local = new LocalProvider(this.config.getLocalConfig());
32
+
33
+ // Initialize watch provider
34
+ this.providers.watch = new WatchProvider(this.config.getWatchConfig());
35
+
36
+ // Initialize compression provider
37
+ this.providers.compression = new CompressionProvider(this.config.getCompressionConfig());
38
+
39
+ // Initialize remote provider if configured
40
+ if (this.config.hasRemoteConfig()) {
41
+ this.providers.remote = new RemoteProvider(this.config.getRemoteConfig());
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Get a specific provider
47
+ * @param {string} name - Provider name: 'local', 'remote', 'watch', 'compression'
48
+ */
49
+ getProvider(name = 'local') {
50
+ const provider = this.providers[name.toLowerCase()];
51
+ if (!provider) {
52
+ throw new Error(`Provider '${name}' not available. Available providers: ${Object.keys(this.providers).join(', ')}`);
53
+ }
54
+ return provider;
55
+ }
56
+
57
+ /**
58
+ * Convenience method to get compression provider
59
+ */
60
+ getCompressionProvider() {
61
+ return this.getProvider('compression');
62
+ }
63
+
64
+ /**
65
+ * Get list of available provider names
66
+ */
67
+ getAvailableProviders() {
68
+ return Object.keys(this.providers);
69
+ }
70
+
71
+ // ============================================================================
72
+ // FILE OPERATIONS (delegates to local or remote provider)
73
+ // ============================================================================
74
+
75
+ async uploadFile(sourcePath, targetPath, providerName = 'local') {
76
+ const provider = this.getProvider(providerName);
77
+ return await provider.uploadFile(sourcePath, targetPath);
78
+ }
79
+
80
+ async downloadFile(sourcePath, targetPath, providerName = 'local') {
81
+ const provider = this.getProvider(providerName);
82
+ return await provider.downloadFile(sourcePath, targetPath);
83
+ }
84
+
85
+ async getFileInfo(filePath, providerName = 'local') {
86
+ const provider = this.getProvider(providerName);
87
+ return await provider.getFile(filePath);
88
+ }
89
+
90
+ async listFiles(directoryPath = '/', providerName = 'local', options = {}) {
91
+ const provider = this.getProvider(providerName);
92
+ return await provider.listFiles(directoryPath, options);
93
+ }
94
+
95
+ async deleteFile(filePath, providerName = 'local') {
96
+ const provider = this.getProvider(providerName);
97
+ return await provider.deleteFile(filePath);
98
+ }
99
+
100
+ async renameFile(oldPath, newName, providerName = 'local') {
101
+ const provider = this.getProvider(providerName);
102
+ return await provider.renameFile(oldPath, newName);
103
+ }
104
+
105
+ async copyFile(sourcePath, targetPath, providerName = 'local') {
106
+ const provider = this.getProvider(providerName);
107
+ return await provider.copyFile(sourcePath, targetPath);
108
+ }
109
+
110
+ async moveFile(sourcePath, targetPath, providerName = 'local') {
111
+ const provider = this.getProvider(providerName);
112
+ return await provider.moveFile(sourcePath, targetPath);
113
+ }
114
+
115
+ // ============================================================================
116
+ // FOLDER OPERATIONS
117
+ // ============================================================================
118
+
119
+ async createFolder(folderPath, providerName = 'local') {
120
+ const provider = this.getProvider(providerName);
121
+ return await provider.createFolder(folderPath);
122
+ }
123
+
124
+ async listFolders(directoryPath = '/', providerName = 'local') {
125
+ const provider = this.getProvider(providerName);
126
+ return await provider.listFolders(directoryPath);
127
+ }
128
+
129
+ async deleteFolder(folderPath, recursive = false, providerName = 'local') {
130
+ const provider = this.getProvider(providerName);
131
+ return await provider.deleteFolder(folderPath, recursive);
132
+ }
133
+
134
+ async renameFolder(oldPath, newName, providerName = 'local') {
135
+ const provider = this.getProvider(providerName);
136
+ return await provider.renameFolder(oldPath, newName);
137
+ }
138
+
139
+ async getFolderInfo(folderPath, providerName = 'local') {
140
+ const provider = this.getProvider(providerName);
141
+ return await provider.getFolder(folderPath);
142
+ }
143
+
144
+ async copyFolder(sourcePath, targetPath, providerName = 'local') {
145
+ const provider = this.getProvider(providerName);
146
+ return await provider.copyFolder(sourcePath, targetPath);
147
+ }
148
+
149
+ async moveFolder(sourcePath, targetPath, providerName = 'local') {
150
+ const provider = this.getProvider(providerName);
151
+ return await provider.moveFolder(sourcePath, targetPath);
152
+ }
153
+
154
+ // ============================================================================
155
+ // SEARCH OPERATIONS
156
+ // ============================================================================
157
+
158
+ async searchFiles(query, providerName = 'local', options = {}) {
159
+ const provider = this.getProvider(providerName);
160
+ return await provider.searchFiles(query, options);
161
+ }
162
+
163
+ // ============================================================================
164
+ // UTILITY OPERATIONS
165
+ // ============================================================================
166
+
167
+ async testConnection(providerName = 'local') {
168
+ const provider = this.getProvider(providerName);
169
+ return await provider.testConnection();
170
+ }
171
+
172
+ async validateProvider(providerName = 'local') {
173
+ const provider = this.getProvider(providerName);
174
+ if (provider.validateConfig) {
175
+ return await provider.validateConfig();
176
+ }
177
+ return { isValid: true, errors: [], warnings: [] };
178
+ }
179
+
180
+ // ============================================================================
181
+ // FILE WATCHING METHODS
182
+ // ============================================================================
183
+
184
+ async startWatching(directoryPath, options = {}) {
185
+ const watchProvider = this.getProvider('watch');
186
+ return await watchProvider.startWatching(directoryPath, options);
187
+ }
188
+
189
+ async stopWatching(watchIdOrPath) {
190
+ const watchProvider = this.getProvider('watch');
191
+ return await watchProvider.stopWatching(watchIdOrPath);
192
+ }
193
+
194
+ async stopAllWatching() {
195
+ const watchProvider = this.getProvider('watch');
196
+ return await watchProvider.stopAllWatching();
197
+ }
198
+
199
+ listActiveWatchers() {
200
+ const watchProvider = this.getProvider('watch');
201
+ return watchProvider.listActiveWatchers();
202
+ }
203
+
204
+ getWatcherInfo(watchIdOrPath) {
205
+ const watchProvider = this.getProvider('watch');
206
+ return watchProvider.getWatcherInfo(watchIdOrPath);
207
+ }
208
+
209
+ getWatchingStatus() {
210
+ const watchProvider = this.getProvider('watch');
211
+ return watchProvider.getWatchingStatus();
212
+ }
213
+
214
+ // Alias for tests that use watch()
215
+ async watch(directoryPath, options = {}) {
216
+ const watchProvider = this.getProvider('watch');
217
+ const watchId = await watchProvider.startWatching(directoryPath, options);
218
+
219
+ return {
220
+ id: watchId,
221
+ stop: () => watchProvider.stopWatching(watchId),
222
+ getInfo: () => watchProvider.getWatcherInfo(watchId)
223
+ };
224
+ }
225
+
226
+ async stopWatch(watchId) {
227
+ return this.stopWatching(watchId);
228
+ }
229
+
230
+ async listWatches() {
231
+ return this.listActiveWatchers();
232
+ }
233
+
234
+ // ============================================================================
235
+ // COMPRESSION METHODS
236
+ // ============================================================================
237
+
238
+ async compressFile(inputPath, outputPath, options = {}) {
239
+ const compressionProvider = this.getProvider('compression');
240
+ return await compressionProvider.compressFile(inputPath, outputPath, options);
241
+ }
242
+
243
+ async decompressFile(archivePath, outputDirectory, options = {}) {
244
+ const compressionProvider = this.getProvider('compression');
245
+ return await compressionProvider.decompressFile(archivePath, outputDirectory, options);
246
+ }
247
+
248
+ async compress(source, output, options = {}) {
249
+ const compressionProvider = this.getProvider('compression');
250
+ return await compressionProvider.compress(source, output, options);
251
+ }
252
+
253
+ async decompress(archive, outputDir, options = {}) {
254
+ const compressionProvider = this.getProvider('compression');
255
+ return await compressionProvider.decompress(archive, outputDir, options);
256
+ }
257
+
258
+ async compressMultipleFiles(fileList, outputDirectory, options = {}) {
259
+ const compressionProvider = this.getProvider('compression');
260
+ return await compressionProvider.compressMultipleFiles(fileList, outputDirectory, options);
261
+ }
262
+
263
+ async decompressMultipleFiles(archiveList, outputDirectory, options = {}) {
264
+ const compressionProvider = this.getProvider('compression');
265
+ return await compressionProvider.decompressMultipleFiles(archiveList, outputDirectory, options);
266
+ }
267
+
268
+ listActiveCompressionOperations() {
269
+ const compressionProvider = this.getProvider('compression');
270
+ return compressionProvider.listActiveOperations();
271
+ }
272
+
273
+ getCompressionOperationInfo(operationId) {
274
+ const compressionProvider = this.getProvider('compression');
275
+ return compressionProvider.getOperationInfo(operationId);
276
+ }
277
+
278
+ getCompressionStatus() {
279
+ const compressionProvider = this.getProvider('compression');
280
+ return compressionProvider.getCompressionStatus();
281
+ }
282
+
283
+ // ============================================================================
284
+ // REMOTE-SPECIFIC OPERATIONS
285
+ // ============================================================================
286
+
287
+ async connectRemote(options = {}) {
288
+ if (!this.providers.remote) {
289
+ this.providers.remote = new RemoteProvider({
290
+ ...this.config.getRemoteConfig(),
291
+ ...options
292
+ });
293
+ }
294
+ return this.providers.remote.connect();
295
+ }
296
+
297
+ async disconnectRemote() {
298
+ if (this.providers.remote) {
299
+ return this.providers.remote.disconnect();
300
+ }
301
+ }
302
+
303
+ // ============================================================================
304
+ // CROSS-PROVIDER OPERATIONS
305
+ // ============================================================================
306
+
307
+ async syncToRemote(localPath, remotePath, options = {}) {
308
+ const local = this.getProvider('local');
309
+ const remote = this.getProvider('remote');
310
+
311
+ const localFiles = await local.listFiles(localPath, { recursive: true });
312
+ const results = [];
313
+
314
+ for (const file of localFiles) {
315
+ const targetPath = remotePath + '/' + (file.relativePath || file.name);
316
+ try {
317
+ await remote.uploadFile(file.path, targetPath);
318
+ results.push({ file: file.path, status: 'success' });
319
+ } catch (error) {
320
+ results.push({ file: file.path, status: 'error', error: error.message });
321
+ }
322
+ }
323
+
324
+ return results;
325
+ }
326
+
327
+ async syncFromRemote(remotePath, localPath, options = {}) {
328
+ const remote = this.getProvider('remote');
329
+
330
+ const remoteFiles = await remote.listFiles(remotePath, { recursive: true });
331
+ const results = [];
332
+
333
+ for (const file of remoteFiles) {
334
+ const targetPath = localPath + '/' + (file.relativePath || file.name);
335
+ try {
336
+ await remote.downloadFile(file.path, targetPath);
337
+ results.push({ file: file.path, status: 'success' });
338
+ } catch (error) {
339
+ results.push({ file: file.path, status: 'error', error: error.message });
340
+ }
341
+ }
342
+
343
+ return results;
344
+ }
345
+
346
+ // ============================================================================
347
+ // BATCH OPERATIONS
348
+ // ============================================================================
349
+
350
+ async uploadMultipleFiles(fileList, targetDirectory = '/', providerName = 'local') {
351
+ const results = [];
352
+ const errors = [];
353
+
354
+ for (const filePath of fileList) {
355
+ try {
356
+ const fileName = path.basename(filePath);
357
+ const targetPath = path.join(targetDirectory, fileName);
358
+ const result = await this.uploadFile(filePath, targetPath, providerName);
359
+ results.push({ sourcePath: filePath, targetPath, result });
360
+ } catch (error) {
361
+ errors.push({ filePath, error: error.message });
362
+ }
363
+ }
364
+
365
+ return { results, errors };
366
+ }
367
+
368
+ async downloadMultipleFiles(fileList, targetDirectory = './', providerName = 'local') {
369
+ const results = [];
370
+ const errors = [];
371
+
372
+ for (const filePath of fileList) {
373
+ try {
374
+ const fileName = path.basename(filePath);
375
+ const targetPath = path.join(targetDirectory, fileName);
376
+ const result = await this.downloadFile(filePath, targetPath, providerName);
377
+ results.push({ sourcePath: filePath, targetPath, result });
378
+ } catch (error) {
379
+ errors.push({ filePath, error: error.message });
380
+ }
381
+ }
382
+
383
+ return { results, errors };
384
+ }
385
+
386
+ // ============================================================================
387
+ // EVENT HANDLING SETUP
388
+ // ============================================================================
389
+
390
+ _setupEventHandling() {
391
+ try {
392
+ // Forward file watching events
393
+ const watchProvider = this.getProvider('watch');
394
+ watchProvider.on('fileEvent', (eventData) => {
395
+ this.emit('fileEvent', eventData);
396
+ });
397
+ watchProvider.on('watcherError', (errorData) => {
398
+ this.emit('watcherError', errorData);
399
+ });
400
+ } catch (error) {
401
+ // Watch provider might not be available
402
+ }
403
+
404
+ try {
405
+ // Forward compression events
406
+ const compressionProvider = this.getProvider('compression');
407
+ compressionProvider.on('compressionProgress', (progressData) => {
408
+ this.emit('compressionProgress', progressData);
409
+ });
410
+ compressionProvider.on('decompressionProgress', (progressData) => {
411
+ this.emit('decompressionProgress', progressData);
412
+ });
413
+ } catch (error) {
414
+ // Compression provider might not be available
415
+ }
416
+ }
417
+
418
+ // ============================================================================
419
+ // UTILITY METHODS
420
+ // ============================================================================
421
+
422
+ formatBytes(bytes) {
423
+ if (bytes === 0) return '0 Bytes';
424
+ const k = 1024;
425
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
426
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
427
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
428
+ }
429
+
430
+ getSystemInfo() {
431
+ return {
432
+ platform: os.platform(),
433
+ arch: os.arch(),
434
+ nodeVersion: process.version,
435
+ availableProviders: this.getAvailableProviders(),
436
+ config: {
437
+ local: this.config.getLocalConfig(),
438
+ performance: this.config.getPerformanceConfig ? this.config.getPerformanceConfig() : {},
439
+ watch: this.config.getWatchConfig(),
440
+ compression: this.config.getCompressionConfig()
441
+ }
442
+ };
443
+ }
444
+
445
+ async getUsageStats() {
446
+ const provider = this.getProvider('local');
447
+ const localConfig = this.config.getLocalConfig();
448
+ const fsPromises = (await import('fs')).promises;
449
+
450
+ const stats = {
451
+ uploadDirectory: {
452
+ path: localConfig.uploadDirectory,
453
+ exists: false,
454
+ fileCount: 0,
455
+ totalSize: 0
456
+ },
457
+ downloadDirectory: {
458
+ path: localConfig.downloadDirectory,
459
+ exists: false,
460
+ fileCount: 0,
461
+ totalSize: 0
462
+ },
463
+ tempDirectory: {
464
+ path: localConfig.tempDirectory,
465
+ exists: false,
466
+ fileCount: 0,
467
+ totalSize: 0
468
+ },
469
+ watchStatus: this.getWatchingStatus(),
470
+ compressionStatus: this.getCompressionStatus()
471
+ };
472
+
473
+ for (const [key, dirInfo] of Object.entries(stats)) {
474
+ if (key === 'watchStatus' || key === 'compressionStatus') continue;
475
+
476
+ try {
477
+ const dirStat = await fsPromises.stat(dirInfo.path);
478
+ if (dirStat.isDirectory()) {
479
+ dirInfo.exists = true;
480
+ const files = await provider.listFiles(dirInfo.path, { recursive: true });
481
+ dirInfo.fileCount = files.length;
482
+ dirInfo.totalSize = files.reduce((sum, file) => sum + (file.size || 0), 0);
483
+ }
484
+ } catch (error) {
485
+ dirInfo.exists = false;
486
+ }
487
+ }
488
+
489
+ return stats;
490
+ }
491
+
492
+ // ============================================================================
493
+ // CLEANUP AND SHUTDOWN
494
+ // ============================================================================
495
+
496
+ async shutdown() {
497
+ try {
498
+ // Stop all watchers first
499
+ const watchProvider = this.getProvider('watch');
500
+ await watchProvider.shutdown();
501
+ } catch (error) {
502
+ // Watch provider might not be available
503
+ }
504
+
505
+ try {
506
+ // Shutdown compression provider
507
+ const compressionProvider = this.getProvider('compression');
508
+ if (compressionProvider.shutdown) {
509
+ await compressionProvider.shutdown();
510
+ }
511
+ } catch (error) {
512
+ // Compression provider might not be available
513
+ }
514
+
515
+ try {
516
+ // Disconnect remote if connected
517
+ if (this.providers.remote) {
518
+ await this.providers.remote.disconnect();
519
+ }
520
+ } catch (error) {
521
+ // Remote provider might not be available
522
+ }
523
+ }
524
+ }
525
+
526
+ export { FileManager };
package/src/index.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @kadi.build/file-manager
3
+ *
4
+ * Complete local and remote file management library.
5
+ * Provides file operations, watching, compression, and remote SSH/SFTP support.
6
+ */
7
+
8
+ // Providers
9
+ export { LocalProvider } from './providers/LocalProvider.js';
10
+ export { WatchProvider } from './providers/WatchProvider.js';
11
+ export { CompressionProvider } from './providers/CompressionProvider.js';
12
+ export { RemoteProvider } from './providers/RemoteProvider.js';
13
+
14
+ // Utilities
15
+ export { FileStreamingUtils, FileStreamer, DownloadTracker } from './utils/FileStreamingUtils.js';
16
+ export { PathUtils } from './utils/PathUtils.js';
17
+
18
+ // Main classes
19
+ export { FileManager } from './FileManager.js';
20
+ export { ConfigManager } from './ConfigManager.js';
21
+
22
+ // Factory function
23
+ export async function createFileManager(options = {}) {
24
+ const { ConfigManager: CM } = await import('./ConfigManager.js');
25
+ const { FileManager: FM } = await import('./FileManager.js');
26
+ const config = new CM();
27
+ await config.load(options);
28
+ return new FM(config);
29
+ }
30
+
31
+ // Convenience functions
32
+ export async function compressFile(sourcePath, outputPath, options = {}) {
33
+ const manager = await createFileManager();
34
+ return manager.compress(sourcePath, outputPath, options);
35
+ }
36
+
37
+ export async function decompressFile(archivePath, outputDir, options = {}) {
38
+ const manager = await createFileManager();
39
+ return manager.decompress(archivePath, outputDir, options);
40
+ }
41
+
42
+ export async function watchDirectory(dirPath, options = {}) {
43
+ const manager = await createFileManager();
44
+ return manager.watch(dirPath, options);
45
+ }
46
+
47
+ // Default export
48
+ export { FileManager as default } from './FileManager.js';