// // AppDelegate.swift // USB Meter // // Created by Bogdan Timofte on 01/03/2020. // Copyright © 2020 Bogdan Timofte. All rights reserved. // import CloudKit import CoreData import OSLog import UIKit import UserNotifications //let btSerial = BluetoothSerial(delegate: BSD()) let appData = AppData() private let restoreLogger = Logger(subsystem: "ro.xdev.USB-Meter", category: "Restore") enum Constants { static let chartUnderscan: CGFloat = 0.5 static let chartOverscan: CGFloat = 1 - chartUnderscan } // MARK: Clock // MARK: Debug public func track(_ message: String = "", file: String = #file, function: String = #function, line: Int = #line ) { guard shouldEmitTrackMessage(message, file: file, function: function) else { return } let date = Date() let calendar = Calendar.current let hour = calendar.component(.hour, from: date) let minutes = calendar.component(.minute, from: date) let seconds = calendar.component(.second, from: date) print("\(hour):\(minutes):\(seconds) - \(file):\(line) - \(function) \(message)") } public func restoreTrace(_ message: String) { restoreLogger.debug("\(message, privacy: .public)") } private func shouldEmitTrackMessage(_ message: String, file: String, function: String) -> Bool { #if DEBUG if ProcessInfo.processInfo.environment["USB_METER_VERBOSE_LOGS"] == "1" { return true } #if targetEnvironment(macCatalyst) let importantMarkers = [ "Error", "error", "Failed", "failed", "timeout", "Timeout", "Missing", "missing", "overflow", "Disconnect", "disconnect", "Disconnected", "unauthorized", "not authorized", "not supported", "Unexpected", "Invalid Context", "ignored", "Guard:", "Skip data request", "Dropping unsolicited data", "This is not possible!", "Inferred", "Clearing", "Reconnecting" ] if importantMarkers.contains(where: { message.contains($0) }) { return true } let noisyFunctions: Set = [ "logRuntimeICloudDiagnostics()", "refreshCloudAvailability(reason:)", "start()", "centralManagerDidUpdateState(_:)", "discoveredMeter(peripheral:advertising:rssi:)", "connect()", "connectionEstablished()", "peripheral(_:didDiscoverServices:)", "peripheral(_:didDiscoverCharacteristicsFor:error:)", "refreshOperationalStateIfReady()", "peripheral(_:didUpdateNotificationStateFor:error:)", "scheduleDataDumpRequest(after:reason:)" ] if noisyFunctions.contains(function) { return false } let noisyMarkers = [ "Runtime iCloud diagnostics", "iCloud availability", "Starting Bluetooth manager", "Bluetooth is On... Start scanning...", "adding new USB Meter", "Connect called for", "Connection established for", "Optional([ Bool { logRuntimeICloudDiagnostics() UNUserNotificationCenter.current().delegate = self application.registerForRemoteNotifications() appData.activateChargeInsights(context: persistentContainer.viewContext) return true } private func logRuntimeICloudDiagnostics() { #if DEBUG let hasUbiquityIdentityToken = FileManager.default.ubiquityIdentityToken != nil track("Runtime iCloud diagnostics: ubiquityIdentityTokenAvailable=\(hasUbiquityIdentityToken)") CKContainer(identifier: cloudKitContainerIdentifier).accountStatus { status, error in if let error { track("CloudKit account status error: \(error.localizedDescription)") return } track("CloudKit account status: \(status.rawValue)") } #endif } // MARK: UISceneSession Lifecycle func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } func applicationWillTerminate(_ application: UIApplication) { _ = appData.flushChargeInsights() saveContext() } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { #if DEBUG track("Registered for remote notifications with device token length \(deviceToken.count)") #endif } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { track("Remote notification registration failed: \(error.localizedDescription)") } func application( _ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void ) { #if DEBUG track("Received remote notification with keys: \(userInfo.keys.map(String.init(describing:)).joined(separator: ", "))") #endif completionHandler(.newData) } lazy var persistentContainer: NSPersistentCloudKitContainer = { let container = NSPersistentCloudKitContainer(name: "CKModel") if let description = container.persistentStoreDescriptions.first { description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions( containerIdentifier: cloudKitContainerIdentifier ) description.shouldMigrateStoreAutomatically = true description.shouldInferMappingModelAutomatically = true description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) } container.loadPersistentStores { storeDescription, error in if let error = error as NSError? { // Log the error but do NOT destroy the store — wiping local data and // waiting for a full CloudKit re-sync is far worse than a degraded launch. NSLog( "Core Data store load failed (url=%@): %@ — %@", storeDescription.url?.path ?? "unknown", error.localizedDescription, error.userInfo ) #if DEBUG fatalError("Unresolved Core Data error \(error), \(error.userInfo)") #endif } } container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy return container }() func saveContext() { let context = persistentContainer.viewContext guard context.hasChanges else { return } do { try context.save() } catch { let nsError = error as NSError NSLog("Core Data save failed: %@", nsError.localizedDescription) #if DEBUG fatalError("Unresolved Core Data save error \(nsError), \(nsError.userInfo)") #endif } } func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { completionHandler([.banner, .sound, .list]) } }