pEp macOS Desktop adapter
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

317 lines
11 KiB

//
// AppDelegate.swift
// pEpNotifications
//
// Created by Volker Birk on 28.05.20.
// Copyright © 2020 pp foundation. All rights reserved.
// This file is under GNU General Public License 3.0
//
import Cocoa
import SwiftUI
import UserNotifications
enum DNType : Int { case ready = 0, downloading, downloadArrived, noDownloadAvailable }
@objc(PEPMacOSAdapterProtocol) protocol PEPMacOSAdapterProtocol {
func subscribeForUpdate(_ endpoint: NSXPCListenerEndpoint?)
func unsubscribeForUpdate()
func updateNow()
func scheduleUpdates()
func stopUpdates()
}
@NSApplicationMain
class AppDelegate: NSObject {
// MARK: - Outlets
@IBOutlet weak var pEpMenu: NSMenu!
@IBOutlet weak var statusText: NSMenuItem!
@IBOutlet weak var updateNowMenuItem: NSMenuItem!
@IBOutlet weak var scheduleUpdatesMenuItem: NSMenuItem!
@IBOutlet weak var alwaysShowThisMenuMenuItem: NSMenuItem!
// MARK: - Properties
private var statusBarItem: NSStatusItem? = nil
private lazy var connection: NSXPCConnection = NSXPCConnection.init(machServiceName: "foundation.pEp.adapter.macOS_OpenStep")
private var service: PEPMacOSAdapterProtocol?
private var nc = NSUserNotificationCenter.default
private lazy var clientListener: NSXPCListener = NSXPCListener.anonymous()
private var receiver: PEPNotification?
private lazy var downloadStateNotifier = DownloadStateNotifier(at: statusText)
@available(OSX 10.14, *)
//private lazy var notificationCenter = UNUserNotificationCenter.current()
private lazy var downloadNotificationManager = DownloadNotificationManager()
// MARK: - Action methods
@IBAction func scheduleUpdates(_ sender: NSMenuItem) {
if sender.state == NSControl.StateValue.on {
sender.state = NSControl.StateValue.off
UserDefaults.standard.set(false, forKey: "ScheduleUpdates")
service?.stopUpdates()
} else {
sender.state = NSControl.StateValue.on
UserDefaults.standard.set(true, forKey: "ScheduleUpdates")
service?.scheduleUpdates()
}
}
@IBAction func alwaysShowThisMenu(_ sender: NSMenuItem) {
if sender.state == NSControl.StateValue.on {
sender.state = NSControl.StateValue.off
UserDefaults.standard.set(false, forKey: "AlwaysShowThisMenu")
if statusText.representedObject == nil {
uninstallMenuExtra()
}
} else {
sender.state = NSControl.StateValue.on
UserDefaults.standard.set(true, forKey: "AlwaysShowThisMenu")
}
}
@IBAction func installNow(_ sender: NSMenuItem) {
uninstallMenuExtra()
let product = sender.representedObject as! Dictionary<String, Any>
let un = product["notification"] as! NSUserNotification
nc.removeDeliveredNotification(un)
NSLog("pEpNotifications: installNow clicked for %@", product["name"] as! String)
NSWorkspace.shared.openFile(product["filename"] as! String)
sender.representedObject = nil
downloadStateNotifier.notify(.Connected)
}
}
// MARK: - Private
extension AppDelegate {
@objc
private func updateNow() {
nc.removeAllDeliveredNotifications()
service?.updateNow()
}
private func setupAppDefaults() {
let appDefaults = ["ScheduleUpdates": true, "AlwaysShowThisMenu": false]
UserDefaults.standard.register(defaults: appDefaults)
if UserDefaults.standard.bool(forKey: "AlwaysShowThisMenu") {
alwaysShowThisMenuMenuItem.state = NSControl.StateValue.on
installMenuExtra()
} else {
alwaysShowThisMenuMenuItem.state = NSControl.StateValue.off
}
}
private func initXPCConnection() {
connection.remoteObjectInterface = NSXPCInterface.init(with: PEPMacOSAdapterProtocol.self)
connection.resume()
service = connection.remoteObjectProxyWithErrorHandler(proxyErrorHandler) as? PEPMacOSAdapterProtocol
}
private func initClientService() {
clientListener.delegate = self
clientListener.resume()
}
private func setupServiceSubscription() {
service?.subscribeForUpdate(clientListener.endpoint)
if UserDefaults.standard.bool(forKey: "ScheduleUpdates") {
service?.scheduleUpdates()
scheduleUpdatesMenuItem.state = NSControl.StateValue.on
}
else {
service?.stopUpdates()
scheduleUpdatesMenuItem.state = NSControl.StateValue.off
}
}
private func initNotificationCenter() {
if #available(OSX 10.14, *) {
downloadNotificationManager.delegate = self
} else {
nc.removeAllDeliveredNotifications()
nc.delegate = self
}
}
private func proxyErrorHandler(err: Error) -> Void {
NSLog("%@", err.localizedDescription)
downloadStateNotifier.notify(.ConnectionFailed)
}
/// This removes _all_ previous notifications from a pEp product.
/// Currently there is only one pEp Product (pEp4Thunderbird) to take care of.
/// - Parameter productName: the pEp product name
private func removeDeliveredNotifications(with productName: NSString) {
nc.deliveredNotifications.forEach { notification in
guard
let userInfoName = notification.userInfo?["name"] as? NSString,
userInfoName == productName
else {
return
}
nc.removeDeliveredNotification(notification)
}
}
private func installMenuExtra() {
guard statusBarItem == nil else {
return
}
statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusBarItem?.button?.title = "p≡p"
statusBarItem?.menu = NSApp.menu?.item(at: 0)?.submenu
updateNowMenuItem.action = #selector(updateNow)
}
private func uninstallMenuExtra() {
guard
statusBarItem != nil && !UserDefaults.standard.bool(forKey: "AlwaysShowThisMenu")
else {
return
}
NSStatusBar.system.removeStatusItem(statusBarItem!)
statusBarItem = nil
}
}
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
}
// MARK: - PEPNotificationProtocol
extension AppDelegate: PEPNotificationProtocol {
func notifyDownload(_ type: Int, withName: NSString, withFilename: NSString) {
let _type = DNType.init(rawValue: type)
switch _type {
case .downloading:
NSLog("pEpNotifications: downloading")
downloadStateNotifier.notify(.Downloading(String(withName)))
DispatchQueue.main.async { [weak self] in
guard let me = self else {
return
}
me.installMenuExtra()
}
case .downloadArrived:
let product = ["name": withName, "filename": withFilename]
if #available(OSX 10.14, *) {
downloadNotificationManager.scheduleDownloadNotification(with: product)
} else {
removeDeliveredNotifications(with: withName)
let un = NSUserNotification()
un.title = NSLocalizedString("Update available", comment: "")
un.informativeText = String(format: NSLocalizedString("A new update for %@ is ready to be installed.", comment: ""), withName)
un.actionButtonTitle = NSLocalizedString("Install", comment: "")
un.hasActionButton = true
un.otherButtonTitle = NSLocalizedString("Not now", comment: "")
un.soundName = NSUserNotificationDefaultSoundName
un.setValue(true, forKey: "_showsButtons")
un.userInfo = ["name": withName, "filename": withFilename]
nc.deliver(un)
}
//let product = ["name": withName, "filename": withFilename, "notification": un]
downloadStateNotifier.notify(.NewVersionAvailable(String(withName)), with: product)
case .noDownloadAvailable:
NSLog("pEpNotifications: no download available")
downloadStateNotifier.notify(.UpToDate)
case .ready:
NSLog("pEpNotifications: ready.")
downloadStateNotifier.notify(.Connected)
case .none:
break
}
}
}
// MARK: - NSApplicationDelegate
extension AppDelegate: NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// preference defaults
setupAppDefaults()
downloadStateNotifier.notify(.Connecting)
// connect to XPC service
initXPCConnection()
// init client callback service
initClientService()
// subscribe and schedule updates
setupServiceSubscription()
service?.updateNow()
initNotificationCenter()
}
func applicationWillTerminate(_ aNotification: Notification) {
service?.unsubscribeForUpdate()
connection.invalidate()
}
}
// MARK: - NSUserNotificationCenterDelegate
extension AppDelegate: NSUserNotificationCenterDelegate {
@objc func userNotificationCenter(_ : NSUserNotificationCenter, didActivate: NSUserNotification) {
guard
didActivate.activationType == NSUserNotification.ActivationType.actionButtonClicked
else {
return
}
uninstallMenuExtra()
let filename : String = didActivate.userInfo?["filename"] as! String
NSLog("pEpNotifications: actionButtonClicked for %@", filename)
NSWorkspace.shared.openFile(filename)
downloadStateNotifier.notify(.Connected)
}
}
// MARK: - NSXPCListenerDelegate
extension AppDelegate: NSXPCListenerDelegate {
@objc func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
newConnection.exportedInterface = NSXPCInterface.init(with: PEPNotificationProtocol.self)
let obj = PEPNotification()
obj.delegate = self
newConnection.exportedObject = obj
newConnection.resume()
return true
}
}
// MARK: - DownloadNotificationManagerDelegate
extension AppDelegate: DownloadNotificationManagerDelegate {
func installSelected(with product: Product) {
defer {
uninstallMenuExtra()
}
guard let filename = product["filename"] as? String else {
return
}
NSLog("pEpNotifications: actionButtonClicked for %@", filename)
NSWorkspace.shared.openFile(filename)
downloadStateNotifier.notify(.Connected)
}
}