Refactor activities table
This commit is contained in:
parent
03b4f84807
commit
8cf18f070f
@ -51,7 +51,7 @@
|
|||||||
E27BC68A2B5FC255003A8873 /* UnitStringsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6892B5FC255003A8873 /* UnitStringsTable.swift */; };
|
E27BC68A2B5FC255003A8873 /* UnitStringsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6892B5FC255003A8873 /* UnitStringsTable.swift */; };
|
||||||
E27BC68C2B5FC842003A8873 /* ActivitySamplesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC68B2B5FC842003A8873 /* ActivitySamplesView.swift */; };
|
E27BC68C2B5FC842003A8873 /* ActivitySamplesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC68B2B5FC842003A8873 /* ActivitySamplesView.swift */; };
|
||||||
E27BC68E2B5FCBD5003A8873 /* WorkoutEventsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC68D2B5FCBD5003A8873 /* WorkoutEventsTable.swift */; };
|
E27BC68E2B5FCBD5003A8873 /* WorkoutEventsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC68D2B5FCBD5003A8873 /* WorkoutEventsTable.swift */; };
|
||||||
E27BC6902B5FCEA4003A8873 /* WorkoutActivity+SQLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC68F2B5FCEA4003A8873 /* WorkoutActivity+SQLite.swift */; };
|
E27BC6902B5FCEA4003A8873 /* WorkoutActivitiesTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC68F2B5FCEA4003A8873 /* WorkoutActivitiesTable.swift */; };
|
||||||
E27BC6922B5FD488003A8873 /* HealthDatabase+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6912B5FD488003A8873 /* HealthDatabase+Mock.swift */; };
|
E27BC6922B5FD488003A8873 /* HealthDatabase+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6912B5FD488003A8873 /* HealthDatabase+Mock.swift */; };
|
||||||
E27BC6942B5FD587003A8873 /* Workout+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6932B5FD587003A8873 /* Workout+Mock.swift */; };
|
E27BC6942B5FD587003A8873 /* Workout+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6932B5FD587003A8873 /* Workout+Mock.swift */; };
|
||||||
E27BC6962B5FD61D003A8873 /* WorkoutEvent+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6952B5FD61D003A8873 /* WorkoutEvent+Mock.swift */; };
|
E27BC6962B5FD61D003A8873 /* WorkoutEvent+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27BC6952B5FD61D003A8873 /* WorkoutEvent+Mock.swift */; };
|
||||||
@ -65,6 +65,7 @@
|
|||||||
E2FDFF252B6C50A80080A7B3 /* ObjectsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF242B6C50A80080A7B3 /* ObjectsTable.swift */; };
|
E2FDFF252B6C50A80080A7B3 /* ObjectsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF242B6C50A80080A7B3 /* ObjectsTable.swift */; };
|
||||||
E2FDFF272B6C56C70080A7B3 /* DataProvenancesTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF262B6C56C70080A7B3 /* DataProvenancesTable.swift */; };
|
E2FDFF272B6C56C70080A7B3 /* DataProvenancesTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF262B6C56C70080A7B3 /* DataProvenancesTable.swift */; };
|
||||||
E2FDFF292B6D10D60080A7B3 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF282B6D10D60080A7B3 /* String+Extensions.swift */; };
|
E2FDFF292B6D10D60080A7B3 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF282B6D10D60080A7B3 /* String+Extensions.swift */; };
|
||||||
|
E2FDFF2B2B6D1E5E0080A7B3 /* HKWorkoutActivity+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF2A2B6D1E5E0080A7B3 /* HKWorkoutActivity+Comparable.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@ -109,7 +110,7 @@
|
|||||||
E27BC6892B5FC255003A8873 /* UnitStringsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitStringsTable.swift; sourceTree = "<group>"; };
|
E27BC6892B5FC255003A8873 /* UnitStringsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitStringsTable.swift; sourceTree = "<group>"; };
|
||||||
E27BC68B2B5FC842003A8873 /* ActivitySamplesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivitySamplesView.swift; sourceTree = "<group>"; };
|
E27BC68B2B5FC842003A8873 /* ActivitySamplesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivitySamplesView.swift; sourceTree = "<group>"; };
|
||||||
E27BC68D2B5FCBD5003A8873 /* WorkoutEventsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutEventsTable.swift; sourceTree = "<group>"; };
|
E27BC68D2B5FCBD5003A8873 /* WorkoutEventsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutEventsTable.swift; sourceTree = "<group>"; };
|
||||||
E27BC68F2B5FCEA4003A8873 /* WorkoutActivity+SQLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkoutActivity+SQLite.swift"; sourceTree = "<group>"; };
|
E27BC68F2B5FCEA4003A8873 /* WorkoutActivitiesTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutActivitiesTable.swift; sourceTree = "<group>"; };
|
||||||
E27BC6912B5FD488003A8873 /* HealthDatabase+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HealthDatabase+Mock.swift"; sourceTree = "<group>"; };
|
E27BC6912B5FD488003A8873 /* HealthDatabase+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HealthDatabase+Mock.swift"; sourceTree = "<group>"; };
|
||||||
E27BC6932B5FD587003A8873 /* Workout+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Workout+Mock.swift"; sourceTree = "<group>"; };
|
E27BC6932B5FD587003A8873 /* Workout+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Workout+Mock.swift"; sourceTree = "<group>"; };
|
||||||
E27BC6952B5FD61D003A8873 /* WorkoutEvent+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkoutEvent+Mock.swift"; sourceTree = "<group>"; };
|
E27BC6952B5FD61D003A8873 /* WorkoutEvent+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkoutEvent+Mock.swift"; sourceTree = "<group>"; };
|
||||||
@ -121,6 +122,7 @@
|
|||||||
E2FDFF242B6C50A80080A7B3 /* ObjectsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectsTable.swift; sourceTree = "<group>"; };
|
E2FDFF242B6C50A80080A7B3 /* ObjectsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectsTable.swift; sourceTree = "<group>"; };
|
||||||
E2FDFF262B6C56C70080A7B3 /* DataProvenancesTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvenancesTable.swift; sourceTree = "<group>"; };
|
E2FDFF262B6C56C70080A7B3 /* DataProvenancesTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvenancesTable.swift; sourceTree = "<group>"; };
|
||||||
E2FDFF282B6D10D60080A7B3 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
E2FDFF282B6D10D60080A7B3 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
|
E2FDFF2A2B6D1E5E0080A7B3 /* HKWorkoutActivity+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HKWorkoutActivity+Comparable.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -216,7 +218,7 @@
|
|||||||
8850029E2B5D1C7000E7D4DB /* MetadataValue+SQLite.swift */,
|
8850029E2B5D1C7000E7D4DB /* MetadataValue+SQLite.swift */,
|
||||||
E27BC6852B5FBF0B003A8873 /* Sample.swift */,
|
E27BC6852B5FBF0B003A8873 /* Sample.swift */,
|
||||||
8850027E2B5C36A700E7D4DB /* Workout.swift */,
|
8850027E2B5C36A700E7D4DB /* Workout.swift */,
|
||||||
E27BC68F2B5FCEA4003A8873 /* WorkoutActivity+SQLite.swift */,
|
E2FDFF2A2B6D1E5E0080A7B3 /* HKWorkoutActivity+Comparable.swift */,
|
||||||
885002842B5C7AD600E7D4DB /* HKWorkoutEvent+Identifiable.swift */,
|
885002842B5C7AD600E7D4DB /* HKWorkoutEvent+Identifiable.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
@ -257,6 +259,7 @@
|
|||||||
E27BC6872B5FC220003A8873 /* QuantitySamplesTable.swift */,
|
E27BC6872B5FC220003A8873 /* QuantitySamplesTable.swift */,
|
||||||
E201EC7C2B62930E005B83D3 /* SamplesTable.swift */,
|
E201EC7C2B62930E005B83D3 /* SamplesTable.swift */,
|
||||||
E27BC6892B5FC255003A8873 /* UnitStringsTable.swift */,
|
E27BC6892B5FC255003A8873 /* UnitStringsTable.swift */,
|
||||||
|
E27BC68F2B5FCEA4003A8873 /* WorkoutActivitiesTable.swift */,
|
||||||
E27BC68D2B5FCBD5003A8873 /* WorkoutEventsTable.swift */,
|
E27BC68D2B5FCBD5003A8873 /* WorkoutEventsTable.swift */,
|
||||||
8850027A2B5C35BF00E7D4DB /* WorkoutsTable.swift */,
|
8850027A2B5C35BF00E7D4DB /* WorkoutsTable.swift */,
|
||||||
);
|
);
|
||||||
@ -365,7 +368,7 @@
|
|||||||
885002912B5D0F9200E7D4DB /* HKWorkoutEventType+Extensions.swift in Sources */,
|
885002912B5D0F9200E7D4DB /* HKWorkoutEventType+Extensions.swift in Sources */,
|
||||||
E2FDFF182B6BB61D0080A7B3 /* HKHealthStoreInterface.swift in Sources */,
|
E2FDFF182B6BB61D0080A7B3 /* HKHealthStoreInterface.swift in Sources */,
|
||||||
E27BC6922B5FD488003A8873 /* HealthDatabase+Mock.swift in Sources */,
|
E27BC6922B5FD488003A8873 /* HealthDatabase+Mock.swift in Sources */,
|
||||||
E27BC6902B5FCEA4003A8873 /* WorkoutActivity+SQLite.swift in Sources */,
|
E27BC6902B5FCEA4003A8873 /* WorkoutActivitiesTable.swift in Sources */,
|
||||||
E201EC772B626FC1005B83D3 /* MetadataKey.swift in Sources */,
|
E201EC772B626FC1005B83D3 /* MetadataKey.swift in Sources */,
|
||||||
8850027F2B5C36A700E7D4DB /* Workout.swift in Sources */,
|
8850027F2B5C36A700E7D4DB /* Workout.swift in Sources */,
|
||||||
E2FDFF222B6BE35B0080A7B3 /* EventMetadata.pb.swift in Sources */,
|
E2FDFF222B6BE35B0080A7B3 /* EventMetadata.pb.swift in Sources */,
|
||||||
@ -373,6 +376,7 @@
|
|||||||
E27BC6842B5E76A4003A8873 /* Location+Mock.swift in Sources */,
|
E27BC6842B5E76A4003A8873 /* Location+Mock.swift in Sources */,
|
||||||
885002932B5D129300E7D4DB /* ActivityDetailView.swift in Sources */,
|
885002932B5D129300E7D4DB /* ActivityDetailView.swift in Sources */,
|
||||||
8850029F2B5D1C7000E7D4DB /* MetadataValue+SQLite.swift in Sources */,
|
8850029F2B5D1C7000E7D4DB /* MetadataValue+SQLite.swift in Sources */,
|
||||||
|
E2FDFF2B2B6D1E5E0080A7B3 /* HKWorkoutActivity+Comparable.swift in Sources */,
|
||||||
E27BC6882B5FC220003A8873 /* QuantitySamplesTable.swift in Sources */,
|
E27BC6882B5FC220003A8873 /* QuantitySamplesTable.swift in Sources */,
|
||||||
E201EC7D2B62930E005B83D3 /* SamplesTable.swift in Sources */,
|
E201EC7D2B62930E005B83D3 /* SamplesTable.swift in Sources */,
|
||||||
8850027B2B5C35BF00E7D4DB /* WorkoutsTable.swift in Sources */,
|
8850027B2B5C35BF00E7D4DB /* WorkoutsTable.swift in Sources */,
|
||||||
|
19
HealthImport/Model/HKWorkoutActivity+Comparable.swift
Normal file
19
HealthImport/Model/HKWorkoutActivity+Comparable.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Foundation
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
|
extension HKWorkoutActivity: Comparable {
|
||||||
|
|
||||||
|
public static func < (lhs: HKWorkoutActivity, rhs: HKWorkoutActivity) -> Bool {
|
||||||
|
lhs.startDate < rhs.startDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HKWorkoutActivity {
|
||||||
|
|
||||||
|
var externalUUID: UUID? {
|
||||||
|
guard let string = metadata?[HKMetadataKeyExternalUUID] as? String else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return UUID(uuidString: string)
|
||||||
|
}
|
||||||
|
}
|
119
HealthImport/Model/Tables/WorkoutActivitiesTable.swift
Normal file
119
HealthImport/Model/Tables/WorkoutActivitiesTable.swift
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import Foundation
|
||||||
|
import SQLite
|
||||||
|
import HealthKit
|
||||||
|
|
||||||
|
struct WorkoutActivitiesTable {
|
||||||
|
|
||||||
|
private let database: Connection
|
||||||
|
|
||||||
|
init(database: Connection) {
|
||||||
|
self.database = database
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = Table("workout_activities")
|
||||||
|
|
||||||
|
let rowId = Expression<Int>("ROWID")
|
||||||
|
|
||||||
|
let uuid = Expression<Data>("uuid")
|
||||||
|
|
||||||
|
let ownerId = Expression<Int>("owner_id")
|
||||||
|
|
||||||
|
let isPrimaryActivity = Expression<Bool>("is_primary_activity")
|
||||||
|
|
||||||
|
let activityType = Expression<Int>("activity_type")
|
||||||
|
|
||||||
|
let locationType = Expression<Int>("location_type")
|
||||||
|
|
||||||
|
let swimmingLocationType = Expression<Int>("swimming_location_type")
|
||||||
|
|
||||||
|
let lapLength = Expression<Data?>("lap_length")
|
||||||
|
|
||||||
|
let startDate = Expression<Double>("start_date")
|
||||||
|
|
||||||
|
let endDate = Expression<Double>("end_date")
|
||||||
|
|
||||||
|
let duration = Expression<Double>("duration")
|
||||||
|
|
||||||
|
let metadata = Expression<Data?>("metadata")
|
||||||
|
|
||||||
|
func activities() throws -> [HKWorkoutActivity] {
|
||||||
|
try database.prepare(table).map(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func activity(from row: Row) throws -> HKWorkoutActivity {
|
||||||
|
let configuration = HKWorkoutConfiguration()
|
||||||
|
configuration.lapLength = try row[lapLength].map(WorkoutActivitiesTable.lapLength)
|
||||||
|
configuration.activityType = .init(rawValue: UInt(row[activityType]))!
|
||||||
|
configuration.locationType = .init(rawValue: row[locationType])!
|
||||||
|
configuration.swimmingLocationType = .init(rawValue: row[swimmingLocationType])!
|
||||||
|
|
||||||
|
let start = Date(timeIntervalSinceReferenceDate: row[startDate])
|
||||||
|
let end = Date(timeIntervalSinceReferenceDate: row[endDate])
|
||||||
|
let uuid = row[uuid].uuidString
|
||||||
|
|
||||||
|
var metadata: [String : Any] = [ : ]
|
||||||
|
metadata[HKMetadataKeyExternalUUID] = uuid
|
||||||
|
|
||||||
|
// duration: row[columnDuration]
|
||||||
|
// isPrimaryActivity: row[columnIsPrimaryActivity]
|
||||||
|
|
||||||
|
// metadata: row[columnMetadata]
|
||||||
|
// TODO: Decode activity metadata
|
||||||
|
return .init(
|
||||||
|
workoutConfiguration: configuration,
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
metadata: metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func activities(for workoutId: Int) throws -> [HKWorkoutActivity] {
|
||||||
|
try database.prepare(table.filter(ownerId == workoutId)).map(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func create(referencing workouts: WorkoutsTable) throws {
|
||||||
|
try database.execute("CREATE TABLE workout_activities (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, uuid BLOB UNIQUE NOT NULL, owner_id INTEGER NOT NULL REFERENCES workouts(data_id) ON DELETE CASCADE, is_primary_activity INTEGER NOT NULL, activity_type INTEGER NOT NULL, location_type INTEGER NOT NULL, swimming_location_type INTEGER NOT NULL, lap_length BLOB, start_date REAL NOT NULL, end_date REAL NOT NULL, duration REAL NOT NULL, metadata BLOB)")
|
||||||
|
/*
|
||||||
|
try database.run(table.create { t in
|
||||||
|
t.column(rowId, primaryKey: .autoincrement)
|
||||||
|
t.column(uuid, unique: true)
|
||||||
|
t.column(ownerId, references: workouts.table, workouts.dataId) // TODO: ON DELETE CASCADE
|
||||||
|
t.column(isPrimaryActivity)
|
||||||
|
t.column(activityType)
|
||||||
|
t.column(locationType)
|
||||||
|
t.column(swimmingLocationType)
|
||||||
|
t.column(lapLength)
|
||||||
|
t.column(startDate)
|
||||||
|
t.column(endDate)
|
||||||
|
t.column(duration)
|
||||||
|
t.column(metadata)
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert(_ element: HKWorkoutActivity, isPrimaryActivity: Bool, dataId: Int) throws {
|
||||||
|
try database.run(table.insert(
|
||||||
|
uuid <- (element.externalUUID ?? element.uuid).uuidString.data(using: .utf8)!,
|
||||||
|
ownerId <- dataId,
|
||||||
|
self.isPrimaryActivity <- isPrimaryActivity, // Seems to always be 1
|
||||||
|
activityType <- Int(element.workoutConfiguration.activityType.rawValue),
|
||||||
|
locationType <- element.workoutConfiguration.locationType.rawValue,
|
||||||
|
swimmingLocationType <- element.workoutConfiguration.swimmingLocationType.rawValue,
|
||||||
|
lapLength <- try WorkoutActivitiesTable.lapLengthData(lapLength: element.workoutConfiguration.lapLength),
|
||||||
|
startDate <- element.startDate.timeIntervalSinceReferenceDate,
|
||||||
|
endDate <- element.endDate?.timeIntervalSinceReferenceDate ?? element.startDate.addingTimeInterval(element.duration).timeIntervalSinceReferenceDate,
|
||||||
|
duration <- element.duration,
|
||||||
|
metadata <- nil)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension WorkoutActivitiesTable {
|
||||||
|
|
||||||
|
static func lapLengthData(lapLength: HKQuantity?) throws -> Data? {
|
||||||
|
try lapLength.map { try NSKeyedArchiver.archivedData(withRootObject: $0, requiringSecureCoding: false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
static func lapLength(from data: Data) throws -> HKQuantity? {
|
||||||
|
try NSKeyedUnarchiver.unarchivedObject(ofClass: HKQuantity.self, from: data)
|
||||||
|
}
|
||||||
|
}
|
@ -8,9 +8,12 @@ struct WorkoutsTable {
|
|||||||
|
|
||||||
let events: WorkoutEventsTable
|
let events: WorkoutEventsTable
|
||||||
|
|
||||||
|
let activities: WorkoutActivitiesTable
|
||||||
|
|
||||||
init(database: Connection) {
|
init(database: Connection) {
|
||||||
self.database = database
|
self.database = database
|
||||||
self.events = .init(database: database)
|
self.events = .init(database: database)
|
||||||
|
self.activities = .init(database: database)
|
||||||
}
|
}
|
||||||
|
|
||||||
let table = Table("workouts")
|
let table = Table("workouts")
|
||||||
@ -40,7 +43,7 @@ struct WorkoutsTable {
|
|||||||
let id = row[dataId]
|
let id = row[dataId]
|
||||||
|
|
||||||
let events = try events.events(for: id, in: database)
|
let events = try events.events(for: id, in: database)
|
||||||
let activities = try WorkoutActivityTable.activities(for: id, in: database)
|
let activities = try activities.activities(for: id)
|
||||||
let metadata = try Metadata.metadata(for: id, in: database, keyMap: metadataKeys)
|
let metadata = try Metadata.metadata(for: id, in: database, keyMap: metadataKeys)
|
||||||
return .init(
|
return .init(
|
||||||
id: id,
|
id: id,
|
||||||
@ -70,6 +73,7 @@ struct WorkoutsTable {
|
|||||||
func createAll() throws {
|
func createAll() throws {
|
||||||
try create()
|
try create()
|
||||||
try events.create(referencing: self)
|
try events.create(referencing: self)
|
||||||
|
try activities.create(referencing: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func insert(_ element: Workout) throws {
|
func insert(_ element: Workout) throws {
|
||||||
@ -86,7 +90,7 @@ struct WorkoutsTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for activity in element.activities {
|
for activity in element.activities {
|
||||||
try WorkoutActivityTable.insert(activity, isPrimaryActivity: true, dataId: dataId, in: database)
|
try activities.insert(activity, isPrimaryActivity: true, dataId: dataId)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key, value) in element.metadata {
|
for (key, value) in element.metadata {
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import SQLite
|
|
||||||
import HealthKit
|
|
||||||
|
|
||||||
extension HKWorkoutActivity: Comparable {
|
|
||||||
|
|
||||||
public static func < (lhs: HKWorkoutActivity, rhs: HKWorkoutActivity) -> Bool {
|
|
||||||
lhs.startDate < rhs.startDate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WorkoutActivityTable {
|
|
||||||
|
|
||||||
private static let table = Table("workout_activities")
|
|
||||||
|
|
||||||
private static let columnId = Expression<Int>("ROWID")
|
|
||||||
|
|
||||||
private static let columnUUID = Expression<Data>("uuid")
|
|
||||||
|
|
||||||
private static let columnOwnerId = Expression<Int>("owner_id")
|
|
||||||
|
|
||||||
private static let columnIsPrimaryActivity = Expression<Bool>("is_primary_activity")
|
|
||||||
|
|
||||||
private static let columnActivityType = Expression<Int>("activity_type")
|
|
||||||
|
|
||||||
private static let columnLocationType = Expression<Int>("location_type")
|
|
||||||
|
|
||||||
private static let columnSwimmingLocationType = Expression<Int>("swimming_location_type")
|
|
||||||
|
|
||||||
private static let columnLapLength = Expression<Data?>("lap_length")
|
|
||||||
|
|
||||||
private static let columnStartDate = Expression<Double>("start_date")
|
|
||||||
|
|
||||||
private static let columnEndDate = Expression<Double>("end_date")
|
|
||||||
|
|
||||||
private static let columnDuration = Expression<Double>("duration")
|
|
||||||
|
|
||||||
private static let columnMetadata = Expression<Data?>("metadata")
|
|
||||||
|
|
||||||
private static func readAll(in database: Connection) throws -> [HKWorkoutActivity] {
|
|
||||||
try database.prepare(table).map(activity)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func activity(from row: Row) throws -> HKWorkoutActivity {
|
|
||||||
let configuration = HKWorkoutConfiguration()
|
|
||||||
configuration.lapLength = try row[columnLapLength].map(lapLength)
|
|
||||||
configuration.activityType = .init(rawValue: UInt(row[columnActivityType]))!
|
|
||||||
configuration.locationType = .init(rawValue: row[columnLocationType])!
|
|
||||||
configuration.swimmingLocationType = .init(rawValue: row[columnSwimmingLocationType])!
|
|
||||||
|
|
||||||
let start = Date(timeIntervalSinceReferenceDate: row[columnStartDate])
|
|
||||||
let end = Date(timeIntervalSinceReferenceDate: row[columnEndDate])
|
|
||||||
let uuid = row[columnUUID].uuidString
|
|
||||||
|
|
||||||
var metadata: [String : Any] = [ : ]
|
|
||||||
metadata[HKMetadataKeyExternalUUID] = uuid
|
|
||||||
|
|
||||||
// duration: row[columnDuration]
|
|
||||||
// isPrimaryActivity: row[columnIsPrimaryActivity]
|
|
||||||
|
|
||||||
// metadata: row[columnMetadata]
|
|
||||||
#warning("Decode metadata")
|
|
||||||
return .init(
|
|
||||||
workoutConfiguration: configuration,
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
metadata: metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func activities(for workoutId: Int, in database: Connection) throws -> [HKWorkoutActivity] {
|
|
||||||
try database.prepare(table.filter(columnOwnerId == workoutId)).map(activity)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func create(in database: Connection) throws {
|
|
||||||
//try database.execute("CREATE TABLE workout_activities (ROWID INTEGER PRIMARY KEY AUTOINCREMENT, uuid BLOB UNIQUE NOT NULL, owner_id INTEGER NOT NULL REFERENCES workouts(data_id) ON DELETE CASCADE, is_primary_activity INTEGER NOT NULL, activity_type INTEGER NOT NULL, location_type INTEGER NOT NULL, swimming_location_type INTEGER NOT NULL, lap_length BLOB, start_date REAL NOT NULL, end_date REAL NOT NULL, duration REAL NOT NULL, metadata BLOB)")
|
|
||||||
try database.run(table.create { t in
|
|
||||||
t.column(columnId, primaryKey: .autoincrement)
|
|
||||||
t.column(columnUUID)
|
|
||||||
t.column(columnOwnerId, references: Table("workouts"), Expression<Int>("data_id"))
|
|
||||||
t.column(columnIsPrimaryActivity)
|
|
||||||
t.column(columnActivityType)
|
|
||||||
t.column(columnLocationType)
|
|
||||||
t.column(columnSwimmingLocationType)
|
|
||||||
t.column(columnLapLength)
|
|
||||||
t.column(columnStartDate)
|
|
||||||
t.column(columnEndDate)
|
|
||||||
t.column(columnDuration)
|
|
||||||
t.column(columnMetadata)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static func insert(_ element: HKWorkoutActivity, isPrimaryActivity: Bool, dataId: Int, in database: Connection) throws {
|
|
||||||
try database.run(table.insert(
|
|
||||||
columnUUID <- (element.externalUUID ?? element.uuid).uuidString.data(using: .utf8)!,
|
|
||||||
columnOwnerId <- dataId,
|
|
||||||
columnIsPrimaryActivity <- isPrimaryActivity, // Seems to always be 1
|
|
||||||
columnActivityType <- Int(element.workoutConfiguration.activityType.rawValue),
|
|
||||||
columnLocationType <- element.workoutConfiguration.locationType.rawValue,
|
|
||||||
columnSwimmingLocationType <- element.workoutConfiguration.swimmingLocationType.rawValue,
|
|
||||||
columnLapLength <- try lapLengthData(lapLength: element.workoutConfiguration.lapLength),
|
|
||||||
columnStartDate <- element.startDate.timeIntervalSinceReferenceDate,
|
|
||||||
columnEndDate <- element.endDate?.timeIntervalSinceReferenceDate ?? element.startDate.addingTimeInterval(element.duration).timeIntervalSinceReferenceDate,
|
|
||||||
columnDuration <- element.duration)
|
|
||||||
//columnMetadata <- element.metadata)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension HKWorkoutActivity {
|
|
||||||
|
|
||||||
var externalUUID: UUID? {
|
|
||||||
guard let string = metadata?[HKMetadataKeyExternalUUID] as? String else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return UUID(uuidString: string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension WorkoutActivityTable {
|
|
||||||
|
|
||||||
static func lapLengthData(lapLength: HKQuantity?) throws -> Data? {
|
|
||||||
try lapLength.map { try NSKeyedArchiver.archivedData(withRootObject: $0, requiringSecureCoding: false) }
|
|
||||||
}
|
|
||||||
|
|
||||||
static func lapLength(from data: Data) throws -> HKQuantity? {
|
|
||||||
try NSKeyedUnarchiver.unarchivedObject(ofClass: HKQuantity.self, from: data)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user