Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PDF worker support #1071

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bec438c
Bundle zotero/pdf-worker
mvasilak Dec 19, 2024
a9de38d
Add RecognizerController
mvasilak Jan 28, 2025
01413d8
Use recognizer controller when sharing a local PDF file
mvasilak Feb 13, 2025
ad75e66
Fix RecognizerController identifier lookup workflow
mvasilak Feb 14, 2025
62c5d00
Create parent for item that has its metadata retrieved
mvasilak Feb 17, 2025
17f9e48
Retrieve metadata and create parent if needed for added files
mvasilak Feb 17, 2025
7b0eff7
Improve RecognizerController
mvasilak Feb 18, 2025
9a039e8
Indicate when an item is retrieving metadata
mvasilak Feb 19, 2025
1160d58
Improve PDFWorkerController
mvasilak Feb 21, 2025
daf1203
Add PDFWorkerController tests
mvasilak Feb 21, 2025
68f57cb
Update submodule pdf-worker
mvasilak Feb 21, 2025
11a19aa
Cancel all Recognizer tasks on user logout
mvasilak Feb 24, 2025
1f2f7b1
Improve WebViewHandler
mvasilak Feb 24, 2025
18790c0
Simplify PDFWorkerController
mvasilak Feb 24, 2025
4b7a358
Improve code
mvasilak Feb 24, 2025
c71f8dc
Refactor RecognizerController internal types names
mvasilak Feb 24, 2025
4992213
Improve code
mvasilak Feb 24, 2025
6ac3b32
Fix PDFWorkerControllerSpec.swift tests
mvasilak Feb 24, 2025
7b0dfa5
Improve queuing & cleanup in PDFWorkerController & RecognizerController
mvasilak Feb 25, 2025
d0578c6
Refactor PDFWorkerWebViewHandler
mvasilak Feb 25, 2025
74093a9
Merge RecognizerController identifiers lookup methods
mvasilak Feb 25, 2025
143b0c0
Improve PDFWorkerController
mvasilak Feb 26, 2025
b61031c
Improve RecognizerController
mvasilak Feb 26, 2025
9372341
Improve RecognizerController
mvasilak Feb 26, 2025
028cc93
Improve PDFWorkerController & RecognizerController relay bindings
mvasilak Feb 26, 2025
0c3c2a3
Simplify RecognizerController.Update.Kind
mvasilak Feb 26, 2025
7f2925b
Improve code
mvasilak Mar 4, 2025
2dfdcfc
Improve RecognizerController code
mvasilak Mar 4, 2025
72cfdfd
Improve code
mvasilak Mar 4, 2025
cc6f493
Improve PDFWorkerWebViewHandler code
mvasilak Mar 4, 2025
05d319b
Improve code
mvasilak Mar 5, 2025
e4b5490
Improve code
mvasilak Mar 5, 2025
7d85b6a
Improve code
mvasilak Mar 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,4 @@ bundled/translators
bundled/styles
bundled/locales
bundled/note_editor
bundled/pdf_worker
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
[submodule "note-editor"]
path = note-editor
url = https://github.com/zotero/note-editor
[submodule "pdf-worker"]
path = pdf-worker
url = https://github.com/zotero/pdf-worker
2 changes: 1 addition & 1 deletion ZShare/Controllers/TranslationWebViewHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ final class TranslationWebViewHandler {
self.observable.on(.error(Error.noSuccessfulTranslators))

case .log:
DDLogInfo("JSLOG: \(body)")
DDLogInfo("TranslationWebViewHandler: JSLOG - \(body)")
}
}
}
40 changes: 37 additions & 3 deletions ZShare/View Controllers/ShareViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ final class ShareViewController: UIViewController {
private var fileStorage: FileStorage!
private var debugLogging: DebugLogging!
private var schemaController: SchemaController!
private var pdfWorkerController: PDFWorkerController!
private var secureStorage: KeychainSecureStorage!
private var viewModel: ExtensionViewModel!
private var storeCancellable: AnyCancellable?
Expand Down Expand Up @@ -714,15 +715,18 @@ final class ShareViewController: UIViewController {
let translatorsController = TranslatorsAndStylesController(apiClient: apiClient, bundledDataStorage: bundledDataStorage, fileStorage: fileStorage)
let secureStorage = KeychainSecureStorage()
let webDavController = WebDavControllerImpl(dbStorage: dbStorage, fileStorage: fileStorage, sessionStorage: SecureWebDavSessionStorage(secureStorage: secureStorage))
let pdfWorkerController = PDFWorkerController()

apiClient.set(authToken: ("Bearer " + session.apiToken))
translatorsController.updateFromRepo(type: .shareExtension)
pdfWorkerController.webViewProvider = self

self.fileStorage = fileStorage
self.schemaController = schemaController
self.dbStorage = dbStorage
self.bundledDataStorage = bundledDataStorage
self.translatorsController = translatorsController
self.pdfWorkerController = pdfWorkerController
self.secureStorage = secureStorage

self.viewModel = self.createViewModel(for: session.userId, dbStorage: dbStorage, apiClient: apiClient, schemaController: schemaController, fileStorage: fileStorage,
Expand All @@ -741,9 +745,39 @@ final class ShareViewController: UIViewController {
let attachmentDownloader = AttachmentDownloader(userId: userId, apiClient: apiClient, fileStorage: fileStorage, dbStorage: dbStorage, webDavController: webDavController)
let syncController = SyncController(userId: userId, apiClient: apiClient, dbStorage: dbStorage, fileStorage: fileStorage, schemaController: schemaController, dateParser: dateParser,
backgroundUploaderContext: backgroundUploadContext, webDavController: webDavController, attachmentDownloader: attachmentDownloader, syncDelayIntervals: DelayIntervals.sync, maxRetryCount: DelayIntervals.retry.count)
let recognizerController = RecognizerController(
pdfWorkerController: pdfWorkerController,
apiClient: apiClient,
translatorsController: translatorsController,
schemaController: schemaController,
dbStorage: dbStorage,
dateParser: dateParser
)
recognizerController.webViewProvider = self

return ExtensionViewModel(
webView: webView,
apiClient: apiClient,
attachmentDownloader: attachmentDownloader,
backgroundUploader: backgroundUploader,
backgroundUploadObserver: backgroundUploadObserver,
dbStorage: dbStorage,
schemaController: schemaController,
webDavController: webDavController,
dateParser: dateParser,
fileStorage: fileStorage,
syncController: syncController,
translatorsController: translatorsController,
recognizerController: recognizerController
)
}
}

return ExtensionViewModel(webView: self.webView, apiClient: apiClient, attachmentDownloader: attachmentDownloader, backgroundUploader: backgroundUploader, backgroundUploadObserver: backgroundUploadObserver, dbStorage: dbStorage,
schemaController: schemaController, webDavController: webDavController, dateParser: dateParser, fileStorage: fileStorage, syncController: syncController,
translatorsController: translatorsController)
extension ShareViewController: WebViewProvider {
func addWebView(configuration: WKWebViewConfiguration?) -> WKWebView {
let webView: WKWebView = configuration.flatMap({ WKWebView(frame: .zero, configuration: $0) }) ?? WKWebView()
webView.isHidden = true
view.insertSubview(webView, at: 0)
return webView
}
}
79 changes: 67 additions & 12 deletions ZShare/ViewModels/ExtensionViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ final class ExtensionViewModel {
private let backgroundUploadObserver: BackgroundUploadObserver
private let backgroundQueue: DispatchQueue
private let backgroundScheduler: SerialDispatchQueueScheduler
private let recognizerController: RecognizerController
private let disposeBag: DisposeBag
// Custom `URLSession` has to be used for downloading, instead of existing `apiClient`, so that we can include original cookies in download requests.
private let downloadUrlSession: URLSession
Expand All @@ -325,8 +326,21 @@ final class ExtensionViewModel {
let mtime: Int
}

init(webView: WKWebView, apiClient: ApiClient, attachmentDownloader: AttachmentDownloader, backgroundUploader: BackgroundUploader, backgroundUploadObserver: BackgroundUploadObserver, dbStorage: DbStorage, schemaController: SchemaController,
webDavController: WebDavController, dateParser: DateParser, fileStorage: FileStorage, syncController: SyncController, translatorsController: TranslatorsAndStylesController) {
init(
webView: WKWebView,
apiClient: ApiClient,
attachmentDownloader: AttachmentDownloader,
backgroundUploader: BackgroundUploader,
backgroundUploadObserver: BackgroundUploadObserver,
dbStorage: DbStorage,
schemaController: SchemaController,
webDavController: WebDavController,
dateParser: DateParser,
fileStorage: FileStorage,
syncController: SyncController,
translatorsController: TranslatorsAndStylesController,
recognizerController: RecognizerController
) {
let queue = DispatchQueue(label: "org.zotero.ZShare.BackgroundQueue", qos: .userInteractive)

let storage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: AppGroup.identifier)
Expand Down Expand Up @@ -355,6 +369,7 @@ final class ExtensionViewModel {
self.translationHandler = TranslationWebViewHandler(webView: webView, translatorsController: translatorsController)
self.backgroundQueue = queue
self.backgroundScheduler = SerialDispatchQueueScheduler(queue: queue, internalSerialQueueName: "org.zotero.ZShare.BackgroundScheduler")
self.recognizerController = recognizerController
self.state = State()
self.disposeBag = DisposeBag()

Expand Down Expand Up @@ -501,17 +516,57 @@ final class ExtensionViewModel {
let filename = url.lastPathComponent
let tmpFile = Files.temporaryFile(ext: url.pathExtension)

self.copyFile(from: url.path, to: tmpFile)
.subscribe(with: self, onSuccess: { `self`, _ in
var state = self.state
state.processedAttachment = .file(file: tmpFile, filename: filename)
state.expectedAttachment = (filename, tmpFile)
state.attachmentState = .processed
self.state = state
}, onFailure: { `self`, _ in
self.state.attachmentState = .failed(.fileMissing)
copyFile(from: url.path, to: tmpFile)
.subscribe(onSuccess: { [weak self] _ in
guard let self else { return }
guard fileStorage.isPdf(file: tmpFile), let file = tmpFile as? FileData else {
updateState(with: .processed)
return
}
recognize(file: file)
}, onFailure: { [weak self] _ in
self?.state.attachmentState = .failed(.fileMissing)
})
.disposed(by: self.disposeBag)
.disposed(by: disposeBag)

func updateState(with attachmentState: State.AttachmentState) {
var state = self.state
state.processedAttachment = .file(file: tmpFile, filename: filename)
state.expectedAttachment = (filename, tmpFile)
state.attachmentState = attachmentState
self.state = state
}

func recognize(file: FileData) {
recognizerController.queue(task: .init(file: file, kind: .simple))
.subscribe { [weak self] update in
guard let self else { return }
switch update.kind {
case .failed(let error):
DDLogError("ExtensionViewModel: could not recognize shared file - \(error)")
updateState(with: .processed)

case .cancelled:
updateState(with: .processed)

case .inProgress:
updateState(with: .decoding)

case .translated(let item):
var state = self.state
state.expectedItem = item
state.expectedAttachment = (filename, tmpFile)
state.attachmentState = .processed
// attachment is only used to get an optional URL, so we can safely pass an empty dictionary
state.processedAttachment = .itemWithAttachment(item: item, attachment: [:], attachmentFile: file)
self.state = state

case .enqueued, .createdParent:
break
}
}
.disposed(by: disposeBag)
}
}

private func process(remoteFileUrl url: URL, contentType: String, cookies: String, userAgent: String, referrer: String) {
Expand Down
Loading
Loading