Extract metadata
This commit is contained in:
57
HealthImport/Database Entries/DBMetadata.swift
Normal file
57
HealthImport/Database Entries/DBMetadata.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
struct DBMetadata {
|
||||
|
||||
private static let table = Table("metadata_values")
|
||||
|
||||
private static let rowKeyId = Expression<Int?>("key_id")
|
||||
|
||||
private static let rowObjectId = Expression<Int?>("object_id")
|
||||
|
||||
private static let rowValueType = Expression<Int>("value_type")
|
||||
|
||||
private static let rowStringValue = Expression<String?>("string_value")
|
||||
|
||||
private static let rowNumericalValue = Expression<Double?>("numerical_value")
|
||||
|
||||
private static let rowDateValue = Expression<Double?>("date_value")
|
||||
|
||||
private static let rowDataValue = Expression<Data?>("data_value")
|
||||
|
||||
static func readAll(in database: Connection) throws -> [Self] {
|
||||
try database.prepare(table).map(Self.init)
|
||||
}
|
||||
|
||||
static func metadata(for workoutId: Int, in database: Connection) throws -> [Self] {
|
||||
try database.prepare(table.filter(rowObjectId == workoutId)).map(Self.init)
|
||||
}
|
||||
|
||||
let keyId: Int?
|
||||
|
||||
let objectId: Int?
|
||||
|
||||
let valueType: Int
|
||||
|
||||
let string: String?
|
||||
|
||||
let number: Double?
|
||||
|
||||
let date: Double?
|
||||
|
||||
let data: Data?
|
||||
}
|
||||
|
||||
extension DBMetadata {
|
||||
|
||||
init(row: Row) {
|
||||
self.keyId = row[DBMetadata.rowKeyId]
|
||||
self.objectId = row[DBMetadata.rowObjectId]
|
||||
self.valueType = row[DBMetadata.rowValueType]
|
||||
self.string = row[DBMetadata.rowStringValue]
|
||||
self.number = row[DBMetadata.rowNumericalValue]
|
||||
self.date = row[DBMetadata.rowDateValue]
|
||||
self.data = row[DBMetadata.rowDataValue]
|
||||
}
|
||||
}
|
||||
|
21
HealthImport/Database Entries/DBMetadataKey.swift
Normal file
21
HealthImport/Database Entries/DBMetadataKey.swift
Normal file
@@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
import SQLite
|
||||
|
||||
struct DBMetadataKey {
|
||||
|
||||
private static let table = Table("metadata_keys")
|
||||
|
||||
private static let rowId = Expression<Int>("ROWID")
|
||||
|
||||
private static let rowKey = Expression<String>("key")
|
||||
|
||||
static func key(for keyId: Int, in database: Connection) throws -> String {
|
||||
try database.prepare(table.filter(rowId == keyId).limit(1)).map { $0[rowKey] }.first!
|
||||
}
|
||||
|
||||
static func readAll(in database: Connection) throws -> [ Int: String] {
|
||||
try database.prepare(table).reduce(into: [:]) { dict, row in
|
||||
dict[row[rowId]] = row[rowKey]
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,10 +21,15 @@ final class HealthDatabase: ObservableObject {
|
||||
func readAllWorkouts() {
|
||||
do {
|
||||
let dbWorkouts = try DBWorkout.readAll(in: database)
|
||||
let metadataKeys = try DBMetadataKey.readAll(in: database)
|
||||
let workouts = try dbWorkouts.map { entry in
|
||||
let events = try DBWorkoutEvent.events(for: entry.dataId, in: database)
|
||||
let activities = try DBWorkoutActivity.activities(for: entry.dataId, in: database)
|
||||
return Workout(entry: entry, events: events, activities: activities)
|
||||
let metadata: [String : MetadataValue] = try DBMetadata.metadata(for: entry.dataId, in: database).reduce(into: [:]) { dict, item in
|
||||
let key = metadataKeys[item.keyId!]!
|
||||
dict[key] = MetadataValue(entry: item)
|
||||
}
|
||||
return Workout(entry: entry, events: events, activities: activities, metadata: metadata)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
65
HealthImport/Model/MetadataValue.swift
Normal file
65
HealthImport/Model/MetadataValue.swift
Normal file
@@ -0,0 +1,65 @@
|
||||
import Foundation
|
||||
|
||||
enum MetadataValue {
|
||||
|
||||
case string(value: String)
|
||||
case number(value: Double)
|
||||
case date(value: Date)
|
||||
case numerical(value: Double, unit: String)
|
||||
case data(value: Data)
|
||||
|
||||
enum ValueType: Int {
|
||||
|
||||
/// Uses only the `string_value` column
|
||||
case string = 0
|
||||
|
||||
/// Uses only the `numerical_value` column
|
||||
case number = 1
|
||||
|
||||
/// Uses only the `date_value` column
|
||||
case date = 2
|
||||
|
||||
/// Uses the `string_value` column for the unit, and the `numerical_value` column for the number
|
||||
case numerical = 3
|
||||
|
||||
/// Uses only the `data_value` column
|
||||
case data = 4
|
||||
}
|
||||
}
|
||||
|
||||
extension MetadataValue {
|
||||
|
||||
init(entry: DBMetadata) {
|
||||
let valueType = ValueType(rawValue: entry.valueType)!
|
||||
switch valueType {
|
||||
case .string:
|
||||
self = .string(value: entry.string!)
|
||||
case .number:
|
||||
self = .number(value: entry.number!)
|
||||
case .date:
|
||||
self = .date(value: .init(timeIntervalSinceReferenceDate: entry.date!))
|
||||
case .numerical:
|
||||
self = .numerical(value: entry.number!, unit: entry.string!)
|
||||
case .data:
|
||||
self = .data(value: entry.data!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MetadataValue: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .string(let value):
|
||||
return value
|
||||
case .number(let value):
|
||||
return "\(value)"
|
||||
case .date(let value):
|
||||
return value.timeAndDateText
|
||||
case .numerical(let value, let unit):
|
||||
return String(format: "%.3f %s", value, unit)
|
||||
case .data(let value):
|
||||
return value.description
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
import Collections
|
||||
|
||||
private let df: DateFormatter = {
|
||||
let df = DateFormatter()
|
||||
@@ -28,6 +28,8 @@ struct Workout {
|
||||
|
||||
let activities: [WorkoutActivity]
|
||||
|
||||
let metadata: OrderedDictionary<String, MetadataValue>
|
||||
|
||||
var firstActivityDate: Date? {
|
||||
activities.map { $0.startDate }.min()
|
||||
}
|
||||
@@ -51,7 +53,7 @@ struct Workout {
|
||||
activities.first?.activityType.description ?? "Unknown activity"
|
||||
}
|
||||
|
||||
init(id: Int, totalDistance: Double? = nil, goalType: Int? = nil, goal: Double? = nil, condenserVersion: Int? = nil, condenserDate: Date? = nil, events: [WorkoutEvent] = [], activities: [WorkoutActivity] = []) {
|
||||
init(id: Int, totalDistance: Double? = nil, goalType: Int? = nil, goal: Double? = nil, condenserVersion: Int? = nil, condenserDate: Date? = nil, events: [WorkoutEvent] = [], activities: [WorkoutActivity] = [], metadata: [String : MetadataValue] = [:]) {
|
||||
self.id = id
|
||||
self.totalDistance = totalDistance
|
||||
self.goalType = goalType
|
||||
@@ -60,12 +62,13 @@ struct Workout {
|
||||
self.condenserDate = condenserDate
|
||||
self.events = events
|
||||
self.activities = activities
|
||||
self.metadata = .init(uniqueKeys: metadata.keys, values: metadata.values)
|
||||
}
|
||||
}
|
||||
|
||||
extension Workout {
|
||||
|
||||
init(entry: DBWorkout, events: [DBWorkoutEvent], activities: [DBWorkoutActivity]) {
|
||||
init(entry: DBWorkout, events: [DBWorkoutEvent], activities: [DBWorkoutActivity], metadata: [String : MetadataValue]) {
|
||||
self.id = entry.dataId
|
||||
self.totalDistance = entry.totalDistance
|
||||
self.goalType = entry.goalType
|
||||
@@ -74,6 +77,7 @@ extension Workout {
|
||||
self.condenserDate = entry.condenserDate.map { Date(timeIntervalSinceReferenceDate: $0) }
|
||||
self.events = events.map(WorkoutEvent.init)
|
||||
self.activities = activities.map(WorkoutActivity.init)
|
||||
self.metadata = .init(uniqueKeys: metadata.keys, values: metadata.values)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import Collections
|
||||
|
||||
struct WorkoutDetailView: View {
|
||||
|
||||
@@ -37,6 +38,11 @@ struct WorkoutDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !workout.metadata.isEmpty {
|
||||
ForEach(workout.metadata.elements, id:\.key) { (key, value) in
|
||||
DetailRow(key, value: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(workout.typeString)
|
||||
.navigationDestination(for: WorkoutActivity.self) { activity in
|
||||
@@ -81,3 +87,8 @@ struct WorkoutDetailView: View {
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
extension String: Identifiable {
|
||||
|
||||
public var id: Self { self }
|
||||
}
|
||||
|
Reference in New Issue
Block a user