| import 'dart:io'; | |
| import '../../compiler_api.dart' as api; | |
| /// Implementation of [api.BinaryOutputSink] that writes to a provided file. | |
| class FileBinaryOutputSink extends api.BinaryOutputSink { | |
| final RandomAccessFile _fileOut; | |
| void Function(int bytesWritten)? onClose; | |
| int _bytesWritten = 0; | |
| FileBinaryOutputSink(this._fileOut, {this.onClose}); | |
| @override | |
| void add(List<int> data, [int start = 0, int? end]) { | |
| _fileOut.writeFromSync(data, start, end); | |
| _bytesWritten += (end ?? data.length) - start; | |
| } | |
| @override | |
| void close() { | |
| _fileOut.closeSync(); | |
| if (onClose != null) { | |
| onClose!(_bytesWritten); | |
| } | |
| } | |
| } | |
| /// Implementation of [api.OutputSink] that writes to a provided file. | |
| class FileStringOutputSink implements api.OutputSink { | |
| final RandomAccessFile _fileOut; | |
| void Function(int charactersWritten)? onClose; | |
| int _charactersWritten = 0; | |
| FileStringOutputSink(this._fileOut, {this.onClose}); | |
| @override | |
| void add(String text) { | |
| _fileOut.writeStringSync(text); | |
| _charactersWritten += text.length; | |
| } | |
| @override | |
| void close() { | |
| _fileOut.closeSync(); | |
| if (onClose != null) { | |
| onClose!(_charactersWritten); | |
| } | |
| } | |
| } | |
| /// [StringSink] that buffers data written to the underlying [api.OutputSink]. | |
| /// | |
| /// Useful for strings that are built slowly over many writes, avoids keeping | |
| /// the entire string in memory. Each write operation checks if the internal | |
| /// buffer is longer than a maximum length and writes the contents to the | |
| /// underlying [api.OutputSink] if so. Chunks large writes to prevent OOM | |
| /// issues. | |
| /// | |
| /// When wrapping an [api.OutputSink], will check if the provided sink is itself | |
| /// a [BufferedStringSinkWrapper]. If it is the same object will be returned, | |
| /// otherwise returns a new [BufferedStringSinkWrapper] wrapping the provided | |
| /// sink. | |
| /// | |
| /// Avoid large writes to a [BufferedStringSinkWrapper] to ensure the buffer is | |
| /// effective. | |
| class BufferedStringSinkWrapper implements api.OutputSink, StringSink { | |
| static const _maxBufferSize = 1024; | |
| /// This should be at most 8kb, otherwise we risk running OOM. If | |
| /// [_maxBufferSize] is less than [_maxChunkSize] then this chunking will only | |
| /// be used if a single write exceeds this size. | |
| static const _maxChunkSize = 8 * 1024; | |
| final _buffer = StringBuffer(); | |
| final api.OutputSink _outputSink; | |
| BufferedStringSinkWrapper._(this._outputSink); | |
| factory BufferedStringSinkWrapper(api.OutputSink outputSink) { | |
| return outputSink is BufferedStringSinkWrapper | |
| ? outputSink | |
| : BufferedStringSinkWrapper._(outputSink); | |
| } | |
| static bool _isLeadSurrogate(int codeUnit) => (codeUnit & 0xFC00) == 0xD800; | |
| /// Write the data in chunks if it is too large for a single write. | |
| void _flushChunked(String data) { | |
| int offset = 0; | |
| while (offset < data.length) { | |
| String chunk; | |
| int cut = offset + _maxChunkSize; | |
| if (cut < data.length) { | |
| // Don't break the string in the middle of a code point encoded as two | |
| // surrogate pairs since `writeStringSync` will encode the unpaired | |
| // surrogates as U+FFFD REPLACEMENT CHARACTER. | |
| int lastCodeUnit = data.codeUnitAt(cut - 1); | |
| if (_isLeadSurrogate(lastCodeUnit)) { | |
| cut -= 1; | |
| } | |
| chunk = data.substring(offset, cut); | |
| } else { | |
| chunk = offset == 0 ? data : data.substring(offset); | |
| } | |
| _outputSink.add(chunk); | |
| offset += chunk.length; | |
| } | |
| } | |
| void _flush() { | |
| final data = _buffer.toString(); | |
| if (data.length > _maxChunkSize) { | |
| _flushChunked(data); | |
| } else { | |
| _outputSink.add(data); | |
| } | |
| _buffer.clear(); | |
| } | |
| void _flushIfNecessary() { | |
| if (_buffer.length >= _maxBufferSize) { | |
| _flush(); | |
| } | |
| } | |
| @override | |
| void add(String data) { | |
| _buffer.write(data); | |
| _flushIfNecessary(); | |
| } | |
| @override | |
| void close() { | |
| _flush(); | |
| _outputSink.close(); | |
| } | |
| @override | |
| void write(Object? object) { | |
| _buffer.write(object); | |
| _flushIfNecessary(); | |
| } | |
| @override | |
| void writeAll(Iterable<Object?> objects, [String separator = ""]) { | |
| _buffer.writeAll(objects, separator); | |
| _flushIfNecessary(); | |
| } | |
| @override | |
| void writeCharCode(int charCode) { | |
| _buffer.writeCharCode(charCode); | |
| _flushIfNecessary(); | |
| } | |
| @override | |
| void writeln([Object? object = ""]) { | |
| _buffer.writeln(object); | |
| _flushIfNecessary(); | |
| } | |
| } |