diff --git a/ZShare/Controllers/TranslationWebViewHandler.swift b/ZShare/Controllers/TranslationWebViewHandler.swift index 075cecdf2..34eb3a1fe 100644 --- a/ZShare/Controllers/TranslationWebViewHandler.swift +++ b/ZShare/Controllers/TranslationWebViewHandler.swift @@ -269,7 +269,7 @@ final class TranslationWebViewHandler { self.observable.on(.error(Error.noSuccessfulTranslators)) case .log: - DDLogInfo("JSLOG: \(body)") + DDLogInfo("TranslationWebViewHandler: JSLOG - \(body)") } } } diff --git a/Zotero/Controllers/PDFWorkerController.swift b/Zotero/Controllers/PDFWorkerController.swift index c2fc22cd4..431730e79 100644 --- a/Zotero/Controllers/PDFWorkerController.swift +++ b/Zotero/Controllers/PDFWorkerController.swift @@ -37,11 +37,6 @@ final class PDFWorkerController { let kind: Kind } - enum PDFWorkState { - case enqueued - case inProgress - } - // MARK: Properties private let dispatchSpecificKey: DispatchSpecificKey private let accessQueueLabel: String @@ -52,11 +47,9 @@ final class PDFWorkerController { // Accessed only via accessQueue private static let maxConcurrentPDFWorkers: Int = 1 - // Using an OrderedDictionary instead of an Array, so we can O(1) when cancelling a work that is still queued. - private var queue: OrderedDictionary> = [:] + private var queue: [PDFWork] = [] private var subjectsByPDFWork: [PDFWork: PublishSubject] = [:] private var pdfWorkerWebViewHandlersByPDFWork: [PDFWork: PDFWorkerWebViewHandler] = [:] - private var statesByPDFWork: [PDFWork: PDFWorkState] = [:] // MARK: Object Lifecycle init() { @@ -76,9 +69,8 @@ final class PDFWorkerController { existingSubject.bind(to: subject).disposed(by: disposeBag) return } - queue[work] = subject + queue.append(work) subjectsByPDFWork[work] = subject - statesByPDFWork[work] = .enqueued startWorkIfNeeded() } @@ -87,7 +79,11 @@ final class PDFWorkerController { private func startWorkIfNeeded() { guard pdfWorkerWebViewHandlersByPDFWork.count < Self.maxConcurrentPDFWorkers, !queue.isEmpty else { return } - let (work, subject) = queue.removeFirst() + let work = queue.removeFirst() + guard let subject = subjectsByPDFWork[work] else { + startWorkIfNeeded() + return + } start(work: work, subject: subject) startWorkIfNeeded() @@ -110,7 +106,6 @@ final class PDFWorkerController { return } - statesByPDFWork[work] = .inProgress setupObserver(for: pdfWorkerWebViewHandler) subject.on(.next(Update(work: work, kind: .inProgress))) switch work.kind { @@ -125,11 +120,11 @@ final class PDFWorkerController { pdfWorkerWebViewHandler.observable .subscribe(onNext: { [weak self] in guard let self else { return } - process(result: $0) + process(result: $0, self: self) }) .disposed(by: disposeBag) - func process(result: Result) { + func process(result: Result, self: PDFWorkerController) { switch result { case .success(let data): switch data { @@ -162,7 +157,7 @@ final class PDFWorkerController { pdfWorkerWebViewHandlersByPDFWork.values.forEach { $0.webViewHandler.removeFromSuperviewAsynchronously() } pdfWorkerWebViewHandlersByPDFWork = [:] // Then cancel actual works, and send cancelled event for each queued work. - let works = subjectsByPDFWork.keys + Array(queue.keys) + let works = subjectsByPDFWork.keys + queue for work in works { cancel(work: work) } @@ -176,20 +171,20 @@ final class PDFWorkerController { return Disposables.create() } if DispatchQueue.getSpecific(key: dispatchSpecificKey) == accessQueueLabel { - cleanup(for: work, maybe: maybe) + cleanup(for: work, maybe: maybe, self: self) } else { - accessQueue.async(flags: .barrier) { - cleanup(for: work, maybe: maybe) + accessQueue.async(flags: .barrier) { [weak self] in + guard let self else { return } + cleanup(for: work, maybe: maybe, self: self) } } return Disposables.create() } - func cleanup(for work: PDFWork, maybe: (MaybeEvent>) -> Void) { - let subject = queue[work] ?? subjectsByPDFWork[work] - queue[work] = nil + func cleanup(for work: PDFWork, maybe: (MaybeEvent>) -> Void, self: PDFWorkerController) { + let subject = subjectsByPDFWork[work] + queue.removeAll(where: { $0 == work }) subjectsByPDFWork[work] = nil - statesByPDFWork[work] = nil DDLogInfo("PDFWorkerController: cleaned up for \(work)") pdfWorkerWebViewHandlersByPDFWork.removeValue(forKey: work)?.webViewHandler.removeFromSuperviewAsynchronously() if let subject { diff --git a/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift b/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift index 18f399be3..736bff5de 100644 --- a/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/LookupWebViewHandler.swift @@ -40,7 +40,7 @@ final class LookupWebViewHandler { case item([String: Any]) } - private enum InitializationResult { + private enum InitializationState { case initialized case inProgress case failed(Swift.Error) @@ -51,14 +51,14 @@ final class LookupWebViewHandler { private let disposeBag: DisposeBag let observable: PublishSubject> - private var isLoading: BehaviorRelay + private var initializationState: BehaviorRelay init(webView: WKWebView, translatorsController: TranslatorsAndStylesController) { self.translatorsController = translatorsController webViewHandler = WebViewHandler(webView: webView, javascriptHandlers: JSHandlers.allCases.map({ $0.rawValue })) observable = PublishSubject() disposeBag = DisposeBag() - isLoading = BehaviorRelay(value: .inProgress) + initializationState = BehaviorRelay(value: .inProgress) webViewHandler.receivedMessageHandler = { [weak self] name, body in self?.receiveMessage(name: name, body: body) @@ -69,10 +69,10 @@ final class LookupWebViewHandler { .observe(on: MainScheduler.instance) .subscribe(onSuccess: { [weak self] _ in DDLogInfo("LookupWebViewHandler: initialization succeeded") - self?.isLoading.accept(.initialized) + self?.initializationState.accept(.initialized) }, onFailure: { [weak self] error in DDLogInfo("LookupWebViewHandler: initialization failed - \(error)") - self?.isLoading.accept(.failed(error)) + self?.initializationState.accept(.failed(error)) }) .disposed(by: disposeBag) @@ -134,7 +134,7 @@ final class LookupWebViewHandler { } func lookUp(identifier: String) { - switch isLoading.value { + switch initializationState.value { case .failed(let error): observable.on(.next(.failure(error))) @@ -142,7 +142,7 @@ final class LookupWebViewHandler { performLookUp(for: identifier) case .inProgress: - isLoading.filter { result in + initializationState.filter { result in switch result { case .inProgress: return false @@ -210,7 +210,7 @@ final class LookupWebViewHandler { observable.on(.next(.success(.identifiers(rawData)))) case .log: - DDLogInfo("JSLOG: \(body)") + DDLogInfo("LookupWebViewHandler: JSLOG - \(body)") case .request: guard let body = body as? [String: Any], diff --git a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift index 11d8dff38..eee49a3b7 100644 --- a/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/PDFWorkerWebViewHandler.swift @@ -33,7 +33,7 @@ final class PDFWorkerWebViewHandler { case fullText(data: [String: Any]) } - private enum InitializationResult { + private enum InitializationState { case initialized case inProgress case failed(Swift.Error) @@ -43,13 +43,13 @@ final class PDFWorkerWebViewHandler { private let disposeBag: DisposeBag let observable: PublishSubject> - private var isLoading: BehaviorRelay + private var initializationState: BehaviorRelay init(webView: WKWebView) { webViewHandler = WebViewHandler(webView: webView, javascriptHandlers: JSHandlers.allCases.map({ $0.rawValue })) observable = PublishSubject() disposeBag = DisposeBag() - isLoading = BehaviorRelay(value: .inProgress) + initializationState = BehaviorRelay(value: .inProgress) webViewHandler.receivedMessageHandler = { [weak self] name, body in self?.receiveMessage(name: name, body: body) @@ -60,19 +60,16 @@ final class PDFWorkerWebViewHandler { .observe(on: MainScheduler.instance) .subscribe(onSuccess: { [weak self] _ in DDLogInfo("PDFWorkerWebViewHandler: initialization succeeded") - self?.isLoading.accept(.initialized) + self?.initializationState.accept(.initialized) }, onFailure: { [weak self] error in DDLogInfo("PDFWorkerWebViewHandler: initialization failed - \(error)") - self?.isLoading.accept(.failed(error)) + self?.initializationState.accept(.failed(error)) }) .disposed(by: disposeBag) - func initialize() -> Single { + func initialize() -> Single<()> { DDLogInfo("PDFWorkerWebViewHandler: initialize web view") return loadIndex() - .flatMap { _ in - Single.just(Void()) - } func loadIndex() -> Single<()> { guard let indexUrl = Bundle.main.url(forResource: "worker", withExtension: "html") else { @@ -84,7 +81,7 @@ final class PDFWorkerWebViewHandler { } private func performCall(completion: @escaping () -> Void) { - switch isLoading.value { + switch initializationState.value { case .failed(let error): observable.on(.next(.failure(error))) @@ -92,7 +89,7 @@ final class PDFWorkerWebViewHandler { completion() case .inProgress: - isLoading.filter { result in + initializationState.filter { result in switch result { case .inProgress: return false @@ -121,11 +118,12 @@ final class PDFWorkerWebViewHandler { func recognize(file: FileData) { let filePath = file.createUrl().path - performCall { - performRecognize(for: filePath) + performCall { [weak self] in + guard let self else { return } + performRecognize(for: filePath, self: self) } - func performRecognize(for path: String) { + func performRecognize(for path: String, self: PDFWorkerWebViewHandler) { DDLogInfo("PDFWorkerWebViewHandler: call recognize js") return webViewHandler.call(javascript: "recognize('\(path)');") .subscribe(on: MainScheduler.instance) @@ -140,11 +138,12 @@ final class PDFWorkerWebViewHandler { func getFullText(file: FileData) { let filePath = file.createUrl().path - performCall { - performGetFullText(for: filePath) + performCall { [weak self] in + guard let self else { return } + performGetFullText(for: filePath, self: self) } - func performGetFullText(for path: String) { + func performGetFullText(for path: String, self: PDFWorkerWebViewHandler) { DDLogInfo("PDFWorkerWebViewHandler: call getFullText js") return webViewHandler.call(javascript: "getFullText('\(path)');") .subscribe(on: MainScheduler.instance) @@ -172,7 +171,7 @@ final class PDFWorkerWebViewHandler { observable.on(.next(.success(.recognizerData(data: data)))) case .log: - DDLogInfo("JSLOG: \(body)") + DDLogInfo("PDFWorkerWebViewHandler: JSLOG - \(body)") } } } diff --git a/Zotero/Controllers/Web View Handling/WebViewHandler.swift b/Zotero/Controllers/Web View Handling/WebViewHandler.swift index e9e246c5e..ad480d9a7 100644 --- a/Zotero/Controllers/Web View Handling/WebViewHandler.swift +++ b/Zotero/Controllers/Web View Handling/WebViewHandler.swift @@ -78,15 +78,6 @@ final class WebViewHandler: NSObject { return createWebLoadedSingle() } - func loadHTMLString(_ string: String, baseURL: URL?) -> Single<()> { - guard let webView else { - DDLogError("WebViewHandler: web view is nil") - return .error(Error.webViewMissing) - } - webView.loadHTMLString(string, baseURL: baseURL) - return createWebLoadedSingle() - } - func load(webUrl: URL) -> Single<()> { guard let webView else { DDLogError("WebViewHandler: web view is nil")