MotorSignal-App/lib/database/database.dart

131 lines
5.3 KiB
Dart

import 'dart:io';
import 'dart:math';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'tables.dart';
part 'database.g.dart';
@DriftDatabase(tables: [Rides, PerformanceRuns, TelemetrySamples, RideEvents, GarageMotorcycles, FieldTestSessions, FieldTestEvents, FieldTestSummaries, FieldTestGpsRecords, FieldTestImuRecords])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
AppDatabase.forTesting(super.e);
static final Random _random = Random.secure();
@override
int get schemaVersion => 6;
@override
MigrationStrategy get migration => MigrationStrategy(
onCreate: (Migrator m) async {
await m.createAll();
// database.g.dart is generated during normal development/CI. Keep the
// schema self-healing for installations that temporarily have a stale
// generated file: cloud identity must exist even on a brand-new DB.
await customStatement('ALTER TABLE rides ADD COLUMN cloud_ride_id TEXT');
await customStatement('CREATE UNIQUE INDEX rides_cloud_ride_id_idx ON rides(cloud_ride_id)');
},
onUpgrade: (Migrator m, int from, int to) async {
if (from < 2) {
await m.addColumn(rides, rides.distance);
await m.addColumn(rides, rides.movingTime);
await m.addColumn(rides, rides.stoppedTime);
await m.addColumn(rides, rides.averageSpeed);
await m.addColumn(rides, rides.topSpeed);
await m.addColumn(rides, rides.elevationGain);
await m.addColumn(rides, rides.elevationLoss);
await m.createTable(telemetrySamples);
await m.createTable(rideEvents);
}
if (from < 3) {
await m.createTable(garageMotorcycles);
await m.addColumn(rides, rides.motorcycleId);
await m.addColumn(rides, rides.motorcycleYear);
await m.addColumn(rides, rides.motorcycleMake);
await m.addColumn(rides, rides.motorcycleModel);
await m.addColumn(rides, rides.motorcycleVariant);
}
if (from < 4) {
await m.createTable(fieldTestSessions);
await m.createTable(fieldTestEvents);
await m.createTable(fieldTestGpsRecords);
await m.createTable(fieldTestImuRecords);
}
if (from < 5) {
await m.createTable(fieldTestSummaries);
try {
await m.addColumn(fieldTestEvents, fieldTestEvents.rideId);
await m.addColumn(fieldTestEvents, fieldTestEvents.telemetryTimestamp);
await m.addColumn(fieldTestEvents, fieldTestEvents.gpsAgeMs);
await m.addColumn(fieldTestEvents, fieldTestEvents.speed);
await m.addColumn(fieldTestEvents, fieldTestEvents.latitude);
await m.addColumn(fieldTestEvents, fieldTestEvents.longitude);
await m.addColumn(fieldTestEvents, fieldTestEvents.gpsAccuracy);
await m.addColumn(fieldTestEvents, fieldTestEvents.leanAngle);
await m.addColumn(fieldTestEvents, fieldTestEvents.longitudinalG);
await m.addColumn(fieldTestEvents, fieldTestEvents.lateralG);
await m.addColumn(fieldTestEvents, fieldTestEvents.overallConfidence);
await m.addColumn(fieldTestEvents, fieldTestEvents.metadata);
} catch (_) {}
}
if (from < 6) {
await m.addColumn(rides, rides.cloudRideId);
await customStatement('CREATE UNIQUE INDEX rides_cloud_ride_id_idx ON rides(cloud_ride_id)');
final rows = await customSelect('SELECT id FROM rides WHERE cloud_ride_id IS NULL').get();
for (final row in rows) {
await customStatement('UPDATE rides SET cloud_ride_id = ? WHERE id = ?', [_newCloudRideId(), row.data['id']]);
}
}
},
);
String _newCloudRideId() {
final bytes = List<int>.generate(16, (_) => _random.nextInt(256));
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
return 'ride_$hex';
}
Future<String?> getCloudRideId(int rideId) async {
final rows = await customSelect('SELECT cloud_ride_id FROM rides WHERE id = ?', variables: [Variable<int>(rideId)]).get();
if (rows.isEmpty) return null;
return rows.first.data['cloud_ride_id'] as String?;
}
Future<String> ensureCloudRideId(int rideId) async {
final existing = await getCloudRideId(rideId);
if (existing != null && existing.isNotEmpty) return existing;
final created = _newCloudRideId();
await customStatement('UPDATE rides SET cloud_ride_id = ? WHERE id = ?', [created, rideId]);
return created;
}
Future<int> startRide() async {
final id = await into(rides).insert(RidesCompanion.insert(startTime: DateTime.now()));
await ensureCloudRideId(id);
return id;
}
Future<void> endRide(int rideId, RidesCompanion data) async {
await (update(rides)..where((t) => t.id.equals(rideId))).write(data.copyWith(endTime: Value(DateTime.now())));
}
}
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'motorsignal.sqlite'));
return NativeDatabase.createInBackground(file);
});
}
final databaseProvider = Provider<AppDatabase>((ref) {
final db = AppDatabase();
ref.onDispose(db.close);
return db;
});