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