From 0af483df239f3903aea1cac39edf21c9e182c714 Mon Sep 17 00:00:00 2001 From: vjrj <vjrj@comunes.org> Date: Wed, 5 Apr 2023 23:33:13 +0200 Subject: [PATCH] Memory fallback in contact cache --- lib/ui/contacts_cache.dart | 170 ++++++++++++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 10 deletions(-) diff --git a/lib/ui/contacts_cache.dart b/lib/ui/contacts_cache.dart index 8683fc14..8837f5a8 100644 --- a/lib/ui/contacts_cache.dart +++ b/lib/ui/contacts_cache.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; import 'dart:io'; @@ -22,13 +23,18 @@ class ContactsCache { Box<dynamic>? _box; Future<void> init() async { - if (kIsWeb) { - _box = await Hive.openBox(_boxName); - } else { - final Directory appDocDir = await getApplicationDocumentsDirectory(); - final String appDocPath = appDocDir.path; - _box = await Hive.openBox(_boxName, path: appDocPath); + try { + if (kIsWeb) { + _box = await Hive.openBox(_boxName); + } else { + final Directory appDocDir = await getApplicationDocumentsDirectory(); + final String appDocPath = appDocDir.path; + _box = await Hive.openBox(_boxName, path: appDocPath); + } + } catch (e) { + logger('Error opening Hive: $e'); } + _box ??= _MemoryFallbackBox<Contact>(); } Future<void> dispose() async { @@ -37,9 +43,9 @@ class ContactsCache { static ContactsCache? _instance; final Map<String, List<Completer<Contact>>> _pendingRequests = - <String, List<Completer<Contact>>>{}; + <String, List<Completer<Contact>>>{}; static Duration duration = - kReleaseMode ? const Duration(days: 3) : const Duration(hours: 5); + kReleaseMode ? const Duration(days: 3) : const Duration(hours: 5); final String _boxName = 'contacts_cache'; @@ -135,9 +141,9 @@ class ContactsCache { if (record != null) { final Map<String, dynamic> typedRecord = - Map<String, dynamic>.from(record as Map<dynamic, dynamic>); + Map<String, dynamic>.from(record as Map<dynamic, dynamic>); final DateTime timestamp = - DateTime.parse(typedRecord['timestamp'] as String); + DateTime.parse(typedRecord['timestamp'] as String); final bool before = DateTime.now().isBefore(timestamp.add(duration)); if (before) { final Contact contact = Contact.fromJson( @@ -148,3 +154,147 @@ class ContactsCache { return null; } } + +class _MemoryFallbackBox<E> extends Box<E> { + final Map<String, dynamic> _storage = HashMap<String, dynamic>(); + + @override + String get name => '_memory_fallback_box'; + + @override + bool get isOpen => true; + + @override + String? get path => null; + + @override + bool get lazy => false; + + @override + Iterable<dynamic> get keys => _storage.keys; + + @override + int get length => _storage.length; + + @override + bool get isEmpty => _storage.isEmpty; + + @override + bool get isNotEmpty => _storage.isNotEmpty; + + @override + dynamic keyAt(int index) { + return _storage.keys.elementAt(index); + } + + @override + Stream<BoxEvent> watch({dynamic key}) { + throw UnimplementedError('watch() is not supported in _MemoryFallbackBox'); + } + + @override + bool containsKey(dynamic key) { + return _storage.containsKey(key); + } + + @override + Future<void> put(dynamic key, E value) async { + _storage[key as String] = value; + } + + @override + Future<void> putAt(int index, E value) async { + _storage[_storage.keys.elementAt(index)] = value; + } + + @override + Future<void> putAll(Map<dynamic, E> entries) async { + _storage.addAll(entries as Map<String, dynamic>); + } + + @override + Future<int> add(E value) async { + throw UnimplementedError('add() is not supported in _MemoryFallbackBox'); + } + + @override + Future<Iterable<int>> addAll(Iterable<E> values) async { + throw UnimplementedError('addAll() is not supported in _MemoryFallbackBox'); + } + + @override + Future<void> delete(dynamic key) async { + _storage.remove(key); + } + + @override + Future<void> deleteAt(int index) async { + _storage.remove(_storage.keys.elementAt(index)); + } + + @override + Future<void> deleteAll(Iterable<dynamic> keys) async { + // ignore: prefer_foreach + for (final dynamic key in keys) { + _storage.remove(key); + } + } + + @override + Future<void> compact() async {} + + @override + Future<int> clear() async { + final int count = _storage.length; + _storage.clear(); + return count; + } + + @override + Future<void> close() async {} + + @override + Future<void> deleteFromDisk() async {} + + @override + Future<void> flush() async {} + + @override + E? get(dynamic key, {E? defaultValue}) { + return _storage.containsKey(key) ? _storage[key] as E : defaultValue; + } + + @override + E? getAt(int index) { + return _storage.values.elementAt(index) as E?; + } + + @override + Map<dynamic, E> toMap() { + return Map<dynamic, E>.from(_storage); + } + + @override + Iterable<E> get values => _storage.values.cast<E>(); + + @override + Iterable<E> valuesBetween({dynamic startKey, dynamic endKey}) { + if (startKey == null && endKey == null) { + return values; + } + + final int startIndex = startKey != null ? _storage.keys.toList().indexOf( + startKey as String) : 0; + final int endIndex = endKey != null ? _storage.keys.toList().indexOf( + endKey as String) : _storage.length - 1; + + if (startIndex < 0 || endIndex < 0) { + throw ArgumentError('Start key or end key not found in the box.'); + } + + return _storage.values.skip(startIndex) + .take(endIndex - startIndex + 1) + .cast<E>(); + } + +} -- GitLab