2017-06-13 16:55:07 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'package:angel_framework/angel_framework.dart';
|
2018-07-12 19:58:30 +00:00
|
|
|
import 'package:dart2_constant/convert.dart';
|
2017-12-21 06:37:03 +00:00
|
|
|
import 'package:file/file.dart';
|
2017-06-13 16:55:07 +00:00
|
|
|
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,
|
2017-12-21 06:37:03 +00:00
|
|
|
{bool allowRemoveAll: false, bool allowQuery: true, MapService store}) {
|
|
|
|
_store = store ??
|
|
|
|
new MapService(
|
|
|
|
allowRemoveAll: allowRemoveAll == true,
|
|
|
|
allowQuery: allowQuery != false);
|
2017-06-13 16:55:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-12 19:58:30 +00:00
|
|
|
Future _load() {
|
|
|
|
return _mutex.withResource(() async {
|
|
|
|
if (!await file.exists()) await file.writeAsString(json.encode([]));
|
|
|
|
var stat = await file.stat();
|
|
|
|
//
|
2017-06-13 16:55:07 +00:00
|
|
|
|
2018-07-12 19:58:30 +00:00
|
|
|
if (_lastStat == null ||
|
|
|
|
stat.modified.millisecondsSinceEpoch >
|
|
|
|
_lastStat.modified.millisecondsSinceEpoch) {
|
|
|
|
_lastStat = stat;
|
2017-06-13 16:55:07 +00:00
|
|
|
|
2018-07-12 19:58:30 +00:00
|
|
|
var contents = await file.readAsString();
|
2017-06-13 16:55:07 +00:00
|
|
|
|
2018-07-12 19:58:30 +00:00
|
|
|
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<String, dynamic>)); // 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}.');
|
|
|
|
}
|
|
|
|
}
|
2017-06-13 16:55:07 +00:00
|
|
|
}
|
2018-07-12 19:58:30 +00:00
|
|
|
});
|
2017-06-13 16:55:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-12 19:58:30 +00:00
|
|
|
_save() {
|
|
|
|
return _mutex.withResource(() {
|
|
|
|
return file
|
|
|
|
.writeAsString(json.encode(_store.items.map(_jsonify).toList()));
|
|
|
|
});
|
2017-06-13 16:55:07 +00:00
|
|
|
}
|
|
|
|
|
2017-12-21 06:37:03 +00:00
|
|
|
@override
|
2018-07-12 19:58:30 +00:00
|
|
|
Future close() async {
|
|
|
|
_store.close();
|
2017-12-21 06:37:03 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 16:55:07 +00:00
|
|
|
@override
|
|
|
|
Future<List> index([Map params]) async =>
|
|
|
|
_load().then((_) => _store.index(params));
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<Map> read(id, [Map params]) =>
|
|
|
|
_load().then((_) => _store.read(id, params));
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<Map> create(data, [Map params]) async {
|
|
|
|
await _load();
|
2017-12-22 13:04:38 +00:00
|
|
|
var created = await _store.create(data, params);
|
2017-06-13 16:55:07 +00:00
|
|
|
await _save();
|
2017-12-22 13:04:38 +00:00
|
|
|
return created;
|
2017-06-13 16:55:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<Map> remove(id, [Map params]) async {
|
|
|
|
await _load();
|
|
|
|
var r = await _store.remove(id, params);
|
|
|
|
await _save();
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<Map> update(id, data, [Map params]) async {
|
|
|
|
await _load();
|
|
|
|
var r = await _store.update(id, data, params);
|
|
|
|
await _save();
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<Map> 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<Map>({}, (out, k) => out..[k] = _safeForJson(map[k]));
|
|
|
|
}
|
|
|
|
|
2017-12-21 06:38:49 +00:00
|
|
|
dynamic _revive(x) {
|
2017-06-13 16:55:07 +00:00
|
|
|
if (x is Map) {
|
|
|
|
return x.keys.fold<Map>({}, (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;
|
|
|
|
}
|