housekeeping for breadings and calibrations, readings older than 90 days will be deleted from core data. Calibrations older than 90 days and that do not have anymore readings referring to it, will be deleted

This commit is contained in:
Johan Degraeve 2020-10-04 21:21:41 +02:00
parent 25565dee08
commit 74899d8fb4
7 changed files with 221 additions and 5 deletions

View File

@ -237,6 +237,7 @@
F85DC2F321CFE3D400B9F74A /* Calibration+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DC2F021CFE3D400B9F74A /* Calibration+CoreDataClass.swift */; };
F85DC2F421CFE3D400B9F74A /* Sensor+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DC2F121CFE3D400B9F74A /* Sensor+CoreDataClass.swift */; };
F85DC2F521CFE3D400B9F74A /* BgReading+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85DC2F221CFE3D400B9F74A /* BgReading+CoreDataClass.swift */; };
F85FF39125288870004E6FF1 /* HouseKeeper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85FF39025288870004E6FF1 /* HouseKeeper.swift */; };
F867E2612252ADAB000FD265 /* Calibration+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = F867E25D2252ADAB000FD265 /* Calibration+CoreDataProperties.swift */; };
F8691888239CEEFA0065B607 /* BluetoothPeripheralViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8691887239CEEFA0065B607 /* BluetoothPeripheralViewModel.swift */; };
F869188C23A044340065B607 /* TextsM5StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F869188B23A044340065B607 /* TextsM5StackView.swift */; };
@ -408,6 +409,7 @@
F8E53FCE251D35FB00052CE5 /* Common.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8E53FD0251D35FB00052CE5 /* Common.strings */; };
F8E53FE9251F7B8800052CE5 /* Texts.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E53FE8251F7B8800052CE5 /* Texts.swift */; };
F8E540462521227000052CE5 /* xdrip.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F8E5403F2521227000052CE5 /* xdrip.xcdatamodeld */; };
F8E5404C2522624800052CE5 /* ConstantsHousekeeping.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E5404B2522624800052CE5 /* ConstantsHousekeeping.swift */; };
F8E6C78C24CDDB83007C1199 /* SnoozeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6C78B24CDDB83007C1199 /* SnoozeViewController.swift */; };
F8E6C79024CEC22A007C1199 /* TextsSnooze.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6C78F24CEC22A007C1199 /* TextsSnooze.swift */; };
F8E6C79124CEC2E3007C1199 /* Snooze.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8E6C79324CEC2E3007C1199 /* Snooze.strings */; };
@ -742,6 +744,7 @@
F85DC2F021CFE3D400B9F74A /* Calibration+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Calibration+CoreDataClass.swift"; sourceTree = "<group>"; };
F85DC2F121CFE3D400B9F74A /* Sensor+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sensor+CoreDataClass.swift"; sourceTree = "<group>"; };
F85DC2F221CFE3D400B9F74A /* BgReading+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BgReading+CoreDataClass.swift"; sourceTree = "<group>"; };
F85FF39025288870004E6FF1 /* HouseKeeper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HouseKeeper.swift; sourceTree = "<group>"; };
F867E25D2252ADAB000FD265 /* Calibration+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Calibration+CoreDataProperties.swift"; path = "xdrip/Core Data/extensions/Calibration+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; };
F8691887239CEEFA0065B607 /* BluetoothPeripheralViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothPeripheralViewModel.swift; sourceTree = "<group>"; };
F869188B23A044340065B607 /* TextsM5StackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsM5StackView.swift; sourceTree = "<group>"; };
@ -1016,6 +1019,7 @@
F8E540432521227000052CE5 /* xdrip v11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "xdrip v11.xcdatamodel"; sourceTree = "<group>"; };
F8E540442521227000052CE5 /* xdrip v9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "xdrip v9.xcdatamodel"; sourceTree = "<group>"; };
F8E540452521227000052CE5 /* xdrip v12.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "xdrip v12.xcdatamodel"; sourceTree = "<group>"; };
F8E5404B2522624800052CE5 /* ConstantsHousekeeping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsHousekeeping.swift; sourceTree = "<group>"; };
F8E6C78B24CDDB83007C1199 /* SnoozeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnoozeViewController.swift; sourceTree = "<group>"; };
F8E6C78F24CEC22A007C1199 /* TextsSnooze.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsSnooze.swift; sourceTree = "<group>"; };
F8E6C79224CEC2E3007C1199 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Snooze.strings; sourceTree = "<group>"; };
@ -1592,6 +1596,14 @@
path = "Application Delegate";
sourceTree = "<group>";
};
F85FF38F25288860004E6FF1 /* HouseKeeping */ = {
isa = PBXGroup;
children = (
F85FF39025288870004E6FF1 /* HouseKeeper.swift */,
);
path = HouseKeeping;
sourceTree = "<group>";
};
F870D3D425126A49008967B0 /* xDrip4iOS Widget */ = {
isa = PBXGroup;
children = (
@ -2080,7 +2092,6 @@
F8EA6C7D21B70DEA0082976B /* Constants */ = {
isa = PBXGroup;
children = (
F8252866243E50FE0067AF77 /* ConstantsLibre.swift */,
F8A1585022EDB597007F5B5D /* ConstantsBGGraphBuilder.swift */,
F8A1585222EDB602007F5B5D /* ConstantsBloodGlucose.swift */,
F856CE5A22EDC8E50083E436 /* ConstantsBluetoothPairing.swift */,
@ -2093,17 +2104,19 @@
F8A1587222EDC893007F5B5D /* ConstantsDexcomShare.swift */,
F80859262364355F00F3829D /* ConstantsGlucoseChart.swift */,
F8A1586622EDB8BF007F5B5D /* ConstantsHomeView.swift */,
F8E5404B2522624800052CE5 /* ConstantsHousekeeping.swift */,
F8252866243E50FE0067AF77 /* ConstantsLibre.swift */,
F8A1585E22EDB81E007F5B5D /* ConstantsLog.swift */,
F8A389C723203E3E0010F405 /* ConstantsM5Stack.swift */,
F8A1586A22EDB967007F5B5D /* ConstantsMaster.swift */,
F8A389EC23342EB10010F405 /* ConstantsNightScout.swift */,
F8A1586022EDB844007F5B5D /* ConstantsNotifications.swift */,
F8E51D5E2448E2E8001C9E5A /* ConstantsShareWithLoop.swift */,
F8A1586222EDB86E007F5B5D /* ConstantsSounds.swift */,
F8A1587022EDC865007F5B5D /* ConstantsSpeakReadingLanguages.swift */,
F8A1586E22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift */,
F8E3A2AA23DA520B00E5E98A /* ConstantsWatch.swift */,
F8E51D5E2448E2E8001C9E5A /* ConstantsShareWithLoop.swift */,
F8AF36142455C6F700B5977B /* ConstantsTrace.swift */,
F8E3A2AA23DA520B00E5E98A /* ConstantsWatch.swift */,
);
name = Constants;
path = xdrip/Constants;
@ -2112,6 +2125,7 @@
F8EA6C8021B723A80082976B /* Utilities */ = {
isa = PBXGroup;
children = (
F85FF38F25288860004E6FF1 /* HouseKeeping */,
F8AF11F624B1E6D700AE5BA2 /* Errors */,
F8C5EBE622F38F0E00563B5F /* Trace.swift */,
F821CF69229FC22D005C1E43 /* Network */,
@ -3005,6 +3019,7 @@
F8F9722723A5915900C3F17D /* CGMBluconTransmitter.swift in Sources */,
F8E3A2A923D906C200E5E98A /* WatchManager.swift in Sources */,
F8B3A80A227A3D11004BA588 /* TextsAlertTypeSettings.swift in Sources */,
F85FF39125288870004E6FF1 /* HouseKeeper.swift in Sources */,
F8F9721823A5915900C3F17D /* CGMBlueReaderTransmitter.swift in Sources */,
F8177029248ED6E600AA3600 /* LibreRawGlucoseOOPData.swift in Sources */,
F8252867243E50FE0067AF77 /* ConstantsLibre.swift in Sources */,
@ -3100,6 +3115,7 @@
F8A389EB233175A10010F405 /* SettingsViewM5StackSettingsViewModel.swift in Sources */,
F816E1242439DB63009EE65B /* DexcomG4+BluetoothPeripheral.swift in Sources */,
F8AF11F124B11FD400AE5BA2 /* LibreOOPWebError.swift in Sources */,
F8E5404C2522624800052CE5 /* ConstantsHousekeeping.swift in Sources */,
F8AF120124B9082000AE5BA2 /* Calibration+NightScout.swift in Sources */,
F808D2D2240329E80084B5DB /* Bubble+BluetoothPeripheral.swift in Sources */,
F8025C1321DA683400ECF0C0 /* Data.swift in Sources */,

View File

@ -0,0 +1,8 @@
import Foundation
enum ConstantsHousekeeping {
/// how long to keep bgReadings
static let retentionPeriodBgReadingsInDays = 90.0
}

View File

@ -136,5 +136,8 @@ enum ConstantsLog {
/// trace
static let categoryTraceSettingsViewModel = "TraceSettingsViewModel"
/// housekeeping
static let categoryHouseKeeper = "HouseKeeper "
}

View File

@ -105,7 +105,7 @@ class BgReadingsAccessor {
}
}
/// gets readings on a managedObjectContact that is created with concurrencyType: .privateQueueConcurrencyType
/// gets readings on a managedObjectContext that is created with concurrencyType: .privateQueueConcurrencyType
/// - returns:
/// readings sorted by timestamp, ascending (ie first is oldest)
/// - parameters:
@ -144,6 +144,20 @@ class BgReadingsAccessor {
}
// deletes readings, to be used for readings retrieved with getBgReadingsOnPrivateManagedObjectContext, to be called on background thread
func deleteReadingOnPrivateManagedObjectContext(bgReading: BgReading) {
privateManagedObjectContext.delete(bgReading)
// save changes to coredata
do {
try self.privateManagedObjectContext.save()
} catch {
trace("in deleteReadingOnPrivateManagedObjectContext, Unable to Save Changes of Private Managed Object Context, error.localizedDescription = %{public}@", log: self.log, category: ConstantsLog.categoryApplicationDataBgReadings, type: .error, error.localizedDescription)
}
}
// MARK: - private helper functions
/// returnvalue can be empty array

View File

@ -12,10 +12,18 @@ class CalibrationsAccessor {
/// CoreDataManager to use
private let coreDataManager:CoreDataManager
/// to be used when fetch request needs to run on a background thread
private let privateManagedObjectContext: NSManagedObjectContext
// MARK: - initializer
init(coreDataManager:CoreDataManager) {
self.coreDataManager = coreDataManager
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.persistentStoreCoordinator = coreDataManager.mainManagedObjectContext.persistentStoreCoordinator
}
// MARK: - functions
@ -80,6 +88,60 @@ class CalibrationsAccessor {
return calibrations
}
/// gets calibrations on a managedObjectContext that is created with concurrencyType: .privateQueueConcurrencyType
/// - returns:
/// calibrations sorted by timestamp, ascending (ie first is oldest)
/// - parameters:
/// - to : if specified, only return calibrations with timestamp smaller than fromDate (not equal to)
/// - from : if specified, only return calibrations with timestamp greater than fromDate (not equal to)
func getCalibrationsOnPrivateManagedObjectContext(from: Date?, to: Date?) -> [Calibration] {
let fetchRequest: NSFetchRequest<Calibration> = Calibration.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Calibration.timeStamp), ascending: true)]
// create predicate
if let from = from, to == nil {
let predicate = NSPredicate(format: "timeStamp > %@", NSDate(timeIntervalSince1970: from.timeIntervalSince1970))
fetchRequest.predicate = predicate
} else if let to = to, from == nil {
let predicate = NSPredicate(format: "timeStamp < %@", NSDate(timeIntervalSince1970: to.timeIntervalSince1970))
fetchRequest.predicate = predicate
} else if let to = to, let from = from {
let predicate = NSPredicate(format: "timeStamp < %@ AND timeStamp > %@", NSDate(timeIntervalSince1970: to.timeIntervalSince1970), NSDate(timeIntervalSince1970: from.timeIntervalSince1970))
fetchRequest.predicate = predicate
}
var calibrations = [Calibration]()
privateManagedObjectContext.performAndWait {
do {
// Execute Fetch Request
calibrations = try fetchRequest.execute()
} catch {
let fetchError = error as NSError
trace("in getCalibrationsOnPrivateManagedObjectContext, Unable to Execute Calibration Fetch Request : %{public}@", log: self.log, category: ConstantsLog.categoryApplicationDataCalibrations, type: .error, fetchError.localizedDescription)
}
}
return calibrations
}
// deletes Calibration, to be used for Calibration retrieved with getCalibrationOnPrivateManagedObjectContext, to be called on background thread
func deleteCalibrationOnPrivateManagedObjectContext(calibration: Calibration) {
privateManagedObjectContext.delete(calibration)
// save changes to coredata
do {
try self.privateManagedObjectContext.save()
} catch {
trace("in deleteCalibrationOnPrivateManagedObjectContext, Unable to Save Changes of Private Managed Object Context, error.localizedDescription = %{public}@", log: self.log, category: ConstantsLog.categoryApplicationDataCalibrations, type: .error, error.localizedDescription)
}
}
// MARK: - private helper functions
private func getFirstOrLastCalibration(withActivesensor sensor:Sensor, first:Bool) -> Calibration? {

View File

@ -0,0 +1,101 @@
import Foundation
import os
/// housekeeping like remove old readings from coredata
class HouseKeeper {
// MARK: - private properties
/// for logging
private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryHouseKeeper)
/// BgReadingsAccessor instance
private var bgReadingsAccessor:BgReadingsAccessor
/// CalibrationsAccessor instance
private var calibrationsAccessor:CalibrationsAccessor
// up to which date shall we delete old calibrations
private var toDate: Date
// MARK: - intializer
init(bgReadingsAccessor: BgReadingsAccessor, calibrationsAccessor: CalibrationsAccessor) {
self.bgReadingsAccessor = bgReadingsAccessor
self.calibrationsAccessor = calibrationsAccessor
self.toDate = Date(timeIntervalSinceNow: -ConstantsHousekeeping.retentionPeriodBgReadingsInDays*24*3600)
}
// MARK: - public functions
/// housekeeping activities to be done only once per app start up like delete old readings
public func doAppStartUpHouseKeeping() {
DispatchQueue.global().async {
// delete old readings
self.deleteOldReadings()
// delete old calibrations
self.deleteOldCalibrations()
}
}
// MARK: - private functions
/// deletes old readings. Readings older than ConstantsHousekeeping.retentionPeriodBgReadingsInDays will be deleted
private func deleteOldReadings() {
// get old readings to delete
let oldReadings = self.bgReadingsAccessor.getBgReadingsOnPrivateManagedObjectContext(from: nil, to: self.toDate)
if oldReadings.count > 0 {
trace("in deleteOldReadings, number of bg readings to delete : %{public}@, to date = %{public}@", log: self.log, category: ConstantsLog.categoryHouseKeeper, type: .info, oldReadings.count.description, self.toDate.description(with: .current))
}
// delete them
for oldReading in oldReadings {
bgReadingsAccessor.deleteReadingOnPrivateManagedObjectContext(bgReading: oldReading)
}
}
/// deletes old calibrations. Readings older than ConstantsHousekeeping.retentionPeriodBgReadingsInDays will be deleted
private func deleteOldCalibrations() {
// get old calibrations to delete
let oldCalibrations = self.calibrationsAccessor.getCalibrationsOnPrivateManagedObjectContext(from: nil, to: self.toDate)
if oldCalibrations.count > 0 {
trace("in deleteOldCalibrations, number of calibrations candidate for deletion : %{public}@, to date = %{public}@", log: self.log, category: ConstantsLog.categoryHouseKeeper, type: .info, oldCalibrations.count.description, self.toDate.description(with: .current))
}
// for each calibration that doesn't have any bg readings anymore, delete it
for oldCalibration in oldCalibrations {
if (oldCalibration.bgreadings.count > 0 ) {
trace("in deleteOldCalibrations, calibration with date %{public}@ will not be deleted beause there's still %{public}@ bgreadings", log: self.log, category: ConstantsLog.categoryHouseKeeper, type: .info, oldCalibration.timeStamp.description(with: .current), oldCalibration.bgreadings.count.description)
} else {
calibrationsAccessor.deleteCalibrationOnPrivateManagedObjectContext(calibration: oldCalibration)
}
}
}
}

View File

@ -206,6 +206,9 @@ final class RootViewController: UIViewController {
return dateFormatter
}()
/// housekeeper instance
private var houseKeeper: HouseKeeper?
/// current value of webOPEnabled, if nil then it means no cgmTransmitter connected yet , false is used as value
/// - used to detect changes in the value
///
@ -272,6 +275,9 @@ final class RootViewController: UIViewController {
self.setupApplicationData()
// housekeeper should be non nil here, kall housekeeper
self.houseKeeper?.doAppStartUpHouseKeeping()
// glucoseChartManager still needs the reference to coreDataManager
self.glucoseChartManager.coreDataManager = self.coreDataManager
@ -408,7 +414,7 @@ final class RootViewController: UIViewController {
}
}
// creates activeSensor, bgreadingsAccessor, calibrationsAccessor, NightScoutUploadManager, soundPlayer, dexcomShareUploadManager, nightScoutFollowManager, alertManager, healthKitManager, bgReadingSpeaker, bluetoothPeripheralManager, watchManager
// creates activeSensor, bgreadingsAccessor, calibrationsAccessor, NightScoutUploadManager, soundPlayer, dexcomShareUploadManager, nightScoutFollowManager, alertManager, healthKitManager, bgReadingSpeaker, bluetoothPeripheralManager, watchManager, housekeeper
private func setupApplicationData() {
// setup Trace
@ -430,6 +436,12 @@ final class RootViewController: UIViewController {
// instantiate calibrations
calibrationsAccessor = CalibrationsAccessor(coreDataManager: coreDataManager)
guard let calibrationsAccessor = calibrationsAccessor else {
fatalError("In setupApplicationData, failed to initialize calibrationsAccessor")
}
// instanstiate Housekeeper
houseKeeper = HouseKeeper(bgReadingsAccessor: bgReadingsAccessor, calibrationsAccessor: calibrationsAccessor)
// setup nightscout synchronizer
nightScoutUploadManager = NightScoutUploadManager(coreDataManager: coreDataManager, messageHandler: { (title:String, message:String) in