| 1 | import archive.tar |
| 2 | import flag |
| 3 | import net.http |
| 4 | import os |
| 5 | import term |
| 6 | |
| 7 | const default_url = 'https://github.com/vlang/v/archive/refs/tags/v0.1.3.tar.gz' |
| 8 | |
| 9 | @[heap] |
| 10 | struct Context { |
| 11 | url string // Web starting with http:// or https://. Local starting with file:/// |
| 12 | chunks bool // true: decompress with callback |
| 13 | debug int // print debug lines |
| 14 | max_blocks int // if max_blocks > 0 and is reached stops early. |
| 15 | filename string // if filename is found as a path of a data block, stops early. |
| 16 | } |
| 17 | |
| 18 | fn (ctx &Context) read_last_block(mut read tar.Read) bool { |
| 19 | if ctx.max_blocks > 0 && ctx.max_blocks < read.get_block_number() { |
| 20 | read.stop_early = true |
| 21 | return true |
| 22 | } |
| 23 | return false |
| 24 | } |
| 25 | |
| 26 | fn new_context() !&Context { |
| 27 | mut fp := flag.new_flag_parser(os.args) |
| 28 | fp.application('tar_gz_reader') |
| 29 | fp.version('0.0.20250721') |
| 30 | fp.description('Reads into memory selected sections of *.tar.gz. archives from https or home_dir.') |
| 31 | fp.skip_executable() |
| 32 | ctx := &Context{ |
| 33 | url: fp.string('url', `u`, default_url, |
| 34 | 'archive *.tar.gz URL, default(${default_url}). Start name with file:/// for local') |
| 35 | chunks: fp.bool('chunks', `c`, false, |
| 36 | 'decompress with chunks to reduce RAM usage, default(false)') |
| 37 | debug: fp.int('debug', `d`, 0, |
| 38 | 'prints blocks: 1=other, 2:+dirs, 3=+files, 4=+data, default(0=silent)') |
| 39 | max_blocks: fp.int('max_blocks', `m`, 0, |
| 40 | 'maximum blocks to read, stop early. Default(0=read all)') |
| 41 | filename: fp.string('filename', `f`, '', |
| 42 | 'filename content complete print, stop early. Default(empty means none)') |
| 43 | } |
| 44 | additional := fp.finalize()! |
| 45 | if additional.len > 0 { |
| 46 | println('unprocessed args ${additional.join_lines()}') |
| 47 | } |
| 48 | return ctx |
| 49 | } |
| 50 | |
| 51 | // Downloader downloads a *.tar.gz using HTTP chunks |
| 52 | struct Downloader { |
| 53 | mut: |
| 54 | chunks int |
| 55 | data []u8 |
| 56 | } |
| 57 | |
| 58 | fn new_downloader(url string) !&Downloader { |
| 59 | mut downloader := &Downloader{} |
| 60 | params := http.DownloaderParams{ |
| 61 | downloader: downloader |
| 62 | } |
| 63 | if url.starts_with('http://') || url.starts_with('https://') { |
| 64 | http.download_file_with_progress(url, '', params)! |
| 65 | } else if url.starts_with('file:///') { |
| 66 | path := '${os.home_dir()}/${url[8..]}' |
| 67 | println('path ${path}') |
| 68 | downloader.data = os.read_bytes(path)! |
| 69 | } |
| 70 | return downloader |
| 71 | } |
| 72 | |
| 73 | fn (mut d Downloader) on_start(mut _request http.Request, _path string) ! {} |
| 74 | |
| 75 | fn (mut d Downloader) on_chunk(_request &http.Request, chunk []u8, _already_received u64, expected u64) ! { |
| 76 | if expected == 0 { |
| 77 | return |
| 78 | } |
| 79 | d.chunks++ |
| 80 | d.data << chunk |
| 81 | } |
| 82 | |
| 83 | fn (mut d Downloader) on_finish(_request &http.Request, _response &http.Response) ! {} |
| 84 | |
| 85 | struct FileReader implements tar.Reader { |
| 86 | ctx &Context |
| 87 | mut: |
| 88 | filepath string |
| 89 | content []u8 |
| 90 | } |
| 91 | |
| 92 | fn (mut f FileReader) other_block(mut read tar.Read, details string) { |
| 93 | if f.ctx.read_last_block(mut read) { |
| 94 | return |
| 95 | } |
| 96 | if f.ctx.debug > 0 { |
| 97 | row := 'OTHER block:${read.get_block_number():6} ${read.get_special()} ${details} ${read.get_path()} ' |
| 98 | println(term.colorize(term.bright_yellow, row)) |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | fn (mut f FileReader) dir_block(mut read tar.Read, size u64) { |
| 103 | if f.ctx.read_last_block(mut read) { |
| 104 | return |
| 105 | } |
| 106 | if f.ctx.debug > 1 { |
| 107 | row := 'DIR block:${read.get_block_number():6} ${read.get_path()} size:${size}' |
| 108 | println(term.colorize(term.green, row)) |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | fn (mut f FileReader) file_block(mut read tar.Read, size u64) { |
| 113 | if f.ctx.read_last_block(mut read) { |
| 114 | return |
| 115 | } |
| 116 | path := read.get_path() |
| 117 | if f.ctx.debug > 2 { |
| 118 | row := ' FILE block:${read.get_block_number():6} ${path} size:${size}' |
| 119 | println(term.colorize(term.bright_blue, row)) |
| 120 | } |
| 121 | if f.ctx.filename != '' && f.filepath == '' && path.ends_with(f.ctx.filename) { |
| 122 | f.filepath = path |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | fn (mut f FileReader) data_block(mut read tar.Read, data []u8, pending int) { |
| 127 | if f.ctx.read_last_block(mut read) { |
| 128 | return |
| 129 | } |
| 130 | path := read.get_path() |
| 131 | if f.ctx.debug > 3 { |
| 132 | println(' DATA block:${read.get_block_number():6} ${path} len:${data.len} pend:${pending}') |
| 133 | } |
| 134 | if f.ctx.filename != '' { |
| 135 | if f.filepath == path { |
| 136 | f.content << data |
| 137 | if pending == 0 { |
| 138 | // our file of interest data is complete |
| 139 | read.stop_early = true |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | fn main() { |
| 146 | ctx := new_context()! |
| 147 | reader := FileReader{ |
| 148 | ctx: ctx |
| 149 | } |
| 150 | mut untar := tar.new_untar(reader) |
| 151 | mut decompressor := tar.new_decompressor(untar) |
| 152 | downloader := new_downloader(ctx.url)! |
| 153 | if ctx.chunks { |
| 154 | decompressor.read_chunks(downloader.data)! |
| 155 | } else { |
| 156 | decompressor.read_all(downloader.data)! |
| 157 | } |
| 158 | println('-'.repeat(80)) |
| 159 | println('Download: ${ctx.url} chunks:${downloader.chunks} bytes=${downloader.data.len}') |
| 160 | println('Untar: ${untar}') |
| 161 | println('Content: Path:${reader.filepath} bytes:${reader.content.len}') |
| 162 | println('-'.repeat(80)) |
| 163 | println('${reader.content.bytestr()}') |
| 164 | println('-'.repeat(80)) |
| 165 | } |
| 166 | |