1 module szip; 2 3 import std.file; 4 import std.path; 5 import std.algorithm; 6 import std.exception; 7 import std.bitmanip; 8 import std.zlib; 9 import std.string; 10 11 const ubyte[] magic = [12, 29]; 12 13 void zip(string sourceDirOrFileName, string outputFilename) { 14 enforce(std.file.exists(sourceDirOrFileName)); 15 enforce(outputFilename != string.init); 16 17 ubyte[] buffer; 18 19 if (std.file.isFile(sourceDirOrFileName)) { 20 put!"file"(sourceDirOrFileName, buffer); 21 } else { 22 readFile(sourceDirOrFileName, string.init, buffer); 23 } 24 25 if (std.file.exists(outputFilename)) std.file.remove(outputFilename); 26 27 std.file.write(outputFilename, magic); 28 std.file.append(outputFilename, compress(buffer)); 29 } 30 31 void unzip(string szipFilename, string outputPath) { 32 enforce(std.file.exists(szipFilename)); 33 34 ubyte[] buffer = cast(ubyte[])std.file.read(szipFilename); 35 enforce(buffer[0 .. 2] == magic, "Not the szip file format."); 36 buffer = cast(ubyte[])uncompress(buffer[2 .. $]); 37 38 if (!std.file.exists(outputPath)) std.file.mkdirRecurse(outputPath); 39 40 if (buffer.length == 0) { 41 return; 42 } 43 44 string dir = outputPath; 45 while (buffer.length > 0) { 46 ubyte type = buffer[0]; 47 ushort len = buffer.peek!ushort(1); 48 string name = cast(string)buffer[3 .. 3 + len]; 49 if (type == 0x01) { 50 dir = _buildPath(outputPath, name); 51 if (!std.file.exists(dir)) std.file.mkdirRecurse(dir); 52 buffer = buffer[3 + len .. $]; 53 } else { 54 string filename = _buildPath(dir, name); 55 uint file_len = buffer.peek!uint(3 + len); 56 ubyte[] content = buffer[3 + len + 4 .. 3 + len + 4 + file_len]; 57 std.file.write(filename, content); 58 buffer = buffer[3 + len + 4 + file_len .. $]; 59 } 60 } 61 } 62 63 private: 64 65 string _buildPath(string rootDir, string path) { 66 string full = std.path.buildPath(rootDir, path); 67 version(Windows) { 68 return full.replace("\\", "/"); 69 } else { 70 return full; 71 } 72 } 73 74 void readFile(string dir, string rootDir, ref ubyte[] buffer) { 75 foreach (DirEntry e; dirEntries(dir, SpanMode.shallow).filter!(a => a.isFile)) { 76 put!"file"(e.name, buffer); 77 } 78 79 foreach (DirEntry e; dirEntries(dir, SpanMode.shallow).filter!(a => a.isDir)) { 80 string t = _buildPath(rootDir, std.path.baseName(e.name)); 81 put!"dir"(t, buffer); 82 readFile(e.name, t, buffer); 83 } 84 } 85 86 void put(string T = "dir")(string name, ref ubyte[] buffer) if (T == "dir" || T == "file") { 87 buffer ~= (T == "dir") ? 0x01 : 0x02; 88 buffer ~= [0x00, 0x00]; 89 string t = (T == "file") ? std.path.baseName(name) : name; 90 buffer.write!ushort(cast(ushort)t.length, buffer.length - 2); 91 buffer ~= cast(ubyte[])t; 92 93 static if (T == "file") { 94 ubyte[] content = cast(ubyte[])std.file.read(name); 95 buffer ~= [0x00, 0x00, 0x00, 0x00]; 96 buffer.write!uint(cast(uint)content.length, buffer.length - 4); 97 buffer ~= content; 98 } 99 } 100 101 unittest 102 { 103 szip.zip("/Users/shove/Desktop/Folder1", "/Users/shove/Desktop/archives.szip"); 104 szip.unzip("/Users/shove/Desktop/archives.szip", "/Users/shove/Desktop/Folder2"); 105 } 106 107 unittest 108 { 109 szip.zip("/Users/shove/Desktop/file1.txt", "/Users/shove/Desktop/archives.szip"); 110 szip.unzip("/Users/shove/Desktop/archives.szip", "/Users/shove/Desktop/Folder2"); 111 } 112 113 /* 114 .szip file format: 115 116 type: a byte, 1:dir, 2:file 117 dir: len(ushort) + long dir name 118 file: len(ushort) + short file name + len(uint) + file content 119 120 szip archives: 121 magic + file1 + ... + fileN + dir + file1 + ... + fileN + ... 122 */