import 'dart:async'; import 'package:angel_framework/angel_framework.dart'; import 'package:dart2_constant/convert.dart'; import 'package:file/file.dart'; import 'package:pool/pool.dart'; /// Persists in-memory changes to a file on disk. class JsonFileService extends Service { FileStat _lastStat; final Pool _mutex = new Pool(1); MapService _store; final File file; JsonFileService(this.file, {bool allowRemoveAll: false, bool allowQuery: true, MapService store}) { _store = store ?? new MapService( allowRemoveAll: allowRemoveAll == true, allowQuery: allowQuery != false); } Future _load() { return _mutex.withResource(() async { if (!await file.exists()) await file.writeAsString(json.encode([])); var stat = await file.stat(); // if (_lastStat == null || stat.modified.millisecondsSinceEpoch > _lastStat.modified.millisecondsSinceEpoch) { _lastStat = stat; var contents = await file.readAsString(); try { var list = json.decode(contents) as Iterable; _store.items.clear(); // Clear exist in-memory copy _store.items.addAll(list.map((x) => _revive(x) as Map)); // Insert all new entries } catch (e) { if (_store.app is Angel) { (_store.app as Angel) .logger .warning('WARNING: Failed to reload contents of ${file.path}.'); } } } }); } _save() { return _mutex.withResource(() { return file .writeAsString(json.encode(_store.items.map(_jsonify).toList())); }); } @override Future close() async { _store.close(); } @override Future index([Map params]) async => _load().then((_) => _store.index(params)); @override Future read(id, [Map params]) => _load().then((_) => _store.read(id, params)); @override Future create(data, [Map params]) async { await _load(); var created = await _store.create(data, params); await _save(); return created; } @override Future remove(id, [Map params]) async { await _load(); var r = await _store.remove(id, params); await _save(); return r; } @override Future update(id, data, [Map params]) async { await _load(); var r = await _store.update(id, data, params); await _save(); return r; } @override Future modify(id, data, [Map params]) async { await _load(); var r = await _store.update(id, data, params); await _save(); return r; } } _safeForJson(x) { if (x is DateTime) return x.toIso8601String(); else if (x is Map) return _jsonify(x); else if (x is num || x is String || x is bool || x == null) return x; else if (x is Iterable) return x.map(_safeForJson).toList(); else return x.toString(); } Map _jsonify(Map map) { return map.keys.fold({}, (out, k) => out..[k] = _safeForJson(map[k])); } dynamic _revive(x) { if (x is Map) { return x.keys.fold({}, (out, k) => out..[k] = _revive(x[k])); } else if (x is Iterable) return x.map(_revive).toList(); else if (x is String) { try { return DateTime.parse(x); } catch (e) { return x; } } else return x; }