| // 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); |
| } |