mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
now charts updates only if a popup is opened; removed shortName from widget; widgets refactoring;
263 lines
8.3 KiB
Swift
Executable File
263 lines
8.3 KiB
Swift
Executable File
//
|
|
// Extensions.swift
|
|
// Stats
|
|
//
|
|
// Created by Serhiy Mytrovtsiy on 29/05/2019.
|
|
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import Cocoa
|
|
|
|
extension Double {
|
|
func roundTo(decimalPlaces: Int) -> String {
|
|
return NSString(format: "%.\(decimalPlaces)f" as NSString, self) as String
|
|
}
|
|
|
|
func rounded(toPlaces places:Int) -> Double {
|
|
let divisor = pow(10.0, Double(places))
|
|
return (self * divisor).rounded() / divisor
|
|
}
|
|
|
|
func usageColor(reversed: Bool = false, color: Bool = false) -> NSColor {
|
|
if !color {
|
|
return NSColor.textColor
|
|
}
|
|
|
|
if reversed {
|
|
switch self {
|
|
case 0.6...0.8:
|
|
return NSColor.systemOrange
|
|
case 0.8...1:
|
|
return NSColor.systemGreen
|
|
default:
|
|
return NSColor.systemRed
|
|
}
|
|
} else {
|
|
switch self {
|
|
case 0.6...0.8:
|
|
return NSColor.systemOrange
|
|
case 0.8...1:
|
|
return NSColor.systemRed
|
|
default:
|
|
return NSColor.systemGreen
|
|
}
|
|
}
|
|
}
|
|
|
|
func batteryColor(color: Bool = false) -> NSColor {
|
|
switch self {
|
|
case 0.2...0.4:
|
|
if !color {
|
|
return NSColor.controlTextColor
|
|
}
|
|
return NSColor.systemOrange
|
|
case 0.4...1:
|
|
if self == 1 {
|
|
return NSColor.controlTextColor
|
|
}
|
|
if !color {
|
|
return NSColor.controlTextColor
|
|
}
|
|
return NSColor.systemGreen
|
|
default:
|
|
return NSColor.systemRed
|
|
}
|
|
}
|
|
|
|
func splitAtDecimal() -> [Int64] {
|
|
return "\(self)".split(separator: ".").map{Int64($0)!}
|
|
}
|
|
}
|
|
|
|
public enum Unit : Float {
|
|
case byte = 1
|
|
case kilobyte = 1024
|
|
case megabyte = 1048576
|
|
case gigabyte = 1073741824
|
|
}
|
|
|
|
public struct Units {
|
|
public let bytes: Int64
|
|
|
|
public init(bytes: Int64) {
|
|
self.bytes = bytes
|
|
}
|
|
|
|
public var kilobytes: Double {
|
|
return Double(bytes) / 1_024
|
|
}
|
|
public var megabytes: Double {
|
|
return kilobytes / 1_024
|
|
}
|
|
public var gigabytes: Double {
|
|
return megabytes / 1_024
|
|
}
|
|
|
|
public func getReadableTuple() -> (Double, String) {
|
|
switch bytes {
|
|
case 0..<1_024:
|
|
return (0, "KB/s")
|
|
case 1_024..<(1_024 * 1_024):
|
|
return (Double(String(format: "%.2f", kilobytes))!, "KB/s")
|
|
case 1_024..<(1_024 * 1_024 * 1_024):
|
|
return (Double(String(format: "%.2f", megabytes))!, "MB/s")
|
|
case (1_024 * 1_024 * 1_024)...Int64.max:
|
|
return (Double(String(format: "%.2f", gigabytes))!, "GB/s")
|
|
default:
|
|
return (Double(String(format: "%.2f", kilobytes))!, "KB/s")
|
|
}
|
|
}
|
|
|
|
public func getReadableSpeed() -> String {
|
|
switch bytes {
|
|
case 0..<1_024:
|
|
return "0 KB/s"
|
|
case 1_024..<(1_024 * 1_024):
|
|
return String(format: "%.0f KB/s", kilobytes)
|
|
case 1_024..<(1_024 * 1_024 * 100):
|
|
return String(format: "%.1f MB/s", megabytes)
|
|
case (1_024 * 1_024 * 100)..<(1_024 * 1_024 * 1_024):
|
|
return String(format: "%.0f MB/s", megabytes)
|
|
case (1_024 * 1_024 * 1_024)...Int64.max:
|
|
return String(format: "%.1f GB/s", gigabytes)
|
|
default:
|
|
return String(format: "%.0f KB/s", kilobytes)
|
|
}
|
|
}
|
|
|
|
public func getReadableMemory() -> String {
|
|
switch bytes {
|
|
case 0..<1_024:
|
|
return "0 KB/s"
|
|
case 1_024..<(1_024 * 1_024):
|
|
return String(format: "%.0f KB", kilobytes)
|
|
case 1_024..<(1_024 * 1_024 * 1_024):
|
|
return String(format: "%.0f MB", megabytes)
|
|
case (1_024 * 1_024 * 1_024)...Int64.max:
|
|
return String(format: "%.2f GB", gigabytes)
|
|
default:
|
|
return String(format: "%.0f KB", kilobytes)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Double {
|
|
|
|
func secondsToHoursMinutesSeconds () -> (Int?, Int?, Int?) {
|
|
let hrs = self / 3600
|
|
let mins = (self.truncatingRemainder(dividingBy: 3600)) / 60
|
|
let seconds = (self.truncatingRemainder(dividingBy:3600)).truncatingRemainder(dividingBy:60)
|
|
return (Int(hrs) > 0 ? Int(hrs) : nil , Int(mins) > 0 ? Int(mins) : nil, Int(seconds) > 0 ? Int(seconds) : nil)
|
|
}
|
|
|
|
func printSecondsToHoursMinutesSeconds () -> String {
|
|
let time = self.secondsToHoursMinutesSeconds()
|
|
|
|
switch time {
|
|
case (nil, let x? , let y?):
|
|
return "\(x) min \(y) sec"
|
|
case (nil, let x?, nil):
|
|
return "\(x) min"
|
|
case (let x?, nil, nil):
|
|
return "\(x) h"
|
|
case (nil, nil, let x?):
|
|
return "\(x) sec"
|
|
case (let x?, nil, let z?):
|
|
return "\(x) h \(z) sec"
|
|
case (let x?, let y?, nil):
|
|
return "\(x) h \(y) min"
|
|
case (let x?, let y?, let z?):
|
|
return "\(x) h \(y) min \(z) sec"
|
|
default:
|
|
return "n/a"
|
|
}
|
|
}
|
|
}
|
|
|
|
extension String {
|
|
func condenseWhitespace() -> String {
|
|
let components = self.components(separatedBy: .whitespacesAndNewlines)
|
|
return components.filter { !$0.isEmpty }.joined(separator: " ")
|
|
}
|
|
}
|
|
|
|
extension NSBezierPath {
|
|
func addArrow(start: CGPoint, end: CGPoint, pointerLineLength: CGFloat, arrowAngle: CGFloat) {
|
|
self.move(to: start)
|
|
self.line(to: end)
|
|
|
|
let startEndAngle = atan((end.y - start.y) / (end.x - start.x)) + ((end.x - start.x) < 0 ? CGFloat(Double.pi) : 0)
|
|
let arrowLine1 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle + arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle + arrowAngle))
|
|
let arrowLine2 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle - arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle - arrowAngle))
|
|
|
|
self.line(to: arrowLine1)
|
|
self.move(to: end)
|
|
self.line(to: arrowLine2)
|
|
}
|
|
}
|
|
|
|
extension NSColor {
|
|
|
|
convenience init(hexString: String, alpha: CGFloat = 1.0) {
|
|
let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
|
let scanner = Scanner(string: hexString)
|
|
if (hexString.hasPrefix("#")) {
|
|
scanner.scanLocation = 1
|
|
}
|
|
var color: UInt32 = 0
|
|
scanner.scanHexInt32(&color)
|
|
let mask = 0x000000FF
|
|
let r = Int(color >> 16) & mask
|
|
let g = Int(color >> 8) & mask
|
|
let b = Int(color) & mask
|
|
let red = CGFloat(r) / 255.0
|
|
let green = CGFloat(g) / 255.0
|
|
let blue = CGFloat(b) / 255.0
|
|
self.init(red:red, green:green, blue:blue, alpha:alpha)
|
|
}
|
|
|
|
func toHexString() -> String {
|
|
var r:CGFloat = 0
|
|
var g:CGFloat = 0
|
|
var b:CGFloat = 0
|
|
var a:CGFloat = 0
|
|
getRed(&r, green: &g, blue: &b, alpha: &a)
|
|
let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0
|
|
return String(format:"#%06x", rgb)
|
|
}
|
|
}
|
|
|
|
extension String {
|
|
mutating func findAndCrop(pattern: String) -> String {
|
|
let regex = try! NSRegularExpression(pattern: pattern)
|
|
let stringRange = NSRange(location: 0, length: self.utf16.count)
|
|
var line = self
|
|
|
|
if let searchRange = regex.firstMatch(in: self, options: [], range: stringRange) {
|
|
let start = self.index(self.startIndex, offsetBy: searchRange.range.lowerBound)
|
|
let end = self.index(self.startIndex, offsetBy: searchRange.range.upperBound)
|
|
let value = String(self[start..<end]).trimmingCharacters(in: .whitespaces)
|
|
line = self.replacingOccurrences(
|
|
of: value,
|
|
with: "",
|
|
options: .regularExpression
|
|
)
|
|
self = line.trimmingCharacters(in: .whitespaces)
|
|
return value.trimmingCharacters(in: .whitespaces)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func matches(_ regex: String) -> Bool {
|
|
return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
|
|
}
|
|
}
|
|
|
|
extension URL {
|
|
func checkFileExist() -> Bool {
|
|
return FileManager.default.fileExists(atPath: self.path)
|
|
}
|
|
}
|