Skip to content

Commit

Permalink
Merge branch 'file_backend'
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeDucret committed Jan 5, 2021
2 parents 2065388 + 66b49b7 commit 7d6ba76
Show file tree
Hide file tree
Showing 10 changed files with 274 additions and 5 deletions.
67 changes: 67 additions & 0 deletions lib/src/backend/file/file_descriptor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2020 Guillaume Ducret. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import 'dart:io';

import 'package:async/async.dart';

abstract class FileDescriptorInterface {
Stream<List<int>> open(String eTag);
String get etag;
dynamic decode(String line);
dynamic decodeKey(String line);
}

class FileDescriptor implements FileDescriptorInterface {
final File _file;
final Uri _uri;
final Decode _decode;
final DecodeKey _decodeKey;
String _eTag;

FileDescriptor.file(File file, {Decode decode, DecodeKey decodeKey})
: _file = file,
_uri = null,
_decode = decode,
_decodeKey = decodeKey;

FileDescriptor.uri(Uri uri, {Decode decode, DecodeKey decodeKey})
: _file = null,
_uri = uri,
_decode = decode,
_decodeKey = decodeKey;

Stream<List<int>> open(String eTag) {
if (_file != null) {
return LazyStream(() async {
_eTag = (await _file.lastModified()).millisecondsSinceEpoch.toString();
return _file.openRead();
});
}

if (_uri != null) {
return LazyStream(() async {
final request = await HttpClient().getUrl(_uri);
request.headers.set(HttpHeaders.ifNoneMatchHeader, eTag);

final response = await request.close();
_eTag = response.headers.value(HttpHeaders.etagHeader);
return response;
});
}

throw StateError('Invalid FileDescriptor');
}

String get etag {
if (_eTag != null) return _eTag;
throw StateError('etag must be called after open()');
}

dynamic decode(String line) => _decode(line);
dynamic decodeKey(String line) => _decodeKey(line);
}

typedef dynamic Decode(String line);
typedef dynamic DecodeKey(String line);
70 changes: 70 additions & 0 deletions lib/src/backend/file/file_mirror_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2020 Guillaume Ducret. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import 'dart:convert';

import '../../handlers/dynamic_mirror_handler.dart';
import '../../handlers/handler_holder.dart';
import '../../hive_mirror.dart';
import '../../metadata.dart';
import '../mirror_manager.dart';
import 'file_descriptor.dart';

class FileMirrorManager implements MirrorManager {
final MirrorHandlerHolder _handler;
final Metadata _metadata;

FileMirrorManager(MirrorHandler handler, Metadata metadata)
: _handler = MirrorHandlerHolder(handler),
_metadata = metadata;

static FileMirrorManager withHandler<T>(
MirrorHandler<T> handler, Metadata metadata) {
return FileMirrorManager(DynamicMirrorHandler<T>(handler), metadata);
}

Future<void> mirror(dynamic fileDescriptor) =>
loadFile(fileDescriptor as FileDescriptorInterface);

Future<void> loadFile(FileDescriptorInterface fileDescriptor) async {
final etag = _metadata.get(metaEtag);
final fileData = fileDescriptor.open(etag);

if (fileDescriptor.etag != etag) {
final lines = fileData.transform(Utf8Decoder()).transform(LineSplitter());

try {
await _applyLines(fileDescriptor.etag, await lines.toList(),
fileDescriptor.decode, fileDescriptor.decodeKey);
} finally {
await _handler.dispose();
}
}
}

Future<void> _applyLines(String etag, Iterable<String> lines, Decode decode,
DecodeKey decodeKey) async {
MapEntry<dynamic, dynamic> decodeLine(String line) {
final dynamic key = decodeKey(line);
if (key != null) {
final dynamic object = decode(line);
return MapEntry<dynamic, dynamic>(key, object);
}
return null;
}

final putEntries = Map<dynamic, dynamic>.fromEntries(
lines.map(decodeLine).where((e) => e != null));

await (await _handler.use()).clear();

if (putEntries.isNotEmpty) {
await (await _handler.use()).putAll(putEntries);
}

await _metadata.put(metaEtag, etag);
}

static const metaEtag = 'etag';
}
17 changes: 13 additions & 4 deletions lib/src/backend/mirror_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@

import '../hive_mirror.dart';
import '../metadata.dart';
import 'file/file_descriptor.dart';
import 'file/file_mirror_manager.dart';
import 'git/git_mirror_manager.dart';
import 'git/git_patch.dart';

abstract class MirrorManager {
Future<void> mirror(dynamic source);

factory MirrorManager.fromSource(dynamic source,
{MirrorHandler handler, Metadata metadata}) {
factory MirrorManager.fromSource(
dynamic source, {
MirrorHandler handler,
Metadata metadata,
}) {
if (source is GitPatchInterface) {
return GitMirrorManager(handler, metadata);
}
throw UnsupportedError('''source $source is not supported.
Use one of the supported sources [GitPatch]''');
if (source is FileDescriptorInterface) {
return FileMirrorManager(handler, metadata);
}
throw ArgumentError('''source $source is not supported.
Use one of the supported sources
[GitPatchInterface, FileDescriptorInterface]''');
}
}
3 changes: 3 additions & 0 deletions lib/src/handlers/box_mirror_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class BoxMirrorHandler<T> implements MirrorHandler<T> {
@override
Future<void> deleteAll(Iterable keys) => _box.deleteAll(keys);

@override
Future<void> clear() => _box.clear();

@override
Future<void> dispose() => _box.close();
}
3 changes: 3 additions & 0 deletions lib/src/handlers/dynamic_mirror_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class DynamicMirrorHandler<T> implements MirrorHandler<dynamic> {
@override
Future<void> deleteAll(Iterable keys) => _delegate.deleteAll(keys);

@override
Future<void> clear() => _delegate.clear();

@override
Future<void> dispose() => _delegate.dispose();
}
1 change: 1 addition & 0 deletions lib/src/hive_mirror.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ abstract class MirrorHandler<T> {
Future<void> init();
Future<void> putAll(Map<dynamic, T> entries);
Future<void> deleteAll(Iterable<dynamic> keys);
Future<void> clear();
Future<void> dispose();
}
2 changes: 2 additions & 0 deletions test/assets/load2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
key1: value1
key2: value2
56 changes: 56 additions & 0 deletions test/file_descriptors.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2020 Guillaume Ducret. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import 'dart:io';

import 'package:hive_mirror/src/backend/file/file_descriptor.dart';

import 'type.dart';

abstract class TestFileDescriptorBase implements FileDescriptorInterface {
@override
final etag;

final String _filePath;
final Decode _decode;
final DecodeKey _decodeKey;

TestFileDescriptorBase._(
this.etag, this._filePath, this._decode, this._decodeKey);

@override
Stream<List<int>> open(String _) => File(_filePath).openRead();

@override
dynamic decode(String line) => _decode(line);

@override
dynamic decodeKey(String line) => _decodeKey(line);
}

class Load2FileDescriptor extends TestFileDescriptorBase {
Load2FileDescriptor.primitive()
: super._(etagValue, filePath, _decodePrimitive, _decodeKey);
Load2FileDescriptor.testType()
: super._(etagValue, filePath, _decodeTestType, _decodeKey);

static const filePath = 'test/assets/load2.yaml';
static const etagValue = 'etag_value';
static const loadMap = {'key1': 'value1', 'key2': 'value2'};
}

String _decodePrimitive(String line) {
final tupple = line.split(':');
return tupple[1].trim();
}

TestType _decodeTestType(String line) {
final tupple = line.split(':');
return TestType(tupple[0].trim(), tupple[1].trim());
}

String _decodeKey(String line) {
final tupple = line.split(':');
return tupple[0].trim();
}
16 changes: 15 additions & 1 deletion test/integration/mirror_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import 'package:hive/hive.dart';
import 'package:hive_mirror/hive_mirror.dart';
import 'package:test/test.dart';

import '../file_descriptors.dart';
import '../patches.dart';

void main() {
group('Mirror', () {
test('box', () async {
test('patch', () async {
HiveMirror.init('.hive');
await Hive.deleteBoxFromDisk('box');

Expand All @@ -21,5 +22,18 @@ void main() {
expect(box.get('key1'), equals('value1'));
expect(box.get('key2'), equals('value2'));
});

test('file', () async {
HiveMirror.init('.hive');
await Hive.deleteBoxFromDisk('box');
await Hive.deleteBoxFromDisk('.hive_mirror_metadata');

final source = Load2FileDescriptor.primitive();
await HiveMirror.mirror(source, BoxMirrorHandler<String>('box'));

final box = await Hive.openBox<String>('box');
expect(box.get('key1'), equals('value1'));
expect(box.get('key2'), equals('value2'));
});
});
}
44 changes: 44 additions & 0 deletions test/unit/backend/file/file_mirror_manager_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2020 Guillaume Ducret. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import 'package:hive_mirror/src/backend/file/file_mirror_manager.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';

import '../../../file_descriptors.dart';
import '../../mocks.dart';

const metaETag = FileMirrorManager.metaEtag;

void main() {
group('FileMirrorManager load()', () {
test('file with new etag', () async {
final handler = MirrorHandlerMock<String>();
final metadata = MetadataMock();
final manager = FileMirrorManager.withHandler(handler, metadata);

await manager.loadFile(Load2FileDescriptor.primitive());

verify(metadata.get(metaETag));
verify(handler.clear());
verify(handler.putAll(argThat(equals(Load2FileDescriptor.loadMap))));
verify(metadata.put(metaETag, Load2FileDescriptor.etagValue));
});

test('file with previous etag', () async {
final handler = MirrorHandlerMock<String>();
final metadata = MetadataMock();
final manager = FileMirrorManager.withHandler(handler, metadata);

when(metadata.get(metaETag)).thenReturn(Load2FileDescriptor.etagValue);

await manager.loadFile(Load2FileDescriptor.primitive());

verify(metadata.get(metaETag));
verifyNever(handler.clear());
verifyNever(handler.putAll(any));
verifyNever(metadata.put(any, any));
});
});
}

0 comments on commit 7d6ba76

Please sign in to comment.