- add option to select an application automatic checks for new version (at start/once per day/once per week/once per month/never)

- adjust select box y position
This commit is contained in:
Serhiy Mytrovtsiy
2020-07-14 17:28:46 +02:00
parent 5d64b5f743
commit 85fb2964fd
7 changed files with 209 additions and 122 deletions

View File

@@ -63,7 +63,7 @@ internal class Settings: NSView, Settings_v {
let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: self.frame.width - (Constants.Settings.margin*2) - x, height: height))
self.addSubview(SelectTitleRow(
frame: NSRect(x: Constants.Settings.margin, y: height - rowHeight, width: self.frame.width - (Constants.Settings.margin*2), height: rowHeight),
frame: NSRect(x: Constants.Settings.margin, y: height - rowHeight, width: view.frame.width, height: rowHeight),
title: "Update interval",
action: #selector(changeUpdateInterval),
items: self.listOfUpdateIntervals.map{ "\($0) sec" },

View File

@@ -96,6 +96,7 @@
9ABFF910248BEE7200C9041A /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF90F248BEE7200C9041A /* readers.swift */; };
9ABFF912248BF39500C9041A /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF911248BF39500C9041A /* Battery.swift */; };
9ABFF914248C30A800C9041A /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF913248C30A800C9041A /* popup.swift */; };
9AD33AC624BCD3EE007E8820 /* helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD33AC524BCD3EE007E8820 /* helpers.swift */; };
9AE29ADC249A50350071B02D /* Sensors.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AE29AD5249A50350071B02D /* Sensors.framework */; };
9AE29ADD249A50350071B02D /* Sensors.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AE29AD5249A50350071B02D /* Sensors.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9AE29AE1249A50640071B02D /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; };
@@ -467,6 +468,7 @@
9ABFF90F248BEE7200C9041A /* readers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = "<group>"; };
9ABFF911248BF39500C9041A /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = "<group>"; };
9ABFF913248C30A800C9041A /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = "<group>"; };
9AD33AC524BCD3EE007E8820 /* helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = helpers.swift; sourceTree = "<group>"; };
9AE29AD5249A50350071B02D /* Sensors.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Sensors.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9AE29AEC249A50960071B02D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Modules/Sensors/Info.plist; sourceTree = SOURCE_ROOT; };
9AE29AF1249A50CD0071B02D /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = main.swift; path = Modules/Sensors/main.swift; sourceTree = SOURCE_ROOT; };
@@ -634,6 +636,7 @@
9A81C74A24499C4B00825D92 /* Views */,
9A5B1CB3229E72A7008B9D3C /* Supporting Files */,
9AABEB79243FD26200668CB0 /* AppDelegate.swift */,
9AD33AC524BCD3EE007E8820 /* helpers.swift */,
);
path = Stats;
sourceTree = "<group>";
@@ -1276,6 +1279,7 @@
9A9EA9452476D34500E3B883 /* Update.swift in Sources */,
9A81C74E24499C7000825D92 /* Settings.swift in Sources */,
9A81C74D24499C7000825D92 /* AppSettings.swift in Sources */,
9AD33AC624BCD3EE007E8820 /* helpers.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -37,14 +37,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
self.parseArguments()
NSUserNotificationCenter.default.removeAllDeliveredNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(toggleSettingsHandler), name: .toggleSettings, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(checkForNewVersion), name: .checkForUpdates, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(updateCron), name: .changeCronInterval, object: nil)
modules.forEach{ $0.mount() }
self.settingsWindow.setModules()
self.setVersion()
self.parseVersion()
self.defaultValues()
self.updateCron()
os_log(.info, log: log, "Stats started in %.4f seconds", startingPoint.timeIntervalSinceNow * -1)
@@ -52,7 +52,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) {
if let uri = notification.userInfo?["url"] as? String {
os_log(.error, log: log, "Downloading new version of app...")
os_log(.debug, log: log, "Downloading new version of app...")
if let url = URL(string: uri) {
updater.download(url)
}
@@ -65,7 +65,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
modules.forEach{ $0.terminate() }
_ = smc.close()
NotificationCenter.default.removeObserver(self)
self.updateActivity.invalidate()
}
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
@@ -77,95 +76,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
return true
}
@objc private func toggleSettingsHandler(_ notification: Notification) {
if !self.settingsWindow.isVisible {
self.settingsWindow.setIsVisible(true)
self.settingsWindow.makeKeyAndOrderFront(nil)
}
if let name = notification.userInfo?["module"] as? String {
self.settingsWindow.openMenu(name)
}
}
private func setVersion() {
let key = "version"
let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
if !store.exist(key: key) {
store.reset()
os_log(.info, log: log, "Previous version not detected. Current version (%s) set", currentVersion)
} else {
let prevVersion = store.string(key: key, defaultValue: "")
if prevVersion == currentVersion {
return
}
if IsNewestVersion(currentVersion: prevVersion, latestVersion: currentVersion) {
let notification = NSUserNotification()
notification.identifier = "updated-from-\(prevVersion)-to-\(currentVersion)"
notification.title = "Successfully updated"
notification.subtitle = "Stats was updated to the v\(currentVersion)"
notification.soundName = NSUserNotificationDefaultSoundName
notification.hasActionButton = false
NSUserNotificationCenter.default.deliver(notification)
}
os_log(.info, log: log, "Detected previous version %s. Current version (%s) set", prevVersion, currentVersion)
}
store.set(key: key, value: currentVersion)
}
private func parseArguments() {
let args = CommandLine.arguments
if args.contains("--reset") {
os_log(.info, log: log, "Receive --reset argument. Reseting store (UserDefaults)...")
store.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)")
}
}
if let dmgIndex = args.firstIndex(of: "--dmg-path") {
if args.indices.contains(dmgIndex+1) {
asyncShell("/bin/rm -rf \(args[dmgIndex+1])")
}
}
}
private func defaultValues() {
if !store.exist(key: "runAtLoginInitialized") {
store.set(key: "runAtLoginInitialized", value: true)
LaunchAtLogin.isEnabled = true
}
if store.exist(key: "dockIcon") {
let dockIconStatus = store.bool(key: "dockIcon", defaultValue: false) ? NSApplication.ActivationPolicy.regular : NSApplication.ActivationPolicy.accessory
NSApp.setActivationPolicy(dockIconStatus)
}
}
@objc private func checkForNewVersion(_ window: Bool = false) {
@objc internal func checkForNewVersion(_ window: Bool = false) {
updater.check() { result, error in
if error != nil {
os_log(.error, log: log, "error updater.check(): %s", "\(error!.localizedDescription)")
@@ -179,13 +90,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
DispatchQueue.main.async(execute: {
if window {
os_log(.error, log: log, "open update window: %s", "\(version.latest)")
os_log(.debug, log: log, "open update window: %s", "\(version.latest)")
self.updateWindow.open(version)
return
}
if version.newest {
os_log(.error, log: log, "show update window because new version of app found: %s", "\(version.latest)")
os_log(.debug, log: log, "show update window because new version of app found: %s", "\(version.latest)")
self.updateNotification.identifier = "new-version-\(version.latest)"
self.updateNotification.title = "New version available"
@@ -203,20 +114,24 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele
}
}
private func updateCron() {
@objc private func updateCron() {
self.updateActivity.invalidate()
self.updateActivity.repeats = true
self.updateActivity.interval = 60 * 60 * 12 // once in 12 hour
if store.bool(key: "checkUpdatesOnLogin", defaultValue: true) {
self.checkForNewVersion(false)
guard let updateInterval = updateIntervals(rawValue: store.string(key: "update-interval", defaultValue: updateIntervals.atStart.rawValue)) else {
return
}
os_log(.debug, log: log, "Application update interval is '%s'", "\(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 .never, .atStart: return
default: return
}
self.updateActivity.schedule { (completion: @escaping NSBackgroundActivityScheduler.CompletionHandler) in
if !store.bool(key: "checkUpdatesOnLogin", defaultValue: true) {
completion(NSBackgroundActivityScheduler.Result.finished)
return
}
self.checkForNewVersion(false)
completion(NSBackgroundActivityScheduler.Result.finished)
}

View File

@@ -17,6 +17,12 @@ class ApplicationSettings: NSView {
private let height: CGFloat = 480
private let deviceInfoHeight: CGFloat = 300
private var updateIntervalValue: updateInterval {
get {
return store.string(key: "update-interval", defaultValue: updateIntervals.atStart.rawValue)
}
}
init() {
super.init(frame: NSRect(x: 0, y: 0, width: width, height: height))
self.wantsLayer = true
@@ -82,11 +88,12 @@ class ApplicationSettings: NSView {
let rightPanel: NSView = NSView(frame: NSRect(x: self.width/2, y: 0, width: view.frame.width/2, height: view.frame.height))
rightPanel.addSubview(makeSettingRow(
rightPanel.addSubview(makeSelectRow(
frame: NSRect(x: rowHorizontalPadding*0.5, y: rowHeight*2, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
title: "Check for updates",
action: #selector(self.toggleUpdates),
state: store.bool(key: "checkUpdatesOnLogin", defaultValue: true)
action: #selector(self.toggleUpdateInterval),
items: updateIntervals.allCases.map{ $0.rawValue },
selected: self.updateIntervalValue
))
rightPanel.addSubview(makeSettingRow(
@@ -108,6 +115,42 @@ class ApplicationSettings: NSView {
self.addSubview(view)
}
func makeSelectRow(frame: NSRect, title: String, action: Selector, items: [String], selected: String) -> NSView {
let row: NSView = NSView(frame: frame)
let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (row.frame.height - 32)/2, width: row.frame.width - 52, height: 32), title)
rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
rowTitle.textColor = .secondaryLabelColor
let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: row.frame.width - 50, y: (row.frame.height-28)/2, width: 50, height: 28))
select.target = self
select.action = action
let menu = NSMenu()
items.forEach { (color: String) in
if color.contains("separator") {
menu.addItem(NSMenuItem.separator())
} else {
let interfaceMenu = NSMenuItem(title: color, action: nil, keyEquivalent: "")
menu.addItem(interfaceMenu)
if selected == color {
interfaceMenu.state = .on
}
}
}
select.menu = menu
select.sizeToFit()
rowTitle.setFrameSize(NSSize(width: row.frame.width - select.frame.width, height: rowTitle.frame.height))
select.setFrameOrigin(NSPoint(x: row.frame.width - select.frame.width, y: select.frame.origin.y))
row.addSubview(select)
row.addSubview(rowTitle)
return row
}
private func makeInfoRow(frame: NSRect, title: String, value: String) -> NSView {
let row: NSView = NSView(frame: frame)
let titleWidth = title.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 10
@@ -235,16 +278,10 @@ class ApplicationSettings: NSView {
NotificationCenter.default.post(name: .checkForUpdates, object: nil, userInfo: nil)
}
@objc func toggleUpdates(_ sender: NSObject) {
var state: NSControl.StateValue? = nil
if #available(OSX 10.15, *) {
state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
} else {
state = sender is NSButton ? (sender as! NSButton).state: nil
}
if state != nil {
store.set(key: "checkUpdatesOnLogin", value: state! == NSControl.StateValue.on)
@objc private func toggleUpdateInterval(_ sender: NSMenuItem) {
if let newUpdateInterval = updateIntervals(rawValue: sender.title) {
store.set(key: "update-interval", value: newUpdateInterval.rawValue)
NotificationCenter.default.post(name: .changeCronInterval, object: nil, userInfo: nil)
}
}

View File

@@ -40,6 +40,23 @@ class SettingsWindow: NSWindow, NSWindowDelegate {
let windowController = NSWindowController()
windowController.window = self
windowController.loadWindow()
NotificationCenter.default.addObserver(self, selector: #selector(toggleSettingsHandler), name: .toggleSettings, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc private func toggleSettingsHandler(_ notification: Notification) {
if !self.isVisible {
self.setIsVisible(true)
self.makeKeyAndOrderFront(nil)
}
if let name = notification.userInfo?["module"] as? String {
self.viewController.openMenu(name)
}
}
public func setModules() {

101
Stats/helpers.swift Normal file
View File

@@ -0,0 +1,101 @@
//
// 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 os.log
import StatsKit
extension AppDelegate {
internal func parseArguments() {
let args = CommandLine.arguments
if args.contains("--reset") {
os_log(.debug, log: log, "Receive --reset argument. Reseting store (UserDefaults)...")
store.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)")
os_log(.debug, log: log, "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])")
os_log(.debug, log: log, "DMG was deleted")
}
}
}
internal func parseVersion() {
let key = "version"
let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
if !store.exist(key: key) {
store.reset()
os_log(.debug, log: log, "Previous version not detected. Current version (%s) set", currentVersion)
} else {
let prevVersion = store.string(key: key, defaultValue: "")
if prevVersion == currentVersion {
return
}
if IsNewestVersion(currentVersion: prevVersion, latestVersion: currentVersion) {
let notification = NSUserNotification()
notification.identifier = "updated-from-\(prevVersion)-to-\(currentVersion)"
notification.title = "Successfully updated"
notification.subtitle = "Stats was updated to the v\(currentVersion)"
notification.soundName = NSUserNotificationDefaultSoundName
notification.hasActionButton = false
NSUserNotificationCenter.default.deliver(notification)
}
os_log(.debug, log: log, "Detected previous version %s. Current version (%s) set", prevVersion, currentVersion)
}
store.set(key: key, value: currentVersion)
}
internal func defaultValues() {
if !store.exist(key: "runAtLoginInitialized") {
store.set(key: "runAtLoginInitialized", value: true)
LaunchAtLogin.isEnabled = true
}
if store.exist(key: "dockIcon") {
let dockIconStatus = store.bool(key: "dockIcon", defaultValue: false) ? NSApplication.ActivationPolicy.regular : NSApplication.ActivationPolicy.accessory
NSApp.setActivationPolicy(dockIconStatus)
}
if updateIntervals(rawValue: store.string(key: "update-interval", defaultValue: updateIntervals.atStart.rawValue)) == .atStart {
self.checkForNewVersion(false)
}
}
}

View File

@@ -351,7 +351,7 @@ public extension NSView {
rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
rowTitle.textColor = .textColor
let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height))
let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: row.frame.width - 50, y: (row.frame.height-26)/2, width: 50, height: 26))
select.target = self
select.action = action
items.forEach { (item: String) in
@@ -361,7 +361,7 @@ public extension NSView {
select.sizeToFit()
rowTitle.setFrameSize(NSSize(width: row.frame.width - select.frame.width, height: rowTitle.frame.height))
select.setFrameOrigin(NSPoint(x: row.frame.width - select.frame.width, y: 0))
select.setFrameOrigin(NSPoint(x: row.frame.width - select.frame.width, y: select.frame.origin.y))
row.addSubview(select)
row.addSubview(rowTitle)
@@ -376,7 +376,7 @@ public extension NSView {
rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
rowTitle.textColor = .textColor
let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height))
let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: row.frame.width - 50, y: (row.frame.height-26)/2, width: 50, height: 26))
select.target = self
select.action = action
@@ -397,7 +397,7 @@ public extension NSView {
select.sizeToFit()
rowTitle.setFrameSize(NSSize(width: row.frame.width - select.frame.width, height: rowTitle.frame.height))
select.setFrameOrigin(NSPoint(x: row.frame.width - select.frame.width, y: 0))
select.setFrameOrigin(NSPoint(x: row.frame.width - select.frame.width, y: select.frame.origin.y))
row.addSubview(select)
row.addSubview(rowTitle)
@@ -412,6 +412,7 @@ public extension Notification.Name {
static let openSettingsView = Notification.Name("openSettingsView")
static let switchWidget = Notification.Name("switchWidget")
static let checkForUpdates = Notification.Name("checkForUpdates")
static let changeCronInterval = Notification.Name("changeCronInterval")
static let clickInSettings = Notification.Name("clickInSettings")
static let updatePopupSize = Notification.Name("updatePopupSize")
}
@@ -754,3 +755,15 @@ public func IsNewestVersion(currentVersion: String, latestVersion: String) -> Bo
return false
}
public typealias updateInterval = String
public enum updateIntervals: updateInterval {
case atStart = "At start"
case separator_1 = "separator_1"
case oncePerDay = "Once per day"
case oncePerWeek = "Once per week"
case oncePerMonth = "Once per month"
case separator_2 = "separator_2"
case never = "Never"
}
extension updateIntervals: CaseIterable {}