// // helpers.swift // Stats // // Created by Serhiy Mytrovtsiy on 13/07/2020. // Using Swift 5.0. // Running on macOS 10.15. // // Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. // import Cocoa import Kit import UserNotifications extension AppDelegate { internal func parseArguments() { let args = CommandLine.arguments if args.contains("--reset") { debug("Receive --reset argument. Resetting store (UserDefaults)...") Store.shared.reset() } if let disableIndex = args.firstIndex(of: "--disable") { if args.indices.contains(disableIndex+1) { let disableModules = args[disableIndex+1].split(separator: ",") disableModules.forEach { (moduleName: Substring) in if let module = modules.first(where: { $0.config.name.lowercased() == moduleName.lowercased()}) { module.unmount() } } } } if let mountIndex = args.firstIndex(of: "--mount-path") { if args.indices.contains(mountIndex+1) { let mountPath = args[mountIndex+1] asyncShell("/usr/bin/hdiutil detach \(mountPath)") asyncShell("/bin/rm -rf \(mountPath)") debug("DMG was unmounted and mountPath deleted") } } if let dmgIndex = args.firstIndex(of: "--dmg-path") { if args.indices.contains(dmgIndex+1) { asyncShell("/bin/rm -rf \(args[dmgIndex+1])") debug("DMG was deleted") } } } internal func parseVersion() { let key = "version" let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String guard let updateInterval = AppUpdateInterval(rawValue: Store.shared.string(key: "update-interval", defaultValue: AppUpdateInterval.silent.rawValue)) else { return } if !Store.shared.exist(key: key) { Store.shared.reset() debug("Previous version not detected. Current version (\(currentVersion) set") } else { let prevVersion = Store.shared.string(key: key, defaultValue: "") if prevVersion == currentVersion { return } if updateInterval != .silent && isNewestVersion(currentVersion: prevVersion, latestVersion: currentVersion) { let title: String = localizedString("Successfully updated") let subtitle: String = localizedString("Stats was updated to v", currentVersion) let id = showNotification(title: title, subtitle: subtitle, delegate: self) DispatchQueue.main.asyncAfter(deadline: .now() + 10) { removeNotification(id) } } debug("Detected previous version \(prevVersion). Current version (\(currentVersion) set") } Store.shared.set(key: key, value: currentVersion) } internal func defaultValues() { if Store.shared.exist(key: "runAtLoginInitialized") { LaunchAtLogin.migrate() } if Store.shared.exist(key: "dockIcon") { let dockIconStatus = Store.shared.bool(key: "dockIcon", defaultValue: false) ? NSApplication.ActivationPolicy.regular : NSApplication.ActivationPolicy.accessory NSApp.setActivationPolicy(dockIconStatus) } self.checkIfShouldShowSupportWindow() self.supportActivity.interval = 60 * 60 * 24 * 30 self.supportActivity.repeats = true self.supportActivity.schedule { (completion: @escaping NSBackgroundActivityScheduler.CompletionHandler) in DispatchQueue.main.async { self.checkIfShouldShowSupportWindow() } completion(NSBackgroundActivityScheduler.Result.finished) } if let updateInterval = AppUpdateInterval(rawValue: Store.shared.string(key: "update-interval", defaultValue: AppUpdateInterval.silent.rawValue)) { self.updateActivity.invalidate() self.updateActivity.repeats = true debug("Application update interval is '\(updateInterval.rawValue)'") switch updateInterval { case .oncePerDay: self.updateActivity.interval = 60 * 60 * 24 case .oncePerWeek: self.updateActivity.interval = 60 * 60 * 24 * 7 case .oncePerMonth: self.updateActivity.interval = 60 * 60 * 24 * 30 case .atStart: self.checkForNewVersion() return case .silent: self.checkForNewVersion(silent: true) return default: return } self.updateActivity.schedule { (completion: @escaping NSBackgroundActivityScheduler.CompletionHandler) in self.checkForNewVersion() completion(NSBackgroundActivityScheduler.Result.finished) } } } internal func setup(completion: @escaping () -> Void) { if Store.shared.exist(key: "setupProcess") || Store.shared.exist(key: "runAtLoginInitialized") { completion() return } debug("showing the setup window") self.setupWindow.show() self.setupWindow.finishHandler = { debug("setup is finished, starting the app") completion() } Store.shared.set(key: "setupProcess", value: true) } internal func checkForNewVersion(silent: Bool = false) { updater.check { result, error in if error != nil { debug("error updater.check(): \(error!.localizedDescription)") return } guard error == nil, let version: version_s = result else { debug("download error(): \(error!.localizedDescription)") return } if !version.newest { return } if silent { if let url = URL(string: version.url) { updater.download(url, completion: { path in updater.install(path: path) { error in if let error { showAlert("Error update Stats", error, .critical) } } }) } return } debug("show update view because new version of app found: \(version.latest)") let center = UNUserNotificationCenter.current() center.getNotificationSettings { settings in switch settings.authorizationStatus { case .authorized, .provisional: self.showUpdateNotification(version: version) case .denied: self.showUpdateWindow(version: version) case .notDetermined: center.requestAuthorization(options: [.sound, .alert, .badge], completionHandler: { (_, error) in if error == nil { NSApplication.shared.registerForRemoteNotifications() self.showUpdateNotification(version: version) } else { self.showUpdateWindow(version: version) } }) @unknown default: self.showUpdateWindow(version: version) error_msg("unknown notification setting") } } } } func checkIfShouldShowSupportWindow() { if !Store.shared.exist(key: "setupProcess") || !Store.shared.exist(key: "runAtLoginInitialized") { return } let now = Int(Date().timeIntervalSince1970) if !Store.shared.exist(key: "support_ts") { Store.shared.set(key: "support_ts", value: now) self.supportWindow.show() return } let lastShow = Store.shared.int(key: "support_ts", defaultValue: now) let diff = (now - lastShow) / (60 * 60 * 24) if diff <= 31 { debug("The support window was shown \(diff) days ago, stopping...") return } Store.shared.set(key: "support_ts", value: now) self.supportWindow.show() } private func showUpdateNotification(version: version_s) { debug("show update notification") _ = showNotification( title: localizedString("New version available"), subtitle: localizedString("Click to install the new version of Stats"), userInfo: ["url": version.url], delegate: self ) } private func showUpdateWindow(version: version_s) { debug("show update window") DispatchQueue.main.async(execute: { self.updateWindow.open(version) }) } @objc internal func listenForAppPause() { for m in modules { if self.pauseState && m.enabled { m.disable() } else if !self.pauseState && !m.enabled && Store.shared.bool(key: "\(m.config.name)_state", defaultValue: m.config.defaultState) { m.enable() } } self.icon() } internal func icon() { if self.pauseState { self.menuBarItem = NSStatusBar.system.statusItem(withLength: AppIcon.size.width) DispatchQueue.main.async(execute: { self.menuBarItem?.autosaveName = "Stats" }) self.menuBarItem?.button?.addSubview(AppIcon()) self.menuBarItem?.button?.target = self self.menuBarItem?.button?.action = #selector(self.openSettings) self.menuBarItem?.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) } else { if let item = self.menuBarItem { NSStatusBar.system.removeStatusItem(item) } self.menuBarItem = nil } } @objc internal func openSettings() { NotificationCenter.default.post(name: .toggleSettings, object: nil, userInfo: ["module": "Dashboard"]) } internal func handleKeyEvent(_ event: NSEvent) { var keyCodes: [UInt16] = [] if event.modifierFlags.contains(.control) { keyCodes.append(59) } if event.modifierFlags.contains(.shift) { keyCodes.append(60) } if event.modifierFlags.contains(.command) { keyCodes.append(55) } if event.modifierFlags.contains(.option) { keyCodes.append(58) } keyCodes.append(event.keyCode) guard !keyCodes.isEmpty, let module = modules.first(where: { $0.enabled && $0.popupKeyboardShortcut == keyCodes }), let widget = module.menuBar.widgets.filter({ $0.isActive }).first, let window = widget.item.window else { return } NotificationCenter.default.post(name: .togglePopup, object: nil, userInfo: [ "module": module.name, "widget": widget.type, "origin": window.frame.origin, "center": window.frame.width/2 ]) } }