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