Add 'packages/file_service/' from commit '8eeae3c286c20b6c8ca89a11be8a25c1ade16689'
git-subtree-dir: packages/file_service git-subtree-mainline:998aa62303
git-subtree-split:8eeae3c286
This commit is contained in:
commit
8e84218dbe
13 changed files with 406 additions and 0 deletions
57
packages/file_service/.gitignore
vendored
Normal file
57
packages/file_service/.gitignore
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# See https://www.dartlang.org/tools/private-files.html
|
||||||
|
|
||||||
|
# Files and directories created by pub
|
||||||
|
.packages
|
||||||
|
.pub/
|
||||||
|
build/
|
||||||
|
# If you're building an application, you may want to check-in your pubspec.lock
|
||||||
|
pubspec.lock
|
||||||
|
|
||||||
|
# Directory created by dartdoc
|
||||||
|
# If you don't generate documentation locally you can remove this line.
|
||||||
|
doc/api/
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.xml
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
.dart_tool
|
16
packages/file_service/.idea/file_service.iml
Normal file
16
packages/file_service/.idea/file_service.iml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
8
packages/file_service/.idea/modules.xml
Normal file
8
packages/file_service/.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/file_service.iml" filepath="$PROJECT_DIR$/.idea/file_service.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
packages/file_service/.idea/vcs.xml
Normal file
6
packages/file_service/.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
4
packages/file_service/.travis.yml
Normal file
4
packages/file_service/.travis.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
language: dart
|
||||||
|
dart:
|
||||||
|
- dev
|
||||||
|
- stable
|
24
packages/file_service/CHANGELOG.md
Normal file
24
packages/file_service/CHANGELOG.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# 2.0.1
|
||||||
|
* Pass everything through `_jsonifyToSD` when returning responses.
|
||||||
|
|
||||||
|
# 2.0.0
|
||||||
|
* Dart/Angel 2 update.
|
||||||
|
* Remove `package:dart2_constant`
|
||||||
|
* Update `package:file` to `^5.0.0`.
|
||||||
|
|
||||||
|
# 1.1.2
|
||||||
|
* Added tests, because tests.
|
||||||
|
|
||||||
|
# 1.1.1
|
||||||
|
* Dart 2 fixes.
|
||||||
|
|
||||||
|
# 1.1.0+2
|
||||||
|
* `create` now uses the underlying store, instead of manually patching
|
||||||
|
|
||||||
|
# 1.1.0+1
|
||||||
|
* Analyzer nitpick for pana
|
||||||
|
|
||||||
|
# 1.1.0
|
||||||
|
* Updated to framework v1.1.x
|
||||||
|
* Use `package:file`
|
||||||
|
* Allow a custom `store`
|
21
packages/file_service/LICENSE
Normal file
21
packages/file_service/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 The Angel Framework
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
31
packages/file_service/README.md
Normal file
31
packages/file_service/README.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# file_service
|
||||||
|
[![Pub](https://img.shields.io/pub/v/angel_file_service.svg)](https://pub.dartlang.org/packages/angel_file_service)
|
||||||
|
[![build status](https://travis-ci.org/angel-dart/file_service.svg)](https://travis-ci.org/angel-dart/file_service)
|
||||||
|
|
||||||
|
Angel service that persists data to a file on disk, stored as a JSON list. It uses a simple
|
||||||
|
mutex to prevent race conditions, and caches contents in memory until changes
|
||||||
|
are made.
|
||||||
|
|
||||||
|
The file will be created on read/write, if it does not already exist.
|
||||||
|
|
||||||
|
This package is useful in development, as it prevents you from having to install
|
||||||
|
an external database to run your server.
|
||||||
|
|
||||||
|
When running a multi-threaded server, there is no guarantee that file operations
|
||||||
|
will be mutually excluded. Thus, try to only use this one a single-threaded server
|
||||||
|
if possible, or one with very low load.
|
||||||
|
|
||||||
|
While not necessarily *slow*, this package makes no promises about performance.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
```dart
|
||||||
|
configureServer(Angel app) async {
|
||||||
|
// Just like a normal service
|
||||||
|
app.use(
|
||||||
|
'/api/todos',
|
||||||
|
new JsonFileService(
|
||||||
|
const LocalFileSystem().file('todos_db.json')
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
3
packages/file_service/analysis_options.yaml
Normal file
3
packages/file_service/analysis_options.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
11
packages/file_service/example/main.dart
Normal file
11
packages/file_service/example/main.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:angel_file_service/angel_file_service.dart';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:file/local.dart';
|
||||||
|
|
||||||
|
configureServer(Angel app) async {
|
||||||
|
// Just like a normal service
|
||||||
|
app.use(
|
||||||
|
'/api/todos',
|
||||||
|
new JsonFileService(const LocalFileSystem().file('todos_db.json')),
|
||||||
|
);
|
||||||
|
}
|
141
packages/file_service/lib/angel_file_service.dart
Normal file
141
packages/file_service/lib/angel_file_service.dart
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:pool/pool.dart';
|
||||||
|
|
||||||
|
/// Persists in-memory changes to a file on disk.
|
||||||
|
class JsonFileService extends Service<String, Map<String, dynamic>> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _coerceStringDynamic(Map m) {
|
||||||
|
return m.keys.fold<Map<String, dynamic>>(
|
||||||
|
<String, dynamic>{}, (out, k) => out..[k.toString()] = m[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
var list = json.decode(contents) as Iterable;
|
||||||
|
_store.items.clear(); // Clear exist in-memory copy
|
||||||
|
_store.items.addAll(list.map((x) =>
|
||||||
|
_coerceStringDynamic(_revive(x) as Map))); // Insert all new entries
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_save() {
|
||||||
|
return _mutex.withResource(() {
|
||||||
|
return file
|
||||||
|
.writeAsString(json.encode(_store.items.map(_jsonify).toList()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future close() async {
|
||||||
|
_store.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Map<String, dynamic>>> index(
|
||||||
|
[Map<String, dynamic> params]) async =>
|
||||||
|
_load()
|
||||||
|
.then((_) => _store.index(params))
|
||||||
|
.then((it) => it.map(_jsonifyToSD).toList());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> read(id, [Map<String, dynamic> params]) =>
|
||||||
|
_load().then((_) => _store.read(id, params)).then(_jsonifyToSD);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> create(data,
|
||||||
|
[Map<String, dynamic> params]) async {
|
||||||
|
await _load();
|
||||||
|
var created = await _store.create(data, params).then(_jsonifyToSD);
|
||||||
|
await _save();
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> remove(id, [Map<String, dynamic> params]) async {
|
||||||
|
await _load();
|
||||||
|
var r = await _store.remove(id, params).then(_jsonifyToSD);
|
||||||
|
await _save();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> update(id, data,
|
||||||
|
[Map<String, dynamic> params]) async {
|
||||||
|
await _load();
|
||||||
|
var r = await _store.update(id, data, params).then(_jsonifyToSD);
|
||||||
|
await _save();
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> modify(id, data,
|
||||||
|
[Map<String, dynamic> params]) async {
|
||||||
|
await _load();
|
||||||
|
var r = await _store.update(id, data, params).then(_jsonifyToSD);
|
||||||
|
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]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _jsonifyToSD(Map<String, dynamic> map) =>
|
||||||
|
_jsonify(map).cast<String, dynamic>();
|
||||||
|
|
||||||
|
dynamic _revive(x) {
|
||||||
|
if (x is Map) {
|
||||||
|
return x.keys.fold<Map<String, dynamic>>(
|
||||||
|
{}, (out, k) => out..[k.toString()] = _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;
|
||||||
|
}
|
13
packages/file_service/pubspec.yaml
Normal file
13
packages/file_service/pubspec.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
name: angel_file_service
|
||||||
|
version: 2.0.1
|
||||||
|
description: Angel service that persists data to a file on disk.
|
||||||
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
|
homepage: https://github.com/angel-dart/file_service
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.0.0-dev <3.0.0"
|
||||||
|
dependencies:
|
||||||
|
angel_framework: ^2.0.0-alpha
|
||||||
|
file: ^5.0.0
|
||||||
|
pool: ^1.0.0
|
||||||
|
dev_dependencies:
|
||||||
|
test: ^1.0.0
|
71
packages/file_service/test/all_test.dart
Normal file
71
packages/file_service/test/all_test.dart
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import 'package:angel_file_service/angel_file_service.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
MemoryFileSystem fs;
|
||||||
|
File dbFile;
|
||||||
|
JsonFileService service;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
fs = new MemoryFileSystem();
|
||||||
|
dbFile = fs.file('db.json');
|
||||||
|
service = new JsonFileService(dbFile);
|
||||||
|
|
||||||
|
await dbFile.writeAsString('''
|
||||||
|
[
|
||||||
|
{"id": "0", "foo": "bar"},
|
||||||
|
{"id": "1", "foo": "baz"},
|
||||||
|
{"id": "2", "foo": "quux"}
|
||||||
|
]
|
||||||
|
''');
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => service.close());
|
||||||
|
|
||||||
|
test('index no params', () async {
|
||||||
|
expect(await service.index(), [
|
||||||
|
{"id": "0", "foo": "bar"},
|
||||||
|
{"id": "1", "foo": "baz"},
|
||||||
|
{"id": "2", "foo": "quux"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index with query', () async {
|
||||||
|
expect(
|
||||||
|
await service.index({
|
||||||
|
'query': {'foo': 'bar'}
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
{"id": "0", "foo": "bar"}
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('read', () async {
|
||||||
|
expect(
|
||||||
|
await service.read('2'),
|
||||||
|
{"id": "2", "foo": "quux"},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('modify', () async {
|
||||||
|
await service.modify('2', {'baz': 'quux'});
|
||||||
|
expect(await service.read('2'), containsPair('baz', 'quux'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update', () async {
|
||||||
|
await service.update('2', {'baz': 'quux'});
|
||||||
|
expect(await service.read('2'), containsPair('baz', 'quux'));
|
||||||
|
expect(await service.read('2'), isNot(containsPair('foo', 'quux')));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('delete', () async {
|
||||||
|
await service.remove('2');
|
||||||
|
expect(await service.index(), [
|
||||||
|
{"id": "0", "foo": "bar"},
|
||||||
|
{"id": "1", "foo": "baz"}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue