195 lines
7.0 KiB
Swift
195 lines
7.0 KiB
Swift
//
|
|
// AppDelegate.swift
|
|
// MastoSearch
|
|
//
|
|
// Created by Shadowfacts on 12/10/21.
|
|
//
|
|
|
|
import Cocoa
|
|
import UniformTypeIdentifiers
|
|
import AuthenticationServices
|
|
import Combine
|
|
import OSLog
|
|
|
|
@main
|
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
|
|
|
@IBOutlet weak var accountMenu: NSMenu!
|
|
|
|
let onSync = PassthroughSubject<Void, Never>()
|
|
|
|
private let syncLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Sync")
|
|
private var authSession: ASWebAuthenticationSession?
|
|
private var syncTotal = 0
|
|
|
|
func applicationWillFinishLaunching(_ notification: Notification) {
|
|
DatabaseController.shared.initialize()
|
|
|
|
updateAccountMenu()
|
|
}
|
|
|
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
|
syncStatuses()
|
|
}
|
|
|
|
func applicationWillTerminate(_ aNotification: Notification) {
|
|
DatabaseController.shared.close()
|
|
}
|
|
|
|
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
|
return true
|
|
}
|
|
|
|
private func updateAccountMenu() {
|
|
accountMenu.removeAllItems()
|
|
if let account = LocalData.account {
|
|
let item = accountMenu.addItem(withTitle: "Logged in to \(account.instanceURL.host!)", action: nil, keyEquivalent: "")
|
|
item.isEnabled = false
|
|
accountMenu.addItem(withTitle: "Log out", action: #selector(logOut), keyEquivalent: "")
|
|
} else {
|
|
accountMenu.addItem(withTitle: "Log in...", action: #selector(logIn), keyEquivalent: "")
|
|
}
|
|
}
|
|
|
|
private func syncStatuses() {
|
|
DatabaseController.shared.getNewestStatus { status in
|
|
guard let status = status else {
|
|
return
|
|
}
|
|
|
|
self.syncLogger.log("Starting sync...")
|
|
self.syncTotal = 0
|
|
self.syncStatuses(range: .after(status.id))
|
|
}
|
|
}
|
|
|
|
private func syncStatuses(range: APIController.RequestRange) {
|
|
APIController.shared.getStatuses(range: range) { response in
|
|
switch response {
|
|
case .failure(let error):
|
|
self.syncLogger.error("Erorr syncing statuses: \(String(describing: error), privacy: .public)")
|
|
DispatchQueue.main.async {
|
|
let alert = NSAlert()
|
|
alert.alertStyle = .warning
|
|
alert.messageText = "Error syncing statuses"
|
|
alert.informativeText = error.localizedDescription
|
|
alert.runModal()
|
|
}
|
|
|
|
case .success(let statuses):
|
|
guard statuses.count > 0 else {
|
|
DispatchQueue.main.async {
|
|
self.syncLogger.log("Finished sync of \(self.syncTotal, privacy: .public) statuses")
|
|
self.onSync.send()
|
|
}
|
|
return
|
|
}
|
|
|
|
DatabaseController.shared.addStatuses(statuses.compactMap {
|
|
if $0.hasReblog {
|
|
return nil
|
|
} else {
|
|
return Status(id: $0.id, url: $0.url, summary: $0.spoiler_text, content: $0.content, published: $0.created_at)
|
|
}
|
|
})
|
|
|
|
self.syncTotal += statuses.count
|
|
|
|
self.syncStatuses(range: .after(statuses.first!.id))
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func importFile(_ sender: Any) {
|
|
let panel = NSOpenPanel()
|
|
panel.canChooseFiles = true
|
|
panel.canChooseDirectories = false
|
|
panel.allowsMultipleSelection = false
|
|
panel.allowedContentTypes = [.commaSeparatedText]
|
|
panel.beginSheetModal(for: NSApp.mainWindow!) { (resp) in
|
|
guard resp == .OK else {
|
|
return
|
|
}
|
|
ImportController.shared.importCSV(url: panel.url!)
|
|
self.onSync.send()
|
|
self.syncStatuses()
|
|
}
|
|
}
|
|
|
|
@objc func logIn() {
|
|
let alert = NSAlert()
|
|
alert.messageText = "Enter instance URL:"
|
|
alert.addButton(withTitle: "OK")
|
|
alert.addButton(withTitle: "Cancel")
|
|
|
|
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
|
|
textField.placeholderString = "https://mastodon.social/"
|
|
alert.accessoryView = textField
|
|
|
|
guard alert.runModal() == .alertFirstButtonReturn,
|
|
let url = URL(string: textField.stringValue) else {
|
|
return
|
|
}
|
|
|
|
LocalData.account = LocalData.AccountInfo(instanceURL: url, clientID: nil, clientSecret: nil, accessToken: nil)
|
|
|
|
APIController.shared.register { response in
|
|
guard case .success(let registration) = response else {
|
|
fatalError()
|
|
}
|
|
|
|
LocalData.account!.clientID = registration.client_id
|
|
LocalData.account!.clientSecret = registration.client_secret
|
|
|
|
var authorizeComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)!
|
|
authorizeComponents.path = "/oauth/authorize"
|
|
authorizeComponents.queryItems = [
|
|
URLQueryItem(name: "client_id", value: LocalData.account!.clientID),
|
|
URLQueryItem(name: "response_type", value: "code"),
|
|
URLQueryItem(name: "scope", value: APIController.shared.scopes),
|
|
URLQueryItem(name: "redirect_uri", value: APIController.shared.redirectURI),
|
|
]
|
|
|
|
self.authSession = ASWebAuthenticationSession(url: authorizeComponents.url!, callbackURLScheme: "mastosearch", completionHandler: { url, error in
|
|
guard error == nil,
|
|
let url = url,
|
|
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
|
let item = components.queryItems?.first(where: { $0.name == "code" }),
|
|
let authCode = item.value else {
|
|
fatalError()
|
|
}
|
|
|
|
APIController.shared.getAccessToken(authCode: authCode) { response in
|
|
guard case .success(let settings) = response else {
|
|
fatalError()
|
|
}
|
|
|
|
LocalData.account!.accessToken = settings.access_token
|
|
|
|
DispatchQueue.main.async {
|
|
self.updateAccountMenu()
|
|
self.syncStatuses()
|
|
}
|
|
}
|
|
})
|
|
DispatchQueue.main.async {
|
|
self.authSession!.presentationContextProvider = self
|
|
self.authSession!.start()
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@objc func logOut() {
|
|
LocalData.account = nil
|
|
updateAccountMenu()
|
|
}
|
|
|
|
}
|
|
|
|
extension AppDelegate: ASWebAuthenticationPresentationContextProviding {
|
|
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
|
return NSApp.keyWindow!
|
|
}
|
|
}
|