@trap_stevo/filetide 0.0.14 → 0.0.16

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.
@@ -8,11 +8,15 @@ const {
8
8
  FileNetClientManager
9
9
  } = require("./HUDManagers/FileNetClientManager");
10
10
  const {
11
- FileUtilityManager
12
- } = require("./HUDManagers/FileUtilityManager");
11
+ FileTransferManager
12
+ } = require("./HUDManagers/FileTransferManager");
13
13
  class FileMessager {
14
- constructor(options = {}) {
14
+ constructor(options = {}, transportOptions = {
15
+ parallelChunks: 3,
16
+ maxRetries: 3
17
+ }) {
15
18
  const serverOptions = FileMessagerConfigManager.getServerOptions(options);
19
+ this.transporter = new FileTransferManager(transportOptions);
16
20
  this.fileNet = new IoTide(serverOptions.port, serverOptions, true, this.onConnect.bind(this), this.onDisconnect.bind(this));
17
21
  this.onlineClients = new Map();
18
22
  return;
@@ -39,98 +43,98 @@ class FileMessager {
39
43
  }
40
44
 
41
45
  /**
42
- * Send file to a specific client
46
+ * Send file chunk to a specific client
43
47
  * @param {String} senderID - The ID of the sender (tide ID)
44
48
  * @param {String} clientId - The ID of the client (socket ID)
45
- * @param {Buffer} fileData - The file data to send
49
+ * @param {Buffer} fileChunk - The file chunk to send
46
50
  * @param {String} fileName - The name of the file
51
+ * @param {Number} chunkIndex - The index of the current chunk
47
52
  */
48
- sendFileToClient(senderID, clientId, fileData, fileName, filePath = process.cwd()) {
49
- if (!FileNetClientManager.getOnlineClient(clientId)) {
50
- console.log(`Client ~ ${clientId} not authorized.`);
51
- return;
52
- }
53
- const senderConnectionID = FileNetClientManager.getOnlineClient(senderID);
54
- const connectionID = FileNetClientManager.getOnlineClient(clientId);
55
- const senderClient = this.onlineClients.get(connectionID);
56
- const client = this.onlineClients.get(connectionID);
53
+ sendFileChunkToClient(senderID, clientId, fileChunk, fileName, chunkIndex, totalChunks, filePath = process.cwd()) {
54
+ const client = this.onlineClients.get(clientId);
57
55
  if (!client) {
58
56
  console.log(`Client ~ ${clientId} not found.`);
59
57
  return;
60
58
  }
61
- client.emit("incoming-file", {
59
+ if (chunkIndex === 0) {
60
+ client.emit("incoming-file", {
61
+ fileName,
62
+ totalChunks,
63
+ chunkIndex,
64
+ path: filePath
65
+ });
66
+ }
67
+ client.emit("transfer-progress", {
62
68
  fileName,
63
- fileData,
69
+ fileChunk,
70
+ chunkIndex,
64
71
  path: filePath
65
72
  });
66
- console.log(`[FileTide ~ File Messager] ~ File ${fileName} sent to client ~ ${clientId}!`);
73
+ console.log(`[FileTide ~ File Messager] ~ Sent chunk ${chunkIndex} of ${fileName} to client ~ ${clientId}!`);
74
+ const senderClient = this.onlineClients.get(senderID);
67
75
  if (senderClient) {
68
76
  senderClient.emit("transfer-status", {
69
- status: `[FileTide ~ File Messager] ~ File ${fileName} sent to client ~ ${clientId}!`,
77
+ status: `Chunk ${chunkIndex} of ${fileName} sent to client ${clientId}!`,
70
78
  recipientID: clientId,
71
79
  success: true
72
80
  });
73
81
  }
74
- return;
75
82
  }
76
-
77
- /**
78
- * Handle file transfer requests between clients
79
- * @param {Object} transferData - The data containing the sender, recipient, and file details
80
- */
81
83
  handleClientToClientTransfer(transferData) {
82
84
  const {
83
85
  senderID,
84
86
  recipientId,
85
87
  fileName,
86
- fileData,
87
- filePath
88
+ fileChunk,
89
+ filePath,
90
+ chunkIndex,
91
+ totalChunks
88
92
  } = transferData;
89
- this.sendFileToClient(senderID, recipientId, fileData, fileName, filePath);
90
- return;
93
+ console.log(`Transferring file ~ ${fileName} | chunk ${chunkIndex + 1} of ${totalChunks} to client (${recipientId})`);
94
+ this.sendFileChunkToClient(FileNetClientManager.getOnlineClient(senderID).id, FileNetClientManager.getOnlineClient(recipientId).id, fileChunk, fileName, chunkIndex, totalChunks, filePath);
95
+ if (chunkIndex + 1 === totalChunks) {
96
+ this.fileNet.emit("transfer-complete", {
97
+ senderID,
98
+ recipientId,
99
+ fileName,
100
+ filePath
101
+ });
102
+ console.log(`[FileTide ~ File Messager] ~ File transfer completed: ${fileName} from ${senderID} to ${recipientId}`);
103
+ }
104
+ }
105
+ handleFileTransferStart(fileData, emitToChannel) {
106
+ console.log(`Starting file transfer: ${fileData.fileName}`);
107
+ }
108
+ handleFileTransferProgress(data, emitToChannel) {
109
+ console.log(`Progress: ${data.progress}`);
110
+ }
111
+ handleFileTransferComplete(data) {
112
+ console.log(`File transfer completed: ${data.fileName}`);
91
113
  }
92
114
  handleClientsOnline(clientID) {
93
- if (!FileNetClientManager.getOnlineClient(clientID)) {
115
+ const connectionDetails = FileNetClientManager.getOnlineClient(clientID);
116
+ if (!connectionDetails) {
94
117
  console.log(`Client ~ ${clientID} not authorized.`);
95
118
  return;
96
119
  }
97
- const connectionID = FileNetClientManager.getOnlineClient(clientID);
98
- const client = this.onlineClients.get(connectionID);
120
+ const client = this.onlineClients.get(connectionDetails.id);
99
121
  if (!client) {
100
122
  console.log(`Client ~ ${clientID} not found.`);
101
123
  return;
102
124
  }
103
- client.emit("current-clients-online", {
104
- currentClients: this.messagerClients
105
- });
125
+ this.fileNet.emitToTide(connectionDetails.tideID, "current-clients-online", Object.fromEntries(FileNetClientManager.getOnlineClients()));
106
126
  return;
107
127
  }
108
- handleClientOnline(clientID, connectionID) {
128
+ handleClientOnline(clientID, tideID, pClientID, connectionID) {
109
129
  if (!this.onlineClients.has(connectionID)) {
110
130
  return;
111
131
  }
112
- FileNetClientManager.addOnlineClient(clientID, "", connectionID);
113
- console.log("Current online messagers: ", FileNetClientManager.getOnlineClients());
132
+ FileNetClientManager.addOnlineClient(clientID, pClientID, tideID, connectionID);
133
+ this.fileNet.emitToTide(tideID, "current-online-clients", Object.fromEntries(FileNetClientManager.getOnlineClients()));
114
134
  return;
115
135
  }
116
136
  handleClientOffline(clientID) {
117
- if (!FileNetClientManager.getOnlineClient(clientID)) {
118
- return;
119
- }
120
137
  FileNetClientManager.clearOnlineClient(clientID);
121
- console.log(this.onlineClients, FileNetClientManager.getOnlineClients());
122
- return;
123
- }
124
- handleFileTransferStart(fileData, emitToChannel) {
125
- console.log(`Starting file transfer: ${fileData.fileName}`);
126
- return;
127
- }
128
- handleFileTransferProgress(data, emitToChannel) {
129
- console.log(`Progress: ${data.progress}`);
130
- return;
131
- }
132
- handleFileTransferComplete(data) {
133
- console.log(`File transfer completed: ${data.fileName}`);
134
138
  return;
135
139
  }
136
140
  }
@@ -6,12 +6,20 @@ const {
6
6
  const {
7
7
  FileMessagerConfigManager
8
8
  } = require("./HUDManagers/FileMessagerConfigManager");
9
+ const {
10
+ FileTransferManager
11
+ } = require("./HUDManagers/FileTransferManager");
9
12
  const {
10
13
  FileUtilityManager
11
14
  } = require("./HUDManagers/FileUtilityManager");
15
+ const path = require("path");
12
16
  class FileMessagerClient {
13
- constructor(options = {}) {
17
+ constructor(options = {}, transportOptions = {
18
+ parallelChunks: 3,
19
+ maxRetries: 3
20
+ }) {
14
21
  this.clientOptions = FileMessagerConfigManager.getClientOptions(options);
22
+ this.transporter = new FileTransferManager(transportOptions);
15
23
  this.clientTide = new HUDIoTide();
16
24
  this.clients = new Map();
17
25
  }
@@ -34,15 +42,71 @@ class FileMessagerClient {
34
42
  });
35
43
  });
36
44
  }
37
- sendFile(clientID, recipientId, fileName, fileData, filePath = process.cwd()) {
38
- this.clientTide.emitEvent(clientID, "client-to-client-transfer", {
39
- senderID: clientID,
40
- recipientId,
41
- fileName,
42
- fileData,
43
- filePath
45
+
46
+ /**
47
+ * Sends all files in a directory in parallel using the sendFile method.
48
+ * @param {string} clientID - The ID of the client sending the files.
49
+ * @param {string} recipientId - The ID of the recipient.
50
+ * @param {Array} filesData - Array of file data objects from the directory.
51
+ * @param {string} baseDirectory - The base directory path that contains the files.
52
+ * @param {string} destinationPath - The path to which the files should be sent.
53
+ */
54
+ sendDirectoryFiles(clientID, recipientId, filesData, baseDirectory, destinationPath) {
55
+ const baseDirectoryName = path.basename(baseDirectory);
56
+ const adjustedDestinationPath = path.join(destinationPath, baseDirectoryName);
57
+ const sendFilePromises = filesData.map(fileInfo => {
58
+ const relativeFilePath = path.dirname(path.relative(baseDirectory, fileInfo.filePath));
59
+ const fileDestinationPath = path.join(adjustedDestinationPath, relativeFilePath);
60
+ return this.sendFile(clientID, recipientId, fileInfo.fileName, fileInfo.fileData, fileDestinationPath);
44
61
  });
45
- return;
62
+ Promise.all(sendFilePromises).then(() => console.log("All files in directory transferred successfully!")).catch(error => console.error("Did not transfer files: ", error));
63
+ }
64
+ sendFile(clientID, recipientId, fileName, file, filePath = process.cwd()) {
65
+ console.log(fileName, ": ", file);
66
+ if (!file) {
67
+ return;
68
+ }
69
+ this.transporter.sendFile(file, {
70
+ onSendChunk: (transferId, chunkIndex, chunkData, totalChunks) => {
71
+ return new Promise((resolve, reject) => {
72
+ this.clientTide.emitEvent(clientID, "client-to-client-transfer", {
73
+ senderID: clientID,
74
+ recipientId,
75
+ fileName,
76
+ fileChunk: chunkData,
77
+ totalChunks,
78
+ chunkIndex,
79
+ filePath
80
+ });
81
+ console.log(`Chunk ${chunkIndex} sent successfully!`);
82
+ resolve();
83
+ });
84
+ },
85
+ onComplete: () => {
86
+ return new Promise((resolve, reject) => {
87
+ console.log({
88
+ senderID: clientID,
89
+ recipientId,
90
+ fileName,
91
+ filePath
92
+ });
93
+ this.clientTide.emitEvent(clientID, "transfer-complete", {
94
+ senderID: clientID,
95
+ recipientId,
96
+ fileName,
97
+ filePath
98
+ });
99
+ resolve();
100
+ });
101
+ },
102
+ onProgress: progress => {
103
+ console.log(`[FileTide ~ File Messager] ~ Progress: ${progress.toFixed(2)}%`);
104
+ },
105
+ fileDetails: {
106
+ name: fileName,
107
+ path: filePath
108
+ }
109
+ }).then(() => console.log('File transfer complete!')).catch(error => console.error('File transfer failed:', error));
46
110
  }
47
111
  onFileChunkReceived(userID) {
48
112
  this.clientTide.onEvent(userID, "transfer-progress", data => {
@@ -3,13 +3,18 @@
3
3
  function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
4
4
  function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
5
5
  function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
6
+ const chalk = require("chalk");
7
+ const path = require("path");
8
+ const fs = require("fs");
6
9
  const {
7
10
  FileNetConfigManager
8
11
  } = require("./HUDManagers/FileNetConfigManager.js");
12
+ const {
13
+ FileUtilityManager
14
+ } = require("./HUDManagers/FileUtilityManager.js");
15
+ const ConsoleTable = require("./HUDComponents/ConsoleTable.js");
9
16
  const FileMessagerClient = require("./FileMessagerClient");
10
17
  const FileMessager = require("./FileMessager");
11
- const path = require("path");
12
- const fs = require("fs");
13
18
  var _FileTide_brand = /*#__PURE__*/new WeakSet();
14
19
  class FileTide {
15
20
  constructor() {
@@ -60,6 +65,16 @@ class FileTide {
60
65
  }
61
66
  return;
62
67
  }
68
+ getDirectoryData(directoryPath) {
69
+ return FileUtilityManager.getDirectoryData(directoryPath);
70
+ }
71
+ getFileData(filePath) {
72
+ return FileUtilityManager.getFileData(filePath);
73
+ }
74
+ getPathData(inputPath) {
75
+ return FileUtilityManager.getPathData(inputPath);
76
+ }
77
+
63
78
  /**
64
79
  * Send a file directly to a device without it needing to create a client.
65
80
  * @param {String} senderID - The ID of the client device that sent the file
@@ -80,8 +95,54 @@ class FileTide {
80
95
  return;
81
96
  }
82
97
  console.log(`[FileTide] ~ Sending file to client ~ ${userID}...`);
83
- this.fileNet.sendFileToClient(senderClient.tideID, currentClient.id, fileData, filePath, fileName);
84
- console.log(`[FileTide] ~ File sent to client ~ ${userID}!`);
98
+ console.log({
99
+ recipientId: userID,
100
+ senderID,
101
+ fileName,
102
+ filePath,
103
+ fileData
104
+ });
105
+ this.fileNet.transporter.sendFile(fileData, {
106
+ onSendChunk: (transferId, chunkIndex, chunkData, totalChunks) => {
107
+ return new Promise((resolve, reject) => {
108
+ this.clientTide.emitEvent(senderID, "client-to-client-transfer", {
109
+ recipientId: userID,
110
+ senderID,
111
+ fileName,
112
+ fileChunk: chunkData,
113
+ totalChunks,
114
+ chunkIndex,
115
+ filePath
116
+ }, ack => {
117
+ if (ack.success) {
118
+ console.log(`Chunk ${chunkIndex} sent successfully!`);
119
+ resolve();
120
+ } else {
121
+ console.error(`Did not send chunk ${chunkIndex}`);
122
+ reject(new Error('Chunk failed.'));
123
+ }
124
+ });
125
+ });
126
+ },
127
+ onComplete: transferId => {
128
+ return new Promise((resolve, reject) => {
129
+ this.clientTide.emitEvent(senderID, "transfer-complete", {
130
+ recipientId: userID,
131
+ senderID,
132
+ fileName,
133
+ filePath
134
+ });
135
+ resolve();
136
+ });
137
+ },
138
+ onProgress: progress => {
139
+ console.log(`[FileTide ~ File Messager] ~ Progress: ${progress.toFixed(2)}%`);
140
+ },
141
+ fileDetails: {
142
+ name: fileName,
143
+ path: filePath
144
+ }
145
+ }).then(() => console.log("File transfer complete!")).catch(error => console.error("File transfer incomplete: ", error));
85
146
  return;
86
147
  }
87
148
  console.log(`[FileTide] ~ No active client ~ ${userID} found.`);
@@ -142,22 +203,90 @@ class FileTide {
142
203
  }
143
204
  }
144
205
  function _setupFileEventListeners(userID, client, onIncomingFile) {
206
+ const cliTable = new ConsoleTable({
207
+ padding: 2,
208
+ headerAlign: "center",
209
+ cellAlign: "left",
210
+ borderStyle: "bold",
211
+ columnOrder: ["clientID", "id", "tideID"],
212
+ columnNames: {
213
+ clientID: "Client Name",
214
+ id: "Identifier",
215
+ tideID: "Tide Identifier"
216
+ },
217
+ title: "✨ Online ✨",
218
+ tableNumber: 1,
219
+ gradient: {
220
+ title: ["#00FFFF", "#1E90FF"],
221
+ header: ["#FFD700", "#FF8C00"],
222
+ border: ["#2e2f30", "#788e9e"],
223
+ cell: ["#00BFFF", "#1E90FF"]
224
+ },
225
+ cellColor: (content, column) => {
226
+ if (column === "clientID") {
227
+ return chalk.blue(content);
228
+ }
229
+ return content;
230
+ }
231
+ });
232
+ let activeTransfers = new Map();
233
+ client.clientTide.onEvent(userID, "current-online-clients", data => {
234
+ const currentClients = {};
235
+ Object.keys(data).forEach(key => {
236
+ if (key !== userID) {
237
+ currentClients[key] = data[key];
238
+ }
239
+ });
240
+ if (Object.keys(currentClients).length <= 0) {
241
+ return;
242
+ }
243
+ cliTable.setTitle(`✨ Online >${Object.keys(currentClients).length}< ✨`, 1);
244
+ cliTable.setData(currentClients);
245
+ console.log(`\n[FileTide ~ FileNet]\n\n`, cliTable.render(), "\n");
246
+ });
145
247
  client.clientTide.onEvent(userID, "incoming-file", data => {
146
- console.log(`[${userID}] Received file: ${data.fileName}`);
248
+ console.log(`[${userID}] Preparing to receive file : ${data.fileName}`);
249
+ activeTransfers.set(data.fileName, {
250
+ receivedChunks: [],
251
+ totalChunks: data.totalChunks,
252
+ fileInfo: data
253
+ });
147
254
  const saveDir = data.path;
148
255
  if (!fs.existsSync(saveDir)) {
149
256
  fs.mkdirSync(saveDir, {
150
257
  recursive: true
151
258
  });
152
- console.log(`[${userID}] Created directory: ${saveDir}`);
259
+ console.log(`[${userID}] Created directory : ${saveDir}`);
260
+ }
261
+ });
262
+ client.clientTide.onEvent(userID, "transfer-progress", data => {
263
+ if (!data || data.fileChunk === undefined || data.fileChunk === null) {
264
+ return;
265
+ }
266
+ const transferState = activeTransfers.get(data.fileName);
267
+ if (!transferState) {
268
+ return;
153
269
  }
154
- const savePath = path.join(saveDir, data.fileName);
155
- fs.writeFileSync(savePath, Buffer.from(data.fileData));
156
- console.log(`[${userID}] File saved at: ${savePath}`);
270
+ console.log(`[${userID}] Receiving chunk ${data.chunkIndex + 1}/${transferState.totalChunks} for file : ${data.fileName}`);
271
+ transferState.receivedChunks[data.chunkIndex] = Buffer.from(data.fileChunk);
272
+ const progress = (data.chunkIndex + 1) / transferState.totalChunks * 100;
273
+ console.log(`[${userID}] Progress : ${progress.toFixed(2)}%`);
157
274
  if (onIncomingFile) {
158
275
  onIncomingFile(data);
159
276
  }
160
277
  });
278
+ client.clientTide.onEvent(userID, "transfer-complete", data => {
279
+ console.log(`[${userID}] File transfer complete : ${data.fileName}`);
280
+ const transferState = activeTransfers.get(data.fileName);
281
+ if (!transferState) {
282
+ return;
283
+ }
284
+ const savePath = path.join(data.filePath, data.fileName);
285
+ const fullFile = Buffer.concat(transferState.receivedChunks);
286
+ fs.writeFileSync(savePath, fullFile);
287
+ console.log(`[${userID}] File saved at : ${savePath}`);
288
+ activeTransfers.delete(data.fileName);
289
+ });
161
290
  return;
162
291
  }
163
292
  ;
@@ -0,0 +1,349 @@
1
+ "use strict";
2
+
3
+ const chalk = require("chalk");
4
+ class ConsoleTable {
5
+ constructor(options = {}) {
6
+ this.options = {
7
+ padding: 1,
8
+ headerAlign: "center",
9
+ cellAlign: "left",
10
+ borderStyle: "solid",
11
+ headerColor: null,
12
+ borderColor: null,
13
+ cellColor: (content, columnName, rowData) => content,
14
+ columnOrder: null,
15
+ columnNames: {},
16
+ title: "",
17
+ tableNumber: null,
18
+ gradient: {
19
+ title: null,
20
+ header: null,
21
+ border: null,
22
+ cell: null
23
+ },
24
+ ...options
25
+ };
26
+ this.borderChars = this.getBorderChars(this.options.borderStyle);
27
+ }
28
+ getBorderChars(style) {
29
+ const styles = {
30
+ solid: {
31
+ topLeft: "┌",
32
+ topRight: "┐",
33
+ bottomLeft: "└",
34
+ bottomRight: "┘",
35
+ horizontal: "─",
36
+ vertical: "│",
37
+ topJoin: "┬",
38
+ bottomJoin: "┴",
39
+ leftJoin: "├",
40
+ rightJoin: "┤",
41
+ center: "┼"
42
+ },
43
+ double: {
44
+ topLeft: "╔",
45
+ topRight: "╗",
46
+ bottomLeft: "╚",
47
+ bottomRight: "╝",
48
+ horizontal: "═",
49
+ vertical: "║",
50
+ topJoin: "╦",
51
+ bottomJoin: "╩",
52
+ leftJoin: "╠",
53
+ rightJoin: "╣",
54
+ center: "╬"
55
+ },
56
+ round: {
57
+ topLeft: "╭",
58
+ topRight: "╮",
59
+ bottomLeft: "╰",
60
+ bottomRight: "╯",
61
+ horizontal: "─",
62
+ vertical: "│",
63
+ topJoin: "┬",
64
+ bottomJoin: "┴",
65
+ leftJoin: "├",
66
+ rightJoin: "┤",
67
+ center: "┼"
68
+ },
69
+ bold: {
70
+ topLeft: "┏",
71
+ topRight: "┓",
72
+ bottomLeft: "┗",
73
+ bottomRight: "┛",
74
+ horizontal: "━",
75
+ vertical: "┃",
76
+ topJoin: "┳",
77
+ bottomJoin: "┻",
78
+ leftJoin: "┣",
79
+ rightJoin: "┫",
80
+ center: "╋"
81
+ }
82
+ };
83
+ return styles[style] || styles["solid"];
84
+ }
85
+ setData(data) {
86
+ if (Array.isArray(data)) {
87
+ this.data = data;
88
+ } else if (typeof data === "object") {
89
+ this.data = Object.values(data);
90
+ } else {
91
+ throw new Error("Array or an object only.");
92
+ }
93
+ this.extractColumns();
94
+ this.calculateColumnWidths();
95
+ }
96
+ extractColumns() {
97
+ if (this.options.columnOrder && Array.isArray(this.options.columnOrder)) {
98
+ this.columns = this.options.columnOrder;
99
+ } else {
100
+ const columnsSet = new Set();
101
+ this.data.forEach(item => {
102
+ Object.keys(item).forEach(key => columnsSet.add(key));
103
+ });
104
+ this.columns = Array.from(columnsSet);
105
+ }
106
+ }
107
+ calculateColumnWidths() {
108
+ const {
109
+ padding
110
+ } = this.options;
111
+ this.columnWidths = {};
112
+ this.totalWidth = 0;
113
+ this.columns.forEach(column => {
114
+ const headerName = this.options.columnNames[column] || column;
115
+ let maxWidth = String(headerName).length;
116
+ this.data.forEach(item => {
117
+ const cellContent = item[column] !== undefined ? String(item[column]) : "";
118
+ maxWidth = Math.max(maxWidth, cellContent.length);
119
+ });
120
+ const columnWidth = maxWidth + padding * 2;
121
+ this.columnWidths[column] = columnWidth;
122
+ this.totalWidth += columnWidth;
123
+ });
124
+ this.totalWidth += this.columns.length + 1;
125
+ }
126
+ alignText(text, width, align) {
127
+ const {
128
+ padding
129
+ } = this.options;
130
+ const textLength = text.length;
131
+ let totalPadding = width - textLength;
132
+ let leftPadding = " ".repeat(padding);
133
+ let rightPadding = " ".repeat(padding);
134
+ if (totalPadding < padding * 2) {
135
+ leftPadding = "";
136
+ rightPadding = "";
137
+ } else {
138
+ totalPadding -= padding * 2;
139
+ if (align === "left") {
140
+ rightPadding += " ".repeat(totalPadding);
141
+ } else if (align === "right") {
142
+ leftPadding += " ".repeat(totalPadding);
143
+ } else {
144
+ const half = Math.floor(totalPadding / 2);
145
+ leftPadding += " ".repeat(half);
146
+ rightPadding += " ".repeat(totalPadding - half);
147
+ }
148
+ }
149
+ return leftPadding + text + rightPadding;
150
+ }
151
+ generateGradientText(text, startColor, endColor) {
152
+ const length = text.length;
153
+ const startRGB = this.hexToRgb(startColor);
154
+ const endRGB = this.hexToRgb(endColor);
155
+ let gradientText = "";
156
+ for (let i = 0; i < length; i++) {
157
+ const ratio = i / (length - 1 || 1);
158
+ const color = {
159
+ r: Math.round(startRGB.r + ratio * (endRGB.r - startRGB.r)),
160
+ g: Math.round(startRGB.g + ratio * (endRGB.g - startRGB.g)),
161
+ b: Math.round(startRGB.b + ratio * (endRGB.b - startRGB.b))
162
+ };
163
+ gradientText += chalk.rgb(color.r, color.g, color.b)(text[i]);
164
+ }
165
+ return gradientText;
166
+ }
167
+ hexToRgb(hex) {
168
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
169
+ return result ? {
170
+ r: parseInt(result[1], 16),
171
+ g: parseInt(result[2], 16),
172
+ b: parseInt(result[3], 16)
173
+ } : null;
174
+ }
175
+ generateBorder(type) {
176
+ const {
177
+ columns,
178
+ columnWidths
179
+ } = this;
180
+ let {
181
+ horizontal,
182
+ vertical,
183
+ topLeft,
184
+ topRight,
185
+ bottomLeft,
186
+ bottomRight,
187
+ topJoin,
188
+ bottomJoin,
189
+ leftJoin,
190
+ rightJoin,
191
+ center
192
+ } = this.borderChars;
193
+ const {
194
+ borderColor
195
+ } = this.options;
196
+ const borderGradient = this.options.gradient.border;
197
+ if (borderGradient && borderGradient.length === 2) {
198
+ const chars = [topLeft, topRight, bottomLeft, bottomRight, horizontal, vertical, topJoin, bottomJoin, leftJoin, rightJoin, center];
199
+ const coloredChars = {};
200
+ chars.forEach((char, index) => {
201
+ coloredChars[char] = this.generateGradientText(char, borderGradient[0], borderGradient[1]);
202
+ });
203
+ bottomRight = coloredChars[bottomRight];
204
+ bottomLeft = coloredChars[bottomLeft];
205
+ topRight = coloredChars[topRight];
206
+ topLeft = coloredChars[topLeft];
207
+ horizontal = coloredChars[horizontal];
208
+ vertical = coloredChars[vertical];
209
+ bottomJoin = coloredChars[bottomJoin];
210
+ rightJoin = coloredChars[rightJoin];
211
+ leftJoin = coloredChars[leftJoin];
212
+ topJoin = coloredChars[topJoin];
213
+ center = coloredChars[center];
214
+ } else if (borderColor) {
215
+ bottomRight = borderColor(bottomRight);
216
+ bottomLeft = borderColor(bottomLeft);
217
+ topRight = borderColor(topRight);
218
+ topLeft = borderColor(topLeft);
219
+ horizontal = borderColor(horizontal);
220
+ vertical = borderColor(vertical);
221
+ bottomJoin = borderColor(bottomJoin);
222
+ rightJoin = borderColor(rightJoin);
223
+ leftJoin = borderColor(leftJoin);
224
+ topJoin = borderColor(topJoin);
225
+ center = borderColor(center);
226
+ }
227
+ let border = "";
228
+ columns.forEach((column, index) => {
229
+ const first = index === 0;
230
+ const last = index === columns.length - 1;
231
+ const width = columnWidths[column];
232
+ const line = horizontal.repeat(width);
233
+ if (type === "top") {
234
+ border += first ? topLeft : topJoin;
235
+ } else if (type === "bottom") {
236
+ border += first ? bottomLeft : bottomJoin;
237
+ } else {
238
+ border += first ? leftJoin : center;
239
+ }
240
+ border += line;
241
+ if (last) {
242
+ if (type === "top") {
243
+ border += topRight;
244
+ } else if (type === "bottom") {
245
+ border += bottomRight;
246
+ } else {
247
+ border += rightJoin;
248
+ }
249
+ }
250
+ });
251
+ return border;
252
+ }
253
+ generateRow(rowData, headerRow = false) {
254
+ const {
255
+ columns,
256
+ columnWidths
257
+ } = this;
258
+ let {
259
+ vertical
260
+ } = this.borderChars;
261
+ const {
262
+ headerAlign,
263
+ cellAlign,
264
+ headerColor,
265
+ cellColor
266
+ } = this.options;
267
+ const align = headerRow ? headerAlign : cellAlign;
268
+ const headerGradient = this.options.gradient.header;
269
+ const cellGradient = this.options.gradient.cell;
270
+ const borderGradient = this.options.gradient.border;
271
+ if (borderGradient && borderGradient.length === 2) {
272
+ vertical = this.generateGradientText(vertical, borderGradient[0], borderGradient[1]);
273
+ } else if (this.options.borderColor) {
274
+ vertical = this.options.borderColor(vertical);
275
+ }
276
+ let row = "";
277
+ columns.forEach((column, index) => {
278
+ const content = rowData[column] !== undefined ? String(rowData[column]) : "";
279
+ const width = columnWidths[column];
280
+ const alignedText = this.alignText(content, width, align);
281
+ let cellContent = alignedText;
282
+ const first = index === 0;
283
+ if (headerRow) {
284
+ if (headerGradient && headerGradient.length === 2) {
285
+ cellContent = this.generateGradientText(cellContent, headerGradient[0], headerGradient[1]);
286
+ } else if (headerColor) {
287
+ cellContent = headerColor(cellContent);
288
+ }
289
+ } else {
290
+ if (cellGradient && cellGradient.length === 2) {
291
+ cellContent = this.generateGradientText(cellContent, cellGradient[0], cellGradient[1]);
292
+ } else if (cellColor) {
293
+ cellContent = cellColor(cellContent, column, rowData);
294
+ }
295
+ }
296
+ if (first) {
297
+ row += vertical;
298
+ }
299
+ row += cellContent + vertical;
300
+ });
301
+ return row;
302
+ }
303
+ setTitle(title, tableNumber = null) {
304
+ this.options.title = title;
305
+ if (tableNumber !== null) {
306
+ this.options.tableNumber = tableNumber;
307
+ }
308
+ }
309
+ renderTitle() {
310
+ let titleText = this.options.title;
311
+ if (this.options.tableNumber !== null) {
312
+ titleText = titleText;
313
+ }
314
+ const totalWidth = this.totalWidth - 2;
315
+ const titleLength = titleText.length;
316
+ let leftPadding = Math.floor((totalWidth - titleLength) / 2);
317
+ if (leftPadding < 0) leftPadding = 0;
318
+ let titleLine = " ".repeat(leftPadding) + titleText;
319
+ const titleGradient = this.options.gradient.title;
320
+ if (titleGradient && titleGradient.length === 2) {
321
+ titleLine = this.generateGradientText(titleLine.trim(), titleGradient[0], titleGradient[1]);
322
+ }
323
+ return titleLine;
324
+ }
325
+ render() {
326
+ const {
327
+ data
328
+ } = this;
329
+ if (!data || data.length === 0) return "";
330
+ const lines = [];
331
+ if (this.options.title) {
332
+ const title = this.renderTitle();
333
+ lines.push(title);
334
+ }
335
+ lines.push(this.generateBorder("top"));
336
+ const headerData = {};
337
+ this.columns.forEach(column => {
338
+ headerData[column] = this.options.columnNames[column] || column;
339
+ });
340
+ lines.push(this.generateRow(headerData, true));
341
+ lines.push(this.generateBorder("middle"));
342
+ data.forEach(rowData => {
343
+ lines.push(this.generateRow(rowData));
344
+ });
345
+ lines.push(this.generateBorder("bottom"));
346
+ return lines.join("\n");
347
+ }
348
+ }
349
+ module.exports = ConsoleTable;
@@ -2,14 +2,15 @@
2
2
 
3
3
  const currentOnlineClients = new Map();
4
4
  class FileNetClientManager {
5
- static addOnlineClient(clientID, pClientID, tideID) {
5
+ static addOnlineClient(clientID, pClientID, tideID, id) {
6
6
  if (currentOnlineClients.get(pClientID)) {
7
7
  currentOnlineClients.delete(pClientID);
8
8
  }
9
9
  currentOnlineClients.set(clientID, {
10
10
  pClientID: pClientID || "",
11
11
  clientID,
12
- tideID
12
+ tideID,
13
+ id
13
14
  });
14
15
  return currentOnlineClients;
15
16
  }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+
3
+ function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
4
+ function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
5
+ function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
6
+ const fs = require("fs");
7
+ var _FileTransferManager_brand = /*#__PURE__*/new WeakSet();
8
+ class FileTransferManager {
9
+ constructor({
10
+ chunkSize = 512 * 1024,
11
+ parallelChunks = 5,
12
+ maxRetries = 3
13
+ } = {}) {
14
+ _classPrivateMethodInitSpec(this, _FileTransferManager_brand);
15
+ this.parallelChunks = parallelChunks;
16
+ this.activeTransfers = new Map();
17
+ this.maxRetries = maxRetries;
18
+ this.chunkSize = chunkSize;
19
+ }
20
+ sendFile(file, {
21
+ fileDetails,
22
+ onSendChunk,
23
+ onComplete,
24
+ onProgress,
25
+ onStart
26
+ }) {
27
+ const transferId = _assertClassBrand(_FileTransferManager_brand, this, _generateTransferId).call(this, fileDetails);
28
+ return _assertClassBrand(_FileTransferManager_brand, this, _startTransfer).call(this, file, transferId, fileDetails, onSendChunk, onComplete, onProgress, onStart);
29
+ }
30
+ }
31
+ function _startTransfer(file, transferId, fileDetails, onSendChunk, onComplete, onProgress, onStart) {
32
+ const totalChunks = Math.ceil(file.length / this.chunkSize);
33
+ this.activeTransfers.set(transferId, {
34
+ totalChunks,
35
+ completedChunks: 0,
36
+ activeChunks: 0,
37
+ failedChunks: new Map(),
38
+ currentChunk: 0
39
+ });
40
+ const updateProgress = () => {
41
+ const transferState = this.activeTransfers.get(transferId);
42
+ if (onProgress) {
43
+ onProgress(transferState.completedChunks / transferState.totalChunks * 100);
44
+ }
45
+ };
46
+ return new Promise((resolve, reject) => {
47
+ const sendChunk = chunkIndex => {
48
+ const transferState = this.activeTransfers.get(transferId);
49
+ if (chunkIndex >= transferState.totalChunks) return;
50
+ const start = chunkIndex * this.chunkSize;
51
+ const end = Math.min(start + this.chunkSize, file.length);
52
+ const chunk = file.slice(start, end);
53
+ if (!fs.existsSync(fileDetails.path)) {
54
+ fs.mkdirSync(fileDetails.path, {
55
+ recursive: true
56
+ });
57
+ console.log(`[FileTide ~ Transporter] Created directory: ${fileDetails.path}`);
58
+ }
59
+ if (onStart && chunkIndex === 0) {
60
+ onStart({
61
+ name: fileDetails.name,
62
+ path: fileDetails.path,
63
+ totalChunks: transferState.totalChunks
64
+ });
65
+ }
66
+ transferState.activeChunks++;
67
+ onSendChunk(transferId, chunkIndex, chunk, transferState.totalChunks).then(() => {
68
+ transferState.completedChunks++;
69
+ transferState.activeChunks--;
70
+ updateProgress();
71
+ if (transferState.activeChunks < this.parallelChunks && transferState.currentChunk < transferState.totalChunks) {
72
+ sendChunk(transferState.currentChunk++);
73
+ }
74
+ if (transferState.completedChunks === transferState.totalChunks) {
75
+ onComplete(transferId).then(() => {
76
+ this.activeTransfers.delete(transferId);
77
+ resolve();
78
+ }).catch(reject);
79
+ }
80
+ }).catch(error => {
81
+ console.error(`Did not send chunk ${chunkIndex}: ${error.message}`);
82
+ transferState.activeChunks--;
83
+ if (transferState.failedChunks.get(chunkIndex) >= this.maxRetries) {
84
+ this.activeTransfers.delete(transferId);
85
+ reject(new Error(`Did not send chunk ${chunkIndex} after ${this.maxRetries} retries.`));
86
+ } else {
87
+ const retryCount = (transferState.failedChunks.get(chunkIndex) || 0) + 1;
88
+ transferState.failedChunks.set(chunkIndex, retryCount);
89
+ sendChunk(chunkIndex);
90
+ }
91
+ });
92
+ };
93
+ const transferState = this.activeTransfers.get(transferId);
94
+ for (let i = 0; i < Math.min(this.parallelChunks, transferState.totalChunks); i++) {
95
+ sendChunk(transferState.currentChunk++);
96
+ }
97
+ });
98
+ }
99
+ function _generateTransferId(file) {
100
+ return `${file.name}-${Date.now()}`;
101
+ }
102
+ module.exports = {
103
+ FileTransferManager
104
+ };
@@ -4,6 +4,115 @@ const path = require('path');
4
4
  const fs = require('fs');
5
5
  const filesPath = path.join(__dirname, '../files');
6
6
  class FileUtilityManager {
7
+ /**
8
+ * Recursively calculates the total size of files in a directory.
9
+ * @param {string} dirPath - The directory path.
10
+ * @returns {number} - The total size in bytes of the directory's contents.
11
+ */
12
+ calculateDirectorySize(dirPath) {
13
+ let totalSize = 0;
14
+ const files = fs.readdirSync(dirPath);
15
+ files.forEach(file => {
16
+ const filePath = path.join(dirPath, file);
17
+ const stats = fs.statSync(filePath);
18
+ if (stats.isDirectory()) {
19
+ totalSize += this.calculateDirectorySize(filePath);
20
+ } else {
21
+ totalSize += stats.size;
22
+ }
23
+ });
24
+ return totalSize;
25
+ }
26
+
27
+ /**
28
+ * Normalizes the input path to handle both single and double slashes.
29
+ * @param {string} inputPath - The path to normalize.
30
+ * @returns {string} - A normalized path with proper slashes.
31
+ */
32
+ static normalizePath(inputPath) {
33
+ return path.normalize(inputPath);
34
+ }
35
+
36
+ /**
37
+ * Gets all files' data from a given directory.
38
+ * @param {string} dirPath - The path to the directory.
39
+ * @returns {Array} - An array of objects containing file data, name, and path.
40
+ */
41
+ static getDirectoryData(inputDirPath) {
42
+ const dirPath = this.normalizePath(inputDirPath);
43
+ if (!fs.existsSync(dirPath)) {
44
+ console.log(`Directory at path ${dirPath} not found.`);
45
+ return [];
46
+ }
47
+ const filesData = [];
48
+ function readDirectory(currentPath) {
49
+ const fileList = fs.readdirSync(currentPath);
50
+ fileList.forEach(file => {
51
+ const filePath = path.join(currentPath, file);
52
+ const stats = fs.statSync(filePath);
53
+ if (stats.isFile()) {
54
+ const fileData = FileUtilityManager.getFileData(filePath);
55
+ if (fileData) {
56
+ filesData.push(fileData);
57
+ }
58
+ } else if (stats.isDirectory()) {
59
+ readDirectory(filePath);
60
+ }
61
+ });
62
+ }
63
+ readDirectory(dirPath);
64
+ return filesData;
65
+ }
66
+
67
+ /**
68
+ * Gets file data from a given file path.
69
+ * @param {string} inputFilePath - The full path to the file.
70
+ * @returns {Object|null} - An object containing file data, name, and path, or null if the file doesn't exist.
71
+ */
72
+ static getFileData(inputFilePath) {
73
+ const filePath = this.normalizePath(inputFilePath);
74
+ if (!fs.existsSync(filePath)) {
75
+ console.log(`File at path ${filePath} not found.`);
76
+ return null;
77
+ }
78
+ const fileData = fs.readFileSync(filePath, {
79
+ encoding: null
80
+ });
81
+ const fileName = path.basename(filePath);
82
+ return {
83
+ fileName,
84
+ fileData,
85
+ filePath
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Gets data from a file or directory based on the provided path.
91
+ * @param {string} inputPath - The path to a file or directory.
92
+ * @returns {Object|Array|null} - File data if it's a file, an array of file data if it's a directory, or null if the path doesn't exist.
93
+ */
94
+ static getPathData(inputPath) {
95
+ const currentInputPath = this.normalizePath(inputPath);
96
+ if (!fs.existsSync(currentInputPath)) {
97
+ console.log(`Path at ${currentInputPath} not found.`);
98
+ return null;
99
+ }
100
+ const stats = fs.statSync(currentInputPath);
101
+ if (stats.isFile()) {
102
+ return {
103
+ content: this.getFileData(currentInputPath),
104
+ type: "file"
105
+ };
106
+ } else if (stats.isDirectory()) {
107
+ return {
108
+ content: this.getDirectoryData(currentInputPath),
109
+ type: "directory"
110
+ };
111
+ }
112
+ console.log(`Path at ${currentInputPath} neither a file nor a directory.`);
113
+ return null;
114
+ }
115
+
7
116
  /**
8
117
  * Save a chunk of a file for a specific user to the specified path with a custom file name.
9
118
  * @param {String} userID - The ID of the user for whom the file is being saved.
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@trap_stevo/filetide",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "Revolutionizing real-time file transfer with seamless, instant communication across any device. Deliver files instantly, regardless of platform, and experience unparalleled speed and control in managing transfers. Elevate your file-sharing capabilities with a tool designed for precision, efficiency, and effortless connectivity.",
5
5
  "main": "dist/cjs/FileTide.js",
6
6
  "scripts": {
7
7
  "build": "babel src -d dist/cjs --env-name cjs",
8
+ "start-messager": "node demos/FileTideClientDemo.js",
9
+ "start-net": "node demos/FileTideNetDemo.js",
8
10
  "start": "node dist/cjs/FileTide.js"
9
11
  },
10
12
  "keywords": [
@@ -28,7 +30,8 @@
28
30
  "license": "ISC",
29
31
  "dependencies": {
30
32
  "@trap_stevo/iotide": "^0.0.36",
31
- "@trap_stevo/iotide-client": "^0.0.14",
33
+ "@trap_stevo/iotide-client": "^0.0.15",
34
+ "chalk": "^4.1.2",
32
35
  "readline": "^1.3.0"
33
36
  },
34
37
  "devDependencies": {