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     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 */