diff --git a/Beiwe/Common.swift b/Beiwe/Common.swift index b96cc3e..5e8bd60 100644 --- a/Beiwe/Common.swift +++ b/Beiwe/Common.swift @@ -114,7 +114,7 @@ public func print(_ items: Any..., separator: String = " ", terminator: String = Swift.print(terminator, separator: "", terminator: "") } -func sentry_warning(_ title: String, _ extra1: String? = nil, _ extra2: String? = nil, _ extra3: String? = nil) { +func sentry_warning(_ title: String, _ extra1: String? = nil, _ extra2: String? = nil, _ extra3: String? = nil, crash: Bool) { if let sentry_client = Client.shared { sentry_client.snapshotStacktrace { let event = Event(level: .warning) diff --git a/Beiwe/Managers/DataStorageManager.swift b/Beiwe/Managers/DataStorageManager.swift index 7d40e2a..b207f04 100644 --- a/Beiwe/Managers/DataStorageManager.swift +++ b/Beiwe/Managers/DataStorageManager.swift @@ -39,9 +39,9 @@ var LEFT_BEHIND_FILES = [String]() let LEFT_BEHIND_FILES_LOCK = NSLock() -//////////////////////////////////////////////////////////// DataStorage Manager ////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////// DataStorage Manager ////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////// DataStorage Manager ////////////////////////////////////////////////////// +//////////////////////////////////////// DataStorage Manager /////////////////////////////////////// +//////////////////////////////////////// DataStorage Manager /////////////////////////////////////// +//////////////////////////////////////// DataStorage Manager /////////////////////////////////////// class DataStorageManager { static let sharedInstance = DataStorageManager() static let dataFileSuffix = ".csv" @@ -49,7 +49,7 @@ class DataStorageManager { var secKeyRef: SecKey? var initted = false - ///////////////////////////////////////////////// Setup ////////////////////////////////////////////////////// + ////////////////////////////////////////// Setup /////////////////////////////////////////////// /// instantiates your DataStorage object - called in every manager // All of these error cases are going to crash the app. If you hit these, you did something wrong. @@ -92,7 +92,7 @@ class DataStorageManager { func dataStorageManagerInit(_ study: Study, secKeyRef: SecKey?) { self.initted = true self.secKeyRef = secKeyRef - // this function used to be called setCurrentStudy, but there was a looked-like-a-race-condition + // this function used to be called in setCurrentStudy, but there was a looked-like-a-race-condition // in stashing these variables early on during app start, and then trying to access them later. // Fully removing the stashing of `self.secKeyRef` resulted in // StudyManager().currentStudy?.keyRef @@ -166,7 +166,7 @@ class DataStorageManager { } } - ///////////////////////////////////////////////// Informational ////////////////////////////////////////////////////// + ////////////////////////////////////// Informational /////////////////////////////////////////// // for years we used the .cache directory. wtaf. static func currentDataDirectory() -> URL { @@ -193,9 +193,12 @@ class DataStorageManager { return filename.hasSuffix(DataStorageManager.dataFileSuffix) || filename.hasSuffix(".mp4") || filename.hasSuffix(".wav") } - ///////////////////////////////////////////////// Upload ////////////////////////////////////////////////////// + ///////////////////////////////////////////////// Upload /////////////////////////////////////// + + /// Moves any left behinde files in the data directory. Called just before upload. func moveLeftBehindFilesToUpload() { + // safely get reference so that it can't be cleared or updated out from under us LEFT_BEHIND_FILES_LOCK.lock() let left_behind_files = LEFT_BEHIND_FILES LEFT_BEHIND_FILES = [] @@ -218,6 +221,8 @@ class DataStorageManager { } } + + /// called at app start, moves any uploadable files that were never moved to upload to upload folder func moveUnknownJunkToUpload() { var filesToUpload: [String] = [] if let enumerator = FileManager.default.enumerator(atPath: DataStorageManager.currentDataDirectory().path) { @@ -283,18 +288,19 @@ class DataStorageManager { } } - // move file function with retry logic, fails silently but that is ok because it is only used prepareForUpload_actual + // move file function with retry logic, fails silently but that is ok because it is + // only used prepareForUpload_actual. private func moveFile(_ src: URL, dst: URL, recur: Int = Constants.RECUR_DEPTH) { do { try FileManager.default.moveItem(at: src, to: dst) } catch CocoaError.fileNoSuchFile { print("File not found (for moving)? \(shortenPath(src))") - sentry_warning("File not found (for moving).", shortenPath(src)) + sentry_warning("File not found (for moving).", shortenPath(src), crash:false) } catch CocoaError.fileWriteFileExists { - print("File already exists (for moving) \(shortenPath(dst)), giving up for now because that's crazy?") - sentry_warning("File already exists (for moving).", shortenPath(dst)) + // print("File already exists (for moving) \(shortenPath(dst)), giving up for now because that's crazy?") + sentry_warning("File already exists (for moving).", shortenPath(dst), crash:false) } catch CocoaError.fileWriteOutOfSpace { - print("Out of space (for moving) \(shortenPath(dst))") + // print("Out of space (for moving) \(shortenPath(dst))") // sentry_warning("Out of space (for moving).", shortenPath(dst)) // never report out of space like this. } catch { // known and not handling: fileWriteVolumeReadOnly, fileWriteInvalidFileName @@ -311,7 +317,7 @@ class DataStorageManager { if let sentry_client = Client.shared { sentry_client.snapshotStacktrace { let event = Event(level: .error) - event.message = "Error moving file 1" + event.message = "not a crash - Error moving file 1" event.environment = Constants.APP_INFO_TAG if event.extra == nil { @@ -536,7 +542,7 @@ class DataStorage { do { try FileManager.default.moveItem(at: self.filename, to: target_location) - print("moved temp data file \(shortenPath(self.filename)) to \(shortenPath(target_location))") + // print("moved temp data file \(shortenPath(self.filename)) to \(shortenPath(target_location))") } catch { print("Error moving temp data \(shortenPath(self.filename)) to \(shortenPath(target_location))") if recur > 0 { @@ -546,7 +552,8 @@ class DataStorage { self.io_error_report( "Error moving file on reset after \(Constants.RECUR_DEPTH) tries.", error: error, - more: ["from": shortenPath(self.filename), "to": shortenPath(target_location)] + more: ["from": shortenPath(self.filename), "to": shortenPath(target_location)], + crash: false ) LEFT_BEHIND_FILES_LOCK.lock() LEFT_BEHIND_FILES.append(self.filename.path) @@ -565,15 +572,16 @@ class DataStorage { var message = if created { "Create new data file" } else { "Could not create new data file" } if !created { - // TODO; this is a really bad fatal error, need to not actually crash the app in this scenario - self.io_error_report("file_creation_1") + // does not crash the app but it is a nasty problem + self.io_error_report("file_creation_1", crash: false) throw DataStorageError.fileCreationError } do { self.file_handle = try FileHandle(forWritingTo: self.filename) } catch { - self.io_error_report("file_creation_2", error: error) + // does not crash the app but it is a nasty problem too + self.io_error_report("file_creation_2", error: error, crash: false) throw DataStorageError.fileCreationError } self.conditionalApplog(event: "file_create", msg: message, d1: self.name) @@ -581,7 +589,7 @@ class DataStorage { } // reports an io error to sentry, prints the error too. - private func io_error_report(_ message: String, error: Error? = nil, more: [String: String]? = nil) { + private func io_error_report(_ message: String, error: Error? = nil, more: [String: String]? = nil, crash: Bool) { // These print statements are not showing up reliably? if let error = error { print("io error: \(message) - error: \(error)") @@ -595,7 +603,12 @@ class DataStorage { if let sentry_client = Client.shared { sentry_client.snapshotStacktrace { let event = Event(level: .error) - event.message = message + // this is actually really important for error triage + if crash { + event.message = message + } else { + event.message = "not a crash - " + message + } event.environment = Constants.APP_INFO_TAG //setup @@ -605,7 +618,7 @@ class DataStorage { // basics if var extras = event.extra { extras["filename"] = shortenPath(self.filename) - extras[" user_id"] = self.patientId + extras["user_id"] = self.patientId if let error = error { extras["error"] = "\(error)" } diff --git a/Beiwe/Managers/StudyManager.swift b/Beiwe/Managers/StudyManager.swift index 125bf0f..733ec35 100644 --- a/Beiwe/Managers/StudyManager.swift +++ b/Beiwe/Managers/StudyManager.swift @@ -112,7 +112,6 @@ class StudyManager { // legacy case - we were using a bad location, the CACHE directory, and we may // still have files there. adding this on 2024-2-20. - DataStorageManager.sharedInstance.moveUnknownJunkToUpload() DataStorageManager.sharedInstance.moveOldUnknownJunkToUpload() // There may be files in the current data directory, this should only happen if the app crashed, @@ -216,7 +215,7 @@ class StudyManager { // get the survey data and write it out var trackingSurvey: TrackingSurveyPresenter if surveyPresenter == nil { - print("hitting case where we were expiring the survey timings? what the fuck?") + // print("hitting case where we were 'expiring' the survey timings?") // expiration logic? what is "expired?" trackingSurvey = TrackingSurveyPresenter(surveyId: surveyId, activeSurvey: activeSurvey, survey: survey) trackingSurvey.addTimingsEvent("expired", question: nil) @@ -293,18 +292,10 @@ class StudyManager { if from_notification || survey.alwaysAvailable { let activeSurvey = ActiveSurvey(survey: survey) - // when we receive a notification we need to record that, this is used to sort surveys on the main screen (I think) + // when we receive a notification we need to record that, this is used to sort + // surveys on the main screen (I think) if from_notification { activeSurvey.received = sentTime - // FIXME: study.receivedAudioSurveys and study.receivedTrackingSurveys are junk, they are assigned but never used. - if let surveyType = survey.surveyType { - switch surveyType { - case .AudioSurvey: - study.receivedAudioSurveys = (study.receivedAudioSurveys) + 1 - case .TrackingSurvey: - study.receivedTrackingSurveys = (study.receivedTrackingSurveys) + 1 - } - } } study.activeSurveys[surveyId] = activeSurvey } @@ -421,27 +412,27 @@ class StudyManager { guard let study = currentStudy, let studySettings = study.studySettings else { return } - if let t = study.nextUploadCheck { print("previous study.nextUploadCheck:", study.nextUploadCheck!, Date(timeIntervalSince1970: Double(t))) } + // if let t = study.nextUploadCheck { print("previous study.nextUploadCheck:", study.nextUploadCheck!, Date(timeIntervalSince1970: Double(t))) } study.nextUploadCheck = Int64(Date().timeIntervalSince1970) + Int64(studySettings.uploadDataFileFrequencySeconds) - if let t = study.nextUploadCheck { print("updated study.nextUploadCheck:", study.nextUploadCheck!, Date(timeIntervalSince1970: Double(t))) } + // if let t = study.nextUploadCheck { print("updated study.nextUploadCheck:", study.nextUploadCheck!, Date(timeIntervalSince1970: Double(t))) } } func setNextSurveyTime() { guard let study = currentStudy, let studySettings = study.studySettings else { return } - if let t = study.nextSurveyCheck { print("previous study.nextSurveyCheck:", study.nextSurveyCheck!, Date(timeIntervalSince1970: Double(t))) } + // if let t = study.nextSurveyCheck { print("previous study.nextSurveyCheck:", study.nextSurveyCheck!, Date(timeIntervalSince1970: Double(t))) } study.nextSurveyCheck = Int64(Date().timeIntervalSince1970) + Int64(studySettings.checkForNewSurveysFreqSeconds) - if let t = study.nextSurveyCheck { print("updated study.nextSurveyCheck:", study.nextSurveyCheck!, Date(timeIntervalSince1970: Double(t))) } + // if let t = study.nextSurveyCheck { print("updated study.nextSurveyCheck:", study.nextSurveyCheck!, Date(timeIntervalSince1970: Double(t))) } } func setNextDeviceSettingsTime() { guard let study = currentStudy else { return } - if let t = study.nextDeviceSettingsCheck { print("previous study.nextDeviceSettingsCheck:", study.nextDeviceSettingsCheck!, Date(timeIntervalSince1970: Double(t))) } + // if let t = study.nextDeviceSettingsCheck { print("previous study.nextDeviceSettingsCheck:", study.nextDeviceSettingsCheck!, Date(timeIntervalSince1970: Double(t))) } study.nextDeviceSettingsCheck = Int64(Date().timeIntervalSince1970) + DEVICE_SETTINGS_INTERVAL - if let t = study.nextDeviceSettingsCheck { print("updated study.nextDeviceSettingsCheck:", study.nextDeviceSettingsCheck!, Date(timeIntervalSince1970: Double(t))) } + // if let t = study.nextDeviceSettingsCheck { print("updated study.nextDeviceSettingsCheck:", study.nextDeviceSettingsCheck!, Date(timeIntervalSince1970: Double(t))) } } ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Beiwe/Managers/TimerManager.swift b/Beiwe/Managers/TimerManager.swift index 7f838a8..f748abf 100644 --- a/Beiwe/Managers/TimerManager.swift +++ b/Beiwe/Managers/TimerManager.swift @@ -182,11 +182,11 @@ class TimerManager { @objc func pollServices() { // handy print statemunt, buuuuut timers are perfect when attached to the debugger so it's not actually very useful?? var t1 = Date() - if t1 > self.nextDataServicesCheck { - print("pollservices was late by \(String(format: "%.3f", t1.timeIntervalSince1970 - self.expected_wakeup.timeIntervalSince1970)) seconds") - } else { - print("pollservices was early by \(String(format: "%.3f", self.expected_wakeup.timeIntervalSince1970 - t1.timeIntervalSince1970)) seconds") - } + // if t1 > self.nextDataServicesCheck { + // print("pollservices was late by \(String(format: "%.3f", t1.timeIntervalSince1970 - self.expected_wakeup.timeIntervalSince1970)) seconds") + // } else { + // print("pollservices was early by \(String(format: "%.3f", self.expected_wakeup.timeIntervalSince1970 - t1.timeIntervalSince1970)) seconds") + // } self.clearPollTimer() AppEventManager.sharedInstance.logAppEvent(event: "poll_service", msg: "Polling service") // probably pointless @@ -275,7 +275,7 @@ class TimerManager { self.timer = Timer.scheduledTimer( timeInterval: seconds, target: self, selector: #selector(self.pollServices), userInfo: nil, repeats: false ) - print("The Timer was set for: \(seconds) seconds") + // print("The Timer was set for: \(seconds) seconds") AppEventManager.sharedInstance.logAppEvent(event: "set_timer", msg: "Set timer for \(seconds) seconds", d1: String(seconds)) } } diff --git a/Beiwe/Models/OneSelection.swift b/Beiwe/Models/OneSelection.swift index 1c27b27..d208a65 100644 --- a/Beiwe/Models/OneSelection.swift +++ b/Beiwe/Models/OneSelection.swift @@ -2,7 +2,7 @@ import Foundation import ObjectMapper // used when ssetting up text questions, its a database backing -struct OneSelection: Mappable { +struct OneSelection: Mappable, Equatable { var text: String = "" init?(map: Map) {} @@ -10,4 +10,8 @@ struct OneSelection: Mappable { mutating func mapping(map: Map) { self.text <- map["text"] } + + static func == (lhs: OneSelection, rhs: OneSelection) -> Bool { + return lhs.text == rhs.text + } } diff --git a/Beiwe/Models/Study.swift b/Beiwe/Models/Study.swift index 9070011..4470e23 100644 --- a/Beiwe/Models/Study.swift +++ b/Beiwe/Models/Study.swift @@ -22,8 +22,6 @@ class Study: ReclineObject { var nextSurveyCheck: Int64? var nextDeviceSettingsCheck: Int64? var lastBadgeCnt = 0 - var receivedAudioSurveys: Int = 0 - var receivedTrackingSurveys: Int = 0 var submittedAudioSurveys: Int = 0 // TODO: what is this and is it breaking uploads var submittedTrackingSurveys: Int = 0 // TODO: what is this and is it breaking uploads @@ -65,8 +63,6 @@ class Study: ReclineObject { self.surveys <- map["surveys"] self.activeSurveys <- map["active_surveys"] self.registerDate <- (map["registerDate"], TransformOf(fromJSON: { $0?.int64Value }, toJSON: { $0.map { NSNumber(value: $0) } })) - self.receivedAudioSurveys <- map["receivedAudioSurveys"] - self.receivedTrackingSurveys <- map["receivedTrackingSurveys"] self.submittedAudioSurveys <- map["submittedAudioSurveys"] self.submittedTrackingSurveys <- map["submittedTrackingSurveys"] self.customApiUrl <- map["customApiUrl"] diff --git a/Beiwe/Models/Survey.swift b/Beiwe/Models/Survey.swift index f7fb0f7..63eb2a8 100644 --- a/Beiwe/Models/Survey.swift +++ b/Beiwe/Models/Survey.swift @@ -6,7 +6,7 @@ enum SurveyTypes: String { case TrackingSurvey = "tracking_survey" } -struct Survey: Mappable { +struct Survey: Mappable, Equatable { var surveyId: String? var surveyType: SurveyTypes? var name = "" @@ -39,4 +39,48 @@ struct Survey: Mappable { self.questions <- map["content"] self.alwaysAvailable <- map["settings.always_available"] } + + static func == (lhs: Survey, rhs: Survey) -> Bool { + let surveyId = lhs.surveyId == rhs.surveyId + let surveyType = lhs.surveyType == rhs.surveyType + let name = lhs.name == rhs.name + let timings = lhs.timings == rhs.timings + let triggerOnFirstDownload = lhs.triggerOnFirstDownload == rhs.triggerOnFirstDownload + let randomize = lhs.randomize == rhs.randomize + let numberOfRandomQuestions = lhs.numberOfRandomQuestions == rhs.numberOfRandomQuestions + let randomizeWithMemory = lhs.randomizeWithMemory == rhs.randomizeWithMemory + let questions = lhs.questions == rhs.questions + let audioSurveyType = lhs.audioSurveyType == rhs.audioSurveyType + let audioSampleRate = lhs.audioSampleRate == rhs.audioSampleRate + let audioBitrate = lhs.audioBitrate == rhs.audioBitrate + let alwaysAvailable = lhs.alwaysAvailable == rhs.alwaysAvailable + + if !surveyId { print(lhs.surveyId, "!=", rhs.surveyId) } + if !surveyType { print(lhs.surveyType, "!=", rhs.surveyType) } + if !name { print(lhs.name, "!=", rhs.name) } + if !timings { print(lhs.timings, "!=", rhs.timings) } + if !triggerOnFirstDownload { print(lhs.triggerOnFirstDownload, "!=", rhs.triggerOnFirstDownload) } + if !randomize { print(lhs.randomize, "!=", rhs.randomize) } + if !numberOfRandomQuestions { print(lhs.numberOfRandomQuestions, "!=", rhs.numberOfRandomQuestions) } + if !randomizeWithMemory { print(lhs.randomizeWithMemory, "!=", rhs.randomizeWithMemory) } + if !questions { print(lhs.questions, "!=", rhs.questions) } + if !audioSurveyType { print(lhs.audioSurveyType, "!=", rhs.audioSurveyType) } + if !audioSampleRate { print(lhs.audioSampleRate, "!=", rhs.audioSampleRate) } + if !audioBitrate { print(lhs.audioBitrate, "!=", rhs.audioBitrate) } + if !alwaysAvailable { print(lhs.alwaysAvailable, "!=", rhs.alwaysAvailable) } + + return surveyId && + surveyType && + name && + timings && + triggerOnFirstDownload && + randomize && + numberOfRandomQuestions && + randomizeWithMemory && + questions && + audioSurveyType && + audioSampleRate && + audioBitrate && + alwaysAvailable + } }