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 initialize({List? 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 _registerTypeAdapters() async { debugPrint('HiveService: Registering type adapters...'); // Register all adapters using the auto-generated extension // This automatically registers all model and enum adapters Hive.registerAdapters(); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.loyaltyTier) ? "✓" : "✗"} LoyaltyTier adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.userRole) ? "✓" : "✗"} UserRole adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.orderStatus) ? "✓" : "✗"} OrderStatus adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.orderStatusModel) ? "✓" : "✗"} OrderStatusModel adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.projectType) ? "✓" : "✗"} ProjectType adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.projectStatusModel) ? "✓" : "✗"} ProjectStatusModel adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.projectProgressModel) ? "✓" : "✗"} ProjectProgressModel adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.entryType) ? "✓" : "✗"} EntryType adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.giftStatus) ? "✓" : "✗"} GiftStatus adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.paymentStatus) ? "✓" : "✗"} PaymentStatus adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.paymentMethod) ? "✓" : "✗"} PaymentMethod adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.cachedData) ? "✓" : "✗"} CachedData adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.favoriteModel) ? "✓" : "✗"} FavoriteModel adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.productModel) ? "✓" : "✗"} ProductModel adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.userModel) ? "✓" : "✗"} UserModel adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.cityModel) ? "✓" : "✗"} CityModel adapter', ); debugPrint( 'HiveService: ${Hive.isAdapterRegistered(HiveTypeIds.wardModel) ? "✓" : "✗"} WardModel adapter', ); debugPrint('HiveService: Type adapters registered successfully'); } /// Open all Hive boxes /// /// Opens boxes for immediate access. Some boxes use encryption if enabled. Future _openBoxes() async { debugPrint('HiveService: Opening boxes...'); try { // Open non-encrypted boxes await Future.wait([ // Settings and preferences (non-sensitive) Hive.openBox(HiveBoxNames.settingsBox), // Cache boxes (non-sensitive) Hive.openBox(HiveBoxNames.cacheBox), Hive.openBox(HiveBoxNames.syncStateBox), // Product and catalog data (non-sensitive) Hive.openBox(HiveBoxNames.productBox), Hive.openBox(HiveBoxNames.rewardsBox), // Notification box (non-sensitive) Hive.openBox(HiveBoxNames.notificationBox), // Favorite products box (non-sensitive) - caches Product entities from wishlist API Hive.openBox(HiveBoxNames.favoriteProductsBox), // Location boxes (non-sensitive) - caches cities and wards for address forms Hive.openBox(HiveBoxNames.cityBox), Hive.openBox(HiveBoxNames.wardBox), // Order status box (non-sensitive) - caches order status list from API Hive.openBox(HiveBoxNames.orderStatusBox), // Project status box (non-sensitive) - caches project status list from API Hive.openBox(HiveBoxNames.projectStatusBox), // Project progress box (non-sensitive) - caches construction progress stages from API Hive.openBox(HiveBoxNames.projectProgressBox), ]); // 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( 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 _performMigrations() async { final settingsBox = Hive.box(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 _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 _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 _compactBoxes() async { for (final boxName in HiveBoxNames.allBoxes) { try { if (Hive.isBoxOpen(boxName)) { final box = Hive.box(boxName); await box.compact(); debugPrint('HiveService: Compacted box: $boxName'); } } catch (e) { debugPrint('HiveService: Error compacting box $boxName: $e'); } } } /// Clear expired cache entries Future _clearExpiredCache() async { // TODO: Implement cache expiration logic // This will be implemented when cache models are created // final cacheBox = Hive.box(HiveBoxNames.cacheBox); debugPrint('HiveService: Cleared expired cache entries'); } /// Limit offline queue size Future _limitOfflineQueue() async { final queueBox = Hive.box(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 getBox(String boxName) { if (!Hive.isBoxOpen(boxName)) { throw HiveError('Box $boxName is not open'); } return Hive.box(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 clearAllData() async { debugPrint('HiveService: Clearing all data...'); for (final boxName in HiveBoxNames.allBoxes) { try { if (Hive.isBoxOpen(boxName)) { final box = Hive.box(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 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(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 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 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 generateEncryptionKey() { return Hive.generateSecureKey(); } }