some updates
[iv.d.git] / arcztest / zpack.d
blobc6de5503b54734d52bd51d650953abfb4c562f26
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 static if (arcz_has_dlzma) enum lzmaop = " --lzma use LZMA compressor\n"; else enum lzmaop = "";
71 write(
72 "zpack [options] -o outfile sourcedir\n"~
73 "options:\n"~
74 " -b block size [1..32MB] (default is 256KB)\n"~
75 " --zlib use ZLib compressor (default)\n"~
76 sbalz~
77 sbalz1~
78 szop~
79 lzmaop
83 string outfname = null;
84 string srcdir = null;
85 ArzCreator.Compressor cpr = ArzCreator.Compressor.ZLib;
87 ubyte[] rdbuf;
88 rdbuf.length = 65536;
90 if (args.length < 2) { usage(); return; }
92 bool nomore = false;
93 int f = 1;
94 while (f < args.length) {
95 string arg = args[f++];
96 if (arg.length == 0) continue;
97 if (!nomore) {
98 if (arg == "--") { nomore = true; continue; }
99 if (arg == "-") throw new Exception("stdin is not supported");
100 if (arg[0] == '-') {
101 if (arg == "--balz") { cpr = ArzCreator.Compressor.Balz; continue; }
102 if (arg == "--balzx") { cpr = ArzCreator.Compressor.BalzMax; continue; }
103 if (arg == "--zopfli") { cpr = ArzCreator.Compressor.Zopfli; continue; }
104 if (arg == "--zlib") { cpr = ArzCreator.Compressor.ZLib; continue; }
105 if (arg == "--lzma") { cpr = ArzCreator.Compressor.Lzma; continue; }
106 if (arg[1] == '-') throw new Exception("long options aren't supported");
107 arg = arg[1..$];
108 while (arg.length) {
109 char ch = arg[0];
110 arg = arg[1..$];
111 if (ch == 'b') {
112 if (arg.length == 0) {
113 if (f >= args.length) throw new Exception("block size?");
114 arg = args[f++];
116 auto sz = parseSize(arg);
117 if (sz < 1 || sz >= 32*1024*1024) throw new Exception("invalid block size: '"~arg~"'");
118 blockSize = cast(uint)sz;
119 break;
121 if (ch == 'h') { usage(); return; }
122 if (ch == 'o') {
123 if (outfname !is null) throw new Exception("duplicate output name");
124 if (arg.length == 0) {
125 if (f >= args.length) throw new Exception("block size?");
126 arg = args[f++];
128 if (arg.length == 0) throw new Exception("empty output name");
129 outfname = arg.defaultExtension(".arz");
130 break;
132 throw new Exception("invalid option: '"~ch~"' ("~arg~")");
134 continue;
137 if (srcdir !is null) throw new Exception("duplicate source directory");
138 if (arg == "-") throw new Exception("stdin is not supported");
139 srcdir = arg;
142 if (srcdir is null) throw new Exception("source directory?");
143 if (outfname is null) throw new Exception("output file name?");
145 writeln("creating archive '", outfname, "' with block size ", blockSize.n2c);
147 static struct FInfo {
148 string diskname;
149 string name;
150 uint size;
152 FInfo[] filelist;
154 writeln("building file list...");
155 long total = 0;
156 foreach (DirEntry e; dirEntries(srcdir, SpanMode.breadth)) {
157 if (e.isFile) {
158 if (e.size > 0x3fff_ffff) throw new Exception("file '"~e.name~"' is too big");
159 total += e.size;
160 string fname = e.name[srcdir.length..$];
161 version(Windows) {
162 import std.string : replace;
163 while (fname.length > 0 && fname[0] == '\\') fname = fname[1..$];
164 fname = fname.replace("\\", "/");
165 } else {
166 while (fname.length > 0 && fname[0] == '/') fname = fname[1..$];
168 if (fname.length == 0) throw new Exception("invalid file name (wtf?!)");
169 filelist ~= FInfo(e.name, fname, cast(uint)e.size);
173 if (filelist.length == 0) throw new Exception("no files found");
174 writeln(filelist.length, " files found, total ", total.n2c, " bytes");
176 // sort files by size and extension
177 import std.algorithm : sort;
179 filelist.sort!((ref a, ref b) {
180 if (a.size < b.size) return true;
181 if (a.size > b.size) return false;
182 // same size, try extension
183 return (a.name.extension < b.name.extension);
186 filelist.sort!((ref a, ref b) {
187 // same size, try extension
188 return (a.name < b.name);
191 auto arcz = new ArzCreator(outfname, blockSize, cpr);
192 auto stt = MonoTime.currTime;
193 foreach (immutable filenum, ref nfo; filelist) {
194 arcz.newFile(nfo.name, nfo.size);
195 auto fi = File(nfo.diskname);
196 for (;;) {
197 auto rd = fi.rawRead(rdbuf[]);
198 if (rd.length == 0) break;
199 arcz.rawWrite(rd[]);
202 auto ctt = MonoTime.currTime;
203 if ((ctt-stt).total!"seconds" > 0) {
204 stt = ctt;
205 stdout.write(" [", (filenum+1).n2c, "/", filelist.length.n2c, "] files processed\r");
206 stdout.flush();
210 arcz.close();
211 writeln("\r", total.n2c, " bytes packed to ", getSize(outfname).n2c, " (", arcz.chunksWritten, " chunks, ", arcz.filesWritten, " files)\x1b[K");
215 // ////////////////////////////////////////////////////////////////////////// //
216 int main (string[] args) {
217 try {
218 doMain(args);
219 return 0;
220 } catch (Exception e) {
221 writeln("FATAL: ", e.msg);
223 return -1;