From 45b44e9ccd08222560879086b7efacfd2f636c28 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 26 Feb 2026 11:23:32 +0300 Subject: [PATCH] archive: add tar library (fixes #1180) --- vlib/archive/tar/README.md | 3 +- vlib/archive/tar/reader.v | 13 +++++++- vlib/archive/tar/reader_test.v | 57 +++++++++++++++++++++++++++------- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/vlib/archive/tar/README.md b/vlib/archive/tar/README.md index 47b02679e..a6d8ce0bd 100644 --- a/vlib/archive/tar/README.md +++ b/vlib/archive/tar/README.md @@ -29,5 +29,6 @@ fn main() { tar.read_tar_gz_file(path, reader)! } ``` -Look also in `examples` folder the `tar_gz_reader.v` program. +For plain archives, use `tar.read_tar_file(path, reader)!`. +Look also in `examples` folder the `tar_gz_reader.v` program. diff --git a/vlib/archive/tar/reader.v b/vlib/archive/tar/reader.v index 67b09939e..b541f4f85 100644 --- a/vlib/archive/tar/reader.v +++ b/vlib/archive/tar/reader.v @@ -3,11 +3,22 @@ module tar import compress.gzip import os +// read_tar_file reads a given local .tar file and parses all blocks with a +// given reader. +pub fn read_tar_file(path string, reader Reader) ! { + all_blocks := os.read_bytes(path)! + read_tar_blocks(all_blocks, reader)! +} + // read_tar_gz_file decompresses a given local file and reads all the blocks // with a given reader. pub fn read_tar_gz_file(path string, reader Reader) ! { tar_gz := os.read_bytes(path)! all_blocks := gzip.decompress(tar_gz)! + read_tar_blocks(all_blocks, reader)! +} + +fn read_tar_blocks(all_blocks []u8, reader Reader) ! { mut untar := Untar{ reader: reader } @@ -251,7 +262,7 @@ fn (mut d ChunksReader) read_blocks(chunk []u8) ReadResult { // after sending all complete blocks move the remaining not sent bytes // to the start of the reused buffer to be prepended before next chunk for i := cut; i < d.pending; i++ { - d.buffer[cut - 512] = d.buffer[i] + d.buffer[i - cut] = d.buffer[i] } d.pending -= cut return .continue diff --git a/vlib/archive/tar/reader_test.v b/vlib/archive/tar/reader_test.v index 6be02a16b..3aa66f108 100644 --- a/vlib/archive/tar/reader_test.v +++ b/vlib/archive/tar/reader_test.v @@ -74,6 +74,49 @@ fn test_long_long_short() { assert r1.texts[cat] == 'https://en.wikipedia.org/wiki/Cat' } +@[heap] +struct ChunksCollector { +mut: + blocks [][]u8 +} + +fn test_chunks_reader_keeps_pending_bytes_between_chunks() { + mut collector := &ChunksCollector{} + read_block := fn [mut collector] (block []u8) !ReadResult { + collector.blocks << block.clone() + return .continue + } + + mut block1 := []u8{len: 512} + mut block2 := []u8{len: 512} + for i in 0 .. 512 { + block1[i] = u8((i * 3) % 251) + block2[i] = u8((i * 7) % 251) + } + + mut data := []u8{} + data << block1 + data << block2 + + mut chunks := ChunksReader{ + read_block_fn: read_block + } + + assert chunks.read_blocks(data[..700]) == .continue + assert collector.blocks.len == 1 + assert collector.blocks[0] == block1 + assert chunks.pending == 188 + + assert chunks.read_blocks(data[700..900]) == .continue + assert collector.blocks.len == 1 + assert chunks.pending == 388 + + assert chunks.read_blocks(data[900..]) == .continue + assert collector.blocks.len == 2 + assert collector.blocks[1] == block2 + assert chunks.pending == 0 +} + struct TestReader { debug bool mut: @@ -92,11 +135,7 @@ fn new_test_reader(tar_file string, debug bool) !&TestReader { mut reader := &TestReader{ debug: debug } - mut untar := Untar{ - reader: reader - } - all_blocks := os.read_bytes('${testdata}/${tar_file}')! - untar.read_all_blocks(all_blocks)! + read_tar_file('${testdata}/${tar_file}', reader)! return reader } @@ -105,13 +144,7 @@ fn new_test_reader_gz(tar_gz_file string, debug bool) !&TestReader { mut reader := &TestReader{ debug: debug } - mut untar := Untar{ - reader: reader - } - mut decompressor := new_decompressor(untar) - tar_gz := os.read_bytes('${testdata}/${tar_gz_file}')! - decompressor.read_all(tar_gz)! - + read_tar_gz_file('${testdata}/${tar_gz_file}', reader)! return reader } -- 2.39.5