You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

231 lines
9.1 KiB

// Copyright © 2015 Abhishek Banthia
import Cocoa
import CoreLoggerKit
import CoreModelKit
enum ViewType {
case futureSlider
case upcomingEventView
case twelveHour
case sunrise
case showMeetingInMenubar
case showAllDayEventsInMenubar
case showAppInForeground
case appDisplayOptions
case dateInMenubar
case placeInMenubar
case dayInMenubar
case menubarCompactMode
case sync
}
class DataStore: NSObject {
private static var sharedStore = DataStore(with: UserDefaults.standard)
private var userDefaults: UserDefaults!
private var ubiquitousStore: NSUbiquitousKeyValueStore?
private var cachedTimezones: [Data]
private var cachedMenubarTimezones: [Data]
private static let timeFormatsWithSuffix: Set<NSNumber> = Set([NSNumber(integerLiteral: 0),
NSNumber(integerLiteral: 3),
NSNumber(integerLiteral: 4),
NSNumber(integerLiteral: 6),
NSNumber(integerLiteral: 7)])
class func shared() -> DataStore {
return sharedStore
}
init(with defaults: UserDefaults) {
cachedTimezones = (defaults.object(forKey: UserDefaultKeys.defaultPreferenceKey) as? [Data]) ?? []
cachedMenubarTimezones = cachedTimezones.filter {
let customTimezone = TimezoneData.customObject(from: $0)
return customTimezone?.isFavourite == 1
}
userDefaults = defaults
super.init()
setupSyncNotification()
}
func setupSyncNotification() {
if shouldDisplay(.sync) {
ubiquitousStore = NSUbiquitousKeyValueStore.default
NotificationCenter.default.addObserver(self,
selector: #selector(ubiquitousKeyValueStoreChanged),
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: NSUbiquitousKeyValueStore.default)
let synchronizationResult = ubiquitousStore?.synchronize() ?? false
let resultString = synchronizationResult ? "successfully" : "unsuccessfully"
Logger.info("Ubiquitous Store synchronized \(resultString)")
} else {
NotificationCenter.default.removeObserver(self,
name: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: nil)
}
}
@objc func ubiquitousKeyValueStoreChanged(_ notification: Notification) {
let userInfo = notification.userInfo ?? [:]
let ubiquitousStore = notification.object as? NSUbiquitousKeyValueStore
Logger.info("Ubiquitous Store Changed: User Info is \(userInfo)")
let currentTimezones = userDefaults.object(forKey: UserDefaultKeys.defaultPreferenceKey) as? [Data]
let cloudTimezones = ubiquitousStore?.object(forKey: UserDefaultKeys.defaultPreferenceKey) as? [Data]
let cloudLastUpdateDate = (ubiquitousStore?.object(forKey: UserDefaultKeys.ubiquitousStoreLastUpdateKey) as? Date) ?? Date()
let defaultsLastUpdateDate = (ubiquitousStore?.object(forKey: UserDefaultKeys.userDefaultsLastUpdateKey) as? Date) ?? Date()
if cloudTimezones == currentTimezones {
Logger.info("Ubiquitous Store timezones aren't equal to current timezones")
}
if defaultsLastUpdateDate.isLaterThanOrEqual(to: cloudLastUpdateDate) {
Logger.info("Ubiquitous Store is stale as compared to User Defaults")
}
if cloudTimezones != currentTimezones, cloudLastUpdateDate.isLaterThanOrEqual(to: defaultsLastUpdateDate) {
Logger.info("Syncing local timezones with data from the ☁. ☁ last update timestamp is recent")
userDefaults.set(cloudTimezones, forKey: UserDefaultKeys.defaultPreferenceKey)
userDefaults.set(Date(), forKey: UserDefaultKeys.userDefaultsLastUpdateKey)
NotificationCenter.default.post(name: DataStore.didSyncFromExternalSourceNotification,
object: self)
return
}
}
func timezones() -> [Data] {
return cachedTimezones
}
func setTimezones(_ timezones: [Data]?) {
userDefaults.set(timezones, forKey: UserDefaultKeys.defaultPreferenceKey)
userDefaults.set(Date(), forKey: UserDefaultKeys.userDefaultsLastUpdateKey)
cachedTimezones = timezones ?? []
cachedMenubarTimezones = cachedTimezones.filter {
let customTimezone = TimezoneData.customObject(from: $0)
return customTimezone?.isFavourite == 1
}
// iCloud sync
ubiquitousStore?.set(timezones, forKey: UserDefaultKeys.defaultPreferenceKey)
ubiquitousStore?.set(Date(), forKey: UserDefaultKeys.ubiquitousStoreLastUpdateKey)
}
func menubarTimezones() -> [Data]? {
return cachedMenubarTimezones
}
func selectedCalendars() -> [String]? {
return userDefaults.array(forKey: UserDefaultKeys.selectedCalendars) as? [String]
}
// MARK: Date (May 8th) in Compact Menubar
func shouldShowDateInMenubar() -> Bool {
return shouldDisplay(.dateInMenubar)
}
// MARK: Day (Sun, Mon etc.) in Compact Menubar
func shouldShowDayInMenubar() -> Bool {
return shouldDisplay(.dayInMenubar)
}
func retrieve(key: String) -> Any? {
return userDefaults.object(forKey: key)
}
func addTimezone(_ timezone: TimezoneData) {
guard let encodedTimezone = NSKeyedArchiver.clocker_archive(with: timezone) else {
return
}
var defaults: [Data] = timezones()
defaults.append(encodedTimezone)
setTimezones(defaults)
}
func removeLastTimezone() {
var currentLineup = timezones()
if currentLineup.isEmpty {
return
}
currentLineup.removeLast()
Logger.log(object: [:], for: "Undo Action Executed during Onboarding")
setTimezones(currentLineup)
}
func timezoneFormat() -> NSNumber {
return userDefaults.object(forKey: UserDefaultKeys.selectedTimeZoneFormatKey) as? NSNumber ?? NSNumber(integerLiteral: 0)
}
func isBufferRequiredForTwelveHourFormats() -> Bool {
return DataStore.timeFormatsWithSuffix.contains(timezoneFormat())
}
func shouldDisplay(_ type: ViewType) -> Bool {
switch type {
case .futureSlider:
guard let value = retrieve(key: UserDefaultKeys.displayFutureSliderKey) as? NSNumber else {
return false
}
return value != 1 // Display slider is 0 and Hide is 1.
case .upcomingEventView:
guard let value = retrieve(key: UserDefaultKeys.showUpcomingEventView) as? NSString else {
return false
}
return value == "YES"
case .twelveHour:
return shouldDisplayHelper(UserDefaultKeys.selectedTimeZoneFormatKey)
case .showAllDayEventsInMenubar:
return shouldDisplayHelper(UserDefaultKeys.showAllDayEventsInUpcomingView)
case .sunrise:
return shouldDisplayHelper(UserDefaultKeys.sunriseSunsetTime)
case .showMeetingInMenubar:
return shouldDisplayHelper(UserDefaultKeys.showMeetingInMenubar)
case .showAppInForeground:
guard let value = retrieve(key: UserDefaultKeys.showAppInForeground) as? NSNumber else {
return false
}
return value.isEqual(to: NSNumber(value: 1))
case .dateInMenubar:
return shouldDisplayNonObjectHelper(UserDefaultKeys.showDateInMenu)
case .placeInMenubar:
return shouldDisplayHelper(UserDefaultKeys.showPlaceInMenu)
case .dayInMenubar:
return shouldDisplayNonObjectHelper(UserDefaultKeys.showDayInMenu)
case .appDisplayOptions:
return shouldDisplayHelper(UserDefaultKeys.appDisplayOptions)
case .menubarCompactMode:
guard let value = retrieve(key: UserDefaultKeys.menubarCompactMode) as? Int else {
return false
}
return value == 0
case .sync:
return shouldDisplayHelper(UserDefaultKeys.enableSyncKey)
}
}
// MARK: Private
private func shouldDisplayHelper(_ key: String) -> Bool {
guard let value = retrieve(key: key) as? NSNumber else {
return false
}
return value.isEqual(to: NSNumber(value: 0))
}
// MARK: Some values are stored as plain integers; objectForKey: will return nil, so using integerForKey:
private func shouldDisplayNonObjectHelper(_ key: String) -> Bool {
let value = userDefaults.integer(forKey: key)
return value == 0
}
}
extension DataStore {
public static let didSyncFromExternalSourceNotification: NSNotification.Name = .init("didSyncFromExternalSourceNotification")
}