@internxt/cli 1.6.0 → 1.6.1

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.
Files changed (56) hide show
  1. package/README.md +89 -27
  2. package/dist/commands/create-folder.d.ts +3 -3
  3. package/dist/commands/delete-permanently-file.d.ts +2 -2
  4. package/dist/commands/delete-permanently-folder.d.ts +2 -2
  5. package/dist/commands/download-file.d.ts +4 -5
  6. package/dist/commands/download-file.js +22 -37
  7. package/dist/commands/list.d.ts +4 -4
  8. package/dist/commands/login-legacy.d.ts +5 -5
  9. package/dist/commands/login.d.ts +2 -2
  10. package/dist/commands/move-file.d.ts +4 -4
  11. package/dist/commands/move-folder.d.ts +3 -3
  12. package/dist/commands/rename-file.d.ts +3 -3
  13. package/dist/commands/rename-folder.d.ts +3 -3
  14. package/dist/commands/trash-clear.d.ts +2 -2
  15. package/dist/commands/trash-file.d.ts +2 -2
  16. package/dist/commands/trash-folder.d.ts +2 -2
  17. package/dist/commands/trash-list.d.ts +2 -2
  18. package/dist/commands/trash-restore-file.d.ts +4 -4
  19. package/dist/commands/trash-restore-folder.d.ts +3 -3
  20. package/dist/commands/upload-file.d.ts +6 -7
  21. package/dist/commands/upload-file.js +7 -40
  22. package/dist/commands/upload-folder.d.ts +16 -0
  23. package/dist/commands/upload-folder.js +87 -0
  24. package/dist/commands/webdav-config.d.ts +6 -6
  25. package/dist/commands/webdav.d.ts +1 -1
  26. package/dist/services/drive/drive-folder.service.d.ts +1 -1
  27. package/dist/services/drive/trash.service.d.ts +1 -1
  28. package/dist/services/local-filesystem/local-filesystem.service.d.ts +6 -0
  29. package/dist/services/local-filesystem/local-filesystem.service.js +57 -0
  30. package/dist/services/local-filesystem/local-filesystem.types.d.ts +13 -0
  31. package/dist/services/local-filesystem/local-filesystem.types.js +2 -0
  32. package/dist/services/network/upload/upload-facade.service.d.ts +9 -0
  33. package/dist/services/network/upload/upload-facade.service.js +53 -0
  34. package/dist/services/network/upload/upload-file.service.d.ts +7 -0
  35. package/dist/services/network/upload/upload-file.service.js +112 -0
  36. package/dist/services/network/upload/upload-folder.service.d.ts +6 -0
  37. package/dist/services/network/upload/upload-folder.service.js +62 -0
  38. package/dist/services/network/upload/upload.types.d.ts +53 -0
  39. package/dist/services/network/upload/upload.types.js +6 -0
  40. package/dist/services/thumbnail.service.js +1 -34
  41. package/dist/types/command.types.d.ts +3 -0
  42. package/dist/types/command.types.js +8 -1
  43. package/dist/types/fast-xml-parser.types.d.ts +59 -0
  44. package/dist/types/fast-xml-parser.types.js +2 -0
  45. package/dist/utils/cli.utils.d.ts +13 -2
  46. package/dist/utils/cli.utils.js +47 -0
  47. package/dist/utils/errors.utils.d.ts +1 -0
  48. package/dist/utils/errors.utils.js +5 -0
  49. package/dist/utils/network.utils.d.ts +2 -2
  50. package/dist/utils/network.utils.js +7 -5
  51. package/dist/utils/thumbnail.utils.d.ts +17 -0
  52. package/dist/utils/thumbnail.utils.js +29 -1
  53. package/dist/utils/xml.utils.d.ts +1 -1
  54. package/dist/webdav/handlers/GET.handler.js +37 -24
  55. package/oclif.manifest.json +62 -1
  56. package/package.json +24 -24
@@ -5,9 +5,9 @@ export default class TrashRestoreFile extends Command {
5
5
  static readonly aliases: string[];
6
6
  static readonly examples: string[];
7
7
  static readonly flags: {
8
- id: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
- destination: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
- 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ destination: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'non-interactive': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  };
12
12
  static readonly enableJsonFlag = true;
13
13
  run: () => Promise<{
@@ -16,7 +16,7 @@ export default class TrashRestoreFile extends Command {
16
16
  file: {
17
17
  id: number;
18
18
  uuid: string;
19
- fileId: string;
19
+ fileId: string | null;
20
20
  name: string;
21
21
  type: string;
22
22
  size: string;
@@ -5,9 +5,9 @@ export default class TrashRestoreFolder extends Command {
5
5
  static readonly aliases: string[];
6
6
  static readonly examples: string[];
7
7
  static readonly flags: {
8
- id: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
- destination: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
- 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ destination: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'non-interactive': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  };
12
12
  static readonly enableJsonFlag = true;
13
13
  run: () => Promise<{
@@ -5,9 +5,9 @@ export default class UploadFile extends Command {
5
5
  static readonly aliases: string[];
6
6
  static readonly examples: string[];
7
7
  static readonly flags: {
8
- file: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
- destination: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
- 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ destination: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'non-interactive': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  };
12
12
  static readonly enableJsonFlag = true;
13
13
  run: () => Promise<{
@@ -15,14 +15,14 @@ export default class UploadFile extends Command {
15
15
  message: string;
16
16
  file: {
17
17
  plainName: string;
18
+ status: "EXISTS" | "TRASHED" | "DELETED";
18
19
  name: string;
19
20
  id: number;
20
21
  uuid: string;
22
+ fileId: string | null;
21
23
  bucket: string;
22
- folderUuid: string;
23
24
  folderId: number;
24
- status: "EXISTS" | "TRASHED" | "DELETED";
25
- fileId: string;
25
+ folderUuid: string;
26
26
  itemType: "file";
27
27
  size: number;
28
28
  createdAt: Date;
@@ -33,6 +33,5 @@ export default class UploadFile extends Command {
33
33
  };
34
34
  }>;
35
35
  catch: (error: Error) => Promise<never>;
36
- private getDestinationFolderUuid;
37
36
  private getFilePath;
38
37
  }
@@ -6,15 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const core_1 = require("@oclif/core");
7
7
  const promises_1 = require("node:fs/promises");
8
8
  const node_fs_1 = require("node:fs");
9
- const network_facade_service_1 = require("../services/network/network-facade.service");
10
- const sdk_manager_service_1 = require("../services/sdk-manager.service");
11
9
  const auth_service_1 = require("../services/auth.service");
12
10
  const cli_utils_1 = require("../utils/cli.utils");
13
11
  const config_service_1 = require("../services/config.service");
14
12
  const node_path_1 = __importDefault(require("node:path"));
15
13
  const drive_file_service_1 = require("../services/drive/drive-file.service");
16
- const crypto_service_1 = require("../services/crypto.service");
17
- const download_service_1 = require("../services/network/download.service");
18
14
  const errors_utils_1 = require("../utils/errors.utils");
19
15
  const command_types_1 = require("../types/command.types");
20
16
  const validation_service_1 = require("../services/validation.service");
@@ -22,7 +18,6 @@ const types_1 = require("@internxt/sdk/dist/drive/storage/types");
22
18
  const thumbnail_service_1 = require("../services/thumbnail.service");
23
19
  const stream_utils_1 = require("../utils/stream.utils");
24
20
  const thumbnail_utils_1 = require("../utils/thumbnail.utils");
25
- const inxt_js_1 = require("@internxt/inxt-js");
26
21
  class UploadFile extends core_1.Command {
27
22
  static args = {};
28
23
  static description = 'Upload a file to Internxt Drive';
@@ -54,24 +49,13 @@ class UploadFile extends core_1.Command {
54
49
  }
55
50
  const fileInfo = node_path_1.default.parse(filePath);
56
51
  const fileType = fileInfo.ext.replaceAll('.', '');
57
- let destinationFolderUuid = await this.getDestinationFolderUuid(flags['destination'], nonInteractive);
58
- if (destinationFolderUuid.trim().length === 0) {
59
- destinationFolderUuid = user.rootFolderId;
60
- }
61
- cli_utils_1.CLIUtils.doing('Preparing Network', flags['json']);
62
- const networkModule = sdk_manager_service_1.SdkManager.instance.getNetwork({
63
- user: user.bridgeUser,
64
- pass: user.userId,
65
- });
66
- const environment = new inxt_js_1.Environment({
67
- bridgeUser: user.bridgeUser,
68
- bridgePass: user.userId,
69
- bridgeUrl: config_service_1.ConfigService.instance.get('NETWORK_URL'),
70
- encryptionKey: user.mnemonic,
71
- appDetails: sdk_manager_service_1.SdkManager.getAppDetails(),
72
- });
73
- const networkFacade = new network_facade_service_1.NetworkFacade(networkModule, environment, download_service_1.DownloadService.instance, crypto_service_1.CryptoService.instance);
74
- cli_utils_1.CLIUtils.done(flags['json']);
52
+ const destinationFolderUuid = (await cli_utils_1.CLIUtils.getDestinationFolderUuid({
53
+ destinationFolderUuidFlag: flags['destination'],
54
+ destinationFlagName: UploadFile.flags['destination'].name,
55
+ nonInteractive,
56
+ reporter: this.log.bind(this),
57
+ })) ?? user.rootFolderId;
58
+ const networkFacade = await cli_utils_1.CLIUtils.prepareNetwork({ loginUserDetails: user, jsonFlag: flags['json'] });
75
59
  const readStream = (0, node_fs_1.createReadStream)(filePath);
76
60
  const timer = cli_utils_1.CLIUtils.timer();
77
61
  const progressBar = cli_utils_1.CLIUtils.progress({
@@ -148,23 +132,6 @@ class UploadFile extends core_1.Command {
148
132
  });
149
133
  this.exit(1);
150
134
  };
151
- getDestinationFolderUuid = async (destinationFolderUuidFlag, nonInteractive) => {
152
- const destinationFolderUuid = await cli_utils_1.CLIUtils.getValueFromFlag({
153
- value: destinationFolderUuidFlag,
154
- name: UploadFile.flags['destination'].name,
155
- }, {
156
- nonInteractive,
157
- prompt: {
158
- message: 'What is the destination folder id? (leave empty for the root folder)',
159
- options: { type: 'input' },
160
- },
161
- }, {
162
- validate: validation_service_1.ValidationService.instance.validateUUIDv4,
163
- error: new command_types_1.NotValidFolderUuidError(),
164
- canBeEmpty: true,
165
- }, this.log.bind(this));
166
- return destinationFolderUuid;
167
- };
168
135
  getFilePath = async (fileFlag, nonInteractive) => {
169
136
  const filePath = await cli_utils_1.CLIUtils.getValueFromFlag({
170
137
  value: fileFlag,
@@ -0,0 +1,16 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class UploadFolder extends Command {
3
+ static readonly args: {};
4
+ static readonly description = "Upload a folder to Internxt Drive";
5
+ static readonly aliases: string[];
6
+ static readonly examples: string[];
7
+ static readonly flags: {
8
+ folder: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ destination: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'non-interactive': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ static readonly enableJsonFlag = true;
13
+ run: () => Promise<void>;
14
+ catch: (error: Error) => Promise<never>;
15
+ private getFolderPath;
16
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const cli_utils_1 = require("../utils/cli.utils");
5
+ const auth_service_1 = require("../services/auth.service");
6
+ const validation_service_1 = require("../services/validation.service");
7
+ const config_service_1 = require("../services/config.service");
8
+ const upload_facade_service_1 = require("../services/network/upload/upload-facade.service");
9
+ const command_types_1 = require("../types/command.types");
10
+ class UploadFolder extends core_1.Command {
11
+ static args = {};
12
+ static description = 'Upload a folder to Internxt Drive';
13
+ static aliases = ['upload:folder'];
14
+ static examples = ['<%= config.bin %> <%= command.id %>'];
15
+ static flags = {
16
+ ...cli_utils_1.CLIUtils.CommonFlags,
17
+ folder: core_1.Flags.string({
18
+ char: 'f',
19
+ description: 'The path to the folder on your system.',
20
+ required: false,
21
+ }),
22
+ destination: core_1.Flags.string({
23
+ char: 'i',
24
+ description: 'The folder id where the folder is going to be uploaded to. Leave empty for the root folder.',
25
+ required: false,
26
+ parse: cli_utils_1.CLIUtils.parseEmpty,
27
+ }),
28
+ };
29
+ static enableJsonFlag = true;
30
+ run = async () => {
31
+ const { user } = await auth_service_1.AuthService.instance.getAuthDetails();
32
+ const { flags } = await this.parse(UploadFolder);
33
+ const localPath = await this.getFolderPath(flags['folder'], flags['non-interactive']);
34
+ const destinationFolderUuid = (await cli_utils_1.CLIUtils.getDestinationFolderUuid({
35
+ destinationFolderUuidFlag: flags['destination'],
36
+ destinationFlagName: UploadFolder.flags['destination'].name,
37
+ nonInteractive: flags['non-interactive'],
38
+ reporter: this.log.bind(this),
39
+ })) ?? user.rootFolderId;
40
+ const progressBar = cli_utils_1.CLIUtils.progress({
41
+ format: 'Uploading folder [{bar}] {percentage}%',
42
+ linewrap: true,
43
+ }, flags['json']);
44
+ progressBar?.start(100, 0);
45
+ const data = await upload_facade_service_1.UploadFacade.instance.uploadFolder({
46
+ localPath,
47
+ destinationFolderUuid,
48
+ loginUserDetails: user,
49
+ jsonFlag: flags['json'],
50
+ onProgress: (progress) => {
51
+ progressBar?.update(progress.percentage);
52
+ },
53
+ });
54
+ progressBar?.update(100);
55
+ progressBar?.stop();
56
+ const driveUrl = config_service_1.ConfigService.instance.get('DRIVE_WEB_URL');
57
+ const folderUrl = `${driveUrl}/folder/${data.rootFolderId}`;
58
+ const message = `Folder uploaded in ${data.uploadTimeMs}ms, view it at ${folderUrl} (${data.totalBytes} bytes)`;
59
+ cli_utils_1.CLIUtils.success(this.log.bind(this), message);
60
+ };
61
+ catch = async (error) => {
62
+ const { flags } = await this.parse(UploadFolder);
63
+ cli_utils_1.CLIUtils.catchError({
64
+ error,
65
+ command: this.id,
66
+ logReporter: this.log.bind(this),
67
+ jsonFlag: flags['json'],
68
+ });
69
+ this.exit(1);
70
+ };
71
+ getFolderPath = async (folderFlag, nonInteractive) => {
72
+ return await cli_utils_1.CLIUtils.getValueFromFlag({
73
+ value: folderFlag,
74
+ name: UploadFolder.flags['folder'].name,
75
+ }, {
76
+ nonInteractive,
77
+ prompt: {
78
+ message: 'What is the path to the folder on your computer?',
79
+ options: { type: 'input' },
80
+ },
81
+ }, {
82
+ validate: validation_service_1.ValidationService.instance.validateDirectoryExists,
83
+ error: new command_types_1.NotValidDirectoryError(),
84
+ }, this.log.bind(this));
85
+ };
86
+ }
87
+ exports.default = UploadFolder;
@@ -5,12 +5,12 @@ export default class WebDAVConfig extends Command {
5
5
  static readonly aliases: never[];
6
6
  static readonly examples: string[];
7
7
  static readonly flags: {
8
- host: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
- port: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
- https: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
- http: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
- timeout: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
- createFullPath: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ host: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ port: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ https: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ http: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ timeout: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ createFullPath: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
14
  };
15
15
  static readonly enableJsonFlag = true;
16
16
  run: () => Promise<{
@@ -1,7 +1,7 @@
1
1
  import { Command } from '@oclif/core';
2
2
  export default class Webdav extends Command {
3
3
  static readonly args: {
4
- action: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
4
+ action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
5
  };
6
6
  static readonly description = "Enable, disable, restart or get the status of the Internxt CLI WebDav server";
7
7
  static readonly aliases: never[];
@@ -30,7 +30,7 @@ export declare class DriveFolderService {
30
30
  files: {
31
31
  id: number;
32
32
  uuid: string;
33
- fileId: string;
33
+ fileId: string | null;
34
34
  name: string;
35
35
  type: string;
36
36
  size: string;
@@ -32,7 +32,7 @@ export declare class TrashService {
32
32
  files: {
33
33
  id: number;
34
34
  uuid: string;
35
- fileId: string;
35
+ fileId: string | null;
36
36
  name: string;
37
37
  type: string;
38
38
  size: string;
@@ -0,0 +1,6 @@
1
+ import { FileSystemNode, ScanResult } from './local-filesystem.types';
2
+ export declare class LocalFilesystemService {
3
+ static readonly instance: LocalFilesystemService;
4
+ scanLocalDirectory(path: string): Promise<ScanResult>;
5
+ scanRecursive(currentPath: string, parentPath: string, folders: FileSystemNode[], files: FileSystemNode[]): Promise<number>;
6
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalFilesystemService = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const logger_utils_1 = require("../../utils/logger.utils");
7
+ class LocalFilesystemService {
8
+ static instance = new LocalFilesystemService();
9
+ async scanLocalDirectory(path) {
10
+ const folders = [];
11
+ const files = [];
12
+ const parentPath = (0, node_path_1.dirname)(path);
13
+ const totalBytes = await this.scanRecursive(path, parentPath, folders, files);
14
+ return {
15
+ folders,
16
+ files,
17
+ totalItems: folders.length + files.length,
18
+ totalBytes,
19
+ };
20
+ }
21
+ async scanRecursive(currentPath, parentPath, folders, files) {
22
+ try {
23
+ const stats = await node_fs_1.promises.stat(currentPath);
24
+ const relativePath = (0, node_path_1.relative)(parentPath, currentPath);
25
+ if (stats.isFile() && stats.size > 0) {
26
+ const fileInfo = (0, node_path_1.parse)(currentPath);
27
+ files.push({
28
+ type: 'file',
29
+ name: fileInfo.name,
30
+ absolutePath: currentPath,
31
+ relativePath,
32
+ size: stats.size,
33
+ });
34
+ return stats.size;
35
+ }
36
+ if (stats.isDirectory()) {
37
+ folders.push({
38
+ type: 'folder',
39
+ name: (0, node_path_1.basename)(currentPath),
40
+ absolutePath: currentPath,
41
+ relativePath,
42
+ size: 0,
43
+ });
44
+ const entries = await node_fs_1.promises.readdir(currentPath, { withFileTypes: true });
45
+ const validEntries = entries.filter((e) => !e.isSymbolicLink());
46
+ const bytesArray = await Promise.all(validEntries.map((e) => this.scanRecursive((0, node_path_1.join)(currentPath, e.name), parentPath, folders, files)));
47
+ return bytesArray.reduce((sum, bytes) => sum + bytes, 0);
48
+ }
49
+ return 0;
50
+ }
51
+ catch (error) {
52
+ logger_utils_1.logger.warn(`Error scanning path ${currentPath}: ${error.message} - skipping...`);
53
+ return 0;
54
+ }
55
+ }
56
+ }
57
+ exports.LocalFilesystemService = LocalFilesystemService;
@@ -0,0 +1,13 @@
1
+ export interface FileSystemNode {
2
+ type: 'file' | 'folder';
3
+ name: string;
4
+ size: number;
5
+ absolutePath: string;
6
+ relativePath: string;
7
+ }
8
+ export interface ScanResult {
9
+ folders: FileSystemNode[];
10
+ files: FileSystemNode[];
11
+ totalItems: number;
12
+ totalBytes: number;
13
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ import { UploadFolderParams } from './upload.types';
2
+ export declare class UploadFacade {
3
+ static readonly instance: UploadFacade;
4
+ uploadFolder({ localPath, destinationFolderUuid, loginUserDetails, jsonFlag, onProgress }: UploadFolderParams): Promise<{
5
+ totalBytes: number;
6
+ rootFolderId: string;
7
+ uploadTimeMs: number;
8
+ }>;
9
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UploadFacade = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const cli_utils_1 = require("../../../utils/cli.utils");
6
+ const logger_utils_1 = require("../../../utils/logger.utils");
7
+ const local_filesystem_service_1 = require("../../local-filesystem/local-filesystem.service");
8
+ const upload_folder_service_1 = require("./upload-folder.service");
9
+ const upload_file_service_1 = require("./upload-file.service");
10
+ const async_utils_1 = require("../../../utils/async.utils");
11
+ class UploadFacade {
12
+ static instance = new UploadFacade();
13
+ async uploadFolder({ localPath, destinationFolderUuid, loginUserDetails, jsonFlag, onProgress }) {
14
+ const timer = cli_utils_1.CLIUtils.timer();
15
+ const network = cli_utils_1.CLIUtils.prepareNetwork({ jsonFlag, loginUserDetails });
16
+ const scanResult = await local_filesystem_service_1.LocalFilesystemService.instance.scanLocalDirectory(localPath);
17
+ logger_utils_1.logger.info(`Scanned folder ${localPath}: found ${scanResult.totalItems} items, total size ${scanResult.totalBytes} bytes.`);
18
+ const currentProgress = { itemsUploaded: 0, bytesUploaded: 0 };
19
+ const emitProgress = () => {
20
+ const itemProgress = currentProgress.itemsUploaded / scanResult.totalItems;
21
+ const sizeProgress = currentProgress.bytesUploaded / scanResult.totalBytes;
22
+ const percentage = Math.floor((itemProgress * 0.5 + sizeProgress * 0.5) * 100);
23
+ onProgress({ percentage });
24
+ };
25
+ const folderMap = await upload_folder_service_1.UploadFolderService.instance.createFolders({
26
+ foldersToCreate: scanResult.folders,
27
+ destinationFolderUuid,
28
+ currentProgress,
29
+ emitProgress,
30
+ });
31
+ if (folderMap.size === 0) {
32
+ throw new Error('Failed to create folders, cannot upload files');
33
+ }
34
+ await async_utils_1.AsyncUtils.sleep(500);
35
+ const totalBytes = await upload_file_service_1.UploadFileService.instance.uploadFilesInChunks({
36
+ network,
37
+ filesToUpload: scanResult.files,
38
+ folderMap,
39
+ bucket: loginUserDetails.bucket,
40
+ destinationFolderUuid,
41
+ currentProgress,
42
+ emitProgress,
43
+ });
44
+ const rootFolderName = (0, node_path_1.basename)(localPath);
45
+ const rootFolderId = folderMap.get(rootFolderName) ?? '';
46
+ return {
47
+ totalBytes,
48
+ rootFolderId,
49
+ uploadTimeMs: timer.stop(),
50
+ };
51
+ }
52
+ }
53
+ exports.UploadFacade = UploadFacade;
@@ -0,0 +1,7 @@
1
+ import { UploadFilesInBatchesParams, UploadFileWithRetryParams } from './upload.types';
2
+ export declare class UploadFileService {
3
+ static readonly instance: UploadFileService;
4
+ uploadFilesInChunks({ network, filesToUpload, folderMap, bucket, destinationFolderUuid, currentProgress, emitProgress, }: UploadFilesInBatchesParams): Promise<number>;
5
+ uploadFileWithRetry({ file, network, bucket, parentFolderUuid, }: UploadFileWithRetryParams): Promise<string | null>;
6
+ private chunkArray;
7
+ }
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UploadFileService = void 0;
4
+ const logger_utils_1 = require("../../../utils/logger.utils");
5
+ const upload_types_1 = require("./upload.types");
6
+ const drive_file_service_1 = require("../../drive/drive-file.service");
7
+ const node_path_1 = require("node:path");
8
+ const errors_utils_1 = require("../../../utils/errors.utils");
9
+ const promises_1 = require("node:fs/promises");
10
+ const types_1 = require("@internxt/sdk/dist/drive/storage/types");
11
+ const thumbnail_utils_1 = require("../../../utils/thumbnail.utils");
12
+ class UploadFileService {
13
+ static instance = new UploadFileService();
14
+ async uploadFilesInChunks({ network, filesToUpload, folderMap, bucket, destinationFolderUuid, currentProgress, emitProgress, }) {
15
+ let bytesUploaded = 0;
16
+ const chunks = this.chunkArray(filesToUpload, upload_types_1.MAX_CONCURRENT_UPLOADS);
17
+ for (const chunk of chunks) {
18
+ await Promise.allSettled(chunk.map(async (file) => {
19
+ const parentPath = (0, node_path_1.dirname)(file.relativePath);
20
+ const parentFolderUuid = parentPath === '.' || parentPath === '' ? destinationFolderUuid : folderMap.get(parentPath);
21
+ if (!parentFolderUuid) {
22
+ logger_utils_1.logger.warn(`Parent folder not found for ${file.relativePath}, skipping...`);
23
+ return null;
24
+ }
25
+ const createdFileUuid = await this.uploadFileWithRetry({
26
+ file,
27
+ network,
28
+ bucket,
29
+ parentFolderUuid,
30
+ });
31
+ if (createdFileUuid) {
32
+ bytesUploaded += file.size;
33
+ currentProgress.bytesUploaded += file.size;
34
+ currentProgress.itemsUploaded++;
35
+ }
36
+ emitProgress();
37
+ }));
38
+ }
39
+ return bytesUploaded;
40
+ }
41
+ async uploadFileWithRetry({ file, network, bucket, parentFolderUuid, }) {
42
+ for (let attempt = 0; attempt <= upload_types_1.MAX_RETRIES; attempt++) {
43
+ try {
44
+ const stats = await (0, promises_1.stat)(file.absolutePath);
45
+ if (!stats.size) {
46
+ logger_utils_1.logger.warn(`Skipping empty file: ${file.relativePath}`);
47
+ return null;
48
+ }
49
+ const fileType = (0, node_path_1.extname)(file.absolutePath).replaceAll('.', '');
50
+ const { fileStream, bufferStream } = (0, thumbnail_utils_1.createFileStreamWithBuffer)({
51
+ path: file.absolutePath,
52
+ fileType,
53
+ });
54
+ const fileId = await new Promise((resolve, reject) => {
55
+ network.uploadFile(fileStream, stats.size, bucket, (err, res) => {
56
+ if (err) {
57
+ return reject(err);
58
+ }
59
+ resolve(res);
60
+ }, () => { });
61
+ });
62
+ const createdDriveFile = await drive_file_service_1.DriveFileService.instance.createFile({
63
+ plainName: file.name,
64
+ type: fileType,
65
+ size: stats.size,
66
+ folderUuid: parentFolderUuid,
67
+ fileId,
68
+ bucket,
69
+ encryptVersion: types_1.EncryptionVersion.Aes03,
70
+ creationTime: stats.birthtime?.toISOString(),
71
+ modificationTime: stats.mtime?.toISOString(),
72
+ });
73
+ if (bufferStream) {
74
+ void (0, thumbnail_utils_1.tryUploadThumbnail)({
75
+ bufferStream,
76
+ fileType,
77
+ userBucket: bucket,
78
+ fileUuid: createdDriveFile.uuid,
79
+ networkFacade: network,
80
+ });
81
+ }
82
+ return createdDriveFile.fileId;
83
+ }
84
+ catch (error) {
85
+ if ((0, errors_utils_1.isAlreadyExistsError)(error)) {
86
+ const msg = `File ${file.name} already exists, skipping...`;
87
+ logger_utils_1.logger.info(msg);
88
+ return null;
89
+ }
90
+ if (attempt < upload_types_1.MAX_RETRIES) {
91
+ const delay = upload_types_1.DELAYS_MS[attempt];
92
+ const retryMsg = `Failed to upload file ${file.name}, retrying in ${delay}ms...`;
93
+ logger_utils_1.logger.warn(`${retryMsg} (attempt ${attempt + 1}/${upload_types_1.MAX_RETRIES + 1})`);
94
+ await new Promise((resolve) => setTimeout(resolve, delay));
95
+ }
96
+ else {
97
+ logger_utils_1.logger.error(`Failed to upload file ${file.name} after ${upload_types_1.MAX_RETRIES + 1} attempts`);
98
+ return null;
99
+ }
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+ chunkArray(array, chunkSize) {
105
+ const chunks = [];
106
+ for (let i = 0; i < array.length; i += chunkSize) {
107
+ chunks.push(array.slice(i, i + chunkSize));
108
+ }
109
+ return chunks;
110
+ }
111
+ }
112
+ exports.UploadFileService = UploadFileService;
@@ -0,0 +1,6 @@
1
+ import { CreateFoldersParams, CreateFolderWithRetryParams } from './upload.types';
2
+ export declare class UploadFolderService {
3
+ static readonly instance: UploadFolderService;
4
+ createFolders({ foldersToCreate, destinationFolderUuid, currentProgress, emitProgress, }: CreateFoldersParams): Promise<Map<string, string>>;
5
+ createFolderWithRetry({ folderName, parentFolderUuid }: CreateFolderWithRetryParams): Promise<string | null>;
6
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UploadFolderService = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const errors_utils_1 = require("../../../utils/errors.utils");
6
+ const logger_utils_1 = require("../../../utils/logger.utils");
7
+ const drive_folder_service_1 = require("../../drive/drive-folder.service");
8
+ const upload_types_1 = require("./upload.types");
9
+ class UploadFolderService {
10
+ static instance = new UploadFolderService();
11
+ async createFolders({ foldersToCreate, destinationFolderUuid, currentProgress, emitProgress, }) {
12
+ const folderMap = new Map();
13
+ for (const folder of foldersToCreate) {
14
+ const parentPath = (0, node_path_1.dirname)(folder.relativePath);
15
+ const parentUuid = parentPath === '.' ? destinationFolderUuid : folderMap.get(parentPath);
16
+ if (!parentUuid) {
17
+ logger_utils_1.logger.warn(`Parent folder not found for ${folder.relativePath}, skipping...`);
18
+ continue;
19
+ }
20
+ const createdFolderUuid = await this.createFolderWithRetry({
21
+ folderName: folder.name,
22
+ parentFolderUuid: parentUuid,
23
+ });
24
+ if (createdFolderUuid) {
25
+ folderMap.set(folder.relativePath, createdFolderUuid);
26
+ currentProgress.itemsUploaded++;
27
+ emitProgress();
28
+ }
29
+ }
30
+ return folderMap;
31
+ }
32
+ async createFolderWithRetry({ folderName, parentFolderUuid }) {
33
+ for (let attempt = 0; attempt <= upload_types_1.MAX_RETRIES; attempt++) {
34
+ try {
35
+ const [createFolderPromise] = drive_folder_service_1.DriveFolderService.instance.createFolder({
36
+ plainName: folderName,
37
+ parentFolderUuid,
38
+ });
39
+ const createdFolder = await createFolderPromise;
40
+ return createdFolder.uuid;
41
+ }
42
+ catch (error) {
43
+ if ((0, errors_utils_1.isAlreadyExistsError)(error)) {
44
+ logger_utils_1.logger.info(`Folder ${folderName} already exists, skipping...`);
45
+ return null;
46
+ }
47
+ if (attempt < upload_types_1.MAX_RETRIES) {
48
+ const delay = upload_types_1.DELAYS_MS[attempt];
49
+ logger_utils_1.logger.warn(`Failed to create folder ${folderName},
50
+ retrying in ${delay}ms... (attempt ${attempt + 1}/${upload_types_1.MAX_RETRIES + 1})`);
51
+ await new Promise((resolve) => setTimeout(resolve, delay));
52
+ }
53
+ else {
54
+ logger_utils_1.logger.error(`Failed to create folder ${folderName} after ${upload_types_1.MAX_RETRIES + 1} attempts`);
55
+ throw error;
56
+ }
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ }
62
+ exports.UploadFolderService = UploadFolderService;