molex-ftp-client 1.0.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +86 -3
- package/index.js +11 -469
- package/lib/FTPClient.js +226 -0
- package/lib/commands.js +323 -0
- package/lib/connection.js +176 -0
- package/lib/utils.js +84 -0
- package/package.json +1 -1
package/lib/FTPClient.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
const { EventEmitter } = require('events');
|
|
2
|
+
const FTPConnection = require('./connection');
|
|
3
|
+
const FTPCommands = require('./commands');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lightweight FTP Client using native Node.js TCP sockets (net module)
|
|
7
|
+
*/
|
|
8
|
+
class FTPClient extends EventEmitter {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
// Connection state
|
|
13
|
+
this.socket = null;
|
|
14
|
+
this.dataSocket = null;
|
|
15
|
+
this.buffer = '';
|
|
16
|
+
this.connected = false;
|
|
17
|
+
this.authenticated = false;
|
|
18
|
+
|
|
19
|
+
// Configuration
|
|
20
|
+
this.debug = options.debug || false;
|
|
21
|
+
this.timeout = options.timeout || 30000;
|
|
22
|
+
this.keepAlive = options.keepAlive !== false;
|
|
23
|
+
this._log = options.logger || console.log;
|
|
24
|
+
|
|
25
|
+
// Statistics
|
|
26
|
+
this._commandCount = 0;
|
|
27
|
+
this._lastCommand = null;
|
|
28
|
+
|
|
29
|
+
// Initialize subsystems
|
|
30
|
+
this._connection = new FTPConnection(this);
|
|
31
|
+
this._commands = new FTPCommands(this);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Log message if debug is enabled
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
_debug(...args) {
|
|
39
|
+
if (this.debug && this._log) {
|
|
40
|
+
this._log('[FTP Debug]', ...args);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Connect to FTP server
|
|
46
|
+
* @param {Object} options - Connection options
|
|
47
|
+
* @param {string} options.host - FTP server host
|
|
48
|
+
* @param {number} [options.port=21] - FTP server port
|
|
49
|
+
* @param {string} [options.user='anonymous'] - Username
|
|
50
|
+
* @param {string} [options.password='anonymous@'] - Password
|
|
51
|
+
* @returns {Promise<void>}
|
|
52
|
+
*/
|
|
53
|
+
async connect(options) {
|
|
54
|
+
return this._connection.connect(options);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Upload file to FTP server
|
|
59
|
+
* @param {string|Buffer} data - File data
|
|
60
|
+
* @param {string} remotePath - Remote file path
|
|
61
|
+
* @returns {Promise<void>}
|
|
62
|
+
*/
|
|
63
|
+
async upload(data, remotePath) {
|
|
64
|
+
return this._commands.upload(data, remotePath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Download file from FTP server
|
|
69
|
+
* @param {string} remotePath - Remote file path
|
|
70
|
+
* @returns {Promise<Buffer>}
|
|
71
|
+
*/
|
|
72
|
+
async download(remotePath) {
|
|
73
|
+
return this._commands.download(remotePath);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* List directory contents
|
|
78
|
+
* @param {string} [path='.'] - Directory path
|
|
79
|
+
* @returns {Promise<string>}
|
|
80
|
+
*/
|
|
81
|
+
async list(path = '.') {
|
|
82
|
+
return this._commands.list(path);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Change working directory
|
|
87
|
+
* @param {string} path - Directory path
|
|
88
|
+
* @returns {Promise<void>}
|
|
89
|
+
*/
|
|
90
|
+
async cd(path) {
|
|
91
|
+
return this._commands.cd(path);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get current working directory
|
|
96
|
+
* @returns {Promise<string>}
|
|
97
|
+
*/
|
|
98
|
+
async pwd() {
|
|
99
|
+
return this._commands.pwd();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create directory
|
|
104
|
+
* @param {string} path - Directory path
|
|
105
|
+
* @returns {Promise<void>}
|
|
106
|
+
*/
|
|
107
|
+
async mkdir(path) {
|
|
108
|
+
return this._commands.mkdir(path);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Delete file
|
|
113
|
+
* @param {string} path - File path
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
116
|
+
async delete(path) {
|
|
117
|
+
return this._commands.delete(path);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Rename file
|
|
122
|
+
* @param {string} from - Current name
|
|
123
|
+
* @param {string} to - New name
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
async rename(from, to) {
|
|
127
|
+
return this._commands.rename(from, to);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get file size
|
|
132
|
+
* @param {string} path - File path
|
|
133
|
+
* @returns {Promise<number>}
|
|
134
|
+
*/
|
|
135
|
+
async size(path) {
|
|
136
|
+
return this._commands.size(path);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if file or directory exists
|
|
141
|
+
* @param {string} path - File or directory path
|
|
142
|
+
* @returns {Promise<boolean>}
|
|
143
|
+
*/
|
|
144
|
+
async exists(path) {
|
|
145
|
+
return this._commands.exists(path);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Ensure directory exists, creating it if necessary
|
|
150
|
+
* @param {string} dirPath - Directory path to ensure exists
|
|
151
|
+
* @param {boolean} recursive - Create parent directories if needed (default: true)
|
|
152
|
+
* @returns {Promise<void>}
|
|
153
|
+
*/
|
|
154
|
+
async ensureDir(dirPath, recursive = true) {
|
|
155
|
+
return this._commands.ensureDir(dirPath, recursive);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Ensure parent directory exists for a file path
|
|
160
|
+
* @param {string} filePath - File path
|
|
161
|
+
* @returns {Promise<void>}
|
|
162
|
+
*/
|
|
163
|
+
async ensureParentDir(filePath) {
|
|
164
|
+
return this._commands.ensureParentDir(filePath);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Upload file and ensure parent directory exists
|
|
169
|
+
* @param {string|Buffer} data - File data
|
|
170
|
+
* @param {string} remotePath - Remote file path
|
|
171
|
+
* @param {boolean} ensureDir - Ensure parent directory exists (default: false)
|
|
172
|
+
* @returns {Promise<void>}
|
|
173
|
+
*/
|
|
174
|
+
async uploadFile(data, remotePath, ensureDir = false) {
|
|
175
|
+
return this._commands.uploadFile(data, remotePath, ensureDir);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get file modification time
|
|
180
|
+
* @param {string} path - File path
|
|
181
|
+
* @returns {Promise<Date>}
|
|
182
|
+
*/
|
|
183
|
+
async modifiedTime(path) {
|
|
184
|
+
return this._commands.modifiedTime(path);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get connection statistics
|
|
189
|
+
* @returns {Object}
|
|
190
|
+
*/
|
|
191
|
+
getStats() {
|
|
192
|
+
return {
|
|
193
|
+
connected: this.connected,
|
|
194
|
+
authenticated: this.authenticated,
|
|
195
|
+
commandCount: this._commandCount,
|
|
196
|
+
lastCommand: this._lastCommand
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Enable or disable debug mode
|
|
202
|
+
* @param {boolean} enabled - Enable debug mode
|
|
203
|
+
*/
|
|
204
|
+
setDebug(enabled) {
|
|
205
|
+
this.debug = enabled;
|
|
206
|
+
this._debug(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Close connection
|
|
211
|
+
* @returns {Promise<void>}
|
|
212
|
+
*/
|
|
213
|
+
async close() {
|
|
214
|
+
return this._connection.close();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Disconnect (alias for close)
|
|
219
|
+
* @returns {Promise<void>}
|
|
220
|
+
*/
|
|
221
|
+
async disconnect() {
|
|
222
|
+
return this.close();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = FTPClient;
|
package/lib/commands.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
const { normalizePath, getParentDir, parseMdtmResponse } = require('./utils');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* FTP command implementations
|
|
6
|
+
*/
|
|
7
|
+
class FTPCommands {
|
|
8
|
+
constructor(client) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
this.connection = client._connection;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Upload file to FTP server
|
|
15
|
+
* @param {string|Buffer} data - File data
|
|
16
|
+
* @param {string} remotePath - Remote file path
|
|
17
|
+
* @returns {Promise<void>}
|
|
18
|
+
*/
|
|
19
|
+
async upload(data, remotePath) {
|
|
20
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
|
|
21
|
+
this.client._debug(`Uploading ${buffer.length} bytes to ${remotePath}`);
|
|
22
|
+
const { host, port } = await this.connection.enterPassiveMode();
|
|
23
|
+
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
let commandSent = false;
|
|
26
|
+
|
|
27
|
+
this.client.dataSocket = net.createConnection({ host, port }, () => {
|
|
28
|
+
// Send STOR command to start upload (expects 150, then 226)
|
|
29
|
+
if (!commandSent) {
|
|
30
|
+
commandSent = true;
|
|
31
|
+
this.client._debug(`Data connection established for upload`);
|
|
32
|
+
this.connection.sendCommand(`STOR ${remotePath}`, true).catch(reject);
|
|
33
|
+
|
|
34
|
+
// Write data to data socket
|
|
35
|
+
this.client.dataSocket.write(buffer);
|
|
36
|
+
this.client.dataSocket.end();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.client.dataSocket.on('error', reject);
|
|
41
|
+
|
|
42
|
+
this.client.dataSocket.on('close', () => {
|
|
43
|
+
// Wait for final response from control socket
|
|
44
|
+
const finalHandler = (line) => {
|
|
45
|
+
const code = parseInt(line.substring(0, 3));
|
|
46
|
+
if (code === 226 || code === 250) {
|
|
47
|
+
this.client.removeListener('response', finalHandler);
|
|
48
|
+
this.client._debug(`Upload completed successfully`);
|
|
49
|
+
resolve();
|
|
50
|
+
} else if (code >= 400) {
|
|
51
|
+
this.client.removeListener('response', finalHandler);
|
|
52
|
+
reject(new Error(`FTP Error ${code}: ${line.substring(4)}`));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
this.client.on('response', finalHandler);
|
|
56
|
+
|
|
57
|
+
// Timeout if no response
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
this.client.removeListener('response', finalHandler);
|
|
60
|
+
resolve();
|
|
61
|
+
}, 5000);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Download file from FTP server
|
|
68
|
+
* @param {string} remotePath - Remote file path
|
|
69
|
+
* @returns {Promise<Buffer>}
|
|
70
|
+
*/
|
|
71
|
+
async download(remotePath) {
|
|
72
|
+
this.client._debug(`Downloading ${remotePath}`);
|
|
73
|
+
const { host, port } = await this.connection.enterPassiveMode();
|
|
74
|
+
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const chunks = [];
|
|
77
|
+
let commandSent = false;
|
|
78
|
+
|
|
79
|
+
this.client.dataSocket = net.createConnection({ host, port }, () => {
|
|
80
|
+
// Send RETR command to start download (expects 150, then 226)
|
|
81
|
+
if (!commandSent) {
|
|
82
|
+
commandSent = true;
|
|
83
|
+
this.client._debug(`Data connection established for download`);
|
|
84
|
+
this.connection.sendCommand(`RETR ${remotePath}`, true).catch(reject);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.client.dataSocket.on('data', (chunk) => {
|
|
89
|
+
chunks.push(chunk);
|
|
90
|
+
this.client._debug(`Received ${chunk.length} bytes`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.client.dataSocket.on('error', reject);
|
|
94
|
+
|
|
95
|
+
this.client.dataSocket.on('close', () => {
|
|
96
|
+
// Wait for final 226 response
|
|
97
|
+
const finalHandler = (line) => {
|
|
98
|
+
const code = parseInt(line.substring(0, 3));
|
|
99
|
+
if (code === 226 || code === 250) {
|
|
100
|
+
this.client.removeListener('response', finalHandler);
|
|
101
|
+
const result = Buffer.concat(chunks);
|
|
102
|
+
this.client._debug(`Download completed: ${result.length} bytes`);
|
|
103
|
+
resolve(result);
|
|
104
|
+
} else if (code >= 400) {
|
|
105
|
+
this.client.removeListener('response', finalHandler);
|
|
106
|
+
reject(new Error(`FTP Error ${code}: ${line.substring(4)}`));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
this.client.on('response', finalHandler);
|
|
110
|
+
|
|
111
|
+
// Timeout if no response
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
this.client.removeListener('response', finalHandler);
|
|
114
|
+
if (chunks.length > 0) {
|
|
115
|
+
resolve(Buffer.concat(chunks));
|
|
116
|
+
}
|
|
117
|
+
}, 5000);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* List directory contents
|
|
124
|
+
* @param {string} [path='.'] - Directory path
|
|
125
|
+
* @returns {Promise<string>}
|
|
126
|
+
*/
|
|
127
|
+
async list(path = '.') {
|
|
128
|
+
this.client._debug(`Listing directory: ${path}`);
|
|
129
|
+
const { host, port } = await this.connection.enterPassiveMode();
|
|
130
|
+
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const chunks = [];
|
|
133
|
+
let commandSent = false;
|
|
134
|
+
|
|
135
|
+
this.client.dataSocket = net.createConnection({ host, port }, () => {
|
|
136
|
+
if (!commandSent) {
|
|
137
|
+
commandSent = true;
|
|
138
|
+
this.connection.sendCommand(`LIST ${path}`, true).catch(reject);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.client.dataSocket.on('data', (chunk) => {
|
|
143
|
+
chunks.push(chunk);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
this.client.dataSocket.on('error', reject);
|
|
147
|
+
|
|
148
|
+
this.client.dataSocket.on('close', () => {
|
|
149
|
+
// Wait for final 226 response
|
|
150
|
+
const finalHandler = (line) => {
|
|
151
|
+
const code = parseInt(line.substring(0, 3));
|
|
152
|
+
if (code === 226 || code === 250) {
|
|
153
|
+
this.client.removeListener('response', finalHandler);
|
|
154
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
this.client.on('response', finalHandler);
|
|
158
|
+
|
|
159
|
+
// Timeout fallback
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
this.client.removeListener('response', finalHandler);
|
|
162
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
163
|
+
}, 3000);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Change working directory
|
|
170
|
+
* @param {string} path - Directory path
|
|
171
|
+
* @returns {Promise<void>}
|
|
172
|
+
*/
|
|
173
|
+
async cd(path) {
|
|
174
|
+
await this.connection.sendCommand(`CWD ${path}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get current working directory
|
|
179
|
+
* @returns {Promise<string>}
|
|
180
|
+
*/
|
|
181
|
+
async pwd() {
|
|
182
|
+
const response = await this.connection.sendCommand('PWD');
|
|
183
|
+
const match = response.message.match(/"(.+)"/);
|
|
184
|
+
return match ? match[1] : '/';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Create directory
|
|
189
|
+
* @param {string} path - Directory path
|
|
190
|
+
* @returns {Promise<void>}
|
|
191
|
+
*/
|
|
192
|
+
async mkdir(path) {
|
|
193
|
+
await this.connection.sendCommand(`MKD ${path}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Delete file
|
|
198
|
+
* @param {string} path - File path
|
|
199
|
+
* @returns {Promise<void>}
|
|
200
|
+
*/
|
|
201
|
+
async delete(path) {
|
|
202
|
+
await this.connection.sendCommand(`DELE ${path}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Rename file
|
|
207
|
+
* @param {string} from - Current name
|
|
208
|
+
* @param {string} to - New name
|
|
209
|
+
* @returns {Promise<void>}
|
|
210
|
+
*/
|
|
211
|
+
async rename(from, to) {
|
|
212
|
+
await this.connection.sendCommand(`RNFR ${from}`);
|
|
213
|
+
await this.connection.sendCommand(`RNTO ${to}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get file size
|
|
218
|
+
* @param {string} path - File path
|
|
219
|
+
* @returns {Promise<number>}
|
|
220
|
+
*/
|
|
221
|
+
async size(path) {
|
|
222
|
+
this.client._debug(`Getting size of ${path}`)
|
|
223
|
+
const response = await this.connection.sendCommand(`SIZE ${path}`);
|
|
224
|
+
return parseInt(response.message);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check if file or directory exists
|
|
229
|
+
* @param {string} path - File or directory path
|
|
230
|
+
* @returns {Promise<boolean>}
|
|
231
|
+
*/
|
|
232
|
+
async exists(path) {
|
|
233
|
+
try {
|
|
234
|
+
await this.size(path);
|
|
235
|
+
return true;
|
|
236
|
+
} catch (err) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Ensure directory exists, creating it if necessary
|
|
243
|
+
* @param {string} dirPath - Directory path to ensure exists
|
|
244
|
+
* @param {boolean} recursive - Create parent directories if needed (default: true)
|
|
245
|
+
* @returns {Promise<void>}
|
|
246
|
+
*/
|
|
247
|
+
async ensureDir(dirPath, recursive = true) {
|
|
248
|
+
this.client._debug(`Ensuring directory exists: ${dirPath}`);
|
|
249
|
+
|
|
250
|
+
// Normalize path
|
|
251
|
+
const normalized = normalizePath(dirPath);
|
|
252
|
+
if (normalized === '/' || normalized === '.') {
|
|
253
|
+
return; // Root or current directory always exists
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Try to cd to the directory
|
|
257
|
+
try {
|
|
258
|
+
await this.cd(normalized);
|
|
259
|
+
this.client._debug(`Directory already exists: ${normalized}`);
|
|
260
|
+
return;
|
|
261
|
+
} catch (err) {
|
|
262
|
+
this.client._debug(`Directory doesn't exist: ${normalized}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// If recursive, ensure parent directory exists first
|
|
266
|
+
if (recursive) {
|
|
267
|
+
const parentDir = normalized.substring(0, normalized.lastIndexOf('/')) || '/';
|
|
268
|
+
if (parentDir !== '/' && parentDir !== '.') {
|
|
269
|
+
await this.ensureDir(parentDir, true);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Create the directory
|
|
274
|
+
try {
|
|
275
|
+
await this.mkdir(normalized);
|
|
276
|
+
this.client._debug(`Created directory: ${normalized}`);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
// Ignore error if directory was created by another process
|
|
279
|
+
if (!err.message.includes('550') && !err.message.includes('exists')) {
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Ensure parent directory exists for a file path
|
|
287
|
+
* @param {string} filePath - File path
|
|
288
|
+
* @returns {Promise<void>}
|
|
289
|
+
*/
|
|
290
|
+
async ensureParentDir(filePath) {
|
|
291
|
+
const parentDir = getParentDir(filePath);
|
|
292
|
+
if (parentDir && parentDir !== '.' && parentDir !== '/') {
|
|
293
|
+
await this.ensureDir(parentDir);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Upload file and ensure parent directory exists
|
|
299
|
+
* @param {string|Buffer} data - File data
|
|
300
|
+
* @param {string} remotePath - Remote file path
|
|
301
|
+
* @param {boolean} ensureDir - Ensure parent directory exists (default: false)
|
|
302
|
+
* @returns {Promise<void>}
|
|
303
|
+
*/
|
|
304
|
+
async uploadFile(data, remotePath, ensureDir = false) {
|
|
305
|
+
if (ensureDir) {
|
|
306
|
+
await this.ensureParentDir(remotePath);
|
|
307
|
+
}
|
|
308
|
+
return this.upload(data, remotePath);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Get file modification time
|
|
313
|
+
* @param {string} path - File path
|
|
314
|
+
* @returns {Promise<Date>}
|
|
315
|
+
*/
|
|
316
|
+
async modifiedTime(path) {
|
|
317
|
+
this.client._debug(`Getting modification time of ${path}`);
|
|
318
|
+
const response = await this.connection.sendCommand(`MDTM ${path}`);
|
|
319
|
+
return parseMdtmResponse(response.message);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
module.exports = FTPCommands;
|