If syncing fails, the item is still saved so we don't change the title. When syncing fails make sure the red error cloud is shown.
178 lines
5.4 KiB
Swift
178 lines
5.4 KiB
Swift
//
|
|
// File.swift
|
|
//
|
|
//
|
|
// Created by Jackson Harper on 6/1/22.
|
|
//
|
|
|
|
import Foundation
|
|
import Models
|
|
import Services
|
|
import Views
|
|
|
|
class ExtensionSaveService {
|
|
let queue: OperationQueue
|
|
|
|
init() {
|
|
self.queue = OperationQueue()
|
|
}
|
|
|
|
private func queueSaveOperation(_ pageScrape: PageScrapePayload, requestId: String, shareExtensionViewModel: ShareExtensionChildViewModel) {
|
|
ProcessInfo().performExpiringActivity(withReason: "app.omnivore.SaveActivity") { [self] expiring in
|
|
guard !expiring else {
|
|
self.queue.cancelAllOperations()
|
|
self.queue.waitUntilAllOperationsAreFinished()
|
|
return
|
|
}
|
|
|
|
let operation = SaveOperation(pageScrapePayload: pageScrape, requestId: requestId, shareExtensionViewModel: shareExtensionViewModel)
|
|
|
|
self.queue.addOperation(operation)
|
|
self.queue.waitUntilAllOperationsAreFinished()
|
|
}
|
|
}
|
|
|
|
public func save(_ extensionContext: NSExtensionContext, requestId: String, shareExtensionViewModel: ShareExtensionChildViewModel) {
|
|
PageScraper.scrape(extensionContext: extensionContext) { [weak self] result in
|
|
guard let self = self else { return }
|
|
|
|
switch result {
|
|
case let .success(payload):
|
|
DispatchQueue.main.async {
|
|
shareExtensionViewModel.status = .saved
|
|
|
|
let url = URLComponents(string: payload.url)
|
|
let hostname = URL(string: payload.url)?.host ?? ""
|
|
|
|
switch payload.contentType {
|
|
case let .html(html: _, title: title, iconURL: iconURL):
|
|
shareExtensionViewModel.title = title
|
|
shareExtensionViewModel.iconURL = iconURL
|
|
shareExtensionViewModel.url = hostname
|
|
case .none:
|
|
shareExtensionViewModel.url = hostname
|
|
shareExtensionViewModel.title = payload.url
|
|
if var url = url {
|
|
url.path = "/favicon.ico"
|
|
shareExtensionViewModel.iconURL = url.url?.absoluteString
|
|
}
|
|
case let .pdf(localUrl: _):
|
|
shareExtensionViewModel.title = payload.url
|
|
shareExtensionViewModel.url = hostname
|
|
}
|
|
}
|
|
self.queueSaveOperation(payload, requestId: requestId, shareExtensionViewModel: shareExtensionViewModel)
|
|
case let .failure(error):
|
|
DispatchQueue.main.async {
|
|
shareExtensionViewModel.status = .failed(error: .unknown(description: "Could not retrieve content"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class SaveOperation: Operation, URLSessionDelegate {
|
|
let requestId: String
|
|
let services: Services
|
|
let pageScrapePayload: PageScrapePayload
|
|
let shareExtensionViewModel: ShareExtensionChildViewModel
|
|
|
|
var queue: OperationQueue?
|
|
var uploadTask: URLSessionTask?
|
|
|
|
enum State: Int {
|
|
case created
|
|
case started
|
|
case finished
|
|
}
|
|
|
|
init(pageScrapePayload: PageScrapePayload, requestId: String, shareExtensionViewModel: ShareExtensionChildViewModel) {
|
|
self.pageScrapePayload = pageScrapePayload
|
|
self.requestId = requestId
|
|
self.shareExtensionViewModel = shareExtensionViewModel
|
|
|
|
self.state = .created
|
|
self.services = Services()
|
|
}
|
|
|
|
open var state: State = .created {
|
|
willSet {
|
|
willChangeValue(forKey: "isReady")
|
|
willChangeValue(forKey: "isExecuting")
|
|
willChangeValue(forKey: "isFinished")
|
|
willChangeValue(forKey: "isCancelled")
|
|
}
|
|
didSet {
|
|
didChangeValue(forKey: "isCancelled")
|
|
didChangeValue(forKey: "isFinished")
|
|
didChangeValue(forKey: "isExecuting")
|
|
didChangeValue(forKey: "isReady")
|
|
}
|
|
}
|
|
|
|
override var isAsynchronous: Bool {
|
|
true
|
|
}
|
|
|
|
override var isReady: Bool {
|
|
true
|
|
}
|
|
|
|
override var isExecuting: Bool {
|
|
self.state == .started
|
|
}
|
|
|
|
override var isFinished: Bool {
|
|
self.state == .finished
|
|
}
|
|
|
|
override func start() {
|
|
guard !isCancelled else { return }
|
|
state = .started
|
|
queue = OperationQueue()
|
|
|
|
Task {
|
|
await persist(services: self.services, pageScrapePayload: self.pageScrapePayload, requestId: self.requestId)
|
|
}
|
|
}
|
|
|
|
override func cancel() {
|
|
super.cancel()
|
|
}
|
|
|
|
private func updateStatus(newStatus: ShareExtensionStatus) {
|
|
DispatchQueue.main.async {
|
|
self.shareExtensionViewModel.status = newStatus
|
|
}
|
|
}
|
|
|
|
private func persist(services: Services, pageScrapePayload: PageScrapePayload, requestId: String) async {
|
|
do {
|
|
try await services.dataService.persistPageScrapePayload(pageScrapePayload, requestId: requestId)
|
|
} catch {
|
|
updateStatus(newStatus: .failed(error: SaveArticleError.unknown(description: "Unable to access content")))
|
|
return
|
|
}
|
|
|
|
do {
|
|
updateStatus(newStatus: .saved)
|
|
|
|
switch pageScrapePayload.contentType {
|
|
case .none:
|
|
try await services.dataService.syncUrl(id: requestId, url: pageScrapePayload.url)
|
|
case let .pdf(localUrl):
|
|
try await services.dataService.syncPdf(id: requestId, localPdfURL: localUrl, url: pageScrapePayload.url)
|
|
case let .html(html, title, _):
|
|
try await services.dataService.syncPage(id: requestId, originalHtml: html, title: title, url: pageScrapePayload.url)
|
|
}
|
|
|
|
} catch {
|
|
updateStatus(newStatus: .syncFailed(error: SaveArticleError.unknown(description: "Unknown Error")))
|
|
return
|
|
}
|
|
|
|
updateStatus(newStatus: .synced)
|
|
state = .finished
|
|
}
|
|
}
|
|
}
|