packStream static method

Stream<List<int>> packStream(
  1. List<PqPackEntry> entries, {
  2. int chunkSize = 1 << 20,
  3. void onEntryStart(
    1. PqPackEntry entry,
    2. int contentLength
    )?,
  4. void onEntryProgress(
    1. PqPackEntry entry,
    2. int bytesProcessed,
    3. int contentLength
    )?,
})

Emits entries as one sequential pack stream — the streaming counterpart of pack, for piping straight into an AEAD writer so the plaintext pack never exists on disk. Bounded memory: one chunk in flight at a time.

Implementation

static Stream<List<int>> packStream(
  List<PqPackEntry> entries, {
  int chunkSize = 1 << 20,
  void Function(PqPackEntry entry, int contentLength)? onEntryStart,
  void Function(PqPackEntry entry, int bytesProcessed, int contentLength)?
  onEntryProgress,
}) async* {
  for (final entry in entries) {
    _requireSafeRelativePath(entry.relativePath);
    final pathBytes = PqBytes.utf8Bytes(entry.relativePath);
    if (pathBytes.length > maxPathBytes) {
      throw PqForgeException(
        'Pack entry path too long: ${entry.relativePath}',
      );
    }
    final source = File(entry.sourcePath);
    final length = await source.length();
    onEntryStart?.call(entry, length);
    yield (BytesBuilder(copy: false)
          ..add(PqBytes.uint32(pathBytes.length))
          ..add(pathBytes)
          ..add(PqBytes.uint64(length)))
        .toBytes();
    final reader = await source.open();
    try {
      var remaining = length;
      while (remaining > 0) {
        final want = remaining < chunkSize ? remaining : chunkSize;
        // Fresh buffer per chunk: the consumer may hold it across awaits.
        final chunk = Uint8List(want);
        var offset = 0;
        while (offset < want) {
          final n = await reader.readInto(chunk, offset, want);
          if (n <= 0) {
            throw PqForgeException(
              'Short read packing ${entry.relativePath}',
            );
          }
          offset += n;
        }
        remaining -= want;
        yield chunk;
        onEntryProgress?.call(entry, length - remaining, length);
      }
    } finally {
      await reader.close();
    }
  }
}