Showing
5 changed files
with
168 additions
and
24 deletions
| @@ -2110,7 +2110,7 @@ class HealthRawStressLocalStore { | @@ -2110,7 +2110,7 @@ class HealthRawStressLocalStore { | ||
| 2110 | final db = await factory.openDatabase( | 2110 | final db = await factory.openDatabase( |
| 2111 | path, | 2111 | path, |
| 2112 | options: OpenDatabaseOptions( | 2112 | options: OpenDatabaseOptions( |
| 2113 | - version: 8, | 2113 | + version: 9, |
| 2114 | onCreate: (db, version) async { | 2114 | onCreate: (db, version) async { |
| 2115 | await _createTables(db); | 2115 | await _createTables(db); |
| 2116 | }, | 2116 | }, |
| @@ -2137,6 +2137,9 @@ class HealthRawStressLocalStore { | @@ -2137,6 +2137,9 @@ class HealthRawStressLocalStore { | ||
| 2137 | if (oldVersion < 8) { | 2137 | if (oldVersion < 8) { |
| 2138 | await _addUpdateTimeColumns(db); | 2138 | await _addUpdateTimeColumns(db); |
| 2139 | } | 2139 | } |
| 2140 | + if (oldVersion < 9) { | ||
| 2141 | + await _addDateKeyColumns(db); | ||
| 2142 | + } | ||
| 2140 | }, | 2143 | }, |
| 2141 | ), | 2144 | ), |
| 2142 | ); | 2145 | ); |
| @@ -2148,6 +2151,7 @@ class HealthRawStressLocalStore { | @@ -2148,6 +2151,7 @@ class HealthRawStressLocalStore { | ||
| 2148 | await db.execute(''' | 2151 | await db.execute(''' |
| 2149 | CREATE TABLE IF NOT EXISTS $hrvResultsTable ( | 2152 | CREATE TABLE IF NOT EXISTS $hrvResultsTable ( |
| 2150 | raw_end_time INTEGER PRIMARY KEY, | 2153 | raw_end_time INTEGER PRIMARY KEY, |
| 2154 | + date_key INTEGER NOT NULL DEFAULT 0, | ||
| 2151 | user_id INTEGER NOT NULL, | 2155 | user_id INTEGER NOT NULL, |
| 2152 | raw_hrv REAL NOT NULL DEFAULT 0, | 2156 | raw_hrv REAL NOT NULL DEFAULT 0, |
| 2153 | result REAL NOT NULL, | 2157 | result REAL NOT NULL, |
| @@ -2169,6 +2173,7 @@ CREATE TABLE IF NOT EXISTS $hrvResultsTable ( | @@ -2169,6 +2173,7 @@ CREATE TABLE IF NOT EXISTS $hrvResultsTable ( | ||
| 2169 | await db.execute(''' | 2173 | await db.execute(''' |
| 2170 | CREATE TABLE IF NOT EXISTS $realtimeStressResultsTable ( | 2174 | CREATE TABLE IF NOT EXISTS $realtimeStressResultsTable ( |
| 2171 | raw_end_time INTEGER PRIMARY KEY, | 2175 | raw_end_time INTEGER PRIMARY KEY, |
| 2176 | + date_key INTEGER NOT NULL DEFAULT 0, | ||
| 2172 | user_id INTEGER NOT NULL, | 2177 | user_id INTEGER NOT NULL, |
| 2173 | raw_hr REAL NOT NULL DEFAULT 0, | 2178 | raw_hr REAL NOT NULL DEFAULT 0, |
| 2174 | result REAL NOT NULL, | 2179 | result REAL NOT NULL, |
| @@ -2190,6 +2195,7 @@ CREATE TABLE IF NOT EXISTS $realtimeStressResultsTable ( | @@ -2190,6 +2195,7 @@ CREATE TABLE IF NOT EXISTS $realtimeStressResultsTable ( | ||
| 2190 | await db.execute(''' | 2195 | await db.execute(''' |
| 2191 | CREATE TABLE IF NOT EXISTS $dailyStressResultsTable ( | 2196 | CREATE TABLE IF NOT EXISTS $dailyStressResultsTable ( |
| 2192 | date INTEGER PRIMARY KEY, | 2197 | date INTEGER PRIMARY KEY, |
| 2198 | + date_key INTEGER NOT NULL DEFAULT 0, | ||
| 2193 | user_id INTEGER NOT NULL, | 2199 | user_id INTEGER NOT NULL, |
| 2194 | stress_value REAL NOT NULL, | 2200 | stress_value REAL NOT NULL, |
| 2195 | stress_score INTEGER NOT NULL, | 2201 | stress_score INTEGER NOT NULL, |
| @@ -2205,6 +2211,7 @@ CREATE TABLE IF NOT EXISTS $dailyStressResultsTable ( | @@ -2205,6 +2211,7 @@ CREATE TABLE IF NOT EXISTS $dailyStressResultsTable ( | ||
| 2205 | await db.execute(''' | 2211 | await db.execute(''' |
| 2206 | CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | 2212 | CREATE TABLE IF NOT EXISTS $sleepResultsTable ( |
| 2207 | date INTEGER PRIMARY KEY, | 2213 | date INTEGER PRIMARY KEY, |
| 2214 | + date_key INTEGER NOT NULL DEFAULT 0, | ||
| 2208 | user_id INTEGER NOT NULL, | 2215 | user_id INTEGER NOT NULL, |
| 2209 | start_date INTEGER NOT NULL, | 2216 | start_date INTEGER NOT NULL, |
| 2210 | sleep_score INTEGER NOT NULL, | 2217 | sleep_score INTEGER NOT NULL, |
| @@ -2235,6 +2242,44 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | @@ -2235,6 +2242,44 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | ||
| 2235 | } | 2242 | } |
| 2236 | } | 2243 | } |
| 2237 | 2244 | ||
| 2245 | + Future<void> _backfillDateKey(DatabaseExecutor db, String table) async { | ||
| 2246 | + final timeColumn = switch (table) { | ||
| 2247 | + hrvResultsTable || realtimeStressResultsTable => 'raw_end_time', | ||
| 2248 | + dailyStressResultsTable || sleepResultsTable => 'date', | ||
| 2249 | + _ => null, | ||
| 2250 | + }; | ||
| 2251 | + if (timeColumn == null) return; | ||
| 2252 | + if (table == dailyStressResultsTable) { | ||
| 2253 | + await db.execute( | ||
| 2254 | + 'UPDATE $table SET date_key = date WHERE date_key = 0 AND date > 0', | ||
| 2255 | + ); | ||
| 2256 | + return; | ||
| 2257 | + } | ||
| 2258 | + await db.execute( | ||
| 2259 | + "UPDATE $table SET date_key = CAST(strftime('%Y%m%d', " | ||
| 2260 | + "$timeColumn, 'unixepoch', 'localtime') AS INTEGER) " | ||
| 2261 | + 'WHERE date_key = 0 AND $timeColumn > 0', | ||
| 2262 | + ); | ||
| 2263 | + } | ||
| 2264 | + | ||
| 2265 | + Future<void> _addDateKeyColumns(DatabaseExecutor db) async { | ||
| 2266 | + for (final table in const [ | ||
| 2267 | + hrvResultsTable, | ||
| 2268 | + realtimeStressResultsTable, | ||
| 2269 | + dailyStressResultsTable, | ||
| 2270 | + sleepResultsTable, | ||
| 2271 | + ]) { | ||
| 2272 | + try { | ||
| 2273 | + await db.execute( | ||
| 2274 | + 'ALTER TABLE $table ADD COLUMN date_key INTEGER NOT NULL DEFAULT 0', | ||
| 2275 | + ); | ||
| 2276 | + } on DatabaseException catch (error) { | ||
| 2277 | + if (!error.isDuplicateColumnError()) rethrow; | ||
| 2278 | + } | ||
| 2279 | + await _backfillDateKey(db, table); | ||
| 2280 | + } | ||
| 2281 | + } | ||
| 2282 | + | ||
| 2238 | Future<void> _addFlagColumns(DatabaseExecutor db, String table) async { | 2283 | Future<void> _addFlagColumns(DatabaseExecutor db, String table) async { |
| 2239 | for (final column in const [ | 2284 | for (final column in const [ |
| 2240 | 'is_workout', | 2285 | 'is_workout', |
| @@ -2381,9 +2426,15 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | @@ -2381,9 +2426,15 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | ||
| 2381 | return now.year * 10000 + now.month * 100 + now.day; | 2426 | return now.year * 10000 + now.month * 100 + now.day; |
| 2382 | } | 2427 | } |
| 2383 | 2428 | ||
| 2429 | + int _dateKeyFromUnixSeconds(int seconds) { | ||
| 2430 | + final date = DateTime.fromMillisecondsSinceEpoch(seconds * 1000); | ||
| 2431 | + return date.year * 10000 + date.month * 100 + date.day; | ||
| 2432 | + } | ||
| 2433 | + | ||
| 2384 | Map<String, Object?> _hrvRow(HealthRawHrvStressPoint point) { | 2434 | Map<String, Object?> _hrvRow(HealthRawHrvStressPoint point) { |
| 2385 | return <String, Object?>{ | 2435 | return <String, Object?>{ |
| 2386 | 'raw_end_time': point.rawEndTime, | 2436 | 'raw_end_time': point.rawEndTime, |
| 2437 | + 'date_key': _dateKeyFromUnixSeconds(point.rawEndTime), | ||
| 2387 | 'user_id': point.userId, | 2438 | 'user_id': point.userId, |
| 2388 | 'raw_hrv': point.rawHrv, | 2439 | 'raw_hrv': point.rawHrv, |
| 2389 | 'result': _integerDouble(point.result), | 2440 | 'result': _integerDouble(point.result), |
| @@ -2403,6 +2454,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | @@ -2403,6 +2454,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | ||
| 2403 | Map<String, Object?> _realtimeRow(HealthRawRealtimeStressPoint point) { | 2454 | Map<String, Object?> _realtimeRow(HealthRawRealtimeStressPoint point) { |
| 2404 | return <String, Object?>{ | 2455 | return <String, Object?>{ |
| 2405 | 'raw_end_time': point.rawEndTime, | 2456 | 'raw_end_time': point.rawEndTime, |
| 2457 | + 'date_key': _dateKeyFromUnixSeconds(point.rawEndTime), | ||
| 2406 | 'user_id': point.userId, | 2458 | 'user_id': point.userId, |
| 2407 | 'raw_hr': point.rawHr, | 2459 | 'raw_hr': point.rawHr, |
| 2408 | 'result': _integerDouble(point.result), | 2460 | 'result': _integerDouble(point.result), |
| @@ -2417,6 +2469,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | @@ -2417,6 +2469,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | ||
| 2417 | Map<String, Object?> _dailyStressRow(HealthRawDailyStressPoint point) { | 2469 | Map<String, Object?> _dailyStressRow(HealthRawDailyStressPoint point) { |
| 2418 | return <String, Object?>{ | 2470 | return <String, Object?>{ |
| 2419 | 'date': point.date, | 2471 | 'date': point.date, |
| 2472 | + 'date_key': point.date, | ||
| 2420 | 'user_id': point.userId, | 2473 | 'user_id': point.userId, |
| 2421 | 'stress_value': _integerDouble(point.stressValue), | 2474 | 'stress_value': _integerDouble(point.stressValue), |
| 2422 | 'stress_score': point.stressScore, | 2475 | 'stress_score': point.stressScore, |
| @@ -2430,6 +2483,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | @@ -2430,6 +2483,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | ||
| 2430 | Map<String, Object?> _sleepRow(HealthRawSleepResult result) { | 2483 | Map<String, Object?> _sleepRow(HealthRawSleepResult result) { |
| 2431 | return <String, Object?>{ | 2484 | return <String, Object?>{ |
| 2432 | 'date': result.date, | 2485 | 'date': result.date, |
| 2486 | + 'date_key': _dateKeyFromUnixSeconds(result.date), | ||
| 2433 | 'user_id': result.userId, | 2487 | 'user_id': result.userId, |
| 2434 | 'start_date': result.startDate, | 2488 | 'start_date': result.startDate, |
| 2435 | 'sleep_score': result.sleepScore, | 2489 | 'sleep_score': result.sleepScore, |
| @@ -2487,6 +2541,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | @@ -2487,6 +2541,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | ||
| 2487 | table, | 2541 | table, |
| 2488 | <String, Object?>{ | 2542 | <String, Object?>{ |
| 2489 | 'user_id': row['user_id'], | 2543 | 'user_id': row['user_id'], |
| 2544 | + 'date_key': row['date_key'], | ||
| 2490 | if (row.containsKey('raw_hrv')) 'raw_hrv': row['raw_hrv'], | 2545 | if (row.containsKey('raw_hrv')) 'raw_hrv': row['raw_hrv'], |
| 2491 | if (row.containsKey('raw_hr')) 'raw_hr': row['raw_hr'], | 2546 | if (row.containsKey('raw_hr')) 'raw_hr': row['raw_hr'], |
| 2492 | 'result': row['result'], | 2547 | 'result': row['result'], |
| @@ -2586,6 +2641,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | @@ -2586,6 +2641,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | ||
| 2586 | dailyStressResultsTable, | 2641 | dailyStressResultsTable, |
| 2587 | <String, Object?>{ | 2642 | <String, Object?>{ |
| 2588 | 'user_id': row['user_id'], | 2643 | 'user_id': row['user_id'], |
| 2644 | + 'date_key': row['date_key'], | ||
| 2589 | 'stress_value': row['stress_value'], | 2645 | 'stress_value': row['stress_value'], |
| 2590 | 'stress_score': row['stress_score'], | 2646 | 'stress_score': row['stress_score'], |
| 2591 | 'state': row['state'], | 2647 | 'state': row['state'], |
| @@ -2644,6 +2700,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | @@ -2644,6 +2700,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | ||
| 2644 | sleepResultsTable, | 2700 | sleepResultsTable, |
| 2645 | <String, Object?>{ | 2701 | <String, Object?>{ |
| 2646 | 'user_id': row['user_id'], | 2702 | 'user_id': row['user_id'], |
| 2703 | + 'date_key': row['date_key'], | ||
| 2647 | 'start_date': row['start_date'], | 2704 | 'start_date': row['start_date'], |
| 2648 | 'sleep_score': row['sleep_score'], | 2705 | 'sleep_score': row['sleep_score'], |
| 2649 | 'sleep_state': row['sleep_state'], | 2706 | 'sleep_state': row['sleep_state'], |
| @@ -2693,6 +2750,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | @@ -2693,6 +2750,7 @@ CREATE TABLE IF NOT EXISTS $sleepResultsTable ( | ||
| 2693 | List<String> _rawResultStoredValueKeys(Map<String, Object?> row) { | 2750 | List<String> _rawResultStoredValueKeys(Map<String, Object?> row) { |
| 2694 | return <String>[ | 2751 | return <String>[ |
| 2695 | 'user_id', | 2752 | 'user_id', |
| 2753 | + 'date_key', | ||
| 2696 | if (row.containsKey('raw_hrv')) 'raw_hrv', | 2754 | if (row.containsKey('raw_hrv')) 'raw_hrv', |
| 2697 | if (row.containsKey('raw_hr')) 'raw_hr', | 2755 | if (row.containsKey('raw_hr')) 'raw_hr', |
| 2698 | 'result', | 2756 | 'result', |
| @@ -281,8 +281,13 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore { | @@ -281,8 +281,13 @@ class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore { | ||
| 281 | final db = await factory.openDatabase( | 281 | final db = await factory.openDatabase( |
| 282 | path, | 282 | path, |
| 283 | options: OpenDatabaseOptions( | 283 | options: OpenDatabaseOptions( |
| 284 | - version: 1, | 284 | + version: 2, |
| 285 | onCreate: (db, version) => _createTables(db), | 285 | onCreate: (db, version) => _createTables(db), |
| 286 | + onUpgrade: (db, oldVersion, newVersion) async { | ||
| 287 | + if (oldVersion < 2) { | ||
| 288 | + await _addDateKeyColumns(db); | ||
| 289 | + } | ||
| 290 | + }, | ||
| 286 | ), | 291 | ), |
| 287 | ); | 292 | ); |
| 288 | _opened[userId] = db; | 293 | _opened[userId] = db; |
| @@ -306,6 +311,7 @@ CREATE TABLE IF NOT EXISTS $sleepDataTable ( | @@ -306,6 +311,7 @@ CREATE TABLE IF NOT EXISTS $sleepDataTable ( | ||
| 306 | data_type INTEGER, | 311 | data_type INTEGER, |
| 307 | from_time INTEGER NOT NULL, | 312 | from_time INTEGER NOT NULL, |
| 308 | to_time INTEGER NOT NULL, | 313 | to_time INTEGER NOT NULL, |
| 314 | + date_key INTEGER NOT NULL DEFAULT 0, | ||
| 309 | create_time INTEGER NOT NULL, | 315 | create_time INTEGER NOT NULL, |
| 310 | UNIQUE (from_time) | 316 | UNIQUE (from_time) |
| 311 | ) | 317 | ) |
| @@ -342,6 +348,7 @@ CREATE TABLE IF NOT EXISTS $table ( | @@ -342,6 +348,7 @@ CREATE TABLE IF NOT EXISTS $table ( | ||
| 342 | time INTEGER NOT NULL, | 348 | time INTEGER NOT NULL, |
| 343 | value REAL, | 349 | value REAL, |
| 344 | is_asleep INTEGER, | 350 | is_asleep INTEGER, |
| 351 | + date_key INTEGER NOT NULL DEFAULT 0, | ||
| 345 | create_time INTEGER NOT NULL, | 352 | create_time INTEGER NOT NULL, |
| 346 | UNIQUE (time) | 353 | UNIQUE (time) |
| 347 | ) | 354 | ) |
| @@ -359,6 +366,7 @@ CREATE TABLE IF NOT EXISTS $table ( | @@ -359,6 +366,7 @@ CREATE TABLE IF NOT EXISTS $table ( | ||
| 359 | activity_type INTEGER, | 366 | activity_type INTEGER, |
| 360 | from_time INTEGER NOT NULL, | 367 | from_time INTEGER NOT NULL, |
| 361 | to_time INTEGER NOT NULL, | 368 | to_time INTEGER NOT NULL, |
| 369 | + date_key INTEGER NOT NULL DEFAULT 0, | ||
| 362 | create_time INTEGER NOT NULL, | 370 | create_time INTEGER NOT NULL, |
| 363 | UNIQUE (from_time) | 371 | UNIQUE (from_time) |
| 364 | ) | 372 | ) |
| @@ -372,10 +380,12 @@ CREATE TABLE IF NOT EXISTS $table ( | @@ -372,10 +380,12 @@ CREATE TABLE IF NOT EXISTS $table ( | ||
| 372 | OhosHealthRawDataItem item, | 380 | OhosHealthRawDataItem item, |
| 373 | int createTime, | 381 | int createTime, |
| 374 | ) { | 382 | ) { |
| 383 | + final time = _numPayload(item.payload, 'time') ?? item.dataTime; | ||
| 375 | return { | 384 | return { |
| 376 | - 'time': _numPayload(item.payload, 'time') ?? item.dataTime, | 385 | + 'time': time, |
| 377 | 'value': _numPayload(item.payload, 'value'), | 386 | 'value': _numPayload(item.payload, 'value'), |
| 378 | 'is_asleep': _numPayload(item.payload, 'is_asleep'), | 387 | 'is_asleep': _numPayload(item.payload, 'is_asleep'), |
| 388 | + 'date_key': _dateKeyFromUnixSeconds(time), | ||
| 379 | 'create_time': createTime, | 389 | 'create_time': createTime, |
| 380 | }; | 390 | }; |
| 381 | } | 391 | } |
| @@ -384,14 +394,16 @@ CREATE TABLE IF NOT EXISTS $table ( | @@ -384,14 +394,16 @@ CREATE TABLE IF NOT EXISTS $table ( | ||
| 384 | OhosHealthRawDataItem item, | 394 | OhosHealthRawDataItem item, |
| 385 | int createTime, | 395 | int createTime, |
| 386 | ) { | 396 | ) { |
| 397 | + final toTime = _numPayload(item.payload, 'to_time') ?? | ||
| 398 | + _numPayload(item.payload, 'end_time') ?? | ||
| 399 | + item.dataTime; | ||
| 387 | return { | 400 | return { |
| 388 | 'activity_type': _numPayload(item.payload, 'activity_type'), | 401 | 'activity_type': _numPayload(item.payload, 'activity_type'), |
| 389 | 'from_time': _numPayload(item.payload, 'from_time') ?? | 402 | 'from_time': _numPayload(item.payload, 'from_time') ?? |
| 390 | _numPayload(item.payload, 'start_time') ?? | 403 | _numPayload(item.payload, 'start_time') ?? |
| 391 | 0, | 404 | 0, |
| 392 | - 'to_time': _numPayload(item.payload, 'to_time') ?? | ||
| 393 | - _numPayload(item.payload, 'end_time') ?? | ||
| 394 | - item.dataTime, | 405 | + 'to_time': toTime, |
| 406 | + 'date_key': _dateKeyFromUnixSeconds(toTime), | ||
| 395 | 'create_time': createTime, | 407 | 'create_time': createTime, |
| 396 | }; | 408 | }; |
| 397 | } | 409 | } |
| @@ -400,10 +412,12 @@ CREATE TABLE IF NOT EXISTS $table ( | @@ -400,10 +412,12 @@ CREATE TABLE IF NOT EXISTS $table ( | ||
| 400 | OhosHealthRawDataItem item, | 412 | OhosHealthRawDataItem item, |
| 401 | int createTime, | 413 | int createTime, |
| 402 | ) { | 414 | ) { |
| 415 | + final toTime = _numPayload(item.payload, 'to_time') ?? item.dataTime; | ||
| 403 | return { | 416 | return { |
| 404 | 'data_type': _numPayload(item.payload, 'data_type') ?? item.dataType, | 417 | 'data_type': _numPayload(item.payload, 'data_type') ?? item.dataType, |
| 405 | 'from_time': _numPayload(item.payload, 'from_time') ?? 0, | 418 | 'from_time': _numPayload(item.payload, 'from_time') ?? 0, |
| 406 | - 'to_time': _numPayload(item.payload, 'to_time') ?? item.dataTime, | 419 | + 'to_time': toTime, |
| 420 | + 'date_key': _dateKeyFromUnixSeconds(toTime), | ||
| 407 | 'create_time': createTime, | 421 | 'create_time': createTime, |
| 408 | }; | 422 | }; |
| 409 | } | 423 | } |
| @@ -431,6 +445,7 @@ CREATE TABLE IF NOT EXISTS $table ( | @@ -431,6 +445,7 @@ CREATE TABLE IF NOT EXISTS $table ( | ||
| 431 | 'time': dataTime, | 445 | 'time': dataTime, |
| 432 | 'value': row['value'], | 446 | 'value': row['value'], |
| 433 | 'is_asleep': row['is_asleep'], | 447 | 'is_asleep': row['is_asleep'], |
| 448 | + 'date_key': row['date_key'], | ||
| 434 | }, | 449 | }, |
| 435 | ); | 450 | ); |
| 436 | } | 451 | } |
| @@ -446,6 +461,7 @@ CREATE TABLE IF NOT EXISTS $table ( | @@ -446,6 +461,7 @@ CREATE TABLE IF NOT EXISTS $table ( | ||
| 446 | 'data_type': dataType, | 461 | 'data_type': dataType, |
| 447 | 'from_time': row['from_time'], | 462 | 'from_time': row['from_time'], |
| 448 | 'to_time': toTime, | 463 | 'to_time': toTime, |
| 464 | + 'date_key': row['date_key'], | ||
| 449 | }, | 465 | }, |
| 450 | ); | 466 | ); |
| 451 | } | 467 | } |
| @@ -462,6 +478,7 @@ CREATE TABLE IF NOT EXISTS $table ( | @@ -462,6 +478,7 @@ CREATE TABLE IF NOT EXISTS $table ( | ||
| 462 | 'activity_type': row['activity_type'], | 478 | 'activity_type': row['activity_type'], |
| 463 | 'from_time': row['from_time'], | 479 | 'from_time': row['from_time'], |
| 464 | 'to_time': toTime, | 480 | 'to_time': toTime, |
| 481 | + 'date_key': row['date_key'], | ||
| 465 | }, | 482 | }, |
| 466 | ); | 483 | ); |
| 467 | } | 484 | } |
| @@ -526,6 +543,61 @@ CREATE TABLE IF NOT EXISTS $table ( | @@ -526,6 +543,61 @@ CREATE TABLE IF NOT EXISTS $table ( | ||
| 526 | 1000; | 543 | 1000; |
| 527 | } | 544 | } |
| 528 | 545 | ||
| 546 | + int _dateKeyFromUnixSeconds(int seconds) { | ||
| 547 | + final dateTime = DateTime.fromMillisecondsSinceEpoch(seconds * 1000); | ||
| 548 | + return dateTime.year * 10000 + dateTime.month * 100 + dateTime.day; | ||
| 549 | + } | ||
| 550 | + | ||
| 551 | + Future<void> _addDateKeyColumns(DatabaseExecutor db) async { | ||
| 552 | + final rows = await db.query( | ||
| 553 | + 'sqlite_master', | ||
| 554 | + columns: ['name'], | ||
| 555 | + where: "type = 'table' AND (name = ? OR name LIKE ?)", | ||
| 556 | + whereArgs: [sleepDataTable, '${rawDataTable}_%'], | ||
| 557 | + ); | ||
| 558 | + for (final row in rows) { | ||
| 559 | + final table = row['name'] as String?; | ||
| 560 | + if (table == null) continue; | ||
| 561 | + await _addColumnIfMissing( | ||
| 562 | + db, | ||
| 563 | + table, | ||
| 564 | + 'date_key INTEGER NOT NULL DEFAULT 0', | ||
| 565 | + ); | ||
| 566 | + await _backfillDateKey(db, table); | ||
| 567 | + } | ||
| 568 | + } | ||
| 569 | + | ||
| 570 | + Future<void> _backfillDateKey(DatabaseExecutor db, String table) async { | ||
| 571 | + final columns = await db.rawQuery('PRAGMA table_info($table)'); | ||
| 572 | + final columnNames = | ||
| 573 | + columns.map((row) => row['name']).whereType<String>().toSet(); | ||
| 574 | + final timeColumn = columnNames.contains('to_time') ? 'to_time' : 'time'; | ||
| 575 | + if (!columnNames.contains(timeColumn)) return; | ||
| 576 | + await db.execute( | ||
| 577 | + "UPDATE $table SET date_key = CAST(strftime('%Y%m%d', " | ||
| 578 | + "$timeColumn, 'unixepoch', 'localtime') AS INTEGER) " | ||
| 579 | + 'WHERE date_key = 0 AND $timeColumn > 0', | ||
| 580 | + ); | ||
| 581 | + } | ||
| 582 | + | ||
| 583 | + Future<void> _addColumnIfMissing( | ||
| 584 | + DatabaseExecutor db, | ||
| 585 | + String table, | ||
| 586 | + String columnSql, | ||
| 587 | + ) async { | ||
| 588 | + try { | ||
| 589 | + await db.execute('ALTER TABLE $table ADD COLUMN $columnSql'); | ||
| 590 | + } on DatabaseException catch (error) { | ||
| 591 | + if (!_isDuplicateColumnError(error)) rethrow; | ||
| 592 | + } | ||
| 593 | + } | ||
| 594 | + | ||
| 595 | + bool _isDuplicateColumnError(DatabaseException error) { | ||
| 596 | + final message = error.toString().toLowerCase(); | ||
| 597 | + return message.contains('duplicate column') || | ||
| 598 | + message.contains('duplicate column name'); | ||
| 599 | + } | ||
| 600 | + | ||
| 529 | int? _numPayload(Map<String, Object?> payload, String key) { | 601 | int? _numPayload(Map<String, Object?> payload, String key) { |
| 530 | final value = payload[key]; | 602 | final value = payload[key]; |
| 531 | return value is num ? value.toInt() : null; | 603 | return value is num ? value.toInt() : null; |
| @@ -293,7 +293,8 @@ class OhosHealthRawDataSyncService { | @@ -293,7 +293,8 @@ class OhosHealthRawDataSyncService { | ||
| 293 | 'chunkDays=$fetchChunkDays ' | 293 | 'chunkDays=$fetchChunkDays ' |
| 294 | 'startTime=${range.startTime} endTime=${range.endTime} ' | 294 | 'startTime=${range.startTime} endTime=${range.endTime} ' |
| 295 | 'startDate=${_dateKeyFromUnixSeconds(range.startTime)} ' | 295 | 'startDate=${_dateKeyFromUnixSeconds(range.startTime)} ' |
| 296 | - 'endDate=${_dateKeyFromUnixSeconds(range.endTime)}', | 296 | + 'endDate=${_dateKeyFromUnixSeconds(range.endTime)} ' |
| 297 | + 'apiEndDate=${_exclusiveEndDateKeyFromUnixSeconds(range.endTime)}', | ||
| 297 | ); | 298 | ); |
| 298 | 299 | ||
| 299 | final page = await _remoteDataSource.fetchRawDataPage( | 300 | final page = await _remoteDataSource.fetchRawDataPage( |
| @@ -501,10 +502,9 @@ class OhosHarmonyHealthRawDataRemoteDataSource | @@ -501,10 +502,9 @@ class OhosHarmonyHealthRawDataRemoteDataSource | ||
| 501 | OhosHarmonyHealthRawDataRemoteDataSource( | 502 | OhosHarmonyHealthRawDataRemoteDataSource( |
| 502 | this._client, { | 503 | this._client, { |
| 503 | DateTime Function()? nowProvider, | 504 | DateTime Function()? nowProvider, |
| 504 | - }) : _nowProvider = nowProvider ?? DateTime.now; | 505 | + }); |
| 505 | 506 | ||
| 506 | final OhosHarmonyRawDataClient _client; | 507 | final OhosHarmonyRawDataClient _client; |
| 507 | - final DateTime Function() _nowProvider; | ||
| 508 | 508 | ||
| 509 | @override | 509 | @override |
| 510 | Future<OhosHealthRawDataPage> fetchRawDataPage({ | 510 | Future<OhosHealthRawDataPage> fetchRawDataPage({ |
| @@ -513,7 +513,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource | @@ -513,7 +513,7 @@ class OhosHarmonyHealthRawDataRemoteDataSource | ||
| 513 | required int endTime, | 513 | required int endTime, |
| 514 | }) async { | 514 | }) async { |
| 515 | final startDate = _dateKeyFromUnixSeconds(startTime); | 515 | final startDate = _dateKeyFromUnixSeconds(startTime); |
| 516 | - final endDate = _endDateKeyFromUnixSeconds(endTime); | 516 | + final endDate = _exclusiveEndDateKeyFromUnixSeconds(endTime); |
| 517 | if (dataType == OhosHealthRawDataType.sleepAnalysis) { | 517 | if (dataType == OhosHealthRawDataType.sleepAnalysis) { |
| 518 | final result = await _client.getSleepData(startDate, endDate); | 518 | final result = await _client.getSleepData(startDate, endDate); |
| 519 | return switch (result) { | 519 | return switch (result) { |
| @@ -630,17 +630,6 @@ class OhosHarmonyHealthRawDataRemoteDataSource | @@ -630,17 +630,6 @@ class OhosHarmonyHealthRawDataRemoteDataSource | ||
| 630 | ) | 630 | ) |
| 631 | .toList(growable: false); | 631 | .toList(growable: false); |
| 632 | } | 632 | } |
| 633 | - | ||
| 634 | - int _endDateKeyFromUnixSeconds(int seconds) { | ||
| 635 | - final end = DateTime.fromMillisecondsSinceEpoch(seconds * 1000); | ||
| 636 | - final now = _nowProvider(); | ||
| 637 | - final today = DateTime(now.year, now.month, now.day); | ||
| 638 | - final endDate = DateTime(end.year, end.month, end.day); | ||
| 639 | - if (endDate == today) { | ||
| 640 | - return _dateKeyFromDateTime(today.add(const Duration(days: 1))); | ||
| 641 | - } | ||
| 642 | - return _dateKeyFromDateTime(endDate); | ||
| 643 | - } | ||
| 644 | } | 633 | } |
| 645 | 634 | ||
| 646 | class TodoOhosHealthRawDataRemoteDataSource | 635 | class TodoOhosHealthRawDataRemoteDataSource |
| @@ -830,6 +819,12 @@ int _dateKeyFromUnixSeconds(int seconds) { | @@ -830,6 +819,12 @@ int _dateKeyFromUnixSeconds(int seconds) { | ||
| 830 | return _dateKeyFromDateTime(dateTime); | 819 | return _dateKeyFromDateTime(dateTime); |
| 831 | } | 820 | } |
| 832 | 821 | ||
| 822 | +int _exclusiveEndDateKeyFromUnixSeconds(int seconds) { | ||
| 823 | + final dateTime = DateTime.fromMillisecondsSinceEpoch(seconds * 1000); | ||
| 824 | + final endDate = DateTime(dateTime.year, dateTime.month, dateTime.day); | ||
| 825 | + return _dateKeyFromDateTime(endDate.add(const Duration(days: 1))); | ||
| 826 | +} | ||
| 827 | + | ||
| 833 | int _dateKeyFromDateTime(DateTime dateTime) { | 828 | int _dateKeyFromDateTime(DateTime dateTime) { |
| 834 | return dateTime.year * 10000 + dateTime.month * 100 + dateTime.day; | 829 | return dateTime.year * 10000 + dateTime.month * 100 + dateTime.day; |
| 835 | } | 830 | } |
| @@ -106,6 +106,9 @@ void main() { | @@ -106,6 +106,9 @@ void main() { | ||
| 106 | expect(result.segmentCount, remote.calls.length); | 106 | expect(result.segmentCount, remote.calls.length); |
| 107 | expect(remote.calls.first.startTime, expectedStart); | 107 | expect(remote.calls.first.startTime, expectedStart); |
| 108 | expect(remote.calls.last.endTime, expectedEnd); | 108 | expect(remote.calls.last.endTime, expectedEnd); |
| 109 | + for (var i = 1; i < remote.calls.length; i += 1) { | ||
| 110 | + expect(remote.calls[i].startTime, remote.calls[i - 1].endTime + 1); | ||
| 111 | + } | ||
| 109 | expect(local.storedBatches, isEmpty); | 112 | expect(local.storedBatches, isEmpty); |
| 110 | }); | 113 | }); |
| 111 | 114 | ||
| @@ -480,13 +483,28 @@ void main() { | @@ -480,13 +483,28 @@ void main() { | ||
| 480 | 483 | ||
| 481 | expect(client.healthCalls.single.dataType, HuaweiHealthDataType.hrv); | 484 | expect(client.healthCalls.single.dataType, HuaweiHealthDataType.hrv); |
| 482 | expect(client.healthCalls.single.startDate, 20260801); | 485 | expect(client.healthCalls.single.startDate, 20260801); |
| 483 | - expect(client.healthCalls.single.endDate, 20260831); | 486 | + expect(client.healthCalls.single.endDate, 20260901); |
| 484 | expect(page.items, hasLength(1)); | 487 | expect(page.items, hasLength(1)); |
| 485 | expect(page.items.single.dataType, 1); | 488 | expect(page.items.single.dataType, 1); |
| 486 | expect(page.items.single.dataTime, 1787900000); | 489 | expect(page.items.single.dataTime, 1787900000); |
| 487 | expect(page.items.single.payload['value'], 53); | 490 | expect(page.items.single.payload['value'], 53); |
| 488 | }); | 491 | }); |
| 489 | 492 | ||
| 493 | + test('Harmony remote data source uses exclusive endDate for historical day', | ||
| 494 | + () async { | ||
| 495 | + final client = _FakeOhosHarmonyRawDataClient(); | ||
| 496 | + final remote = OhosHarmonyHealthRawDataRemoteDataSource(client); | ||
| 497 | + | ||
| 498 | + await remote.fetchRawDataPage( | ||
| 499 | + dataType: HuaweiHealthDataType.hrv.dataType, | ||
| 500 | + startTime: _unixSeconds(DateTime(2026, 8, 30)), | ||
| 501 | + endTime: _unixSeconds(DateTime(2026, 8, 30, 23, 59, 59)), | ||
| 502 | + ); | ||
| 503 | + | ||
| 504 | + expect(client.healthCalls.single.startDate, 20260830); | ||
| 505 | + expect(client.healthCalls.single.endDate, 20260831); | ||
| 506 | + }); | ||
| 507 | + | ||
| 490 | test('Harmony remote data source uses tomorrow as endDate for today data', | 508 | test('Harmony remote data source uses tomorrow as endDate for today data', |
| 491 | () async { | 509 | () async { |
| 492 | final client = _FakeOhosHarmonyRawDataClient(); | 510 | final client = _FakeOhosHarmonyRawDataClient(); |
| @@ -530,7 +548,7 @@ void main() { | @@ -530,7 +548,7 @@ void main() { | ||
| 530 | ); | 548 | ); |
| 531 | 549 | ||
| 532 | expect(client.sleepCalls.single.startDate, 20260801); | 550 | expect(client.sleepCalls.single.startDate, 20260801); |
| 533 | - expect(client.sleepCalls.single.endDate, 20260802); | 551 | + expect(client.sleepCalls.single.endDate, 20260803); |
| 534 | expect(page.items, hasLength(1)); | 552 | expect(page.items, hasLength(1)); |
| 535 | expect(page.items.single.dataType, 4); | 553 | expect(page.items.single.dataType, 4); |
| 536 | expect(page.items.single.dataTime, 1787870000); | 554 | expect(page.items.single.dataTime, 1787870000); |
-
Please register or login to post a comment