412 lines
12 KiB
Dart
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('================================');
|
|
}
|
|
}
|