extractFileToDisk function

Future<void> extractFileToDisk(
  1. String inputPath,
  2. String outputPath, {
  3. String? password,
  4. int? bufferSize,
  5. ArchiveCallback? callback,
  6. int? maxSize,
})

Extracts the archive at inputPath into the directory outputPath.

The archive may be a .zip, .tar, or a tar compressed as .tar.gz, .tgz, .tar.bz2, .tbz, .tar.xz, .txz, .tar.zst or .tzst.

A compressed tar is decompressed as it is read and every entry written out as it is reached, so no more than a few megabytes of it are in memory at a time and no temp file is needed.

callback is called for each entry once it has been written. For a compressed tar the entry's content has gone by then and cannot be read from the entry; it is in the file on disk.

bufferSize is the size of the write buffer for each extracted file, and password decrypts an encrypted zip.

maxSize limits the total size of the files written, which a small compressed archive can otherwise make as large as it likes. An entry that would take it past that throws an ArchiveException before it is written. A zip entry is never decoded past the size the archive gives for it.

Entries are kept inside outputPath as extractArchiveToDiskSync describes, and an entry whose content fails to decode throws.

Implementation

Future<void> extractFileToDisk(String inputPath, String outputPath,
    {String? password,
    int? bufferSize,
    ArchiveCallback? callback,
    int? maxSize}) async {
  const String extensionMsg =
      '.tar.gz, .tgz, .tar.bz2, .tbz, .tar.xz, .txz, .tar.zst, .tzst, .tar '
      'or .zip';

  // get the extension of the input file with up to 2 components
  // e.g. for file.tar.gz, it will return '.tar.gz'
  final archiveExt = getInputExtension(inputPath);
  if (archiveExt.isEmpty) {
    throw ArgumentError.value(
      inputPath,
      'inputPath',
      'No file extension detected, must end with $extensionMsg',
    );
  }
  if (archiveExt != '.zip' && !_tarExtensions.contains(archiveExt)) {
    throw ArgumentError.value(inputPath, 'inputPath', 'Must end $extensionMsg');
  }

  final writer = EntryWriter(outputPath,
      bufferSize: bufferSize, maxSize: maxSize, setPermissions: true);
  final file = InputFileStream(inputPath);
  try {
    if (archiveExt == '.zip') {
      final archive = ZipDecoder()
          .decodeStream(file, password: password, callback: callback);
      for (final entry in archive) {
        writer.write(entry);
      }
      await archive.clear();
    } else {
      final input = tarStreamFor(archiveExt, file)!;
      // Each entry is written as the decoder reaches it, which is the only
      // time its content is at hand when the tar is being decompressed on the
      // way in.
      try {
        TarDecoder().decodeStream(input, keepEntries: false, callback: (entry) {
          writer.write(entry);
          callback?.call(entry);
        });
      } finally {
        await input.close();
      }
    }
    writer.finish();
  } finally {
    await file.close();
  }
}