blob: 12fabebece86fe5ff07be46ddd2504ed5d6e80a6 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. 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:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:build/build.dart';
import 'package:crypto/crypto.dart';
import 'package:glob/glob.dart';
import 'lru_cache.dart';
import 'reader.dart';
/// An [AssetReader] that caches all results from the delegate.
///
/// Assets are cached until [invalidate] is invoked.
///
/// Does not implement [findAssets].
class CachingAssetReader implements AssetReader {
/// Cached results of [readAsBytes].
final _bytesContentCache = LruCache<AssetId, List<int>>(
1024 * 1024,
1024 * 1024 * 512,
(value) => value is Uint8List ? value.lengthInBytes : value.length * 8);
/// Pending [readAsBytes] operations.
final _pendingBytesContentCache = <AssetId, Future<List<int>>>{};
/// Cached results of [canRead].
///
/// Don't bother using an LRU cache for this since it's just booleans.
final _canReadCache = <AssetId, Future<bool>>{};
/// Cached results of [readAsString].
///
/// These are computed and stored lazily using [readAsBytes].
///
/// Only files read with [utf8] encoding (the default) will ever be cached.
final _stringContentCache = LruCache<AssetId, String>(
1024 * 1024, 1024 * 1024 * 512, (value) => value.length);
/// Pending `readAsString` operations.
final _pendingStringContentCache = <AssetId, Future<String>>{};
final AssetReader _delegate;
CachingAssetReader._(this._delegate);
factory CachingAssetReader(AssetReader delegate) =>
delegate is PathProvidingAssetReader
? _PathProvidingCachingAssetReader._(delegate)
: CachingAssetReader._(delegate);
@override
Future<bool> canRead(AssetId id) =>
_canReadCache.putIfAbsent(id, () => _delegate.canRead(id));
@override
Future<Digest> digest(AssetId id) => _delegate.digest(id);
@override
Stream<AssetId> findAssets(Glob glob) =>
throw UnimplementedError('unimplemented!');
@override
Future<List<int>> readAsBytes(AssetId id, {bool cache = true}) {
var cached = _bytesContentCache[id];
if (cached != null) return Future.value(cached);
return _pendingBytesContentCache.putIfAbsent(
id,
() => _delegate.readAsBytes(id).then((result) {
if (cache) _bytesContentCache[id] = result;
_pendingBytesContentCache.remove(id);
return result;
}));
}
@override
Future<String> readAsString(AssetId id, {Encoding encoding}) {
encoding ??= utf8;
if (encoding != utf8) {
// Fallback case, we never cache the String value for the non-default,
// encoding but we do allow it to cache the bytes.
return readAsBytes(id).then(encoding.decode);
}
var cached = _stringContentCache[id];
if (cached != null) return Future.value(cached);
return _pendingStringContentCache.putIfAbsent(
id,
() => readAsBytes(id, cache: false).then((bytes) {
var decoded = encoding.decode(bytes);
_stringContentCache[id] = decoded;
_pendingStringContentCache.remove(id);
return decoded;
}));
}
/// Clears all [ids] from all caches.
void invalidate(Iterable<AssetId> ids) {
for (var id in ids) {
_bytesContentCache.remove(id);
_canReadCache.remove(id);
_stringContentCache.remove(id);
_pendingBytesContentCache.remove(id);
_pendingStringContentCache.remove(id);
}
}
}
/// A version of a [CachingAssetReader] that implements
/// [PathProvidingAssetReader].
class _PathProvidingCachingAssetReader extends CachingAssetReader
implements PathProvidingAssetReader {
@override
PathProvidingAssetReader get _delegate =>
super._delegate as PathProvidingAssetReader;
_PathProvidingCachingAssetReader._(AssetReader delegate) : super._(delegate);
@override
String pathTo(AssetId id) => _delegate.pathTo(id);
}