database_helper.dart 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import 'dart:io';
  2. import 'package:sqflite/sqflite.dart';
  3. import 'package:path/path.dart';
  4. import '../models/palm_record.dart';
  5. import '../models/palm_correction.dart';
  6. class DatabaseHelper {
  7. static final DatabaseHelper _instance = DatabaseHelper._internal();
  8. factory DatabaseHelper() => _instance;
  9. DatabaseHelper._internal();
  10. static Database? _database;
  11. Future<Database> get database async {
  12. if (_database != null) return _database!;
  13. _database = await _initDatabase();
  14. return _database!;
  15. }
  16. Future<Database> _initDatabase() async {
  17. String path = join(await getDatabasesPath(), 'palm_oil_history.db');
  18. return await openDatabase(
  19. path,
  20. version: 4,
  21. onCreate: _onCreate,
  22. onUpgrade: _onUpgrade,
  23. );
  24. }
  25. Future<void> _onCreate(Database db, int version) async {
  26. await db.execute('''
  27. CREATE TABLE history (
  28. id INTEGER PRIMARY KEY AUTOINCREMENT,
  29. image_path TEXT,
  30. ripeness_class TEXT,
  31. confidence REAL,
  32. timestamp TEXT,
  33. x1 REAL,
  34. y1 REAL,
  35. x2 REAL,
  36. y2 REAL,
  37. detections TEXT,
  38. batch_id TEXT,
  39. inference_ms INTEGER
  40. )
  41. ''');
  42. await db.execute('CREATE INDEX idx_history_batch_id ON history(batch_id)');
  43. await _createCorrectionsTable(db);
  44. }
  45. Future<void> _createCorrectionsTable(Database db) async {
  46. await db.execute('''
  47. CREATE TABLE corrections (
  48. id INTEGER PRIMARY KEY AUTOINCREMENT,
  49. original_record_id INTEGER,
  50. corrected_class TEXT,
  51. corrected_detections TEXT,
  52. timestamp TEXT
  53. )
  54. ''');
  55. await db.execute('CREATE INDEX idx_corrections_original_record_id ON corrections(original_record_id)');
  56. }
  57. Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
  58. if (oldVersion < 2) {
  59. await db.execute('ALTER TABLE history ADD COLUMN detections TEXT');
  60. }
  61. if (oldVersion < 3) {
  62. await db.execute('ALTER TABLE history ADD COLUMN batch_id TEXT');
  63. await db.execute('ALTER TABLE history ADD COLUMN inference_ms INTEGER');
  64. await db.execute('CREATE INDEX idx_history_batch_id ON history(batch_id)');
  65. }
  66. if (oldVersion < 4) {
  67. await _createCorrectionsTable(db);
  68. }
  69. }
  70. Future<int> insertRecord(PalmRecord record) async {
  71. Database db = await database;
  72. return await db.insert('history', record.toMap());
  73. }
  74. Future<List<PalmRecord>> getAllRecords() async {
  75. Database db = await database;
  76. List<Map<String, dynamic>> maps = await db.query('history', orderBy: 'timestamp DESC');
  77. return List.generate(maps.length, (i) {
  78. return PalmRecord.fromMap(maps[i]);
  79. });
  80. }
  81. Future<List<PalmRecord>> getRecordsByBatch(String batchId) async {
  82. Database db = await database;
  83. List<Map<String, dynamic>> maps = await db.query(
  84. 'history',
  85. where: 'batch_id = ?',
  86. whereArgs: [batchId],
  87. orderBy: 'timestamp ASC',
  88. );
  89. return List.generate(maps.length, (i) {
  90. return PalmRecord.fromMap(maps[i]);
  91. });
  92. }
  93. Future<int> deleteRecord(PalmRecord record) async {
  94. Database db = await database;
  95. await _deleteImageFile(record.imagePath);
  96. return await db.delete('history', where: 'id = ?', whereArgs: [record.id]);
  97. }
  98. Future<int> deleteBatch(String batchId) async {
  99. Database db = await database;
  100. final records = await getRecordsByBatch(batchId);
  101. for (final record in records) {
  102. await _deleteImageFile(record.imagePath);
  103. }
  104. return await db.delete('history', where: 'batch_id = ?', whereArgs: [batchId]);
  105. }
  106. Future<int> clearAllRecords() async {
  107. Database db = await database;
  108. final records = await getAllRecords();
  109. for (final record in records) {
  110. await _deleteImageFile(record.imagePath);
  111. }
  112. return await db.delete('history');
  113. }
  114. Future<int> insertCorrection(PalmCorrection correction) async {
  115. Database db = await database;
  116. return await db.insert('corrections', correction.toMap());
  117. }
  118. Future<int> deleteCorrectionsForRecord(int recordId) async {
  119. Database db = await database;
  120. return await db.delete('corrections', where: 'original_record_id = ?', whereArgs: [recordId]);
  121. }
  122. Future<PalmCorrection?> getLatestCorrection(int recordId) async {
  123. Database db = await database;
  124. List<Map<String, dynamic>> maps = await db.query(
  125. 'corrections',
  126. where: 'original_record_id = ?',
  127. whereArgs: [recordId],
  128. orderBy: 'timestamp DESC',
  129. limit: 1,
  130. );
  131. if (maps.isEmpty) return null;
  132. return PalmCorrection.fromMap(maps.first);
  133. }
  134. Future<void> _deleteImageFile(String imagePath) async {
  135. try {
  136. final file = File(imagePath);
  137. if (await file.exists()) {
  138. await file.delete();
  139. }
  140. } catch (_) {
  141. // A missing/locked file shouldn't block the DB delete.
  142. }
  143. }
  144. }