mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
301 lines
11 KiB
Swift
301 lines
11 KiB
Swift
//
|
|
// 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
|
|
])
|
|
}
|
|
}
|