import 'dart:convert'; import 'dart:io'; import 'package:comunic/constants.dart'; import 'package:comunic/utils/flutter_utils.dart'; import 'package:comunic/utils/log_utils.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; /// Base serialization helper /// /// @author Pierre Hubert abstract class SerializableElement<T> extends Comparable<T> { Map<String, dynamic> toJson(); } abstract class BaseSerializationHelper<T extends SerializableElement> { /// List cache List<T> _cache; /// The name of the type of data to serialise String get type; /// Parse an json entry into a [T] object T parse(Map<String, dynamic> m); /// Get the file where data should be stored Future<File> _getFilePath() async { final dir = await getApplicationDocumentsDirectory(); final targetDir = Directory(path.join(dir.absolute.path, SERIALIZATION_DIRECTORY)); targetDir.create(recursive: true); return File(path.join(targetDir.absolute.path, type)); } /// Load the cache Future<void> _loadCache() async { if (_cache != null) return; if (isWeb) { _cache = []; return; } try { final file = await _getFilePath(); if (!await file.exists()) return _cache = []; final List<dynamic> json = jsonDecode(await file.readAsString()); _cache = json.cast<Map<String, dynamic>>().map(parse).toList(); _cache.sort(); } catch (e, s) { logError(e, s); print("Failed to read serialized data!"); _cache = []; } } /// Save the cache to the persistent memory Future<void> _saveCache() async { if (isWeb) return; try { final file = await _getFilePath(); await file.writeAsString(jsonEncode( _cache.map((e) => e.toJson()).toList().cast<Map<String, dynamic>>())); } catch (e, s) { print("Failed to write file!"); logError(e, s); } } /// Get the current list of elements Future<List<T>> getList() async { await _loadCache(); return List.from(_cache); } /// Set a new list of conversations Future<void> setList(List<T> list) async { _cache = List.from(list); await _saveCache(); } /// Insert new element Future<void> insert(T el) async { await _loadCache(); _cache.add(el); _cache.sort(); await _saveCache(); } /// Insert new element Future<void> insertMany(List<T> els) async { await _loadCache(); _cache.addAll(els); _cache.sort(); await _saveCache(); } /// Check if any entry in the last match the predicate Future<bool> any(bool isContained(T t)) async { await _loadCache(); return _cache.any((element) => isContained(element)); } Future<bool> has(T el) => any((t) => t == el); /// Check if any entry in the last match the predicate Future<T> first(bool filter(T t)) async { await _loadCache(); return _cache.firstWhere((element) => filter(element)); } /// Replace an element with another one Future<void> insertOrReplaceElement(bool isToReplace(T t), T newEl) async { await _loadCache(); // Insert or replace the element _cache = _cache.where((element) => !isToReplace(element)).toList(); _cache.add(newEl); _cache.sort(); await _saveCache(); } /// Insert or replace many elements Future<void> insertOrReplaceElements(List<T> list) async { await _loadCache(); _cache.removeWhere((element) => list.any((newEl) => element == newEl)); _cache.addAll(list); await _saveCache(); } /// Remove elements Future<void> removeElement(bool isToRemove(T t)) async { await _loadCache(); _cache.removeWhere((element) => isToRemove(element)); await _saveCache(); } /// Remove all elements Future<void> removeAll() async { _cache = []; await _saveCache(); } }