@trap_stevo/filetide 0.0.32 → 0.0.33

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.
@@ -10,6 +10,7 @@ const {
10
10
  const {
11
11
  FileTransferManager
12
12
  } = require("./HUDManagers/FileTransferManager");
13
+ const HUDTargetQueue = require("./HUDManagers/HUDTargetQueueManager");
13
14
  class FileMessager {
14
15
  constructor(options = {
15
16
  pingTimeout: 3600000,
@@ -20,6 +21,9 @@ class FileMessager {
20
21
  }) {
21
22
  const serverOptions = FileMessagerConfigManager.getServerOptions(options);
22
23
  this.transporter = new FileTransferManager(transportOptions);
24
+ this.transferQueue = new HUDTargetQueue();
25
+ this.currentTransferBarrierTransfers = new Map();
26
+ this.clientTransferBarriers = new Map();
23
27
  this.fileNet = new IoTide(serverOptions.port, serverOptions, true, this.onConnect.bind(this), this.onDisconnect.bind(this));
24
28
  this.onlineClients = new Map();
25
29
  return;
@@ -35,6 +39,8 @@ class FileMessager {
35
39
  return;
36
40
  }
37
41
  start() {
42
+ this.fileNet.on("get-client-transfer-barrier-response", this.handleClientTransferBarrierResponse.bind(this));
43
+ this.fileNet.on("notify-transfer-barrier", this.handleNotifyTransferBarrier.bind(this));
38
44
  this.fileNet.on("transfer-start", this.handleFileTransferStart.bind(this));
39
45
  this.fileNet.on("transfer-progress", this.handleFileTransferProgress.bind(this));
40
46
  this.fileNet.on("transfer-complete", this.handleFileTransferComplete.bind(this));
@@ -63,7 +69,8 @@ class FileMessager {
63
69
  senderID,
64
70
  requesterID,
65
71
  path: filePath,
66
- destinationPath
72
+ destinationPath,
73
+ type: "requestedTransfer"
67
74
  });
68
75
  }
69
76
 
@@ -147,9 +154,48 @@ class FileMessager {
147
154
  console.log(`[FileTide ~ File Messager] ~ File transfer completed: ${fileName} from ${senderID} to ${recipientId}`);
148
155
  }
149
156
  }
157
+ handleClientTransferBarrierResponse(data) {
158
+ console.log(`Checking ${data.recipientID}'s transfer barrier for transfer ~ ${data.transferID}...`);
159
+ const recipientConnectionID = FileNetClientManager.getOnlineClient(data.recipientID).id;
160
+ const senderConnectionID = FileNetClientManager.getOnlineClient(data.clientID).id;
161
+ const senderClient = this.getClientFromID(senderConnectionID);
162
+ const client = this.getClientFromID(recipientConnectionID);
163
+ if (!client || !senderClient) {
164
+ return;
165
+ }
166
+ this.fileNet.emitToTide(client.tideID, "queue-incoming-transfer", {
167
+ ...data,
168
+ transferType: "send"
169
+ });
170
+ this.transferQueue.waitForData(data.transferID, () => this.currentTransferBarrierTransfers.get(data.transferID), [true, false], 100, 50000).then(accepted => {
171
+ console.log(`Transfer ${accepted ? "accepted!" : "denied."}`);
172
+ this.fileNet.emitToTide(senderClient.tideID, "transfer-barrier-response", accepted);
173
+ this.currentTransferBarrierTransfers.delete(data.transferID);
174
+ if (accepted) {
175
+ this.fileNet.emitToTide(client.tideID, "accepted-transfer", {
176
+ accepted,
177
+ ...data
178
+ });
179
+ return;
180
+ }
181
+ this.fileNet.emitToTide(client.tideID, "denied-transfer", {
182
+ accepted,
183
+ ...data
184
+ });
185
+ }).catch(error => {
186
+ console.error(error.message);
187
+ });
188
+ return;
189
+ }
190
+ handleNotifyTransferBarrier(data, emitToChannel) {
191
+ const accepted = data.accepted;
192
+ const transferID = data.data.transferID;
193
+ this.currentTransferBarrierTransfers.set(transferID, accepted);
194
+ }
150
195
  handleFileTransferStart(data, emitToChannel) {
151
196
  console.log(`Alerting client of incoming transfer from ${data.senderID}...`);
152
- const client = this.getClientFromID(data.recipientID);
197
+ const recipientConnectionID = FileNetClientManager.getOnlineClient(data.recipientID).id;
198
+ const client = this.getClientFromID(recipientConnectionID);
153
199
  if (!client) {
154
200
  return;
155
201
  }
@@ -175,11 +221,15 @@ class FileMessager {
175
221
  this.fileNet.emitToTide(connectionDetails.tideID, "current-online-clients", Object.fromEntries(FileNetClientManager.getOnlineClients()));
176
222
  return;
177
223
  }
178
- handleClientOnline(clientID, tideID, pClientID, connectionID) {
224
+ handleClientOnline(clientID, tideID, pClientID, connectionID, activeTransferBarrier) {
179
225
  if (!this.onlineClients.has(connectionID)) {
180
226
  return;
181
227
  }
182
228
  FileNetClientManager.addOnlineClient(clientID, pClientID, tideID, connectionID);
229
+ this.clientTransferBarriers.set(tideID, {
230
+ clientID,
231
+ activeTransferBarrier
232
+ });
183
233
  this.fileNet.emitToTide(tideID, "current-online-clients", Object.fromEntries(FileNetClientManager.getOnlineClients()));
184
234
  return;
185
235
  }
@@ -30,6 +30,7 @@ class FileMessagerClient {
30
30
  this.progressBarManager = new ConsoleProgressBarManager([], {
31
31
  completionMessageColor: chalk.blue.bold
32
32
  });
33
+ this.activeTransferTypes = new Map();
33
34
  this.clientTide = new HUDIoTide();
34
35
  this.clients = new Map();
35
36
  }
@@ -52,7 +53,11 @@ class FileMessagerClient {
52
53
  });
53
54
  });
54
55
  }
55
- requestTransfer(clientID, senderID, filePath, destinationPath) {
56
+ async requestTransfer(clientID, senderID, filePath, destinationPath) {
57
+ const transferAllowed = await this.allowedTransfer(clientID, senderID, destinationPath, "N/A", "requestSend");
58
+ if (!transferAllowed) {
59
+ return;
60
+ }
56
61
  this.clientTide.emitEvent(clientID, "request-transfer-from-client", {
57
62
  requesterID: clientID,
58
63
  senderID,
@@ -61,6 +66,34 @@ class FileMessagerClient {
61
66
  });
62
67
  return;
63
68
  }
69
+ async allowedTransfer(clientID, recipientID, filePath, fileSize, transferType = "send") {
70
+ try {
71
+ const currentDate = Date.now();
72
+ const transferID = `${clientID}_${filePath}_${currentDate}`;
73
+ const transferAccepted = await this.clientTide.emitEventWithResponse(clientID, "get-client-transfer-barrier-response", "transfer-barrier-response", {
74
+ clientID,
75
+ recipientID,
76
+ transferID,
77
+ path: filePath,
78
+ date: currentDate,
79
+ transferType
80
+ }, 30000);
81
+ if (!transferAccepted) {
82
+ FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ${recipientID} denied your transfer.`, errorMessageColors);
83
+ return false;
84
+ } else if (transferAccepted) {
85
+ FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ${recipientID} accepted your transfer!`, significantMessageColors);
86
+ return true;
87
+ }
88
+ } catch (error) {
89
+ if (error.message.startsWith("Timeout: No response")) {
90
+ FileNetUtilityManager.outputGradient("[FileTide ~ File Messager] Transfer approval timed out.", errorMessageColors);
91
+ return false;
92
+ }
93
+ FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] Transfer approval refused.\n${error}`, errorMessageColors);
94
+ return false;
95
+ }
96
+ }
64
97
 
65
98
  /**
66
99
  * Sends all files in a directory in parallel using the sendFile method.
@@ -70,21 +103,33 @@ class FileMessagerClient {
70
103
  * @param {string} baseDirectory - The base directory path that contains the files.
71
104
  * @param {string} destinationPath - The path to which the files should be sent.
72
105
  */
73
- sendDirectoryFiles(clientID, recipientId, filesData, baseDirectory, destinationPath) {
106
+ async sendDirectoryFiles(clientID, recipientId, filesData, baseDirectory, destinationPath) {
74
107
  try {
75
108
  const baseDirectoryName = path.basename(baseDirectory);
76
109
  const adjustedDestinationPath = FileUtilityManager.normalizePath(path.join(destinationPath, baseDirectoryName));
77
- const sendFilePromises = filesData.map((fileInfo, index) => {
110
+ let totalSize = 0;
111
+ filesData.forEach(fileInfo => {
112
+ totalSize += fileInfo.size;
113
+ });
114
+ const incomingType = this.activeTransferTypes.get(destinationPath);
115
+ const transferAllowed = incomingType && incomingType === "requestedTransfer" ? true : await this.allowedTransfer(clientID, recipientId, adjustedDestinationPath, totalSize, "send");
116
+ if (incomingType) {
117
+ this.activeTransferTypes.delete(destinationPath);
118
+ }
119
+ if (!transferAllowed) {
120
+ return;
121
+ }
122
+ const sendFilePromises = filesData.map(async (fileInfo, index) => {
78
123
  const relativeFilePath = path.dirname(path.relative(baseDirectory, fileInfo.filePath));
79
124
  const fileDestinationPath = FileUtilityManager.normalizePath(path.join(adjustedDestinationPath, relativeFilePath));
80
- return this.sendFile(clientID, recipientId, fileInfo.fileName, fileInfo.fileData, fileDestinationPath, fileInfo.size, adjustedDestinationPath, index);
125
+ return await this.sendDirectoryFile(clientID, recipientId, fileInfo.fileName, fileInfo.fileData, fileDestinationPath, fileInfo.size, adjustedDestinationPath, index);
81
126
  });
82
127
  Promise.all(sendFilePromises).then(() => console.log("\nAll files in directory transferred successfully!")).catch(error => console.error("Did not transfer directory: ", error));
83
128
  } catch (error) {
84
129
  FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Refused directory transfer: ${error}`, errorMessageColors);
85
130
  }
86
131
  }
87
- sendFile(clientID, recipientId, fileName, file, filePath = process.cwd(), fileSize, directoryPath = null, index = 0) {
132
+ sendDirectoryFile(clientID, recipientId, fileName, file, filePath = process.cwd(), fileSize, directoryPath = null, index = 0) {
88
133
  if (!file) {
89
134
  return;
90
135
  }
@@ -146,6 +191,76 @@ class FileMessagerClient {
146
191
  }
147
192
  }).then(() => FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ File ~ ${fileName} transfer complete!`, significantMessageColors)).catch(error => FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Refused file transfer: ${error}`, errorMessageColors));
148
193
  }
194
+ async sendFile(clientID, recipientId, fileName, file, filePath = process.cwd(), fileSize) {
195
+ if (!file) {
196
+ return;
197
+ }
198
+ const destinationPath = FileUtilityManager.normalizePath(path.join(filePath, fileName));
199
+ FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Requesting to transfer ~ ${destinationPath} to ${recipientId}!`, significantMessageColors);
200
+ const incomingType = this.activeTransferTypes.get(filePath);
201
+ const transferAllowed = incomingType && incomingType === "requestedTransfer" ? true : await this.allowedTransfer(clientID, recipientId, destinationPath, fileSize, "send");
202
+ if (incomingType) {
203
+ this.activeTransferTypes.delete(filePath);
204
+ }
205
+ if (!transferAllowed) {
206
+ return;
207
+ }
208
+ this.progressBarManager.completionMessage = `Successfully sent to ${recipientId}!`;
209
+ this.progressBarManager.displayPage("Page Number:", "Press 'n' for next page, 'p' for previous page.", chalk.blue, chalk.magenta);
210
+ this.progressBarManager.listenForInput("Page Number:", "Press 'n' for next page, 'p' for previous page.", chalk.blue, chalk.magenta, chalk.yellow);
211
+ this.clientTide.emitEvent(clientID, "transfer-start", {
212
+ fileName,
213
+ senderID: clientID,
214
+ recipientID: recipientId,
215
+ path: filePath
216
+ });
217
+ this.transporter.sendFile(file, {
218
+ onSendChunk: (transferId, chunkIndex, chunkData, totalChunks) => {
219
+ if (chunkIndex === 0) {
220
+ this.progressBarManager.addTask({
221
+ name: fileName,
222
+ size: fileSize
223
+ });
224
+ }
225
+ return new Promise((resolve, reject) => {
226
+ this.clientTide.emitEvent(clientID, "client-to-client-transfer", {
227
+ senderID: clientID,
228
+ recipientId,
229
+ fileName,
230
+ fileChunk: chunkData,
231
+ totalChunks,
232
+ chunkIndex,
233
+ filePath,
234
+ fileSize
235
+ });
236
+ resolve();
237
+ });
238
+ },
239
+ onComplete: () => {
240
+ return new Promise((resolve, reject) => {
241
+ this.clientTide.emitEvent(clientID, "transfer-complete", {
242
+ senderID: clientID,
243
+ recipientId,
244
+ fileName,
245
+ filePath
246
+ });
247
+ resolve();
248
+ });
249
+ },
250
+ onProgress: progress => {
251
+ this.progressBarManager.updateTaskProgress(fileName, 512 * 1024);
252
+ },
253
+ originDetails: {
254
+ recipientID: recipientId,
255
+ senderID: clientID
256
+ },
257
+ fileDetails: {
258
+ name: fileName,
259
+ path: filePath,
260
+ size: fileSize
261
+ }
262
+ }).then(() => FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ File ~ ${fileName} transfer complete!`, significantMessageColors)).catch(error => FileNetUtilityManager.outputGradient(`[FileTide ~ File Messager] ~ Refused file transfer: ${error}`, errorMessageColors));
263
+ }
149
264
  }
150
265
  ;
151
266
  module.exports = FileMessagerClient;
@@ -34,6 +34,7 @@ class FileTide {
34
34
  completionMessageColor: chalk.blue.bold
35
35
  });
36
36
  this.clientShorePolicies = new Map();
37
+ this.transferBarrier = false;
37
38
  this.clients = new Map();
38
39
  this.currentUserID = "";
39
40
  return;
@@ -65,34 +66,47 @@ class FileTide {
65
66
  * @param {String} roomName - The name of the room to join
66
67
  * @param {String} userID - The ID of the client user
67
68
  * @param {Function} onLaunch - Callback for when a file messager launches
69
+ * @param {Function} onIncomingTransfer - Callback for incoming transfers
70
+ * @param {Function} onTransferBarrier - Callback for when an incoming transfer status updates
68
71
  */
69
- launchMessager(clientOptions = {}, connectionOptions = {}, roomName, userID, onLaunch, onIncomingFile) {
72
+ launchMessager(clientOptions = {}, connectionOptions = {}, roomName, userID, onLaunch, onIncomingFile, onIncomingTransfer, onTransferBarrier, enableTransferBarrier = true) {
70
73
  if (this.clients.has(userID)) {
71
74
  this.stopMessager(userID);
72
75
  }
73
76
  const newClient = new FileMessagerClient(clientOptions);
74
77
  newClient.joinRoom(userID, roomName, connectionOptions);
78
+ this.transferBarrier = enableTransferBarrier;
75
79
  this.clientShorePolicies.set(userID, [FileUtilityManager.getTidePath(">CurrentUser"), FileUtilityManager.getTidePath(">Downloads"), FileUtilityManager.getTidePath(">Documents")]);
76
80
  this.clients.set(userID, newClient);
77
81
  FileTide.outputGradient(`[FileTide] ~ Messager launched successfully for client ~ ${userID}!`, significantMessageColors);
78
- _assertClassBrand(_FileTide_brand, this, _setupFileEventListeners).call(this, userID, newClient, onIncomingFile);
82
+ _assertClassBrand(_FileTide_brand, this, _setupFileEventListeners).call(this, userID, newClient, onIncomingFile, onIncomingTransfer, onTransferBarrier);
79
83
  if (onLaunch) {
80
84
  onLaunch(newClient);
81
85
  }
82
86
  return;
83
87
  }
84
- getDirectoryData(directoryPath) {
85
- return FileUtilityManager.getDirectoryData(directoryPath);
88
+ async getDirectoryData(directoryPath) {
89
+ return await FileUtilityManager.getDirectoryData(directoryPath);
86
90
  }
87
91
  getFileData(filePath) {
88
92
  return FileUtilityManager.getFileData(filePath);
89
93
  }
90
- getPathData(inputPath) {
91
- return FileUtilityManager.getPathData(inputPath);
94
+ async getPathData(inputPath, filterContent = []) {
95
+ return await FileUtilityManager.getPathData(inputPath, filterContent);
92
96
  }
93
97
  getTidePath(inputPath) {
94
98
  return FileUtilityManager.getTidePath(inputPath);
95
99
  }
100
+ filterFilesAndDirectories(dirPath, filterContent = [], callback) {
101
+ FileUtilityManager.filterFilesAndDirectories(dirPath, filterContent, (files, error) => {
102
+ if (callback) {
103
+ callback(files, error);
104
+ }
105
+ });
106
+ }
107
+ async listFiles(directories) {
108
+ return await FileUtilityManager.listFiles(directories);
109
+ }
96
110
  clearShorePolicies(clientID, shores = []) {
97
111
  let clientShorePolicies = this.clientShorePolicies.get(clientID);
98
112
  if (!shores || shores.length <= 0 || !clientShorePolicies || !clientID) {
@@ -122,15 +136,16 @@ class FileTide {
122
136
  * @param {String} recipientID - The ID of the client device to send the data to
123
137
  * @param {Buffer} fileData - The path of the data to send
124
138
  * @param {String} destinationPath - The destination path on the device
139
+ * @param {Array} filterDirectoryContent - List of paths to filter
125
140
  */
126
- sendToDevice(recipientID, filePath, destinationPath) {
127
- const fileDetails = this.getPathData(filePath);
141
+ async sendToDevice(recipientID, filePath, destinationPath, filterDirectoryContent = []) {
128
142
  const clientID = this.currentUserID;
129
143
  const clientMessager = this.clients.get(clientID);
130
144
  if (!clientMessager) {
131
145
  FileTide.outputGradient(`[FileTide] Client ~ ${clientID} not found.`, ["#F94144", "#F3722C"]);
132
146
  return;
133
147
  }
148
+ const fileDetails = await this.getPathData(filePath, filterDirectoryContent);
134
149
  if (!fileDetails) {
135
150
  FileTide.outputGradient(`[${clientID}] File at path ${filePath} not found.`, ["#F94144", "#F3722C"]);
136
151
  return;
@@ -203,7 +218,7 @@ class FileTide {
203
218
  }
204
219
  }
205
220
  _FileTide = FileTide;
206
- function _setupFileEventListeners(userID, client, onIncomingFile) {
221
+ function _setupFileEventListeners(userID, client, onIncomingFile, onIncomingTransfer, onTransferBarrier) {
207
222
  const cliTable = new ConsoleTable({
208
223
  padding: 2,
209
224
  headerAlign: "center",
@@ -245,9 +260,26 @@ function _setupFileEventListeners(userID, client, onIncomingFile) {
245
260
  cliTable.setData(currentClients);
246
261
  console.log(`\n[FileTide ~ FileNet]\n\n`, cliTable.render(), "\n");
247
262
  });
248
- client.clientTide.onEvent(userID, "incoming-transfer", data => {
263
+ client.clientTide.onEvent(userID, "queue-incoming-transfer", async data => {
264
+ if (this.transferBarrier && onIncomingTransfer) {
265
+ onIncomingTransfer(data, status => {
266
+ client.clientTide.emitEvent(userID, "notify-transfer-barrier", {
267
+ accepted: status,
268
+ data
269
+ });
270
+ });
271
+ return;
272
+ } else if (!this.transferBarrier) {
273
+ client.clientTide.emitEvent(userID, "notify-transfer-barrier", {
274
+ accepted: true,
275
+ data
276
+ });
277
+ }
278
+ return;
279
+ });
280
+ client.clientTide.onEvent(userID, "incoming-transfer", async data => {
249
281
  _FileTide.outputGradient(`[${userID}] Preparing to receive transfer : ${FileUtilityManager.getTidePath(data.path)}`, significantMessageColors);
250
- this.progressBarManager.completionMessage = `Successfully received transfer from ${recipientId}!`;
282
+ this.progressBarManager.completionMessage = `Successfully received transfer from ${data.senderID}!`;
251
283
  this.progressBarManager.displayPage("Page Number:", "Press 'n' for next page, 'p' for previous page.", chalk.blue, chalk.magenta);
252
284
  this.progressBarManager.listenForInput("Page Number:", "Press 'n' for next page, 'p' for previous page.", chalk.blue, chalk.magenta, chalk.yellow);
253
285
  });
@@ -260,11 +292,14 @@ function _setupFileEventListeners(userID, client, onIncomingFile) {
260
292
  fileInfo: data
261
293
  });
262
294
  const saveDir = FileUtilityManager.getTidePath(data.path);
263
- if (data.senderID && data.senderID !== userID && !fs.existsSync(saveDir)) {
295
+ const verifiedSender = data.senderID && data.senderID !== userID;
296
+ if (verifiedSender) {
264
297
  this.progressBarManager.addTask({
265
298
  name: data.fileName,
266
299
  size: data.fileSize
267
300
  });
301
+ }
302
+ if (verifiedSender && !fs.existsSync(saveDir)) {
268
303
  fs.mkdirSync(saveDir, {
269
304
  recursive: true
270
305
  });
@@ -274,6 +309,18 @@ function _setupFileEventListeners(userID, client, onIncomingFile) {
274
309
  _FileTide.outputGradient(`[${userID}] Halted file recieval preparation : ${data.fileName}\n${error}`, errorMessageColors);
275
310
  }
276
311
  });
312
+ client.clientTide.onEvent(userID, "accepted-transfer", async data => {
313
+ _FileTide.outputGradient(`[${userID}] Accepted ${data.clientID}'s transfer!`, significantMessageColors);
314
+ if (onTransferBarrier) {
315
+ onTransferBarrier(data);
316
+ }
317
+ });
318
+ client.clientTide.onEvent(userID, "denied-transfer", async data => {
319
+ _FileTide.outputGradient(`[${userID}] Denied ${data.clientID}'s transfer.`, errorMessageColors);
320
+ if (onTransferBarrier) {
321
+ onTransferBarrier(data);
322
+ }
323
+ });
277
324
  client.clientTide.onEvent(userID, "transfer-progress", data => {
278
325
  if (!data || data.fileChunk === undefined || data.fileChunk === null) {
279
326
  return;
@@ -306,9 +353,11 @@ function _setupFileEventListeners(userID, client, onIncomingFile) {
306
353
  activeTransfers.delete(data.fileName);
307
354
  }
308
355
  });
309
- client.clientTide.onEvent(userID, "transfer-requested", data => {
356
+ client.clientTide.onEvent(userID, "transfer-requested", async data => {
310
357
  _FileTide.outputGradient(`[${userID}] Transfer requested from: ${data.requesterID}`, significantMessageColors);
311
- this.sendToDevice(data.requesterID, data.path, data.destinationPath);
358
+ this.clients.get(userID).activeTransferTypes.set(data.destinationPath, data.type);
359
+ await this.sendToDevice(data.requesterID, data.path, data.destinationPath);
360
+ return;
312
361
  });
313
362
  this.currentUserID = userID;
314
363
  return;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const path = require("path");
4
+ const fsAsync = require("fs").promises;
4
5
  const fs = require("fs");
5
6
  const os = require("os");
6
7
  const filesPath = path.join(__dirname, "../files");
@@ -27,46 +28,154 @@ class FileUtilityManager {
27
28
 
28
29
  /**
29
30
  * Function to filter files and directories.
30
- * @param {string} dirPath - The directory path to scan
31
- * @param {Array} filterContent - Array of paths (files, directories, extensions) to avoid (e.g., ['docs', 'example.txt', '.js'])
32
- * @param {function} callback - A callback function to handle the results
31
+ * @param {string} dirPath - The directory path to scan.
32
+ * @param {Array} filterContent - Array of paths (files, directories, extensions) to avoid (e.g., ['docs', 'example.txt', '.js']).
33
+ * @returns {Promise<Array>} - A promise that resolves to an array of objects containing details about filtered files and directories.
33
34
  */
34
- static filterFilesAndDirectories(dirPath, filterContent = [], callback) {
35
- fs.readdir(dirPath, {
36
- withFileTypes: true
37
- }, (error, items) => {
38
- if (error) {
39
- return callback(error);
40
- }
35
+ static async filterFilesAndDirectories(dirPath, filterContent = []) {
36
+ try {
37
+ const currentDirPath = this.getTidePath(dirPath);
38
+ const items = await fs.promises.readdir(currentDirPath, {
39
+ withFileTypes: true
40
+ });
41
41
  const results = [];
42
- items.forEach(item => {
43
- const itemPath = path.join(dirPath, item.name);
42
+ for (const item of items) {
43
+ const itemPath = path.join(currentDirPath, item.name);
44
44
  const filtered = filterContent.some(filterEntry => {
45
- const resolvedFilterEntry = path.resolve(filterEntry);
45
+ const resolvedFilterEntry = path.resolve(currentDirPath, filterEntry);
46
46
  const fileExtension = filterEntry.startsWith(".");
47
47
  return itemPath === resolvedFilterEntry || item.name === filterEntry || fileExtension && path.extname(item.name) === filterEntry;
48
48
  });
49
49
  if (!filtered) {
50
- fs.stat(itemPath, (error, stats) => {
51
- if (error) {
52
- return callback(error);
53
- }
54
- results.push({
55
- dateCreated: stats.birthtime,
56
- type: item.isDirectory() ? "directory" : "file",
57
- size: stats.size,
58
- name: item.name,
59
- path: itemPath
60
- });
61
- if (results.length === items.length) {
62
- callback(null, results);
63
- }
50
+ const stats = await fs.promises.stat(itemPath);
51
+ results.push({
52
+ type: item.isDirectory() ? "directory" : "file",
53
+ dateCreated: stats.birthtime,
54
+ size: stats.size,
55
+ name: item.name,
56
+ path: itemPath
64
57
  });
65
- } else if (results.length === items.length) {
66
- callback(null, results);
67
58
  }
68
- });
59
+ }
60
+ return results;
61
+ } catch (error) {
62
+ console.error("Did not read directory: ", error);
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Function to list all paths across the system using wildcard '*'.
69
+ * @returns {Promise<Array>} - A promise that resolves to an array of file and directory metadata across the system.
70
+ */
71
+ static async listAllPathsInOS() {
72
+ const rootPaths = ["/", "C:/"];
73
+ let allResults = [];
74
+ const rootPromises = rootPaths.map(async rootPath => {
75
+ if (await this.exists(rootPath)) {
76
+ const subResult = await this.listPaths(rootPath);
77
+ allResults.push(...subResult.results);
78
+ }
69
79
  });
80
+ await Promise.all(rootPromises);
81
+ return allResults;
82
+ }
83
+
84
+ /**
85
+ * Function to list all paths and their metadata recursively.
86
+ * @param {string} dir - The directory path to scan.
87
+ * @returns {Promise<object>} - A promise that resolves to an object containing a list of files/directories with metadata.
88
+ */
89
+ static async listPaths(dir) {
90
+ let results = [];
91
+ const list = await fsAsync.readdir(dir, {
92
+ withFileTypes: true
93
+ });
94
+ const stats = await fsAsync.stat(dir);
95
+ const dirMetadata = {
96
+ totalItems: 0,
97
+ totalSize: 0,
98
+ createdTime: stats.birthtime,
99
+ lastModified: stats.mtime,
100
+ type: "directory",
101
+ path: dir
102
+ };
103
+ results.push(dirMetadata);
104
+ const promises = list.map(async file => {
105
+ const filePath = this.getTidePath(path.join(dir, file.name));
106
+ if (file.isDirectory()) {
107
+ const subDirResult = await this.listPaths(filePath);
108
+ dirMetadata.totalItems += subDirResult.totalItems;
109
+ dirMetadata.totalSize += subDirResult.totalSize;
110
+ results.push(...subDirResult.results);
111
+ } else {
112
+ const fileStats = await fsAsync.stat(filePath);
113
+ const fileMetadata = {
114
+ createdTime: fileStats.birthtime,
115
+ lastModified: fileStats.mtime,
116
+ size: fileStats.size,
117
+ path: filePath,
118
+ type: "file"
119
+ };
120
+ dirMetadata.totalSize += fileStats.size;
121
+ dirMetadata.totalItems += 1;
122
+ results.push(fileMetadata);
123
+ }
124
+ });
125
+ await Promise.all(promises);
126
+ return {
127
+ totalItems: dirMetadata.totalItems,
128
+ totalSize: dirMetadata.totalSize,
129
+ results
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Allows for listing files in a specific directory or all files in the system '*'.
135
+ * @param {Array} directories - Array of directories to scan or '*' for entire system.
136
+ * @returns {Promise<Array>} - A promise that resolves to an array of file and directory metadata from specified directories or the entire system.
137
+ */
138
+ static async listFiles(directories) {
139
+ let fileList = [];
140
+ if (directories.includes("*")) {
141
+ fileList = await this.listAllPathsInOS();
142
+ } else {
143
+ const dirPromises = directories.map(async dir => {
144
+ const currentDir = this.getTidePath(dir);
145
+ if (await this.exists(currentDir)) {
146
+ const subResult = await this.listPaths(currentDir);
147
+ fileList.push(...subResult.results);
148
+ } else {
149
+ console.warn(`Directory does not exist: ${currentDir}`);
150
+ }
151
+ });
152
+ await Promise.all(dirPromises);
153
+ }
154
+ return fileList;
155
+ }
156
+
157
+ /**
158
+ * Checks the path's type.
159
+ * @param {string} inputPath - The path to check.
160
+ * @returns {Promise<boolean>} - Returns a promise that resolves to true if a directory, false otherwise.
161
+ */
162
+ static async directoryPath(inputPath) {
163
+ const stats = await fsAsync.stat(inputPath);
164
+ return stats.isDirectory();
165
+ }
166
+
167
+ /**
168
+ * Helper function to check if a path exists.
169
+ * @param {string} inputPath - The path to check.
170
+ * @returns {Promise<boolean>} - Returns a promise that resolves to true if the path exists, false otherwise.
171
+ */
172
+ static async exists(inputPath) {
173
+ try {
174
+ await fsAsync.access(inputPath);
175
+ return true;
176
+ } catch (e) {
177
+ return false;
178
+ }
70
179
  }
71
180
 
72
181
  /**
@@ -153,31 +262,31 @@ class FileUtilityManager {
153
262
  /**
154
263
  * Gets all files' data from a given directory.
155
264
  * @param {string} dirPath - The path to the directory.
265
+ * @param {Array} filterContent - The paths to filter from the directory.
156
266
  * @returns {Array} - An array of objects containing file data, name, and path.
157
267
  */
158
- static getDirectoryData(inputDirPath) {
268
+ static async getDirectoryData(inputDirPath, filterContent = []) {
159
269
  const dirPath = this.getTidePath(inputDirPath);
160
270
  if (!fs.existsSync(dirPath)) {
161
271
  console.log(`Directory at path ${dirPath} not found.`);
162
272
  return [];
163
273
  }
164
274
  const filesData = [];
165
- function readDirectory(currentPath) {
166
- const fileList = fs.readdirSync(currentPath);
167
- fileList.forEach(file => {
168
- const filePath = FileUtilityManager.normalizePath(path.join(currentPath, file));
169
- const stats = fs.statSync(filePath);
275
+ async function readDirectory(currentPath) {
276
+ const files = await FileUtilityManager.filterFilesAndDirectories(currentPath, filterContent);
277
+ for (const file of files) {
278
+ const stats = await fs.promises.stat(file.path);
170
279
  if (stats.isFile()) {
171
- const fileData = FileUtilityManager.getFileData(filePath);
280
+ const fileData = FileUtilityManager.getFileData(file.path);
172
281
  if (fileData) {
173
282
  filesData.push(fileData);
174
283
  }
175
284
  } else if (stats.isDirectory()) {
176
- readDirectory(filePath);
285
+ await readDirectory(file.path);
177
286
  }
178
- });
287
+ }
179
288
  }
180
- readDirectory(dirPath);
289
+ await readDirectory(dirPath);
181
290
  return filesData;
182
291
  }
183
292
 
@@ -207,9 +316,10 @@ class FileUtilityManager {
207
316
  /**
208
317
  * Gets data from a file or directory based on the provided path.
209
318
  * @param {string} inputPath - The path to a file or directory.
319
+ * @param {Array} filterContent - The paths to filter from the directory if getting a directory.
210
320
  * @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.
211
321
  */
212
- static getPathData(inputPath) {
322
+ static async getPathData(inputPath, filterContent = []) {
213
323
  const currentInputPath = this.getTidePath(inputPath);
214
324
  if (!fs.existsSync(currentInputPath)) {
215
325
  console.log(`Path at ${currentInputPath} not found.`);
@@ -223,7 +333,7 @@ class FileUtilityManager {
223
333
  };
224
334
  } else if (stats.isDirectory()) {
225
335
  return {
226
- content: this.getDirectoryData(currentInputPath),
336
+ content: await this.getDirectoryData(currentInputPath, filterContent),
227
337
  type: "directory"
228
338
  };
229
339
  }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ class HUDTargetQueue {
4
+ constructor() {
5
+ this.queues = new Map();
6
+ }
7
+
8
+ /**
9
+ * Adds a function to the queue for a specific ID and target value.
10
+ * @param {string} id - A unique identifier for the value being watched.
11
+ * @param {function} fn - The function to execute when the target value is reached.
12
+ */
13
+ enqueue(id, fn) {
14
+ if (!this.queues.has(id)) {
15
+ this.queues.set(id, []);
16
+ }
17
+ this.queues.get(id).push(fn);
18
+ }
19
+
20
+ /**
21
+ * Waits for a value to change to the target value and executes only the functions
22
+ * in the queue associated with the specific ID.
23
+ * @param {string} id - A unique identifier for the value being watched.
24
+ * @param {function} getValue - A function that returns the current value.
25
+ * @param {any|array} targetValues - The value(s) to wait for.
26
+ * @param {number} interval - The interval (in milliseconds) to check the value.
27
+ * @param {number} [timeout] - Optional. The maximum time (in milliseconds) to wait before rejecting.
28
+ * @returns {Promise} - Resolves when the value matches the target value, executes queued functions.
29
+ */
30
+ waitForData(id, getValue, targetValues, interval = 100, timeout) {
31
+ return new Promise((resolve, reject) => {
32
+ let elapsedTime = 0;
33
+ const targets = Array.isArray(targetValues) ? targetValues : [targetValues];
34
+ const checkData = async () => {
35
+ const currentValue = await getValue();
36
+ if (typeof currentValue === "object" && targets.some(target => typeof target === "object")) {
37
+ if (targets.some(target => JSON.stringify(currentValue) === JSON.stringify(target))) {
38
+ this.executeQueue(id);
39
+ resolve(currentValue);
40
+ }
41
+ } else if (targets.includes(currentValue)) {
42
+ this.executeQueue(id);
43
+ resolve(currentValue);
44
+ } else if (timeout && elapsedTime >= timeout) {
45
+ reject(new Error(`Timeout: Value did not reach target within ${timeout}ms`));
46
+ } else {
47
+ elapsedTime += interval;
48
+ setTimeout(checkData, interval);
49
+ }
50
+ };
51
+ checkData();
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Executes all functions in the queue associated with the specific ID.
57
+ * @param {string} id - A unique identifier for the value being watched.
58
+ */
59
+ executeQueue(id) {
60
+ const queue = this.queues.get(id);
61
+ if (queue) {
62
+ while (queue.length > 0) {
63
+ const fn = queue.shift();
64
+ if (typeof fn === "function") {
65
+ fn();
66
+ }
67
+ }
68
+ this.queues.delete(id);
69
+ }
70
+ }
71
+ }
72
+ module.exports = HUDTargetQueue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trap_stevo/filetide",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
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": {
@@ -30,7 +30,7 @@
30
30
  "license": "ISC",
31
31
  "dependencies": {
32
32
  "@trap_stevo/iotide": "^0.0.37",
33
- "@trap_stevo/iotide-client": "^0.0.15",
33
+ "@trap_stevo/iotide-client": "^0.0.20",
34
34
  "@trap_stevo/legendarybuilderpronodejs-utilities": "^1.0.40",
35
35
  "chalk": "^4.1.2",
36
36
  "readline": "^1.3.0"