putAll<T> method

Future<void> putAll<T>(
  1. Map<String, T> entries, {
  2. Duration? expiry,
  3. Duration? slidingExpiry,
  4. CachePolicy? policy,
  5. Set<String>? tags,
})

Stores multiple values in the cache with the given keys.

The entries parameter is a map where the keys are the cache keys and the values are the values to store. The expiry parameter can be used to set an optional expiry time for all the data. The slidingExpiry parameter can be used to set an optional sliding expiry time for all the data. The policy parameter can be used to set a cache policy for all the data. The tags parameter can be used to associate tags with all the data for easier retrieval and management.

If both individual parameters (expiry, slidingExpiry) and a policy are provided, the individual parameters will take precedence over the policy.

Throws a CacheException if there is an error storing the data.

Implementation

Future<void> putAll<T>(
  Map<String, T> entries, {
  Duration? expiry,
  Duration? slidingExpiry,
  CachePolicy? policy,
  Set<String>? tags,
}) async {
  try {
    if (entries.isEmpty) {
      return;
    }

    // Check for empty keys
    for (final entry in entries.entries) {
      if (entry.key.isEmpty) {
        throw ArgumentError('Keys cannot be empty');
      }
    }

    final effectivePolicy = policy ?? CachePolicy.defaultPolicy;
    final effectiveExpiry = expiry ?? effectivePolicy.expiry;
    final effectiveSlidingExpiry =
        slidingExpiry ?? effectivePolicy.slidingExpiry;

    // For large datasets, process in isolate
    if (entries.length > 50) {
      _log.fine('Processing ${entries.length} items in isolate');

      // Prepare the batch data
      final batchData = BatchProcessData<T>(
        entries: entries,
        policy: effectivePolicy,
        expiry: effectiveExpiry,
        slidingExpiry: effectiveSlidingExpiry,
        tags: tags,
      );

      // Process in isolate
      final cacheItems = await IsolateRunner.run<BatchProcessData<T>,
          Map<String, CacheItem<dynamic>>>(
        function: _processBatchData,
        message: batchData,
      );

      if (cacheItems.isEmpty) {
        _log.warning('No items to cache after processing in isolate');
        return;
      }

      // Store the processed items
      await _cacheAdapter.putAll(cacheItems);

      // Check if we need to evict items
      if (_eviction != null) {
        await _eviction.checkAndEvict();
      }

      return;
    }

    // For smaller datasets, process normally
    final cacheItems = <String, CacheItem<dynamic>>{};
    final now = DateTime.now();
    final expiryTime =
        effectiveExpiry != null ? now.add(effectiveExpiry) : null;

    for (final entry in entries.entries) {
      final key = entry.key;
      final value = entry.value;

      // Initialize compression variables
      bool isCompressed = false;
      int? originalSize;
      double? compressionRatio;
      dynamic finalValue = value;

      // Check if compression should be applied
      if (effectivePolicy.compression != CompressionMode.never &&
          _compression != null &&
          value is String) {
        // For auto mode, check if compression is beneficial
        if (effectivePolicy.compression == CompressionMode.auto) {
          if (_compression.shouldCompress(value)) {
            // Compress the value
            originalSize =
                value.length * 2; // Rough estimate: 2 bytes per character
            final compressedValue = _compression.compressString(value);
            compressionRatio = originalSize / (compressedValue.length * 2);

            // Only use compression if it actually reduces the size
            if (compressionRatio > 1.1) {
              // At least 10% reduction
              finalValue = compressedValue;
              isCompressed = true;
              _log.fine(
                  'Compressed value for key $key with ratio $compressionRatio');
            }
          }
        } else if (effectivePolicy.compression == CompressionMode.always) {
          // Always compress
          originalSize = value.length * 2;
          final compressedValue = _compression.compressString(value);
          compressionRatio = originalSize / (compressedValue.length * 2);
          finalValue = compressedValue;
          isCompressed = true;
          _log.fine(
              'Compressed value for key $key with ratio $compressionRatio');
        }
      }

      final cacheItem = CacheItem<T>(
        value: finalValue as T,
        expiry: expiryTime,
        slidingExpiry: effectiveSlidingExpiry,
        priority: effectivePolicy.priority,
        isCompressed: isCompressed,
        originalSize: originalSize,
        compressionRatio: compressionRatio,
        tags: tags,
      );

      // Check if the item exceeds the maximum size (if specified)
      if (effectivePolicy.maxSize != null) {
        // Use the SizeEstimator for more accurate size estimation
        final estimatedSize = SizeEstimator.estimateCacheItemSize(
          finalValue,
          hasExpiry: expiryTime != null,
          hasSlidingExpiry: effectiveSlidingExpiry != null,
          isCompressed: isCompressed,
          originalSize: originalSize,
        );

        if (estimatedSize > effectivePolicy.maxSize!) {
          _log.warning(
              'Item exceeds maximum size: $estimatedSize > ${effectivePolicy.maxSize}');
          // Skip this item but continue with others
          continue;
        }
      }

      cacheItems[key] = cacheItem;
    }

    if (cacheItems.isEmpty) {
      _log.warning('No items to cache after size filtering');
      return;
    }

    await _cacheAdapter.putAll(cacheItems);

    // Check if we need to evict items
    if (_eviction != null) {
      await _eviction.checkAndEvict();
    }
  } on HiveError catch (e) {
    _log.severe('Failed to put data into cache (HiveError): $e');
    throw CacheException('Failed to put data into cache: ${e.message}');
  } catch (e) {
    _log.severe('Failed to put data into cache (Unknown Error): $e');
    throw CacheException('Failed to put data into cache: $e');
  }
}