|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import 'package:sqflite/sqflite.dart';
|
|
|
|
|
|
|
|
import 'huawei_health_data_type.dart';
|
|
|
|
import 'ohos_health_raw_data_sync_service.dart';
|
|
|
|
|
|
|
|
class OhosHealthRawDataSqliteStore implements OhosHealthRawDataLocalStore {
|
|
|
|
OhosHealthRawDataSqliteStore({
|
|
|
|
required int Function()? userIdProvider,
|
|
|
|
Directory? rootDirectory,
|
|
|
|
DatabaseFactory? databaseFactory,
|
|
|
|
DateTime Function()? nowProvider,
|
|
|
|
}) : _userIdProvider = userIdProvider,
|
|
|
|
_rootDirectory = rootDirectory,
|
|
|
|
_databaseFactory = databaseFactory,
|
|
|
|
_nowProvider = nowProvider ?? DateTime.now;
|
|
|
|
|
|
|
|
static const rawDataTable = 'ohos_raw_data';
|
|
|
|
static const sleepDataTable = 'ohos_sleep_data';
|
|
|
|
|
|
|
|
final int Function()? _userIdProvider;
|
|
|
|
final Directory? _rootDirectory;
|
|
|
|
final DatabaseFactory? _databaseFactory;
|
|
|
|
final DateTime Function() _nowProvider;
|
|
|
|
final Map<int, Database> _opened = <int, Database>{};
|
|
|
|
|
|
|
|
int get _userId {
|
|
|
|
final userId = _userIdProvider?.call() ?? 0;
|
|
|
|
if (userId <= 0) {
|
|
|
|
throw StateError('OhosHealthRawDataSqliteStore requires a valid userId');
|
|
|
|
}
|
|
|
|
return userId;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<String> dbPath(int userId) async {
|
|
|
|
final dir = _rootDirectory ?? await getApplicationDocumentsDirectory();
|
|
|
|
return '${dir.path}/ohos_raw_data_$userId.sqlite';
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<int?> latestDataTime({required int dataType}) async {
|
|
|
|
final db = await _database(_userId);
|
|
|
|
if (dataType == OhosHealthRawDataType.sleepAnalysis) {
|
|
|
|
final rows = await db.query(
|
|
|
|
sleepDataTable,
|
|
|
|
columns: ['to_time'],
|
|
|
|
orderBy: 'to_time DESC',
|
|
|
|
limit: 1,
|
|
|
|
);
|
|
|
|
return rows.isEmpty ? null : rows.first['to_time'] as int;
|
|
|
|
}
|
|
|
|
|
|
|
|
final storedDataType = _storedHealthDataType(dataType);
|
|
|
|
final rows = await db.query(
|
|
|
|
rawDataTable,
|
|
|
|
columns: ['data_time'],
|
|
|
|
where: 'data_type = ?',
|
|
|
|
whereArgs: [storedDataType],
|
|
|
|
orderBy: 'data_time DESC',
|
|
|
|
limit: 1,
|
|
|
|
);
|
|
|
|
return rows.isEmpty ? null : rows.first['data_time'] as int;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> upsertRawDataBatch({
|
|
|
|
required int dataType,
|
|
|
|
required List<OhosHealthRawDataItem> items,
|
|
|
|
}) async {
|
|
|
|
if (items.isEmpty) return;
|
|
|
|
final db = await _database(_userId);
|
|
|
|
final createTime = _nowProvider().millisecondsSinceEpoch ~/ 1000;
|
|
|
|
await db.transaction((txn) async {
|
|
|
|
if (dataType == OhosHealthRawDataType.sleepAnalysis) {
|
|
|
|
for (final item in items) {
|
|
|
|
await txn.insert(
|
|
|
|
sleepDataTable,
|
|
|
|
_sleepRow(item, createTime),
|
|
|
|
conflictAlgorithm: ConflictAlgorithm.ignore,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (final item in items) {
|
|
|
|
await txn.insert(
|
|
|
|
rawDataTable,
|
|
|
|
_rawRow(item, createTime),
|
|
|
|
conflictAlgorithm: ConflictAlgorithm.ignore,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> close({int? userId}) async {
|
|
|
|
if (userId != null) {
|
|
|
|
final db = _opened.remove(userId);
|
|
|
|
if (db != null) {
|
|
|
|
await db.close();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final databases = _opened.values.toList();
|
|
|
|
_opened.clear();
|
|
|
|
for (final db in databases) {
|
|
|
|
await db.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Database> _database(int userId) async {
|
|
|
|
final existing = _opened[userId];
|
|
|
|
if (existing != null && existing.isOpen) return existing;
|
|
|
|
final path = await dbPath(userId);
|
|
|
|
final parent = File(path).parent;
|
|
|
|
if (!await parent.exists()) {
|
|
|
|
await parent.create(recursive: true);
|
|
|
|
}
|
|
|
|
final factory = _databaseFactory ?? databaseFactory;
|
|
|
|
final db = await factory.openDatabase(
|
|
|
|
path,
|
|
|
|
options: OpenDatabaseOptions(
|
|
|
|
version: 1,
|
|
|
|
onCreate: (db, version) => _createTables(db),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
_opened[userId] = db;
|
|
|
|
return db;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _createTables(Database db) async {
|
|
|
|
await db.execute('''
|
|
|
|
CREATE TABLE IF NOT EXISTS $rawDataTable (
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
data_type INTEGER NOT NULL,
|
|
|
|
data_time INTEGER NOT NULL,
|
|
|
|
payload TEXT NOT NULL,
|
|
|
|
create_time INTEGER NOT NULL,
|
|
|
|
UNIQUE (data_type, data_time, payload)
|
|
|
|
)
|
|
|
|
''');
|
|
|
|
await db.execute('''
|
|
|
|
CREATE TABLE IF NOT EXISTS $sleepDataTable (
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
data_type INTEGER NOT NULL,
|
|
|
|
from_time INTEGER NOT NULL,
|
|
|
|
to_time INTEGER NOT NULL,
|
|
|
|
payload TEXT NOT NULL,
|
|
|
|
create_time INTEGER NOT NULL,
|
|
|
|
UNIQUE (data_type, from_time, to_time, payload)
|
|
|
|
)
|
|
|
|
''');
|
|
|
|
await db.execute(
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_ohos_raw_data_time '
|
|
|
|
'ON $rawDataTable(data_type, data_time)',
|
|
|
|
);
|
|
|
|
await db.execute(
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_ohos_sleep_data_time '
|
|
|
|
'ON $sleepDataTable(to_time)',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, Object?> _rawRow(
|
|
|
|
OhosHealthRawDataItem item,
|
|
|
|
int createTime,
|
|
|
|
) {
|
|
|
|
return {
|
|
|
|
'data_type': item.dataType,
|
|
|
|
'data_time': item.dataTime,
|
|
|
|
'payload': jsonEncode(item.payload),
|
|
|
|
'create_time': createTime,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, Object?> _sleepRow(
|
|
|
|
OhosHealthRawDataItem item,
|
|
|
|
int createTime,
|
|
|
|
) {
|
|
|
|
return {
|
|
|
|
'data_type': item.dataType,
|
|
|
|
'from_time': _numPayload(item.payload, 'from_time') ?? 0,
|
|
|
|
'to_time': _numPayload(item.payload, 'to_time') ?? item.dataTime,
|
|
|
|
'payload': jsonEncode(item.payload),
|
|
|
|
'create_time': createTime,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
int _storedHealthDataType(int dataType) {
|
|
|
|
if (dataType == OhosHealthRawDataType.activitySummary) {
|
|
|
|
return HuaweiHealthDataType.activity.dataType;
|
|
|
|
}
|
|
|
|
if (dataType == OhosHealthRawDataType.workout) {
|
|
|
|
return HuaweiHealthDataType.exerciseDuration.dataType;
|
|
|
|
}
|
|
|
|
return dataType;
|
|
|
|
}
|
|
|
|
|
|
|
|
int? _numPayload(Map<String, Object?> payload, String key) {
|
|
|
|
final value = payload[key];
|
|
|
|
return value is num ? value.toInt() : null;
|
|
|
|
}
|
|
|
|
} |
...
|
...
|
|