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