Unverified Commit 27a6705a authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] chunk the hashing of large files (#57506)

 For files larger than 250 KB (roughly the size of framework.dart), chunk the conversion. This may be important for large assets like images.
parent b0751687
...@@ -16,6 +16,9 @@ import '../base/utils.dart'; ...@@ -16,6 +16,9 @@ import '../base/utils.dart';
import '../convert.dart'; import '../convert.dart';
import 'build_system.dart'; import 'build_system.dart';
/// The default threshold for file chunking is 250 KB, or about the size of `framework.dart`.
const int kDefaultFileChunkThresholdBytes = 250000;
/// An encoded representation of all file hashes. /// An encoded representation of all file hashes.
class FileStorage { class FileStorage {
FileStorage(this.version, this.files); FileStorage(this.version, this.files);
...@@ -91,13 +94,16 @@ class FileStore { ...@@ -91,13 +94,16 @@ class FileStore {
@required File cacheFile, @required File cacheFile,
@required Logger logger, @required Logger logger,
FileStoreStrategy strategy = FileStoreStrategy.hash, FileStoreStrategy strategy = FileStoreStrategy.hash,
int fileChunkThreshold = kDefaultFileChunkThresholdBytes,
}) : _logger = logger, }) : _logger = logger,
_strategy = strategy, _strategy = strategy,
_cacheFile = cacheFile; _cacheFile = cacheFile,
_fileChunkThreshold = fileChunkThreshold;
final File _cacheFile; final File _cacheFile;
final Logger _logger; final Logger _logger;
final FileStoreStrategy _strategy; final FileStoreStrategy _strategy;
final int _fileChunkThreshold;
final HashMap<String, String> previousAssetKeys = HashMap<String, String>(); final HashMap<String, String> previousAssetKeys = HashMap<String, String>();
final HashMap<String, String> currentAssetKeys = HashMap<String, String>(); final HashMap<String, String> currentAssetKeys = HashMap<String, String>();
...@@ -229,7 +235,18 @@ class FileStore { ...@@ -229,7 +235,18 @@ class FileStore {
dirty.add(file); dirty.add(file);
return; return;
} }
final Digest digest = md5.convert(await file.readAsBytes()); Digest digest;
final int fileBytes = file.lengthSync();
// For files larger than a given threshold, chunk the conversion.
if (fileBytes > _fileChunkThreshold) {
final StreamController<Digest> digests = StreamController<Digest>();
final ByteConversionSink inputSink = md5.startChunkedConversion(digests);
await file.openRead().forEach(inputSink.add);
inputSink.close();
digest = await digests.stream.last;
} else {
digest = md5.convert(await file.readAsBytes());
}
final String currentHash = digest.toString(); final String currentHash = digest.toString();
if (currentHash != previousHash) { if (currentHash != previousHash) {
dirty.add(file); dirty.add(file);
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart'; import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/artifacts.dart';
...@@ -173,6 +174,31 @@ void main() { ...@@ -173,6 +174,31 @@ void main() {
expect(logger.errorText, contains('Out of space!')); expect(logger.errorText, contains('Out of space!'));
}); });
testWithoutContext('FileStore handles chunked conversion of a file', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final File cacheFile = fileSystem
.directory('example')
.childFile(FileStore.kFileCache)
..createSync(recursive: true);
final FileStore fileCache = FileStore(
cacheFile: cacheFile,
logger: BufferLogger.test(),
fileChunkThreshold: 1, // Chunk files larger than 1 byte.
);
final File file = fileSystem.file('foo.dart')
..createSync()
..writeAsStringSync('hello');
fileCache.initialize();
cacheFile.parent.deleteSync(recursive: true);
await fileCache.diffFileList(<File>[file]);
// Validate that chunked hash is the same as non-chunked.
expect(fileCache.currentAssetKeys['foo.dart'],
md5.convert(file.readAsBytesSync()).toString());
});
} }
class MockFile extends Mock implements File {} class MockFile extends Mock implements File {}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment