@nitra/zebra 8.2.0 → 8.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.
|
@@ -131,6 +131,8 @@ public class Zebra {
|
|
|
131
131
|
out.write(data, offset, len);
|
|
132
132
|
}
|
|
133
133
|
out.flush();
|
|
134
|
+
// Затримка перед закриттям: дає Bluetooth-стеку та принтеру час відправити/прийняти дані (інакше перший друк іноді не виконується, лише другий).
|
|
135
|
+
Thread.sleep(400);
|
|
134
136
|
|
|
135
137
|
Logger.info("Zebra", "ZPL успішно відправлено: " + data.length + " байт");
|
|
136
138
|
callback.onSuccess(addrTrimmed, data.length);
|
|
@@ -86,7 +86,8 @@ import ExternalAccessory
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/// Відповідає за запис байтів у OutputStream з таймаутом та звітує через completion
|
|
89
|
+
/// Відповідає за запис байтів у OutputStream з таймаутом та звітує через completion.
|
|
90
|
+
/// Закриття потоків і сесії гарантовано виконується один раз (ідемпотентний close), у т.ч. по таймеру при відсутності подій.
|
|
90
91
|
private class StreamWriter: NSObject, StreamDelegate {
|
|
91
92
|
private let output: OutputStream
|
|
92
93
|
private let data: [UInt8]
|
|
@@ -96,6 +97,9 @@ import ExternalAccessory
|
|
|
96
97
|
private var completion: ((Result<PrintResult, ZebraError>) -> Void)?
|
|
97
98
|
private var startTime = Date()
|
|
98
99
|
private let session: EASession
|
|
100
|
+
private var didClose = false
|
|
101
|
+
private let closeLock = NSLock()
|
|
102
|
+
private var timeoutTimer: Timer?
|
|
99
103
|
|
|
100
104
|
init(owner _: Zebra, session: EASession, output: OutputStream, data: [UInt8], address: String, timeout: TimeInterval = 5.0, completion: @escaping (Result<PrintResult, ZebraError>) -> Void) {
|
|
101
105
|
self.session = session
|
|
@@ -108,6 +112,7 @@ import ExternalAccessory
|
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
/// Запускає стріми — обидва (input і output) відкриваються й додаються в RunLoop, як у документації Apple; інакше сесія може не звільнятися коректно.
|
|
115
|
+
/// Таймер гарантує закриття при відсутності подій потоку (наприклад, зависання принтера).
|
|
111
116
|
func start() {
|
|
112
117
|
let runLoop = RunLoop.current
|
|
113
118
|
let mode: RunLoop.Mode = .default
|
|
@@ -121,11 +126,15 @@ import ExternalAccessory
|
|
|
121
126
|
output.open()
|
|
122
127
|
logDebug("StreamWriter started, bytes= \(self.data.count)")
|
|
123
128
|
startTime = Date()
|
|
129
|
+
timeoutTimer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in
|
|
130
|
+
logDebug("StreamWriter timeout (no events)")
|
|
131
|
+
self?.close(with: .failure(.writeFailed("Таймаут запису")))
|
|
132
|
+
}
|
|
124
133
|
}
|
|
125
134
|
|
|
126
|
-
/// Після запису всіх байтів дає час на флаш буфера (iOS може не встигнути відправити на пристрій при миттєвому
|
|
135
|
+
/// Після запису всіх байтів дає час на флаш буфера (iOS може не встигнути відправити на пристрій при миттєвому закритті; при першому друці принтер ще "прокидається" — потрібно більше часу).
|
|
127
136
|
private func flushThenClose() {
|
|
128
|
-
let flushDuration: TimeInterval =
|
|
137
|
+
let flushDuration: TimeInterval = 1.5
|
|
129
138
|
let deadline = Date().addingTimeInterval(flushDuration)
|
|
130
139
|
while Date() < deadline {
|
|
131
140
|
RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.1))
|
|
@@ -134,9 +143,20 @@ import ExternalAccessory
|
|
|
134
143
|
close(with: .success(PrintResult(address: address)))
|
|
135
144
|
}
|
|
136
145
|
|
|
137
|
-
/// Закриває стріми та викликає completion один
|
|
146
|
+
/// Закриває стріми та викликає completion один раз (ідемпотентно).
|
|
138
147
|
/// Порядок важливий для коректного звільнення EASession у iOS — інакше повторне відкриття сесії до того ж аксесуара може повертати nil.
|
|
139
148
|
func close(with result: Result<PrintResult, ZebraError>) {
|
|
149
|
+
closeLock.lock()
|
|
150
|
+
guard !didClose else {
|
|
151
|
+
closeLock.unlock()
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
didClose = true
|
|
155
|
+
closeLock.unlock()
|
|
156
|
+
|
|
157
|
+
timeoutTimer?.invalidate()
|
|
158
|
+
timeoutTimer = nil
|
|
159
|
+
|
|
140
160
|
let runLoop = RunLoop.current
|
|
141
161
|
let mode: RunLoop.Mode = .default
|
|
142
162
|
// Даємо RunLoop обробити останні події перед закриттям
|