runable
This commit is contained in:
411
lib/core/database/database_manager.dart
Normal file
411
lib/core/database/database_manager.dart
Normal file
@@ -0,0 +1,411 @@
|
||||
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('================================');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user