react-native-kookit 0.2.2 → 0.2.3
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/ios/ReactNativeKookitModule.swift +324 -76
- package/package.json +1 -1
|
@@ -68,9 +68,152 @@ enum FtpError: Error, LocalizedError {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
class
|
|
71
|
+
// Helper class to safely manage continuation resumption
|
|
72
|
+
private class ConnectionBox<T>: @unchecked Sendable {
|
|
73
|
+
private let continuation: CheckedContinuation<T, Error>
|
|
74
|
+
private var hasResumed = false
|
|
75
|
+
private let queue = DispatchQueue(label: "ConnectionBox")
|
|
76
|
+
|
|
77
|
+
init(continuation: CheckedContinuation<T, Error>) {
|
|
78
|
+
self.continuation = continuation
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func resume(returning value: T) {
|
|
82
|
+
queue.sync {
|
|
83
|
+
guard !hasResumed else { return }
|
|
84
|
+
hasResumed = true
|
|
85
|
+
continuation.resume(returning: value)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
func resume(throwing error: Error) {
|
|
90
|
+
queue.sync {
|
|
91
|
+
guard !hasResumed else { return }
|
|
92
|
+
hasResumed = true
|
|
93
|
+
continuation.resume(throwing: error)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func resume() where T == Void {
|
|
98
|
+
queue.sync {
|
|
99
|
+
guard !hasResumed else { return }
|
|
100
|
+
hasResumed = true
|
|
101
|
+
continuation.resume()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Helper class specifically for NWConnection state handling
|
|
107
|
+
private class NWConnectionBox: @unchecked Sendable {
|
|
108
|
+
private let connection: NWConnection
|
|
109
|
+
private let continuation: CheckedContinuation<NWConnection, Error>
|
|
110
|
+
private var hasResumed = false
|
|
111
|
+
private let queue = DispatchQueue(label: "NWConnectionBox")
|
|
112
|
+
|
|
113
|
+
init(connection: NWConnection, continuation: CheckedContinuation<NWConnection, Error>) {
|
|
114
|
+
self.connection = connection
|
|
115
|
+
self.continuation = continuation
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func handleStateUpdate(_ state: NWConnection.State) {
|
|
119
|
+
queue.sync {
|
|
120
|
+
guard !hasResumed else { return }
|
|
121
|
+
|
|
122
|
+
switch state {
|
|
123
|
+
case .ready:
|
|
124
|
+
hasResumed = true
|
|
125
|
+
continuation.resume(returning: connection)
|
|
126
|
+
case .failed(let error):
|
|
127
|
+
hasResumed = true
|
|
128
|
+
continuation.resume(throwing: error)
|
|
129
|
+
case .cancelled:
|
|
130
|
+
hasResumed = true
|
|
131
|
+
continuation.resume(throwing: FtpError.connectionCancelled)
|
|
132
|
+
default:
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Helper class for FTP connection authentication
|
|
140
|
+
private class FtpConnectionBox: @unchecked Sendable {
|
|
141
|
+
private let ftpClient: FtpClient
|
|
142
|
+
private let continuation: CheckedContinuation<Void, Error>
|
|
143
|
+
private var hasResumed = false
|
|
144
|
+
private let queue = DispatchQueue(label: "FtpConnectionBox")
|
|
145
|
+
private var timeoutTask: DispatchWorkItem?
|
|
146
|
+
|
|
147
|
+
init(ftpClient: FtpClient, continuation: CheckedContinuation<Void, Error>) {
|
|
148
|
+
self.ftpClient = ftpClient
|
|
149
|
+
self.continuation = continuation
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func setTimeoutTask(_ task: DispatchWorkItem) {
|
|
153
|
+
self.timeoutTask = task
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func handleStateUpdate(_ state: NWConnection.State) {
|
|
157
|
+
queue.async {
|
|
158
|
+
guard !self.hasResumed else { return }
|
|
159
|
+
|
|
160
|
+
switch state {
|
|
161
|
+
case .ready:
|
|
162
|
+
self.timeoutTask?.cancel()
|
|
163
|
+
Task {
|
|
164
|
+
do {
|
|
165
|
+
try await self.ftpClient.performAuthentication()
|
|
166
|
+
self.ftpClient.isConnected = true
|
|
167
|
+
self.queue.sync {
|
|
168
|
+
if !self.hasResumed {
|
|
169
|
+
self.hasResumed = true
|
|
170
|
+
self.continuation.resume()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
self.queue.sync {
|
|
175
|
+
if !self.hasResumed {
|
|
176
|
+
self.hasResumed = true
|
|
177
|
+
self.continuation.resume(throwing: error)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
case .failed(let error):
|
|
183
|
+
self.timeoutTask?.cancel()
|
|
184
|
+
self.queue.sync {
|
|
185
|
+
if !self.hasResumed {
|
|
186
|
+
self.hasResumed = true
|
|
187
|
+
self.continuation.resume(throwing: error)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
case .cancelled:
|
|
191
|
+
self.timeoutTask?.cancel()
|
|
192
|
+
self.queue.sync {
|
|
193
|
+
if !self.hasResumed {
|
|
194
|
+
self.hasResumed = true
|
|
195
|
+
self.continuation.resume(throwing: FtpError.connectionCancelled)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
default:
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func handleTimeout() {
|
|
205
|
+
queue.sync {
|
|
206
|
+
if !hasResumed {
|
|
207
|
+
hasResumed = true
|
|
208
|
+
continuation.resume(throwing: FtpError.connectionTimeout)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
class FtpClient: @unchecked Sendable {
|
|
72
215
|
private var controlConnection: NWConnection?
|
|
73
|
-
|
|
216
|
+
var isConnected = false
|
|
74
217
|
private var config: FtpConnectionConfig?
|
|
75
218
|
private weak var progressDelegate: FtpProgressDelegate?
|
|
76
219
|
|
|
@@ -84,36 +227,24 @@ class FtpClient {
|
|
|
84
227
|
controlConnection = NWConnection(to: endpoint, using: .tcp)
|
|
85
228
|
|
|
86
229
|
return try await withCheckedThrowingContinuation { continuation in
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
try await self.performAuthentication()
|
|
93
|
-
self.isConnected = true
|
|
94
|
-
continuation.resume()
|
|
95
|
-
} catch {
|
|
96
|
-
continuation.resume(throwing: error)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
case .failed(let error):
|
|
100
|
-
continuation.resume(throwing: error)
|
|
101
|
-
case .cancelled:
|
|
102
|
-
continuation.resume(throwing: FtpError.connectionCancelled)
|
|
103
|
-
default:
|
|
104
|
-
break
|
|
230
|
+
let connectionBox = FtpConnectionBox(ftpClient: self, continuation: continuation)
|
|
231
|
+
|
|
232
|
+
let timeoutTask = DispatchWorkItem {
|
|
233
|
+
if !self.isConnected {
|
|
234
|
+
self.controlConnection?.cancel()
|
|
105
235
|
}
|
|
106
236
|
}
|
|
107
237
|
|
|
238
|
+
connectionBox.setTimeoutTask(timeoutTask)
|
|
239
|
+
|
|
240
|
+
controlConnection?.stateUpdateHandler = { state in
|
|
241
|
+
connectionBox.handleStateUpdate(state)
|
|
242
|
+
}
|
|
243
|
+
|
|
108
244
|
controlConnection?.start(queue: .global())
|
|
109
245
|
|
|
110
246
|
// Set timeout
|
|
111
|
-
DispatchQueue.global().asyncAfter(deadline: .now() + config.timeout)
|
|
112
|
-
if !self.isConnected {
|
|
113
|
-
self.controlConnection?.cancel()
|
|
114
|
-
continuation.resume(throwing: FtpError.connectionTimeout)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
247
|
+
DispatchQueue.global().asyncAfter(deadline: .now() + config.timeout, execute: timeoutTask)
|
|
117
248
|
}
|
|
118
249
|
}
|
|
119
250
|
|
|
@@ -133,26 +264,48 @@ class FtpClient {
|
|
|
133
264
|
|
|
134
265
|
func listFiles(path: String? = nil) async throws -> [FtpFileInfo] {
|
|
135
266
|
guard isConnected else { throw FtpError.notConnected }
|
|
136
|
-
|
|
267
|
+
|
|
268
|
+
// Set ASCII mode for directory listings
|
|
269
|
+
print("FTP DEBUG: Setting ASCII mode for directory listing")
|
|
270
|
+
let asciiResponse = try await sendCommand("TYPE A")
|
|
271
|
+
print("FTP DEBUG: ASCII mode response: \(asciiResponse)")
|
|
272
|
+
if !asciiResponse.hasPrefix("200") {
|
|
273
|
+
print("FTP DEBUG: Warning - Failed to set ASCII mode: \(asciiResponse)")
|
|
274
|
+
}
|
|
275
|
+
|
|
137
276
|
let dataConnection = try await enterPassiveMode()
|
|
138
|
-
|
|
277
|
+
|
|
139
278
|
let command = path != nil ? "LIST \(path!)" : "LIST"
|
|
279
|
+
print("FTP DEBUG: Sending command: \(command)")
|
|
140
280
|
let response = try await sendCommand(command)
|
|
141
|
-
|
|
281
|
+
print("FTP DEBUG: LIST command response: \(response)")
|
|
282
|
+
|
|
142
283
|
guard response.hasPrefix("150") || response.hasPrefix("125") else {
|
|
143
284
|
dataConnection.cancel()
|
|
144
285
|
throw FtpError.commandFailed("LIST failed: \(response)")
|
|
145
286
|
}
|
|
146
|
-
|
|
287
|
+
|
|
147
288
|
let data = try await receiveData(from: dataConnection)
|
|
289
|
+
print("FTP DEBUG: Received data length: \(data.count) bytes")
|
|
290
|
+
if let dataString = String(data: data, encoding: .utf8) {
|
|
291
|
+
print("FTP DEBUG: Received data content: '\(dataString)'")
|
|
292
|
+
}
|
|
148
293
|
dataConnection.cancel()
|
|
149
|
-
|
|
294
|
+
|
|
150
295
|
let finalResponse = try await readResponse()
|
|
296
|
+
print("FTP DEBUG: Final response: \(finalResponse)")
|
|
151
297
|
guard finalResponse.hasPrefix("226") else {
|
|
152
298
|
throw FtpError.commandFailed("LIST completion failed: \(finalResponse)")
|
|
153
299
|
}
|
|
300
|
+
|
|
301
|
+
let files = parseListingData(data)
|
|
302
|
+
print("FTP DEBUG: Parsed \(files.count) files")
|
|
303
|
+
|
|
304
|
+
// Reset to binary mode for file transfers
|
|
305
|
+
let binaryResponse = try await sendCommand("TYPE I")
|
|
306
|
+
print("FTP DEBUG: Reset to binary mode response: \(binaryResponse)")
|
|
154
307
|
|
|
155
|
-
return
|
|
308
|
+
return files
|
|
156
309
|
}
|
|
157
310
|
|
|
158
311
|
func downloadFile(remotePath: String, localPath: String) async throws {
|
|
@@ -268,7 +421,7 @@ class FtpClient {
|
|
|
268
421
|
|
|
269
422
|
// MARK: - Private Methods
|
|
270
423
|
|
|
271
|
-
|
|
424
|
+
func performAuthentication() async throws {
|
|
272
425
|
// Read welcome message
|
|
273
426
|
let welcome = try await readResponse()
|
|
274
427
|
guard welcome.hasPrefix("220") else {
|
|
@@ -304,16 +457,18 @@ class FtpClient {
|
|
|
304
457
|
let commandData = "\(command)\r\n".data(using: .utf8)!
|
|
305
458
|
|
|
306
459
|
return try await withCheckedThrowingContinuation { continuation in
|
|
460
|
+
let connectionBox = ConnectionBox<String>(continuation: continuation)
|
|
461
|
+
|
|
307
462
|
connection.send(content: commandData, completion: .contentProcessed { error in
|
|
308
463
|
if let error = error {
|
|
309
|
-
|
|
464
|
+
connectionBox.resume(throwing: error)
|
|
310
465
|
} else {
|
|
311
466
|
Task {
|
|
312
467
|
do {
|
|
313
468
|
let response = try await self.readResponse()
|
|
314
|
-
|
|
469
|
+
connectionBox.resume(returning: response)
|
|
315
470
|
} catch {
|
|
316
|
-
|
|
471
|
+
connectionBox.resume(throwing: error)
|
|
317
472
|
}
|
|
318
473
|
}
|
|
319
474
|
}
|
|
@@ -327,13 +482,35 @@ class FtpClient {
|
|
|
327
482
|
}
|
|
328
483
|
|
|
329
484
|
return try await withCheckedThrowingContinuation { continuation in
|
|
485
|
+
let connectionBox = ConnectionBox<String>(continuation: continuation)
|
|
486
|
+
|
|
330
487
|
connection.receive(minimumIncompleteLength: 1, maximumLength: 4096) { data, _, isComplete, error in
|
|
331
488
|
if let error = error {
|
|
332
|
-
|
|
333
|
-
} else if let data = data
|
|
334
|
-
|
|
489
|
+
connectionBox.resume(throwing: error)
|
|
490
|
+
} else if let data = data {
|
|
491
|
+
// Try multiple encodings for FTP responses
|
|
492
|
+
var response: String?
|
|
493
|
+
|
|
494
|
+
// Try UTF-8 first
|
|
495
|
+
if let utf8Response = String(data: data, encoding: .utf8) {
|
|
496
|
+
response = utf8Response
|
|
497
|
+
}
|
|
498
|
+
// Try GBK for Chinese FTP servers
|
|
499
|
+
else if let gbkResponse = String(data: data, encoding: String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue)))) {
|
|
500
|
+
response = gbkResponse
|
|
501
|
+
}
|
|
502
|
+
// Try ASCII as fallback
|
|
503
|
+
else if let asciiResponse = String(data: data, encoding: .ascii) {
|
|
504
|
+
response = asciiResponse
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if let validResponse = response {
|
|
508
|
+
connectionBox.resume(returning: validResponse.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
509
|
+
} else {
|
|
510
|
+
connectionBox.resume(throwing: FtpError.invalidResponse)
|
|
511
|
+
}
|
|
335
512
|
} else {
|
|
336
|
-
|
|
513
|
+
connectionBox.resume(throwing: FtpError.invalidResponse)
|
|
337
514
|
}
|
|
338
515
|
}
|
|
339
516
|
}
|
|
@@ -371,17 +548,10 @@ class FtpClient {
|
|
|
371
548
|
let dataConnection = NWConnection(to: dataEndpoint, using: .tcp)
|
|
372
549
|
|
|
373
550
|
return try await withCheckedThrowingContinuation { continuation in
|
|
551
|
+
let connectionBox = NWConnectionBox(connection: dataConnection, continuation: continuation)
|
|
552
|
+
|
|
374
553
|
dataConnection.stateUpdateHandler = { state in
|
|
375
|
-
|
|
376
|
-
case .ready:
|
|
377
|
-
continuation.resume(returning: dataConnection)
|
|
378
|
-
case .failed(let error):
|
|
379
|
-
continuation.resume(throwing: error)
|
|
380
|
-
case .cancelled:
|
|
381
|
-
continuation.resume(throwing: FtpError.connectionCancelled)
|
|
382
|
-
default:
|
|
383
|
-
break
|
|
384
|
-
}
|
|
554
|
+
connectionBox.handleStateUpdate(state)
|
|
385
555
|
}
|
|
386
556
|
|
|
387
557
|
dataConnection.start(queue: .global())
|
|
@@ -392,22 +562,26 @@ class FtpClient {
|
|
|
392
562
|
var receivedData = Data()
|
|
393
563
|
|
|
394
564
|
return try await withCheckedThrowingContinuation { continuation in
|
|
565
|
+
let connectionBox = ConnectionBox<Data>(continuation: continuation)
|
|
566
|
+
|
|
395
567
|
func receiveMore() {
|
|
396
568
|
connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { data, _, isComplete, error in
|
|
397
569
|
if let error = error {
|
|
398
|
-
|
|
570
|
+
connectionBox.resume(throwing: error)
|
|
399
571
|
return
|
|
400
572
|
}
|
|
401
573
|
|
|
402
574
|
if let data = data {
|
|
403
575
|
receivedData.append(data)
|
|
404
576
|
if reportProgress {
|
|
405
|
-
|
|
577
|
+
DispatchQueue.main.async {
|
|
578
|
+
self.progressDelegate?.onProgress(transferred: Int64(receivedData.count), total: -1)
|
|
579
|
+
}
|
|
406
580
|
}
|
|
407
581
|
}
|
|
408
582
|
|
|
409
583
|
if isComplete {
|
|
410
|
-
|
|
584
|
+
connectionBox.resume(returning: receivedData)
|
|
411
585
|
} else {
|
|
412
586
|
receiveMore()
|
|
413
587
|
}
|
|
@@ -427,15 +601,19 @@ class FtpClient {
|
|
|
427
601
|
let chunk = data.subdata(in: i..<endIndex)
|
|
428
602
|
|
|
429
603
|
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
|
604
|
+
let connectionBox = ConnectionBox<Void>(continuation: continuation)
|
|
605
|
+
|
|
430
606
|
connection.send(content: chunk, completion: .contentProcessed { error in
|
|
431
607
|
if let error = error {
|
|
432
|
-
|
|
608
|
+
connectionBox.resume(throwing: error)
|
|
433
609
|
} else {
|
|
434
610
|
sentBytes += chunk.count
|
|
435
611
|
if reportProgress {
|
|
436
|
-
|
|
612
|
+
DispatchQueue.main.async {
|
|
613
|
+
self.progressDelegate?.onProgress(transferred: Int64(sentBytes), total: Int64(totalSize))
|
|
614
|
+
}
|
|
437
615
|
}
|
|
438
|
-
|
|
616
|
+
connectionBox.resume()
|
|
439
617
|
}
|
|
440
618
|
})
|
|
441
619
|
}
|
|
@@ -443,48 +621,122 @@ class FtpClient {
|
|
|
443
621
|
}
|
|
444
622
|
|
|
445
623
|
private func parseListingData(_ data: Data) -> [FtpFileInfo] {
|
|
446
|
-
|
|
624
|
+
print("FTP DEBUG: Raw data length: \(data.count) bytes")
|
|
625
|
+
print("FTP DEBUG: Raw data hex: \(data.map { String(format: "%02x", $0) }.joined())")
|
|
447
626
|
|
|
448
|
-
|
|
449
|
-
var
|
|
627
|
+
// Try different encodings, prioritizing Chinese encodings
|
|
628
|
+
var string: String?
|
|
629
|
+
var encoding: String.Encoding = .utf8
|
|
630
|
+
|
|
631
|
+
// Try UTF-8 first
|
|
632
|
+
if let utf8String = String(data: data, encoding: .utf8) {
|
|
633
|
+
string = utf8String
|
|
634
|
+
encoding = .utf8
|
|
635
|
+
print("FTP DEBUG: Successfully decoded with UTF-8")
|
|
636
|
+
}
|
|
637
|
+
// Try GBK/GB2312 for Chinese
|
|
638
|
+
else if let gbkString = String(data: data, encoding: String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue)))) {
|
|
639
|
+
string = gbkString
|
|
640
|
+
encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue)))
|
|
641
|
+
print("FTP DEBUG: Successfully decoded with GBK/GB18030")
|
|
642
|
+
}
|
|
643
|
+
// Try Big5 for Traditional Chinese
|
|
644
|
+
else if let big5String = String(data: data, encoding: String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.big5.rawValue)))) {
|
|
645
|
+
string = big5String
|
|
646
|
+
encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.big5.rawValue)))
|
|
647
|
+
print("FTP DEBUG: Successfully decoded with Big5")
|
|
648
|
+
}
|
|
649
|
+
// Try ASCII
|
|
650
|
+
else if let asciiString = String(data: data, encoding: .ascii) {
|
|
651
|
+
string = asciiString
|
|
652
|
+
encoding = .ascii
|
|
653
|
+
print("FTP DEBUG: Successfully decoded with ASCII")
|
|
654
|
+
}
|
|
655
|
+
// Try Latin1 (ISO-8859-1)
|
|
656
|
+
else if let latin1String = String(data: data, encoding: .isoLatin1) {
|
|
657
|
+
string = latin1String
|
|
658
|
+
encoding = .isoLatin1
|
|
659
|
+
print("FTP DEBUG: Successfully decoded with Latin1")
|
|
660
|
+
}
|
|
661
|
+
// Try Windows-1252
|
|
662
|
+
else if let windowsString = String(data: data, encoding: .windowsCP1252) {
|
|
663
|
+
string = windowsString
|
|
664
|
+
encoding = .windowsCP1252
|
|
665
|
+
print("FTP DEBUG: Successfully decoded with Windows-1252")
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
print("FTP DEBUG: Failed to decode data with any encoding")
|
|
669
|
+
print("FTP DEBUG: Raw bytes: \(Array(data))")
|
|
670
|
+
return []
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
guard let decodedString = string else {
|
|
674
|
+
print("FTP DEBUG: No valid string found")
|
|
675
|
+
return []
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
print("FTP DEBUG: Decoded string with \(encoding): '\(decodedString)'")
|
|
679
|
+
print("FTP DEBUG: String length: \(decodedString.count) characters")
|
|
450
680
|
|
|
451
|
-
|
|
681
|
+
let lines = decodedString.components(separatedBy: .newlines)
|
|
682
|
+
print("FTP DEBUG: Found \(lines.count) lines")
|
|
683
|
+
var files: [FtpFileInfo] = []
|
|
684
|
+
|
|
685
|
+
for (index, line) in lines.enumerated() {
|
|
686
|
+
print("FTP DEBUG: Processing line \(index): '\(line)'")
|
|
452
687
|
if let fileInfo = parseListing(line) {
|
|
453
688
|
files.append(fileInfo)
|
|
689
|
+
print("FTP DEBUG: Parsed file: \(fileInfo.name), isDirectory: \(fileInfo.isDirectory)")
|
|
690
|
+
} else {
|
|
691
|
+
print("FTP DEBUG: Failed to parse line \(index)")
|
|
454
692
|
}
|
|
455
693
|
}
|
|
456
|
-
|
|
694
|
+
|
|
695
|
+
print("FTP DEBUG: Successfully parsed \(files.count) files")
|
|
457
696
|
return files
|
|
458
697
|
}
|
|
459
698
|
|
|
460
699
|
private func parseListing(_ line: String) -> FtpFileInfo? {
|
|
461
700
|
let trimmedLine = line.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
462
|
-
guard !trimmedLine.isEmpty else {
|
|
463
|
-
|
|
701
|
+
guard !trimmedLine.isEmpty else {
|
|
702
|
+
print("FTP DEBUG: Line is empty after trimming")
|
|
703
|
+
return nil
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
print("FTP DEBUG: Parsing line: '\(trimmedLine)'")
|
|
707
|
+
|
|
464
708
|
// Parse Unix-style listing: drwxr-xr-x 3 user group 4096 Mar 15 10:30 dirname
|
|
465
709
|
let components = trimmedLine.components(separatedBy: .whitespaces).filter { !$0.isEmpty }
|
|
466
|
-
|
|
467
|
-
|
|
710
|
+
print("FTP DEBUG: Components count: \(components.count), components: \(components)")
|
|
711
|
+
|
|
712
|
+
guard components.count >= 9 else {
|
|
713
|
+
print("FTP DEBUG: Not enough components (\(components.count)), expected at least 9")
|
|
714
|
+
return nil
|
|
715
|
+
}
|
|
716
|
+
|
|
468
717
|
let permissions = components[0]
|
|
469
718
|
let isDirectory = permissions.hasPrefix("d")
|
|
470
719
|
let size = isDirectory ? 0 : Int64(components[4]) ?? 0
|
|
471
|
-
|
|
720
|
+
|
|
472
721
|
// Reconstruct filename (can contain spaces)
|
|
473
722
|
let name = components[8...].joined(separator: " ")
|
|
474
|
-
|
|
723
|
+
|
|
475
724
|
// Parse date
|
|
476
725
|
let month = components[5]
|
|
477
726
|
let day = components[6]
|
|
478
727
|
let yearOrTime = components[7]
|
|
479
728
|
let lastModified = "\(month) \(day) \(yearOrTime)"
|
|
480
|
-
|
|
481
|
-
|
|
729
|
+
|
|
730
|
+
let fileInfo = FtpFileInfo(
|
|
482
731
|
name: name,
|
|
483
732
|
isDirectory: isDirectory,
|
|
484
733
|
size: size,
|
|
485
734
|
lastModified: lastModified,
|
|
486
735
|
permissions: permissions
|
|
487
736
|
)
|
|
737
|
+
|
|
738
|
+
print("FTP DEBUG: Successfully parsed file: \(fileInfo)")
|
|
739
|
+
return fileInfo
|
|
488
740
|
}
|
|
489
741
|
}
|
|
490
742
|
|
|
@@ -565,13 +817,9 @@ public class ReactNativeKookitModule: Module {
|
|
|
565
817
|
|
|
566
818
|
AsyncFunction("ftpDisconnect") { (promise: Promise) in
|
|
567
819
|
Task {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
promise.resolve()
|
|
572
|
-
} catch {
|
|
573
|
-
promise.reject("FTP_DISCONNECT_ERROR", error.localizedDescription)
|
|
574
|
-
}
|
|
820
|
+
await self.ftpClient?.disconnect()
|
|
821
|
+
self.ftpClient = nil
|
|
822
|
+
promise.resolve()
|
|
575
823
|
}
|
|
576
824
|
}
|
|
577
825
|
|
package/package.json
CHANGED