Files
worker/lib/core/database/hive_service.dart
Phuoc Nguyen b27c5d7742 favorite
2025-10-24 16:20:48 +07:00

413 lines
13 KiB
Dart

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),
// Favorites box (non-sensitive)
Hive.openBox<dynamic>(HiveBoxNames.favoriteBox),
]);
// 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();
}
}