runable
This commit is contained in:
409
lib/core/database/hive_service.dart
Normal file
409
lib/core/database/hive_service.dart
Normal file
@@ -0,0 +1,409 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
import 'package:worker/hive_registrar.g.dart';
|
||||
|
||||
/// Hive CE (Community Edition) Database Service
|
||||
///
|
||||
/// This service manages the initialization, configuration, and lifecycle
|
||||
/// of the Hive database for offline-first functionality.
|
||||
///
|
||||
/// Features:
|
||||
/// - Box initialization and registration
|
||||
/// - Type adapter registration
|
||||
/// - Encryption support
|
||||
/// - Database compaction
|
||||
/// - Migration handling
|
||||
/// - Error recovery
|
||||
class HiveService {
|
||||
HiveService._internal();
|
||||
|
||||
// Singleton pattern
|
||||
factory HiveService() => _instance;
|
||||
|
||||
static final HiveService _instance = HiveService._internal();
|
||||
|
||||
/// Indicates whether Hive has been initialized
|
||||
bool _isInitialized = false;
|
||||
bool get isInitialized => _isInitialized;
|
||||
|
||||
/// Encryption cipher (if enabled)
|
||||
HiveAesCipher? _encryptionCipher;
|
||||
|
||||
/// Initialize Hive database
|
||||
///
|
||||
/// This should be called once during app startup, before any
|
||||
/// Hive operations are performed.
|
||||
///
|
||||
/// [encryptionKey] - Optional 256-bit encryption key for secure storage
|
||||
/// If not provided and encryption is enabled, a new key will be generated.
|
||||
Future<void> initialize({List<int>? encryptionKey}) async {
|
||||
if (_isInitialized) {
|
||||
debugPrint('HiveService: Already initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
debugPrint('HiveService: Initializing Hive CE...');
|
||||
|
||||
// Initialize Hive for Flutter
|
||||
await Hive.initFlutter();
|
||||
|
||||
// Setup encryption if enabled
|
||||
if (HiveDatabaseConfig.enableEncryption) {
|
||||
_encryptionCipher = HiveAesCipher(
|
||||
encryptionKey ?? Hive.generateSecureKey(),
|
||||
);
|
||||
debugPrint('HiveService: Encryption enabled');
|
||||
}
|
||||
|
||||
// Register all type adapters
|
||||
await _registerTypeAdapters();
|
||||
|
||||
// Open all boxes
|
||||
await _openBoxes();
|
||||
|
||||
// Check and perform migrations if needed
|
||||
await _performMigrations();
|
||||
|
||||
// Perform initial cleanup/compaction if needed
|
||||
await _performMaintenance();
|
||||
|
||||
_isInitialized = true;
|
||||
debugPrint('HiveService: Initialization complete');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Initialization failed: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Register all Hive type adapters
|
||||
///
|
||||
/// Type adapters must be registered before opening boxes.
|
||||
/// Uses auto-generated registrar from hive_registrar.g.dart
|
||||
Future<void> _registerTypeAdapters() async {
|
||||
debugPrint('HiveService: Registering type adapters...');
|
||||
|
||||
// Register all adapters using the auto-generated extension
|
||||
// This automatically registers:
|
||||
// - CachedDataAdapter (typeId: 30)
|
||||
// - All enum adapters (typeIds: 20-29)
|
||||
Hive.registerAdapters();
|
||||
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.memberTier) ? "✓" : "✗"} MemberTier adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.userType) ? "✓" : "✗"} UserType adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.orderStatus) ? "✓" : "✗"} OrderStatus adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.projectStatus) ? "✓" : "✗"} ProjectStatus adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.projectType) ? "✓" : "✗"} ProjectType adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.transactionType) ? "✓" : "✗"} TransactionType adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.giftStatus) ? "✓" : "✗"} GiftStatus adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.paymentStatus) ? "✓" : "✗"} PaymentStatus adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.notificationType) ? "✓" : "✗"} NotificationType adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.paymentMethod) ? "✓" : "✗"} PaymentMethod adapter');
|
||||
debugPrint('HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.cachedData) ? "✓" : "✗"} CachedData adapter');
|
||||
|
||||
// TODO: Register actual model type adapters when models are created
|
||||
// These will be added to the auto-generated registrar when models are created
|
||||
// Example:
|
||||
// - UserModel (typeId: 0)
|
||||
// - ProductModel (typeId: 1)
|
||||
// - CartItemModel (typeId: 2)
|
||||
// - OrderModel (typeId: 3)
|
||||
// - ProjectModel (typeId: 4)
|
||||
// - LoyaltyTransactionModel (typeId: 5)
|
||||
// etc.
|
||||
|
||||
debugPrint('HiveService: Type adapters registered successfully');
|
||||
}
|
||||
|
||||
/// Open all Hive boxes
|
||||
///
|
||||
/// Opens boxes for immediate access. Some boxes use encryption if enabled.
|
||||
Future<void> _openBoxes() async {
|
||||
debugPrint('HiveService: Opening boxes...');
|
||||
|
||||
try {
|
||||
// Open non-encrypted boxes
|
||||
await Future.wait([
|
||||
// Settings and preferences (non-sensitive)
|
||||
Hive.openBox<dynamic>(HiveBoxNames.settingsBox),
|
||||
|
||||
// Cache boxes (non-sensitive)
|
||||
Hive.openBox<dynamic>(HiveBoxNames.cacheBox),
|
||||
Hive.openBox<dynamic>(HiveBoxNames.syncStateBox),
|
||||
|
||||
// Product and catalog data (non-sensitive)
|
||||
Hive.openBox<dynamic>(HiveBoxNames.productBox),
|
||||
Hive.openBox<dynamic>(HiveBoxNames.rewardsBox),
|
||||
|
||||
// Notification box (non-sensitive)
|
||||
Hive.openBox<dynamic>(HiveBoxNames.notificationBox),
|
||||
]);
|
||||
|
||||
// Open potentially encrypted boxes (sensitive data)
|
||||
final encryptedBoxes = [
|
||||
HiveBoxNames.userBox,
|
||||
HiveBoxNames.cartBox,
|
||||
HiveBoxNames.orderBox,
|
||||
HiveBoxNames.projectBox,
|
||||
HiveBoxNames.loyaltyBox,
|
||||
HiveBoxNames.addressBox,
|
||||
HiveBoxNames.offlineQueueBox,
|
||||
];
|
||||
|
||||
for (final boxName in encryptedBoxes) {
|
||||
await Hive.openBox<dynamic>(
|
||||
boxName,
|
||||
encryptionCipher: _encryptionCipher,
|
||||
);
|
||||
}
|
||||
|
||||
debugPrint('HiveService: All boxes opened successfully');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Error opening boxes: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform database migrations
|
||||
///
|
||||
/// Handles schema version upgrades and data migrations.
|
||||
Future<void> _performMigrations() async {
|
||||
final settingsBox = Hive.box<dynamic>(HiveBoxNames.settingsBox);
|
||||
final currentVersion = settingsBox.get(
|
||||
HiveKeys.schemaVersion,
|
||||
defaultValue: 0,
|
||||
) as int;
|
||||
|
||||
debugPrint('HiveService: Current schema version: $currentVersion');
|
||||
debugPrint('HiveService: Target schema version: ${HiveDatabaseConfig.currentSchemaVersion}');
|
||||
|
||||
if (currentVersion < HiveDatabaseConfig.currentSchemaVersion) {
|
||||
debugPrint('HiveService: Performing migrations...');
|
||||
|
||||
// Perform migrations sequentially
|
||||
for (int version = currentVersion + 1;
|
||||
version <= HiveDatabaseConfig.currentSchemaVersion;
|
||||
version++) {
|
||||
await _migrateToVersion(version);
|
||||
}
|
||||
|
||||
// Update schema version
|
||||
await settingsBox.put(
|
||||
HiveKeys.schemaVersion,
|
||||
HiveDatabaseConfig.currentSchemaVersion,
|
||||
);
|
||||
|
||||
debugPrint('HiveService: Migrations complete');
|
||||
} else {
|
||||
debugPrint('HiveService: No migrations needed');
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrate to a specific version
|
||||
Future<void> _migrateToVersion(int version) async {
|
||||
debugPrint('HiveService: Migrating to version $version');
|
||||
|
||||
switch (version) {
|
||||
case 1:
|
||||
// Initial version - no migration needed
|
||||
break;
|
||||
|
||||
// Future migrations will be added here
|
||||
// case 2:
|
||||
// await _migrateV1ToV2();
|
||||
// break;
|
||||
|
||||
default:
|
||||
debugPrint('HiveService: Unknown migration version: $version');
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform database maintenance
|
||||
///
|
||||
/// Includes compaction, cleanup of expired cache, etc.
|
||||
Future<void> _performMaintenance() async {
|
||||
debugPrint('HiveService: Performing maintenance...');
|
||||
|
||||
try {
|
||||
// Compact boxes if needed
|
||||
await _compactBoxes();
|
||||
|
||||
// Clear expired cache
|
||||
await _clearExpiredCache();
|
||||
|
||||
// Limit offline queue size
|
||||
await _limitOfflineQueue();
|
||||
|
||||
debugPrint('HiveService: Maintenance complete');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Maintenance error: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
// Don't throw - maintenance errors shouldn't prevent app startup
|
||||
}
|
||||
}
|
||||
|
||||
/// Compact boxes to reduce file size
|
||||
Future<void> _compactBoxes() async {
|
||||
for (final boxName in HiveBoxNames.allBoxes) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
final box = Hive.box<dynamic>(boxName);
|
||||
await box.compact();
|
||||
debugPrint('HiveService: Compacted box: $boxName');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('HiveService: Error compacting box $boxName: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear expired cache entries
|
||||
Future<void> _clearExpiredCache() async {
|
||||
final cacheBox = Hive.box<dynamic>(HiveBoxNames.cacheBox);
|
||||
|
||||
// TODO: Implement cache expiration logic
|
||||
// This will be implemented when cache models are created
|
||||
|
||||
debugPrint('HiveService: Cleared expired cache entries');
|
||||
}
|
||||
|
||||
/// Limit offline queue size
|
||||
Future<void> _limitOfflineQueue() async {
|
||||
final queueBox = Hive.box<dynamic>(HiveBoxNames.offlineQueueBox);
|
||||
|
||||
if (queueBox.length > HiveDatabaseConfig.maxOfflineQueueSize) {
|
||||
final itemsToRemove = queueBox.length - HiveDatabaseConfig.maxOfflineQueueSize;
|
||||
|
||||
// Remove oldest items
|
||||
for (int i = 0; i < itemsToRemove; i++) {
|
||||
await queueBox.deleteAt(0);
|
||||
}
|
||||
|
||||
debugPrint('HiveService: Removed $itemsToRemove old items from offline queue');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a box by name
|
||||
///
|
||||
/// Returns an already opened box. Throws if box is not open.
|
||||
Box<T> getBox<T>(String boxName) {
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
throw HiveError('Box $boxName is not open');
|
||||
}
|
||||
return Hive.box<T>(boxName);
|
||||
}
|
||||
|
||||
/// Check if a box is open
|
||||
bool isBoxOpen(String boxName) {
|
||||
return Hive.isBoxOpen(boxName);
|
||||
}
|
||||
|
||||
/// Clear all data from all boxes
|
||||
///
|
||||
/// WARNING: This will delete all local data. Use with caution.
|
||||
Future<void> clearAllData() async {
|
||||
debugPrint('HiveService: Clearing all data...');
|
||||
|
||||
for (final boxName in HiveBoxNames.allBoxes) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
final box = Hive.box<dynamic>(boxName);
|
||||
await box.clear();
|
||||
debugPrint('HiveService: Cleared box: $boxName');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('HiveService: Error clearing box $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('HiveService: All data cleared');
|
||||
}
|
||||
|
||||
/// Clear user-specific data (logout)
|
||||
///
|
||||
/// Clears user data while preserving app settings and cache
|
||||
Future<void> clearUserData() async {
|
||||
debugPrint('HiveService: Clearing user data...');
|
||||
|
||||
final boxesToClear = [
|
||||
HiveBoxNames.userBox,
|
||||
HiveBoxNames.cartBox,
|
||||
HiveBoxNames.orderBox,
|
||||
HiveBoxNames.projectBox,
|
||||
HiveBoxNames.loyaltyBox,
|
||||
HiveBoxNames.addressBox,
|
||||
HiveBoxNames.notificationBox,
|
||||
];
|
||||
|
||||
for (final boxName in boxesToClear) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
final box = Hive.box<dynamic>(boxName);
|
||||
await box.clear();
|
||||
debugPrint('HiveService: Cleared box: $boxName');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('HiveService: Error clearing box $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('HiveService: User data cleared');
|
||||
}
|
||||
|
||||
/// Close all boxes
|
||||
///
|
||||
/// Should be called when app is terminating
|
||||
Future<void> close() async {
|
||||
debugPrint('HiveService: Closing all boxes...');
|
||||
|
||||
try {
|
||||
await Hive.close();
|
||||
_isInitialized = false;
|
||||
debugPrint('HiveService: All boxes closed');
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Error closing boxes: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete all Hive data from disk
|
||||
///
|
||||
/// WARNING: This completely removes the database. Use only for testing or reset.
|
||||
Future<void> deleteFromDisk() async {
|
||||
debugPrint('HiveService: Deleting database from disk...');
|
||||
|
||||
try {
|
||||
// Close all boxes first
|
||||
await close();
|
||||
|
||||
// Delete Hive directory
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
final hiveDir = Directory('${appDocDir.path}/hive');
|
||||
|
||||
if (await hiveDir.exists()) {
|
||||
await hiveDir.delete(recursive: true);
|
||||
debugPrint('HiveService: Database deleted from disk');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
debugPrint('HiveService: Error deleting database: $e');
|
||||
debugPrint('StackTrace: $stackTrace');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a secure encryption key
|
||||
///
|
||||
/// Returns a 256-bit encryption key for secure box encryption.
|
||||
/// Store this key securely (e.g., in flutter_secure_storage).
|
||||
static List<int> generateEncryptionKey() {
|
||||
return Hive.generateSecureKey();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user