-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
274 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
key1: value1 | ||
key2: value2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
}); | ||
}); | ||
} |