@trap_stevo/filetide 0.0.13 → 0.0.15

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.
@@ -5,13 +5,19 @@ const {
5
5
  FileMessagerConfigManager
6
6
  } = require("./HUDManagers/FileMessagerConfigManager");
7
7
  const {
8
- FileUtilityManager
9
- } = require("./HUDManagers/FileUtilityManager");
8
+ FileNetClientManager
9
+ } = require("./HUDManagers/FileNetClientManager");
10
+ const {
11
+ FileTransferManager
12
+ } = require("./HUDManagers/FileTransferManager");
10
13
  class FileMessager {
11
- constructor(options = {}) {
14
+ constructor(options = {}, transportOptions = {
15
+ parallelChunks: 3,
16
+ maxRetries: 3
17
+ }) {
12
18
  const serverOptions = FileMessagerConfigManager.getServerOptions(options);
19
+ this.transporter = new FileTransferManager(transportOptions);
13
20
  this.fileNet = new IoTide(serverOptions.port, serverOptions, true, this.onConnect.bind(this), this.onDisconnect.bind(this));
14
- this.messagerClients = new Map();
15
21
  this.onlineClients = new Map();
16
22
  return;
17
23
  }
@@ -37,96 +43,100 @@ class FileMessager {
37
43
  }
38
44
 
39
45
  /**
40
- * Send file to a specific client
46
+ * Send file chunk to a specific client
41
47
  * @param {String} senderID - The ID of the sender (tide ID)
42
48
  * @param {String} clientId - The ID of the client (socket ID)
43
- * @param {Buffer} fileData - The file data to send
49
+ * @param {Buffer} fileChunk - The file chunk to send
44
50
  * @param {String} fileName - The name of the file
51
+ * @param {Number} chunkIndex - The index of the current chunk
45
52
  */
46
- sendFileToClient(senderID, clientId, fileData, fileName, filePath = process.cwd()) {
47
- if (!this.messagerClients.has(clientId)) {
48
- console.log(`Client ~ ${clientId} not authorized.`);
49
- return;
50
- }
51
- const senderConnectionID = this.messagerClients.get(senderID);
52
- const connectionID = this.messagerClients.get(clientId);
53
- const senderClient = this.onlineClients.get(connectionID);
54
- const client = this.onlineClients.get(connectionID);
53
+ sendFileChunkToClient(senderID, clientId, fileChunk, fileName, chunkIndex, totalChunks, filePath = process.cwd()) {
54
+ const client = this.onlineClients.get(clientId);
55
55
  if (!client) {
56
56
  console.log(`Client ~ ${clientId} not found.`);
57
57
  return;
58
58
  }
59
- 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", {
60
68
  fileName,
61
- fileData,
69
+ fileChunk,
70
+ chunkIndex,
62
71
  path: filePath
63
72
  });
64
- 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);
65
75
  if (senderClient) {
66
76
  senderClient.emit("transfer-status", {
67
- status: `[FileTide ~ File Messager] ~ File ${fileName} sent to client ~ ${clientId}!`,
77
+ status: `Chunk ${chunkIndex} of ${fileName} sent to client ${clientId}!`,
68
78
  recipientID: clientId,
69
79
  success: true
70
80
  });
71
81
  }
72
- return;
73
82
  }
74
-
75
- /**
76
- * Handle file transfer requests between clients
77
- * @param {Object} transferData - The data containing the sender, recipient, and file details
78
- */
79
83
  handleClientToClientTransfer(transferData) {
80
84
  const {
81
85
  senderID,
82
86
  recipientId,
83
87
  fileName,
84
- fileData,
85
- filePath
88
+ fileChunk,
89
+ filePath,
90
+ chunkIndex,
91
+ totalChunks
86
92
  } = transferData;
87
- this.sendFileToClient(senderID, recipientId, fileData, fileName, filePath);
88
- 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}`);
89
113
  }
90
114
  handleClientsOnline(clientID) {
91
- if (!this.messagerClients.has(clientID)) {
115
+ if (!FileNetClientManager.getOnlineClient(clientID)) {
92
116
  console.log(`Client ~ ${clientID} not authorized.`);
93
117
  return;
94
118
  }
95
- const connectionID = this.messagerClients.get(clientID);
119
+ const connectionID = FileNetClientManager.getOnlineClient(clientID);
96
120
  const client = this.onlineClients.get(connectionID);
97
121
  if (!client) {
98
122
  console.log(`Client ~ ${clientID} not found.`);
99
123
  return;
100
124
  }
101
- client.emit("current-clients-online", {
102
- currentClients: this.messagerClients
125
+ this.fileNet.emitToTide(client.tideID, "current-clients-online", {
126
+ currentClients: FileNetClientManager.getOnlineClients()
103
127
  });
104
128
  return;
105
129
  }
106
- handleClientOnline(clientID, connectionID) {
130
+ handleClientOnline(clientID, tideID, pClientID, connectionID) {
107
131
  if (!this.onlineClients.has(connectionID)) {
108
132
  return;
109
133
  }
110
- this.messagerClients.set(clientID, connectionID);
134
+ FileNetClientManager.addOnlineClient(clientID, pClientID, tideID, connectionID);
135
+ this.fileNet.emitToTide(tideID, "current-online-clients", Object.fromEntries(FileNetClientManager.getOnlineClients()));
111
136
  return;
112
137
  }
113
138
  handleClientOffline(clientID) {
114
- if (!this.messagerClients.has(clientID)) {
115
- return;
116
- }
117
- this.messagerClients.delete(clientID);
118
- return;
119
- }
120
- handleFileTransferStart(fileData, emitToChannel) {
121
- console.log(`Starting file transfer: ${fileData.fileName}`);
122
- return;
123
- }
124
- handleFileTransferProgress(data, emitToChannel) {
125
- console.log(`Progress: ${data.progress}`);
126
- return;
127
- }
128
- handleFileTransferComplete(data) {
129
- console.log(`File transfer completed: ${data.fileName}`);
139
+ FileNetClientManager.clearOnlineClient(clientID);
130
140
  return;
131
141
  }
132
142
  }
@@ -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,10 +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 FileMessagerClient = require("./FileMessagerClient");
7
- const FileMessager = require("./FileMessager");
6
+ const chalk = require("chalk");
8
7
  const path = require("path");
9
8
  const fs = require("fs");
9
+ const {
10
+ FileNetConfigManager
11
+ } = require("./HUDManagers/FileNetConfigManager.js");
12
+ const {
13
+ FileUtilityManager
14
+ } = require("./HUDManagers/FileUtilityManager.js");
15
+ const ConsoleTable = require("./HUDComponents/ConsoleTable.js");
16
+ const FileMessagerClient = require("./FileMessagerClient");
17
+ const FileMessager = require("./FileMessager");
10
18
  var _FileTide_brand = /*#__PURE__*/new WeakSet();
11
19
  class FileTide {
12
20
  constructor() {
@@ -57,6 +65,16 @@ class FileTide {
57
65
  }
58
66
  return;
59
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
+
60
78
  /**
61
79
  * Send a file directly to a device without it needing to create a client.
62
80
  * @param {String} senderID - The ID of the client device that sent the file
@@ -77,8 +95,54 @@ class FileTide {
77
95
  return;
78
96
  }
79
97
  console.log(`[FileTide] ~ Sending file to client ~ ${userID}...`);
80
- this.fileNet.sendFileToClient(senderClient.tideID, currentClient.id, fileData, filePath, fileName);
81
- 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));
82
146
  return;
83
147
  }
84
148
  console.log(`[FileTide] ~ No active client ~ ${userID} found.`);
@@ -139,22 +203,90 @@ class FileTide {
139
203
  }
140
204
  }
141
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
+ });
142
247
  client.clientTide.onEvent(userID, "incoming-file", data => {
143
- 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
+ });
144
254
  const saveDir = data.path;
145
255
  if (!fs.existsSync(saveDir)) {
146
256
  fs.mkdirSync(saveDir, {
147
257
  recursive: true
148
258
  });
149
- 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;
150
269
  }
151
- const savePath = path.join(saveDir, data.fileName);
152
- fs.writeFileSync(savePath, Buffer.from(data.fileData));
153
- 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)}%`);
154
274
  if (onIncomingFile) {
155
275
  onIncomingFile(data);
156
276
  }
157
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
+ });
158
290
  return;
159
291
  }
160
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;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ const currentOnlineClients = new Map();
4
+ class FileNetClientManager {
5
+ static addOnlineClient(clientID, pClientID, tideID, id) {
6
+ if (currentOnlineClients.get(pClientID)) {
7
+ currentOnlineClients.delete(pClientID);
8
+ }
9
+ currentOnlineClients.set(clientID, {
10
+ pClientID: pClientID || "",
11
+ clientID,
12
+ tideID,
13
+ id
14
+ });
15
+ return currentOnlineClients;
16
+ }
17
+ static clearOnlineClient(clientID) {
18
+ if (!currentOnlineClients.get(clientID)) {
19
+ return currentOnlineClients;
20
+ }
21
+ currentOnlineClients.delete(clientID);
22
+ return currentOnlineClients;
23
+ }
24
+ static getOnlineClient(clientID) {
25
+ return currentOnlineClients.get(clientID);
26
+ }
27
+ static getOnlineClients() {
28
+ return currentOnlineClients;
29
+ }
30
+ }
31
+ module.exports = {
32
+ FileNetClientManager
33
+ };
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ const currentOnlineClients = new Map();
4
+ class FileNetConfigManager {
5
+ static addOnlineClient(clientID, pClientID, tideID) {
6
+ if (currentOnlineClients.get(pClientID)) {
7
+ currentOnlineClients.delete(pClientID);
8
+ }
9
+ currentOnlineClients.set(clientID, {
10
+ pClientID: pClientID || "",
11
+ clientID,
12
+ tideID
13
+ });
14
+ return currentOnlineClients;
15
+ }
16
+ static clearOnlineClient(clientID, tideID) {
17
+ if (!currentOnlineClients.get(clientID)) {
18
+ return currentOnlineClients;
19
+ }
20
+ currentOnlineClients.delete(clientID);
21
+ return currentOnlineClients;
22
+ }
23
+ static getOnlineClient(clientID) {
24
+ if (!currentOnlineClients.get(clientID)) {
25
+ return null;
26
+ }
27
+ return currentOnlineClients.get(clientID);
28
+ }
29
+ }
30
+ module.exports = {
31
+ FileNetConfigManager
32
+ };
@@ -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.13",
3
+ "version": "0.0.15",
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": {
package/test.txt DELETED
@@ -1 +0,0 @@
1
- oiyutryuihoj