|
@@ -44,8 +44,9 @@ actor NativeHealthDataUploader { |
|
@@ -44,8 +44,9 @@ actor NativeHealthDataUploader { |
|
44
|
|
44
|
|
|
45
|
private let session: URLSession
|
45
|
private let session: URLSession
|
|
46
|
private let defaultUploadTimeWeek = 1
|
46
|
private let defaultUploadTimeWeek = 1
|
|
47
|
- private let firstUploadYear = 2
|
47
|
+ private let firstUploadYear = 1
|
|
48
|
private let uploadBatchSize = 500
|
48
|
private let uploadBatchSize = 500
|
|
|
|
49
|
+ private let sleepUploadLookback: TimeInterval = 24 * 60 * 60
|
|
49
|
private var lastUploadTriggerDates: [NativeHealthDataType: Date] = [:]
|
50
|
private var lastUploadTriggerDates: [NativeHealthDataType: Date] = [:]
|
|
50
|
|
51
|
|
|
51
|
init(session: URLSession = .shared) {
|
52
|
init(session: URLSession = .shared) {
|
|
@@ -371,6 +372,10 @@ private extension NativeHealthDataUploader { |
|
@@ -371,6 +372,10 @@ private extension NativeHealthDataUploader { |
|
371
|
}
|
372
|
}
|
|
372
|
DebugLogger.debugLog("[activityTarget] uploading, body=\(target.uploadBodyForDebug)")
|
373
|
DebugLogger.debugLog("[activityTarget] uploading, body=\(target.uploadBodyForDebug)")
|
|
373
|
try await uploadActivityTarget(target)
|
374
|
try await uploadActivityTarget(target)
|
|
|
|
375
|
+ let uploadedTypes = try await uploadActivityTargetHealthValues(target)
|
|
|
|
376
|
+ uploadedTypes.forEach {
|
|
|
|
377
|
+ notifyFlutterUpload(type: $0.type, timestamp: $0.timestamp)
|
|
|
|
378
|
+ }
|
|
374
|
DebugLogger.debugLog("[activityTarget] upload success")
|
379
|
DebugLogger.debugLog("[activityTarget] upload success")
|
|
375
|
return UploadTaskResult(success: true, uploadedCount: 1, errorMessage: nil)
|
380
|
return UploadTaskResult(success: true, uploadedCount: 1, errorMessage: nil)
|
|
376
|
} catch {
|
381
|
} catch {
|
|
@@ -394,10 +399,23 @@ private extension NativeHealthDataUploader { |
|
@@ -394,10 +399,23 @@ private extension NativeHealthDataUploader { |
|
394
|
|
399
|
|
|
395
|
for type in NativeHealthDataType.allCases where type != .unknown {
|
400
|
for type in NativeHealthDataType.allCases where type != .unknown {
|
|
396
|
let serverTime = serverTimeList.latestTimeInterval(for: type)
|
401
|
let serverTime = serverTimeList.latestTimeInterval(for: type)
|
|
397
|
- let finalTime = serverTime ?? twoYearsAgoMidnight
|
402
|
+ let finalTime: TimeInterval
|
|
|
|
403
|
+ let sourceDescription: String
|
|
|
|
404
|
+ if type == .sleep {
|
|
|
|
405
|
+ if let serverTime {
|
|
|
|
406
|
+ finalTime = max(0, serverTime - sleepUploadLookback)
|
|
|
|
407
|
+ sourceDescription = "server minus 24h"
|
|
|
|
408
|
+ } else {
|
|
|
|
409
|
+ finalTime = twoYearsAgoMidnight
|
|
|
|
410
|
+ sourceDescription = "missing; first sleep upload uses two years"
|
|
|
|
411
|
+ }
|
|
|
|
412
|
+ } else {
|
|
|
|
413
|
+ finalTime = serverTime ?? twoYearsAgoMidnight
|
|
|
|
414
|
+ sourceDescription = serverTime == nil ? "missing; first upload uses two years" : "server"
|
|
|
|
415
|
+ }
|
|
398
|
|
416
|
|
|
399
|
DebugLogger.debugLog(
|
417
|
DebugLogger.debugLog(
|
|
400
|
- "[\(type.debugName)] server start time=\(serverTime.map { Self.debugDateFormatter.string(from: Date(timeIntervalSince1970: $0)) } ?? "<missing; first upload uses two years>"), resolved=\(Self.debugDateFormatter.string(from: Date(timeIntervalSince1970: finalTime)))"
|
418
|
+ "[\(type.debugName)] server start time=\(serverTime.map { Self.debugDateFormatter.string(from: Date(timeIntervalSince1970: $0)) } ?? "<missing>"), source=\(sourceDescription), resolved=\(Self.debugDateFormatter.string(from: Date(timeIntervalSince1970: finalTime)))"
|
|
401
|
)
|
419
|
)
|
|
402
|
resultTimeList.append(
|
420
|
resultTimeList.append(
|
|
403
|
NativeHealthUploadTimeList.HealthUploadTime(
|
421
|
NativeHealthUploadTimeList.HealthUploadTime(
|
|
@@ -475,6 +493,27 @@ private extension NativeHealthDataUploader { |
|
@@ -475,6 +493,27 @@ private extension NativeHealthDataUploader { |
|
475
|
try await request(path: "/client/doublefeel/health/v2/activity_target/", method: "POST", body: target.uploadBody)
|
493
|
try await request(path: "/client/doublefeel/health/v2/activity_target/", method: "POST", body: target.uploadBody)
|
|
476
|
}
|
494
|
}
|
|
477
|
|
495
|
|
|
|
|
496
|
+ func uploadActivityTargetHealthValues(
|
|
|
|
497
|
+ _ target: NativeActivityTarget,
|
|
|
|
498
|
+ timestamp: TimeInterval = Date().timeIntervalSince1970
|
|
|
|
499
|
+ ) async throws -> [(type: NativeHealthDataType, timestamp: TimeInterval)] {
|
|
|
|
500
|
+ let values: [(NativeHealthDataType, Double?)] = [
|
|
|
|
501
|
+ (.activeEnergy, target.activeEnergyBurned),
|
|
|
|
502
|
+ (.exercise, target.appleExerciseTime),
|
|
|
|
503
|
+ (.stand, target.appleStandHours),
|
|
|
|
504
|
+ ]
|
|
|
|
505
|
+ let data = values.compactMap { item -> NativeHealthDataPoint? in
|
|
|
|
506
|
+ let (type, value) = item
|
|
|
|
507
|
+ guard let value else { return nil }
|
|
|
|
508
|
+ return NativeHealthDataPoint(dataType: type, time: timestamp, value: value)
|
|
|
|
509
|
+ }
|
|
|
|
510
|
+ guard !data.isEmpty else { return [] }
|
|
|
|
511
|
+
|
|
|
|
512
|
+ DebugLogger.debugLog("[activityTarget] uploading common values, count=\(data.count)")
|
|
|
|
513
|
+ try await uploadCommon(data)
|
|
|
|
514
|
+ return data.map { ($0.dataType, $0.time) }
|
|
|
|
515
|
+ }
|
|
|
|
516
|
+
|
|
478
|
func fetchLastUploadTime() async throws -> NativeHealthUploadTimeList {
|
517
|
func fetchLastUploadTime() async throws -> NativeHealthUploadTimeList {
|
|
479
|
let data = try await request(path: "/client/doublefeel/health/v2/data_upload/common/", method: "GET")
|
518
|
let data = try await request(path: "/client/doublefeel/health/v2/data_upload/common/", method: "GET")
|
|
480
|
return try NativeHealthUploadTimeList.decode(from: data)
|
519
|
return try NativeHealthUploadTimeList.decode(from: data)
|