//
|
|
// AppDelegate.swift
|
|
// pEpNotifications
|
|
//
|
|
// Created by Volker Birk on 28.05.20.
|
|
// Copyright © 2020 p≡p 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)
|
|
}
|
|
}
|