| 1 | module szip |
| 2 | |
| 3 | import os |
| 4 | |
| 5 | #flag -I @VEXEROOT/thirdparty/zip |
| 6 | #define MINIZ_NO_ZLIB_COMPATIBLE_NAMES |
| 7 | #include "zip.c" |
| 8 | |
| 9 | @[params] |
| 10 | pub struct ZipFolderOptions { |
| 11 | pub: |
| 12 | omit_empty_folders bool |
| 13 | } |
| 14 | |
| 15 | pub struct C.zip_t { |
| 16 | } |
| 17 | |
| 18 | pub type Zip = C.zip_t |
| 19 | |
| 20 | pub type Fn_on_extract_entry = fn (&&char, &&char) int |
| 21 | |
| 22 | fn C.zip_open(&char, i32, char) &Zip |
| 23 | |
| 24 | fn C.zip_close(&Zip) |
| 25 | |
| 26 | fn C.zip_entry_open(&Zip, &u8) i32 |
| 27 | |
| 28 | fn C.zip_entry_openbyindex(&Zip, usize) i32 |
| 29 | |
| 30 | fn C.zip_entry_close(&Zip) i32 |
| 31 | |
| 32 | fn C.zip_entry_name(&Zip) &u8 |
| 33 | |
| 34 | fn C.zip_entry_index(&Zip) i32 |
| 35 | |
| 36 | fn C.zip_entry_isdir(&Zip) i32 |
| 37 | |
| 38 | fn C.zip_entry_size(&Zip) u64 |
| 39 | |
| 40 | fn C.zip_entry_crc32(&Zip) u32 |
| 41 | |
| 42 | fn C.zip_entry_write(&Zip, voidptr, usize) i32 |
| 43 | |
| 44 | fn C.zip_entry_fwrite(&Zip, &char) i32 |
| 45 | |
| 46 | fn C.zip_entry_read(&Zip, &voidptr, &usize) i32 |
| 47 | |
| 48 | fn C.zip_entry_noallocread(&Zip, voidptr, usize) i32 |
| 49 | |
| 50 | fn C.zip_entry_fread(&Zip, &char) i32 |
| 51 | |
| 52 | fn C.zip_entries_total(&Zip) i32 |
| 53 | |
| 54 | fn C.zip_extract(&char, &char, Fn_on_extract_entry, voidptr) i32 |
| 55 | |
| 56 | fn cb_zip_extract(_filename &&char, _arg &&char) int { |
| 57 | return 0 |
| 58 | } |
| 59 | |
| 60 | pub enum CompressionLevel { |
| 61 | no_compression = C.MZ_NO_COMPRESSION |
| 62 | best_speed = C.MZ_BEST_SPEED |
| 63 | best_compression = C.MZ_BEST_COMPRESSION |
| 64 | uber_compression = C.MZ_UBER_COMPRESSION |
| 65 | default_level = C.MZ_DEFAULT_LEVEL |
| 66 | default_compression = C.MZ_DEFAULT_COMPRESSION |
| 67 | } |
| 68 | |
| 69 | // OpenMode lists the opening modes. |
| 70 | // .write: opens a file for reading/extracting (the file must exists). |
| 71 | // .read_only: creates an empty file for writing. |
| 72 | // .append: appends to an existing archive. |
| 73 | pub enum OpenMode { |
| 74 | write |
| 75 | read_only |
| 76 | append |
| 77 | } |
| 78 | |
| 79 | @[inline] |
| 80 | fn (om OpenMode) to_u8() u8 { |
| 81 | return match om { |
| 82 | .write { |
| 83 | `w` |
| 84 | } |
| 85 | .read_only { |
| 86 | `r` |
| 87 | } |
| 88 | .append { |
| 89 | `a` |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | // open opens zip archive with compression level using the given mode. |
| 95 | // name: the name of the zip file to open. |
| 96 | // level: can be any value of the CompressionLevel enum. |
| 97 | // mode: can be any value of the OpenMode enum. |
| 98 | pub fn open(name string, level CompressionLevel, mode OpenMode) !&Zip { |
| 99 | if name == '' { |
| 100 | return error('szip: name of file empty') |
| 101 | } |
| 102 | p_zip := unsafe { &Zip(C.zip_open(&char(name.str), int(level), char(mode.to_u8()))) } |
| 103 | if isnil(p_zip) { |
| 104 | return error('szip: cannot open/create/append new zip archive') |
| 105 | } |
| 106 | return p_zip |
| 107 | } |
| 108 | |
| 109 | // close closes the zip archive, releases resources - always finalize. |
| 110 | @[inline] |
| 111 | pub fn (mut z Zip) close() { |
| 112 | C.zip_close(z) |
| 113 | } |
| 114 | |
| 115 | // open_entry opens an entry by name in the zip archive. |
| 116 | // For zip archive opened in 'w' or 'a' mode the function will append |
| 117 | // a new entry. In readonly mode the function tries to locate the entry |
| 118 | // in global dictionary. |
| 119 | pub fn (mut zentry Zip) open_entry(name string) ! { |
| 120 | res := C.zip_entry_open(zentry, &char(name.str)) |
| 121 | if res == -1 { |
| 122 | return error('szip: cannot open archive entry') |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | // open_entry_by_index opens an entry by index in the archive. |
| 127 | pub fn (mut z Zip) open_entry_by_index(index int) ! { |
| 128 | res := C.zip_entry_openbyindex(z, index) |
| 129 | if res == -1 { |
| 130 | return error('szip: cannot open archive entry at index ${index}') |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | // close_entry closes a zip entry, flushes buffer and releases resources. |
| 135 | @[inline] |
| 136 | pub fn (mut zentry Zip) close_entry() { |
| 137 | C.zip_entry_close(zentry) |
| 138 | } |
| 139 | |
| 140 | // name returns a local name of the current zip entry. |
| 141 | // The main difference between user's entry name and local entry name |
| 142 | // is optional relative path. |
| 143 | // Following .ZIP File Format Specification - the path stored MUST not contain |
| 144 | // a drive or device letter, or a leading slash. |
| 145 | // All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' |
| 146 | // for compatibility with Amiga and UNIX file systems etc. |
| 147 | pub fn (mut zentry Zip) name() string { |
| 148 | name := unsafe { &u8(C.zip_entry_name(zentry)) } |
| 149 | if name == 0 { |
| 150 | return '' |
| 151 | } |
| 152 | return unsafe { name.vstring() } |
| 153 | } |
| 154 | |
| 155 | // index returns an index of the current zip entry. |
| 156 | pub fn (mut zentry Zip) index() !int { |
| 157 | index := int(C.zip_entry_index(zentry)) |
| 158 | if index == -1 { |
| 159 | return error('szip: cannot get current index of zip entry') |
| 160 | } |
| 161 | return index // must be check for INVALID_VALUE |
| 162 | } |
| 163 | |
| 164 | // is_dir determines if the current zip entry is a directory entry. |
| 165 | pub fn (mut zentry Zip) is_dir() !bool { |
| 166 | isdir := C.zip_entry_isdir(zentry) |
| 167 | if isdir < 0 { |
| 168 | return error('szip: cannot check entry type') |
| 169 | } |
| 170 | return isdir == 1 |
| 171 | } |
| 172 | |
| 173 | // size returns an uncompressed size of the current zip entry. |
| 174 | @[inline] |
| 175 | pub fn (mut zentry Zip) size() u64 { |
| 176 | return C.zip_entry_size(zentry) |
| 177 | } |
| 178 | |
| 179 | // crc32 returns CRC-32 checksum of the current zip entry. |
| 180 | @[inline] |
| 181 | pub fn (mut zentry Zip) crc32() u32 { |
| 182 | return C.zip_entry_crc32(zentry) |
| 183 | } |
| 184 | |
| 185 | // write_entry compresses an input buffer for the current zip entry. |
| 186 | pub fn (mut zentry Zip) write_entry(data []u8) ! { |
| 187 | if data.len > 0 && int(data[0] & 0xff) == -1 { |
| 188 | return error('szip: cannot write entry') |
| 189 | } |
| 190 | res := C.zip_entry_write(zentry, data.data, data.len) |
| 191 | if res != 0 { |
| 192 | return error('szip: failed to write entry') |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // create_entry compresses a file for the current zip entry. |
| 197 | pub fn (mut zentry Zip) create_entry(name string) ! { |
| 198 | res := C.zip_entry_fwrite(zentry, &char(name.str)) |
| 199 | if res != 0 { |
| 200 | return error('szip: failed to create entry') |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | // read_entry extracts the current zip entry into output buffer. |
| 205 | // The function allocates sufficient memory for an output buffer. |
| 206 | // NOTE: remember to release the memory allocated for an output buffer. |
| 207 | // for large entries, please take a look at zip_entry_extract function. |
| 208 | pub fn (mut zentry Zip) read_entry() !voidptr { |
| 209 | mut buf := &u8(unsafe { nil }) |
| 210 | mut bsize := usize(0) |
| 211 | res := C.zip_entry_read(zentry, unsafe { &voidptr(&buf) }, &bsize) |
| 212 | if res == -1 { |
| 213 | return error('szip: cannot read properly data from entry') |
| 214 | } |
| 215 | return buf |
| 216 | } |
| 217 | |
| 218 | // read_entry_buf extracts the current zip entry into user specified buffer. |
| 219 | pub fn (mut zentry Zip) read_entry_buf(buf voidptr, in_bsize int) !int { |
| 220 | bsize := usize(in_bsize) |
| 221 | res := C.zip_entry_noallocread(zentry, buf, bsize) |
| 222 | if res == -1 { |
| 223 | return error('szip: cannot read properly data from entry') |
| 224 | } |
| 225 | return res |
| 226 | } |
| 227 | |
| 228 | // extract_entry extracts the current zip entry into output file. |
| 229 | pub fn (mut zentry Zip) extract_entry(path string) ! { |
| 230 | res := C.zip_entry_fread(zentry, &char(path.str)) |
| 231 | if res != 0 { |
| 232 | return error('szip: failed to extract entry') |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | // extract zip file to directory. |
| 237 | pub fn extract_zip_to_dir(file string, dir string) !bool { |
| 238 | if C.access(&char(dir.str), 0) == -1 { |
| 239 | return error('szip: cannot open directory for extracting, directory not exists') |
| 240 | } |
| 241 | res := C.zip_extract(&char(file.str), &char(dir.str), cb_zip_extract, 0) |
| 242 | return res == 0 |
| 243 | } |
| 244 | |
| 245 | // zip files (full path) to zip file. |
| 246 | pub fn zip_files(path_to_file []string, path_to_export_zip string) ! { |
| 247 | // open or create new zip |
| 248 | mut zip := open(path_to_export_zip, .no_compression, .write) or { panic(err) } |
| 249 | |
| 250 | // add all files from the directory to the archive |
| 251 | for file in path_to_file { |
| 252 | // add file to zip |
| 253 | zip.open_entry(os.base(file)) or { panic(err) } |
| 254 | file_as_byte := os.read_bytes(file) or { panic(err) } |
| 255 | zip.write_entry(file_as_byte) or { panic(err) } |
| 256 | |
| 257 | zip.close_entry() |
| 258 | } |
| 259 | |
| 260 | // close zip |
| 261 | defer { |
| 262 | zip.close() |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | // zip_folder zips all entries in `folder` *recursively* to the zip file at `zip_file`. |
| 267 | // Empty folders will be included, unless specified otherwise in `opt`. |
| 268 | pub fn zip_folder(folder string, zip_file string, opt ZipFolderOptions) ! { |
| 269 | // get list of files from directory |
| 270 | path := folder.trim_right(os.path_separator) |
| 271 | mut files := []string{} |
| 272 | os.walk_with_context(path, &files, fn (ctx voidptr, file string) { |
| 273 | mut files := unsafe { &[]string(ctx) } |
| 274 | files << file |
| 275 | }) |
| 276 | |
| 277 | // open or create new zip |
| 278 | mut zip := open(zip_file, .no_compression, .write)! |
| 279 | // close zip |
| 280 | defer { |
| 281 | zip.close() |
| 282 | } |
| 283 | |
| 284 | // add all files from the directory to the archive |
| 285 | for file in files { |
| 286 | is_dir := os.is_dir(file) |
| 287 | if opt.omit_empty_folders && is_dir { |
| 288 | continue |
| 289 | } |
| 290 | // strip each zip entry for the path prefix - this way |
| 291 | // all files in the archive can be made relative. |
| 292 | mut zip_file_entry := file.trim_string_left(path + os.path_separator) |
| 293 | // Normalize path on Windows \ -> / |
| 294 | $if windows { |
| 295 | zip_file_entry = zip_file_entry.replace(os.path_separator, '/') |
| 296 | } |
| 297 | if is_dir { |
| 298 | zip_file_entry += '/' // Tells the implementation that the entry is a directory |
| 299 | } |
| 300 | // add file or directory (ends with "/") to zip |
| 301 | zip.open_entry(zip_file_entry)! |
| 302 | if !is_dir { |
| 303 | file_as_byte := os.read_bytes(file)! |
| 304 | zip.write_entry(file_as_byte)! |
| 305 | } |
| 306 | zip.close_entry() |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | // total returns the number of all entries (files and directories) in the zip archive. |
| 311 | pub fn (mut zentry Zip) total() !int { |
| 312 | tentry := int(C.zip_entries_total(zentry)) |
| 313 | if tentry == -1 { |
| 314 | return error('szip: cannot count total entries') |
| 315 | } |
| 316 | return tentry |
| 317 | } |
| 318 | |