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