sq3: cosmetix
[iv.d.git] / arcztest / zpack.d
blobaf8e552091d1e65242de44dad97bb38880503a2b
1 module zpack /*is aliced*/;
3 import core.time;
4 import std.stdio;
6 import iv.alice;
7 import iv.arcz;
10 // ////////////////////////////////////////////////////////////////////////// //
11 string n2c (ulong n) {
12 import std.conv : to;
13 string t = to!string(n);
14 if (t.length < 4) return t;
15 string res;
16 while (t.length > 3) {
17 res = ","~t[$-3..$]~res;
18 t = t[0..$-3];
20 if (t.length) res = t~res; else res = res[1..$];
21 return res;
25 // ////////////////////////////////////////////////////////////////////////// //
26 void doMain (string[] args) {
27 import std.file, std.path, std.stdio : File;
29 // -1: error
30 long parseSize (const(char)[] s) {
31 if (s.length == 0) return -1;
32 if (s[0] < '0' || s[0] > '9') return -1;
33 long res = 0;
34 while (s.length && s[0] >= '0' && s[0] <= '9') {
35 res = res*10+s[0]-'0';
36 if (res > 0x3fff_ffff) return -1;
37 s = s[1..$];
39 if (s.length == 0) return res;
40 if (s.length == 1) {
41 switch (s[0]) {
42 case 'K': case 'k': return res*1024;
43 case 'M': case 'm': return res*1024*1024;
44 case 'G': case 'g': return res*1024*1024*1024;
45 default: return -1;
47 } else if (s.length == 2) {
48 char[2] u;
49 foreach (immutable idx, char ch; s) {
50 if (ch >= 'a' && ch <= 'z') ch -= 32;
51 u[idx] = ch;
53 switch (u[]) {
54 case "KB": return res*1024;
55 case "MB": return res*1024*1024;
56 case "GB": return res*1024*1024*1024;
57 default: return -1;
60 return -1;
63 uint blockSize = 256*1024;
65 void usage () {
66 import std.stdio : write;
67 static if (arcz_has_balz) enum sbalz = " --balz use Balz compressor\n"; else sbalz = "";
68 static if (arcz_has_balz) enum sbalz1 = " --balzx use Balz compressor in \"maximum\" mode\n"; else sbalz1 = "";
69 static if (arcz_has_zopfli) enum szop = " --zopfli use Zopfli compressor\n"; else enum szop = "";
70 write(
71 "zpack [options] -o outfile sourcedir\n"~
72 "options:\n"~
73 " -b block size [1..32MB] (default is 256KB)\n"~
74 " --zlib use ZLib compressor (default)\n"~
75 sbalz~
76 sbalz1~
77 szop
81 string outfname = null;
82 string srcdir = null;
83 ArzCreator.Compressor cpr = ArzCreator.Compressor.ZLib;
85 ubyte[] rdbuf;
86 rdbuf.length = 65536;
88 if (args.length < 2) { usage(); return; }
90 bool nomore = false;
91 int f = 1;
92 while (f < args.length) {
93 string arg = args[f++];
94 if (arg.length == 0) continue;
95 if (!nomore) {
96 if (arg == "--") { nomore = true; continue; }
97 if (arg == "-") throw new Exception("stdin is not supported");
98 if (arg[0] == '-') {
99 if (arg == "--balz") { cpr = ArzCreator.Compressor.Balz; continue; }
100 if (arg == "--balzx") { cpr = ArzCreator.Compressor.BalzMax; continue; }
101 if (arg == "--zopfli") { cpr = ArzCreator.Compressor.Zopfli; continue; }
102 if (arg == "--zlib") { cpr = ArzCreator.Compressor.ZLib; continue; }
103 if (arg[1] == '-') throw new Exception("long options aren't supported");
104 arg = arg[1..$];
105 while (arg.length) {
106 char ch = arg[0];
107 arg = arg[1..$];
108 if (ch == 'b') {
109 if (arg.length == 0) {
110 if (f >= args.length) throw new Exception("block size?");
111 arg = args[f++];
113 auto sz = parseSize(arg);
114 if (sz < 1 || sz >= 32*1024*1024) throw new Exception("invalid block size: '"~arg~"'");
115 blockSize = cast(uint)sz;
116 break;
118 if (ch == 'h') { usage(); return; }
119 if (ch == 'o') {
120 if (outfname !is null) throw new Exception("duplicate output name");
121 if (arg.length == 0) {
122 if (f >= args.length) throw new Exception("block size?");
123 arg = args[f++];
125 if (arg.length == 0) throw new Exception("empty output name");
126 outfname = arg.defaultExtension(".arz");
127 break;
129 throw new Exception("invalid option: '"~ch~"' ("~arg~")");
131 continue;
134 if (srcdir !is null) throw new Exception("duplicate source directory");
135 if (arg == "-") throw new Exception("stdin is not supported");
136 srcdir = arg;
139 if (srcdir is null) throw new Exception("source directory?");
140 if (outfname is null) throw new Exception("output file name?");
142 writeln("creating archive '", outfname, "' with block size ", blockSize.n2c);
144 static struct FInfo {
145 string diskname;
146 string name;
147 uint size;
149 FInfo[] filelist;
151 writeln("building file list...");
152 long total = 0;
153 foreach (DirEntry e; dirEntries(srcdir, SpanMode.breadth)) {
154 if (e.isFile) {
155 if (e.size > 0x3fff_ffff) throw new Exception("file '"~e.name~"' is too big");
156 total += e.size;
157 string fname = e.name[srcdir.length..$];
158 version(Windows) {
159 import std.string : replace;
160 while (fname.length > 0 && fname[0] == '\\') fname = fname[1..$];
161 fname = fname.replace("\\", "/");
162 } else {
163 while (fname.length > 0 && fname[0] == '/') fname = fname[1..$];
165 if (fname.length == 0) throw new Exception("invalid file name (wtf?!)");
166 filelist ~= FInfo(e.name, fname, cast(uint)e.size);
170 if (filelist.length == 0) throw new Exception("no files found");
171 writeln(filelist.length, " files found, total ", total.n2c, " bytes");
173 // sort files by size and extension
174 import std.algorithm : sort;
176 filelist.sort!((ref a, ref b) {
177 if (a.size < b.size) return true;
178 if (a.size > b.size) return false;
179 // same size, try extension
180 return (a.name.extension < b.name.extension);
183 filelist.sort!((ref a, ref b) {
184 // same size, try extension
185 return (a.name < b.name);
188 auto arcz = new ArzCreator(outfname, blockSize, cpr);
189 auto stt = MonoTime.currTime;
190 foreach (immutable filenum, ref nfo; filelist) {
191 arcz.newFile(nfo.name, nfo.size);
192 auto fi = File(nfo.diskname);
193 for (;;) {
194 auto rd = fi.rawRead(rdbuf[]);
195 if (rd.length == 0) break;
196 arcz.rawWrite(rd[]);
199 auto ctt = MonoTime.currTime;
200 if ((ctt-stt).total!"seconds" > 0) {
201 stt = ctt;
202 stdout.write("\r[", (filenum+1).n2c, "/", filelist.length.n2c, "] files processed");
203 stdout.flush();
207 arcz.close();
208 writeln("\r", total.n2c, " bytes packed to ", getSize(outfname).n2c, " (", arcz.chunksWritten, " chunks, ", arcz.filesWritten, " files)\x1b[K");
212 // ////////////////////////////////////////////////////////////////////////// //
213 int main (string[] args) {
214 try {
215 doMain(args);
216 return 0;
217 } catch (Exception e) {
218 writeln("FATAL: ", e.msg);
220 return -1;