1 module libzipd.simplezip;
2 
3 import libzipd.libziph;
4 import std..string: toStringz;
5 import std.conv: to;
6 struct Archive
7 {
8     zip_t* archive;
9 
10     this(string destination_file)
11     {
12         this.open(destination_file);
13     }
14 
15     // set the default password after opening the archive
16     // this works only for extracting files
17     void set_password(string pass) {
18         this.archive.zip_set_default_password(pass.toStringz);
19     }
20 
21     void open(string destination_file)
22     {
23         import std.format : format;
24         import core.stdc.errno;
25 
26         int errors = 0;
27         this.archive = zip_open(destination_file.toStringz, ZIP_RDONLY, &errors);
28 
29         if (errors)
30         {
31             char[100] buf;
32             zip_error_to_str(buf.ptr, buf.length, errors, errno);
33             throw new Exception(format!"something wrong while opening zip archive `%s': %s"(destination_file,
34                     buf));
35         }
36 
37         if (archive is null)
38         {
39             throw new Exception("archive is null");
40         }
41     }
42 
43     void create(string destination_file)
44     {
45         import std.format : format;
46         import core.stdc.errno;
47         import std.file: exists;
48 
49         if (destination_file.exists()) {
50             throw new Exception(format!"file %s already exists"(destination_file));
51         }
52 
53         int errors = 0;
54         this.archive = zip_open(destination_file.toStringz, ZIP_CREATE, &errors);
55 
56         if (errors)
57         {
58             char[100] buf;
59             zip_error_to_str(buf.ptr, buf.length, errors, errno);
60             throw new Exception(format!"something wrong while opening zip archive `%s': %s"(destination_file,
61                     buf));
62         }
63 
64         if (archive is null)
65         {
66             throw new Exception("archive is null");
67         }
68     }
69 
70     string[] list_entries()
71     {
72         import std.conv : to;
73 
74         long number_of_entries = this.entries_count();
75         string[] result;
76         for (int i = 0; i < number_of_entries; i++)
77         {
78             result ~= archive.zip_get_name(i, ZIP_FL_ENC_UTF_8).to!string;
79         }
80 
81         return result;
82     }
83 
84     void add_folder(string path) {
85         import std.format : format;
86         import std.file : dirEntries, SpanMode;
87         import std.path: relativePath;
88 
89         // add only the folders under the one in the arguments
90         string strip_path = path;
91 
92         foreach (dir_entry; path.dirEntries(SpanMode.depth)) {
93             if (dir_entry.isDir) {
94                 string stripped_path = dir_entry.name.relativePath(strip_path);
95                 zip_int64_t first_index;
96                 first_index = this.archive.zip_dir_add(stripped_path.toStringz, ZIP_FL_ENC_UTF_8);
97 
98                 if ( first_index == -1 ) {
99                     throw new Exception(format!"failed to add directory %s : %s"(path, zip_strerror(this.archive)));
100                 }
101             }
102             else if (dir_entry.isFile)
103             {
104                 this.add_file(dir_entry.name, strip_path);
105             }
106             else
107             {
108                 // TODO deal with symlinks
109                 throw new Exception(
110                         "adding something that is not a normal file nor a folder is not supported");
111             }
112         }
113     }
114 
115     void add_file(string file_path, string strip_path = "") {
116         import std.format : format;
117         import core.stdc.errno;
118         import core.stdc..string : strerror;
119         import std.path : baseName, relativePath;
120 
121         libzipd.libziph._IO_FILE* file_pointer = cast(libzipd.libziph._IO_FILE*) file_path.toStringz.fopen("r");
122         if (file_pointer is null)
123         {
124             throw new Exception(format!"can't open input file '%s' %s"(file_path, strerror(errno)));
125         }
126 
127         ulong start = 0;
128         long len = -1;
129         zip_source_t* file_source = archive.zip_source_filep(file_pointer, start, len);
130         if (file_source is null)
131         {
132             throw new Exception(format!"error creating file source for '%s': %s"(file_path,
133                     zip_strerror(archive)));
134         }
135 
136         zip_int64_t index;
137         if (strip_path != "") {
138             index = zip_file_add(archive, file_path.relativePath(strip_path).toStringz, file_source, ZIP_FL_OVERWRITE);
139         } else {
140            index = zip_file_add(archive, file_path.toStringz, file_source, ZIP_FL_OVERWRITE);
141         }
142 
143         if (index == -1)
144         {
145             zip_source_free(file_source);
146             throw new Exception(format!"failed to add file '%s': '%s'"(file_path,
147                     zip_strerror(archive)));
148         }
149     }
150 
151     void extract(string destination_path)
152     {
153         import core.stdc.errno;
154         import core.stdc..string : strlen;
155         import std.stdio : File;
156         import std.format : format;
157         import std.file : exists, mkdirRecurse;
158         import std.path : buildPath, dirName;
159         import std.conv : to;
160 
161         if (!exists(destination_path))
162         {
163             mkdirRecurse(destination_path);
164         }
165 
166         long number_of_entries = this.entries_count();
167 
168         for (long index = 0; index < number_of_entries; index++)
169         {
170             zip_stat_t stats_buff;
171             if (zip_stat_index(this.archive, index, 0, &stats_buff) != 0)
172             {
173                 throw new Exception( format!"cannot read the stats for index %d"(index) );
174             }
175 
176             const char* file_name_string = zip_get_name(this.archive, index, ZIP_FL_ENC_UTF_8);
177             // printf("\n### file to extract %s\n\n", file_name_string);
178             ulong name_length = strlen(file_name_string);
179             if (file_name_string[name_length - 1] == '/')
180             {
181                 string destination_path_full = destination_path.buildPath(file_name_string.to!string);
182                 if (!exists(destination_path_full)) {
183                     destination_path_full.mkdirRecurse;
184                 }
185             }
186             else
187             {
188                 zip_file_t* file_entry = zip_fopen_index(this.archive, index, 0);
189                 if (file_entry is null) {
190                     throw new Exception(format!"file %s could not be opened: %s"(file_name_string.to!string, zip_strerror(this.archive).to!string ));
191                 }
192                 string destination_path_full = destination_path.buildPath(file_name_string.to!string);
193                 if(!destination_path_full.dirName.exists) {
194                     destination_path_full.dirName.mkdirRecurse();
195                 }
196                 File destination_file = File(destination_path_full, "wb");
197 
198                 int sum = 0;
199                 char[1024] entry_buffer;
200                 size_t read_length;
201                 ssize_t wrote_length;
202                 while (sum != stats_buff.size)
203                 {
204                     read_length = zip_fread(file_entry, entry_buffer.ptr, 1024);
205                     if (read_length < 0)
206                     {
207                         throw new Exception(
208                                 format!"failed to read from archived file %s"(file_name_string));
209                     }
210                     destination_file.rawWrite(entry_buffer[0 .. read_length]);
211                     sum += read_length;
212                 }
213                 destination_file.close();
214                 zip_fclose(file_entry);
215             }
216         }
217 
218         if (zip_close(archive) != 0)
219         {
220             throw new Exception("failed to close the archive");
221         }
222     }
223 
224     void finish() {
225         this.archive.zip_close();
226     }
227 
228     void close() {
229         zip_close(this.archive);
230     }
231 
232     void discard() {
233         this.archive.zip_discard();
234     }
235 
236     void revert(){
237         this.archive.zip_unchange_all();
238     }
239 
240     long entries_count() {
241         return zip_get_num_entries(this.archive, ZIP_FL_UNCHANGED);
242     }
243 
244     static string make_temp_path() {
245         import std.file: tempDir;
246         import std.path: buildPath;
247         import std.uuid: randomUUID;
248 
249         return buildPath(tempDir, "libzipd_temp", randomUUID().toString);
250     }
251 }
252