@hanwha-ss1/plugin 0.7.1 → 0.7.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.
|
@@ -33,6 +33,63 @@ public class DownloadPlugin: CAPPlugin {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
@objc func fileDownloadChunk(_ call: CAPPluginCall) {
|
|
38
|
+
guard let fileName = call.getString("fileName"),
|
|
39
|
+
let chunkData = call.getString("chunkData") else {
|
|
40
|
+
call.resolve([
|
|
41
|
+
"result": false,
|
|
42
|
+
"message": "fileName 또는 chunkData 누락"
|
|
43
|
+
])
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let chunkIndex = call.getInt("chunkIndex") ?? 0
|
|
48
|
+
let totalChunks = call.getInt("totalChunks") ?? 1
|
|
49
|
+
let isLastChunk = call.getBool("isLastChunk") ?? false
|
|
50
|
+
|
|
51
|
+
// Base64 → Data 변환
|
|
52
|
+
guard let data = Data(base64Encoded: chunkData) else {
|
|
53
|
+
call.resolve([
|
|
54
|
+
"result": false,
|
|
55
|
+
"message": "청크 데이터(Base64) 디코딩 실패"
|
|
56
|
+
])
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 다운로드 생성 (최초 호출 시만)
|
|
61
|
+
if chunkIndex == 0 {
|
|
62
|
+
_ = DownloadService.createDownload(id: fileName, totalChunks: totalChunks)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 오프셋 = 청크 크기 * 인덱스
|
|
66
|
+
let offset = UInt64(data.count * chunkIndex)
|
|
67
|
+
DownloadService.appendChunk(id: fileName, data: data, offset: offset)
|
|
68
|
+
|
|
69
|
+
if isLastChunk {
|
|
70
|
+
if DownloadService.isComplete(id: fileName),
|
|
71
|
+
let finalURL = DownloadService.finishDownload(id: fileName) {
|
|
72
|
+
call.resolve([
|
|
73
|
+
"result": true,
|
|
74
|
+
"message": "청크 다운로드 완료",
|
|
75
|
+
"path": finalURL.path
|
|
76
|
+
])
|
|
77
|
+
return
|
|
78
|
+
} else {
|
|
79
|
+
call.resolve([
|
|
80
|
+
"result": false,
|
|
81
|
+
"message": "마지막 청크 처리 중 오류 발생"
|
|
82
|
+
])
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 마지막 청크가 아니면 그냥 성공 반환
|
|
88
|
+
call.resolve([
|
|
89
|
+
"result": true,
|
|
90
|
+
"message": "청크 \(chunkIndex + 1)/\(totalChunks) 저장 완료"
|
|
91
|
+
])
|
|
92
|
+
}
|
|
36
93
|
|
|
37
94
|
/// 대용량 Base64 문자열을 스트리밍 방식으로 파일로 변환
|
|
38
95
|
private func decodeBase64ToFile(base64: String, fileURL: URL) -> Bool {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import Capacitor
|
|
4
|
+
|
|
5
|
+
public class DownloadService: NSObject {
|
|
6
|
+
|
|
7
|
+
// MARK: - 내부 구조체 (안드로이드의 ChunkDownloadInfo 대응)
|
|
8
|
+
private class ChunkDownloadInfo {
|
|
9
|
+
var tempFile: URL
|
|
10
|
+
var totalChunks: Int
|
|
11
|
+
var receivedChunks: Int
|
|
12
|
+
var fileHandle: FileHandle?
|
|
13
|
+
|
|
14
|
+
init(tempFile: URL, totalChunks: Int) {
|
|
15
|
+
self.tempFile = tempFile
|
|
16
|
+
self.totalChunks = totalChunks
|
|
17
|
+
self.receivedChunks = 0
|
|
18
|
+
|
|
19
|
+
// 임시 파일이 없으면 생성
|
|
20
|
+
if !FileManager.default.fileExists(atPath: tempFile.path) {
|
|
21
|
+
FileManager.default.createFile(atPath: tempFile.path, contents: nil, attributes: nil)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 파일 핸들 열기
|
|
25
|
+
do {
|
|
26
|
+
self.fileHandle = try FileHandle(forWritingTo: tempFile)
|
|
27
|
+
} catch {
|
|
28
|
+
print("❌ 파일 핸들 생성 실패: \(error)")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
deinit {
|
|
33
|
+
if #available(iOS 13.0, *) {
|
|
34
|
+
try? fileHandle?.close()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// MARK: - 다운로드 상태 관리 (안드로이드의 ConcurrentHashMap 대응)
|
|
40
|
+
private static var chunkDownloads: [String: ChunkDownloadInfo] = [:]
|
|
41
|
+
private static let queue = DispatchQueue(label: "DownloadServiceQueue", attributes: .concurrent)
|
|
42
|
+
|
|
43
|
+
// MARK: - 다운로드 생성
|
|
44
|
+
public static func createDownload(id: String, totalChunks: Int) -> URL? {
|
|
45
|
+
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(id).tmp")
|
|
46
|
+
let info = ChunkDownloadInfo(tempFile: tempURL, totalChunks: totalChunks)
|
|
47
|
+
|
|
48
|
+
queue.async(flags: .barrier) {
|
|
49
|
+
chunkDownloads[id] = info
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return tempURL
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// MARK: - 청크 추가
|
|
56
|
+
public static func appendChunk(id: String, data: Data, offset: UInt64) {
|
|
57
|
+
queue.async(flags: .barrier) {
|
|
58
|
+
guard let info = chunkDownloads[id],
|
|
59
|
+
let handle = info.fileHandle else { return }
|
|
60
|
+
|
|
61
|
+
do {
|
|
62
|
+
if #available(iOS 13.0, *) {
|
|
63
|
+
try handle.seek(toOffset: offset)
|
|
64
|
+
}
|
|
65
|
+
handle.write(data)
|
|
66
|
+
info.receivedChunks += 1
|
|
67
|
+
} catch {
|
|
68
|
+
print("❌ 청크 쓰기 실패: \(error)")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// MARK: - 완료 여부 확인
|
|
74
|
+
public static func isComplete(id: String) -> Bool {
|
|
75
|
+
var complete = false
|
|
76
|
+
queue.sync {
|
|
77
|
+
if let info = chunkDownloads[id] {
|
|
78
|
+
complete = info.receivedChunks >= info.totalChunks
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return complete
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// MARK: - 다운로드 마무리
|
|
85
|
+
public static func finishDownload(id: String) -> URL? {
|
|
86
|
+
var fileURL: URL?
|
|
87
|
+
queue.sync(flags: .barrier) {
|
|
88
|
+
if let info = chunkDownloads.removeValue(forKey: id) {
|
|
89
|
+
fileURL = info.tempFile
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return fileURL
|
|
93
|
+
}
|
|
94
|
+
}
|
package/ios/Plugin/Plugin.m
CHANGED
|
@@ -12,6 +12,7 @@ CAP_PLUGIN(Plugin, "Plugin",
|
|
|
12
12
|
CAP_PLUGIN_METHOD(open, CAPPluginReturnPromise);
|
|
13
13
|
CAP_PLUGIN_METHOD(edgeSwipe, CAPPluginReturnPromise);
|
|
14
14
|
CAP_PLUGIN_METHOD(fileDownload, CAPPluginReturnPromise);
|
|
15
|
+
CAP_PLUGIN_METHOD(fileDownloadChunk, CAPPluginReturnPromise);
|
|
15
16
|
CAP_PLUGIN_METHOD(doDisabledCapture, CAPPluginReturnPromise);
|
|
16
17
|
CAP_PLUGIN_METHOD(addKeyboardMenu, CAPPluginReturnPromise);
|
|
17
18
|
CAP_PLUGIN_METHOD(removeKeyboardMenu, CAPPluginReturnPromise);
|
package/ios/Plugin/Plugin.swift
CHANGED
|
@@ -46,15 +46,11 @@ public class Plugin: CAPPlugin {
|
|
|
46
46
|
안드로이드에서만 사용
|
|
47
47
|
*/
|
|
48
48
|
@objc func executeApp(_ call: CAPPluginCall) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if let url = URL(string: call.getString("package") ?? "") {
|
|
49
|
+
if let url = URL(string: call.getString("package") ?? "") {
|
|
52
50
|
DispatchQueue.main.async {
|
|
53
51
|
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
|
54
52
|
}
|
|
55
53
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
54
|
}
|
|
59
55
|
|
|
60
56
|
@objc func auth(_ call: CAPPluginCall) {
|
|
@@ -69,9 +65,6 @@ public class Plugin: CAPPlugin {
|
|
|
69
65
|
KeyboardMenuPlugin().remove(call)
|
|
70
66
|
}
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
68
|
@objc func addContact(_ call: CAPPluginCall) {
|
|
76
69
|
ContactPlugin().addContact(call)
|
|
77
70
|
}
|
|
@@ -97,6 +90,10 @@ public class Plugin: CAPPlugin {
|
|
|
97
90
|
DownloadPlugin().doDownload(call, self.bridge!)
|
|
98
91
|
}
|
|
99
92
|
|
|
93
|
+
@objc func fileDownloadChunk(_ call: CAPPluginCall) {
|
|
94
|
+
DownloadPlugin().fileDownloadChunk(call)
|
|
95
|
+
}
|
|
96
|
+
|
|
100
97
|
@objc func doDisabledCapture(_ call: CAPPluginCall) {
|
|
101
98
|
CapturePlugin().doDiabledCapture(call, self.bridge!)
|
|
102
99
|
}
|