Files
worker/lib/core/database/database_manager.dart
Phuoc Nguyen 628c81ce13 runable
2025-10-17 17:22:28 +07:00

412 lines
12 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:hive_ce/hive.dart';
import 'package:worker/core/constants/storage_constants.dart';
import 'package:worker/core/database/hive_service.dart';
/// Database Manager for common Hive operations
///
/// Provides high-level database operations and utilities for working
/// with Hive boxes across the application.
///
/// Features:
/// - CRUD operations with error handling
/// - Cache management with expiration
/// - Bulk operations
/// - Data validation
/// - Sync state tracking
class DatabaseManager {
DatabaseManager({HiveService? hiveService})
: _hiveService = hiveService ?? HiveService();
final HiveService _hiveService;
/// Get a box safely
Box<T> _getBox<T>(String boxName) {
if (!_hiveService.isBoxOpen(boxName)) {
throw HiveError('Box $boxName is not open. Initialize HiveService first.');
}
return _hiveService.getBox<T>(boxName);
}
// ==================== Generic CRUD Operations ====================
/// Save a value to a box
Future<void> save<T>({
required String boxName,
required String key,
required T value,
}) async {
try {
final box = _getBox<T>(boxName);
await box.put(key, value);
debugPrint('DatabaseManager: Saved $key to $boxName');
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error saving $key to $boxName: $e');
debugPrint('StackTrace: $stackTrace');
rethrow;
}
}
/// Get a value from a box
T? get<T>({
required String boxName,
required String key,
T? defaultValue,
}) {
try {
final box = _getBox<T>(boxName);
return box.get(key, defaultValue: defaultValue);
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error getting $key from $boxName: $e');
debugPrint('StackTrace: $stackTrace');
return defaultValue;
}
}
/// Delete a value from a box
Future<void> delete({
required String boxName,
required String key,
}) async {
try {
final box = _getBox<dynamic>(boxName);
await box.delete(key);
debugPrint('DatabaseManager: Deleted $key from $boxName');
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error deleting $key from $boxName: $e');
debugPrint('StackTrace: $stackTrace');
rethrow;
}
}
/// Check if a key exists in a box
bool exists({
required String boxName,
required String key,
}) {
try {
final box = _getBox<dynamic>(boxName);
return box.containsKey(key);
} catch (e) {
debugPrint('DatabaseManager: Error checking $key in $boxName: $e');
return false;
}
}
/// Get all values from a box
List<T> getAll<T>({required String boxName}) {
try {
final box = _getBox<T>(boxName);
return box.values.toList();
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error getting all from $boxName: $e');
debugPrint('StackTrace: $stackTrace');
return [];
}
}
/// Save multiple values to a box
Future<void> saveAll<T>({
required String boxName,
required Map<String, T> entries,
}) async {
try {
final box = _getBox<T>(boxName);
await box.putAll(entries);
debugPrint('DatabaseManager: Saved ${entries.length} items to $boxName');
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error saving all to $boxName: $e');
debugPrint('StackTrace: $stackTrace');
rethrow;
}
}
/// Clear all data from a box
Future<void> clearBox({required String boxName}) async {
try {
final box = _getBox<dynamic>(boxName);
await box.clear();
debugPrint('DatabaseManager: Cleared $boxName');
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error clearing $boxName: $e');
debugPrint('StackTrace: $stackTrace');
rethrow;
}
}
// ==================== Cache Operations ====================
/// Save data to cache with timestamp
Future<void> saveToCache<T>({
required String key,
required T data,
}) async {
try {
final cacheBox = _getBox<dynamic>(HiveBoxNames.cacheBox);
await cacheBox.put(key, {
'data': data,
'timestamp': DateTime.now().toIso8601String(),
});
debugPrint('DatabaseManager: Cached $key');
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error caching $key: $e');
debugPrint('StackTrace: $stackTrace');
rethrow;
}
}
/// Get data from cache
///
/// Returns null if cache is expired or doesn't exist
T? getFromCache<T>({
required String key,
Duration? maxAge,
}) {
try {
final cacheBox = _getBox<dynamic>(HiveBoxNames.cacheBox);
final cachedData = cacheBox.get(key) as Map<dynamic, dynamic>?;
if (cachedData == null) {
debugPrint('DatabaseManager: Cache miss for $key');
return null;
}
// Check if cache is expired
if (maxAge != null) {
final timestamp = DateTime.parse(cachedData['timestamp'] as String);
final age = DateTime.now().difference(timestamp);
if (age > maxAge) {
debugPrint('DatabaseManager: Cache expired for $key (age: $age)');
return null;
}
}
debugPrint('DatabaseManager: Cache hit for $key');
return cachedData['data'] as T?;
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error getting cache $key: $e');
debugPrint('StackTrace: $stackTrace');
return null;
}
}
/// Check if cache is valid (exists and not expired)
bool isCacheValid({
required String key,
Duration? maxAge,
}) {
try {
final cacheBox = _getBox<dynamic>(HiveBoxNames.cacheBox);
final cachedData = cacheBox.get(key) as Map<dynamic, dynamic>?;
if (cachedData == null) return false;
if (maxAge != null) {
final timestamp = DateTime.parse(cachedData['timestamp'] as String);
final age = DateTime.now().difference(timestamp);
return age <= maxAge;
}
return true;
} catch (e) {
debugPrint('DatabaseManager: Error checking cache validity $key: $e');
return false;
}
}
/// Clear expired cache entries
Future<void> clearExpiredCache() async {
try {
final cacheBox = _getBox<dynamic>(HiveBoxNames.cacheBox);
final keysToDelete = <String>[];
for (final key in cacheBox.keys) {
final cachedData = cacheBox.get(key) as Map<dynamic, dynamic>?;
if (cachedData != null) {
try {
final timestamp = DateTime.parse(cachedData['timestamp'] as String);
final age = DateTime.now().difference(timestamp);
// Use default max age of 24 hours
if (age > const Duration(hours: 24)) {
keysToDelete.add(key as String);
}
} catch (e) {
// Invalid cache entry, mark for deletion
keysToDelete.add(key as String);
}
}
}
for (final key in keysToDelete) {
await cacheBox.delete(key);
}
debugPrint('DatabaseManager: Cleared ${keysToDelete.length} expired cache entries');
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error clearing expired cache: $e');
debugPrint('StackTrace: $stackTrace');
}
}
// ==================== Sync State Operations ====================
/// Update sync timestamp for a data type
Future<void> updateSyncTime(String dataType) async {
try {
final syncBox = _getBox<dynamic>(HiveBoxNames.syncStateBox);
await syncBox.put(dataType, DateTime.now().toIso8601String());
debugPrint('DatabaseManager: Updated sync time for $dataType');
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error updating sync time for $dataType: $e');
debugPrint('StackTrace: $stackTrace');
}
}
/// Get last sync time for a data type
DateTime? getLastSyncTime(String dataType) {
try {
final syncBox = _getBox<dynamic>(HiveBoxNames.syncStateBox);
final timestamp = syncBox.get(dataType);
if (timestamp == null) return null;
return DateTime.parse(timestamp as String);
} catch (e) {
debugPrint('DatabaseManager: Error getting sync time for $dataType: $e');
return null;
}
}
/// Check if data needs sync
bool needsSync({
required String dataType,
required Duration syncInterval,
}) {
final lastSync = getLastSyncTime(dataType);
if (lastSync == null) return true;
final timeSinceSync = DateTime.now().difference(lastSync);
return timeSinceSync > syncInterval;
}
// ==================== Settings Operations ====================
/// Save a setting
Future<void> saveSetting<T>({
required String key,
required T value,
}) async {
await save(
boxName: HiveBoxNames.settingsBox,
key: key,
value: value,
);
}
/// Get a setting
T? getSetting<T>({
required String key,
T? defaultValue,
}) {
return get(
boxName: HiveBoxNames.settingsBox,
key: key,
defaultValue: defaultValue,
);
}
// ==================== Offline Queue Operations ====================
/// Add request to offline queue
Future<void> addToOfflineQueue(Map<String, dynamic> request) async {
try {
final queueBox = _getBox<dynamic>(HiveBoxNames.offlineQueueBox);
// Check queue size limit
if (queueBox.length >= HiveDatabaseConfig.maxOfflineQueueSize) {
debugPrint('DatabaseManager: Offline queue is full, removing oldest item');
await queueBox.deleteAt(0);
}
await queueBox.add({
...request,
'timestamp': DateTime.now().toIso8601String(),
});
debugPrint('DatabaseManager: Added request to offline queue');
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error adding to offline queue: $e');
debugPrint('StackTrace: $stackTrace');
rethrow;
}
}
/// Get all offline queue items
List<Map<String, dynamic>> getOfflineQueue() {
try {
final queueBox = _getBox<dynamic>(HiveBoxNames.offlineQueueBox);
return queueBox.values
.map((e) => Map<String, dynamic>.from(e as Map<dynamic, dynamic>))
.toList();
} catch (e) {
debugPrint('DatabaseManager: Error getting offline queue: $e');
return [];
}
}
/// Remove item from offline queue
Future<void> removeFromOfflineQueue(int index) async {
try {
final queueBox = _getBox<dynamic>(HiveBoxNames.offlineQueueBox);
await queueBox.deleteAt(index);
debugPrint('DatabaseManager: Removed item $index from offline queue');
} catch (e, stackTrace) {
debugPrint('DatabaseManager: Error removing from offline queue: $e');
debugPrint('StackTrace: $stackTrace');
rethrow;
}
}
/// Clear offline queue
Future<void> clearOfflineQueue() async {
await clearBox(boxName: HiveBoxNames.offlineQueueBox);
}
// ==================== Statistics ====================
/// Get database statistics
Map<String, dynamic> getStatistics() {
final stats = <String, dynamic>{};
for (final boxName in HiveBoxNames.allBoxes) {
try {
if (_hiveService.isBoxOpen(boxName)) {
final box = _getBox<dynamic>(boxName);
stats[boxName] = {
'count': box.length,
'keys': box.keys.length,
};
}
} catch (e) {
stats[boxName] = {'error': e.toString()};
}
}
return stats;
}
/// Print database statistics
void printStatistics() {
final stats = getStatistics();
debugPrint('=== Hive Database Statistics ===');
stats.forEach((boxName, data) {
debugPrint('$boxName: $data');
});
debugPrint('================================');
}
}