1/*
2 * Copyright 2014, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <errno.h>
8#include <getopt.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13#include <File.h>
14
15#include <ZlibCompressionAlgorithm.h>
16#include <ZstdCompressionAlgorithm.h>
17
18
19extern const char* __progname;
20const char* kCommandName = __progname;
21
22
23enum CompressionType {
24	ZlibCompression,
25	GzipCompression,
26	ZstdCompression,
27};
28
29
30static const char* kUsage =
31	"Usage: %s <options> <input file> <output file>\n"
32	"Compresses or decompresses (option -d) a file.\n"
33	"\n"
34	"Options:\n"
35	"  -0 ... -9\n"
36	"      Use compression level 0 ... 9. 0 means no, 9 best compression.\n"
37	"      Defaults to 9.\n"
38	"  -d, --decompress\n"
39	"      Decompress the input file (default is compress).\n"
40	"  -f <format>\n"
41	"      Specify the compression format: \"zlib\" (default), \"gzip\"\n"
42	"      or \"zstd\".\n"
43	"  -h, --help\n"
44	"      Print this usage info.\n"
45	"  -i, --input-stream\n"
46	"      Use the input stream API (default is output stream API).\n"
47;
48
49
50static void
51print_usage_and_exit(bool error)
52{
53    fprintf(error ? stderr : stdout, kUsage, kCommandName);
54    exit(error ? 1 : 0);
55}
56
57
58int
59main(int argc, const char* const* argv)
60{
61	int compressionLevel = -1;
62	bool compress = true;
63	bool useInputStream = false;
64	CompressionType compressionType = ZlibCompression;
65
66	while (true) {
67		static struct option sLongOptions[] = {
68			{ "decompress", no_argument, 0, 'd' },
69			{ "help", no_argument, 0, 'h' },
70			{ "input-stream", no_argument, 0, 'i' },
71			{ 0, 0, 0, 0 }
72		};
73
74		opterr = 0; // don't print errors
75		int c = getopt_long(argc, (char**)argv, "+0123456789df:hi",
76			sLongOptions, NULL);
77		if (c == -1)
78			break;
79
80		switch (c) {
81			case '0':
82			case '1':
83			case '2':
84			case '3':
85			case '4':
86			case '5':
87			case '6':
88			case '7':
89			case '8':
90			case '9':
91				compressionLevel = c - '0';
92				break;
93
94			case 'h':
95				print_usage_and_exit(false);
96				break;
97
98			case 'd':
99				compress = false;
100				break;
101
102			case 'f':
103				if (strcmp(optarg, "zlib") == 0) {
104					compressionType = ZlibCompression;
105				} else if (strcmp(optarg, "gzip") == 0) {
106					compressionType = GzipCompression;
107				} else if (strcmp(optarg, "zstd") == 0) {
108					compressionType = ZstdCompression;
109				} else {
110					fprintf(stderr, "Error: Unsupported compression type "
111						"\"%s\"\n", optarg);
112					return 1;
113				}
114				break;
115
116			case 'i':
117				useInputStream = true;
118				break;
119
120			default:
121				print_usage_and_exit(true);
122				break;
123		}
124	}
125
126	// The remaining arguments are input and output file.
127	if (optind + 2 != argc)
128		print_usage_and_exit(true);
129
130	const char* inputFilePath = argv[optind++];
131	const char* outputFilePath = argv[optind++];
132
133	// open input file
134	BFile inputFile;
135	status_t error = inputFile.SetTo(inputFilePath, B_READ_ONLY);
136	if (error != B_OK) {
137		fprintf(stderr, "Error: Failed to open \"%s\": %s\n", inputFilePath,
138			strerror(errno));
139		return 1;
140	}
141
142	// open output file
143	BFile outputFile;
144	error = outputFile.SetTo(outputFilePath,
145		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
146	if (error != B_OK) {
147		fprintf(stderr, "Error: Failed to open \"%s\": %s\n", outputFilePath,
148			strerror(errno));
149		return 1;
150	}
151
152	// create compression algorithm and parameters
153	BCompressionAlgorithm* compressionAlgorithm;
154	BCompressionParameters* compressionParameters;
155	BDecompressionParameters* decompressionParameters;
156	switch (compressionType) {
157		case ZlibCompression:
158		case GzipCompression:
159		{
160			if (compressionLevel < 0)
161				compressionLevel = B_ZLIB_COMPRESSION_DEFAULT;
162			compressionAlgorithm = new BZlibCompressionAlgorithm;
163			BZlibCompressionParameters* zlibCompressionParameters
164				= new BZlibCompressionParameters(compressionLevel);
165			zlibCompressionParameters->SetGzipFormat(
166				compressionType == GzipCompression);
167			compressionParameters = zlibCompressionParameters;
168			decompressionParameters = new BZlibDecompressionParameters;
169			break;
170		}
171		case ZstdCompression:
172		{
173			if (compressionLevel < 0)
174				compressionLevel = B_ZSTD_COMPRESSION_DEFAULT;
175			compressionAlgorithm = new BZstdCompressionAlgorithm;
176			compressionParameters
177				= new BZstdCompressionParameters(compressionLevel);
178			decompressionParameters = new BZstdDecompressionParameters;
179			break;
180		}
181	}
182
183	if (useInputStream) {
184		// create input stream
185		BDataIO* inputStream;
186		if (compress) {
187			error = compressionAlgorithm->CreateCompressingInputStream(
188				&inputFile, compressionParameters, inputStream);
189		} else {
190			error = compressionAlgorithm->CreateDecompressingInputStream(
191				&inputFile, decompressionParameters, inputStream);
192		}
193
194		if (error != B_OK) {
195			fprintf(stderr, "Error: Failed to create input stream: %s\n",
196				strerror(error));
197			return 1;
198		}
199
200		// processing loop
201		for (;;) {
202			uint8 buffer[64 * 1024];
203			ssize_t bytesRead = inputStream->Read(buffer, sizeof(buffer));
204			if (bytesRead < 0) {
205				fprintf(stderr, "Error: Failed to read from input stream: %s\n",
206					strerror(bytesRead));
207				return 1;
208			}
209			if (bytesRead == 0)
210				break;
211
212			error = outputFile.WriteExactly(buffer, bytesRead);
213			if (error != B_OK) {
214				fprintf(stderr, "Error: Failed to write to output file: %s\n",
215					strerror(error));
216				return 1;
217			}
218		}
219	} else {
220		// create output stream
221		BDataIO* outputStream;
222		if (compress) {
223			error = compressionAlgorithm->CreateCompressingOutputStream(
224				&outputFile, compressionParameters, outputStream);
225		} else {
226			error = compressionAlgorithm->CreateDecompressingOutputStream(
227				&outputFile, decompressionParameters, outputStream);
228		}
229
230		if (error != B_OK) {
231			fprintf(stderr, "Error: Failed to create output stream: %s\n",
232				strerror(error));
233			return 1;
234		}
235
236		// processing loop
237		for (;;) {
238			uint8 buffer[64 * 1024];
239			ssize_t bytesRead = inputFile.Read(buffer, sizeof(buffer));
240			if (bytesRead < 0) {
241				fprintf(stderr, "Error: Failed to read from input file: %s\n",
242					strerror(bytesRead));
243				return 1;
244			}
245			if (bytesRead == 0)
246				break;
247
248			error = outputStream->WriteExactly(buffer, bytesRead);
249			if (error != B_OK) {
250				fprintf(stderr, "Error: Failed to write to output stream: %s\n",
251					strerror(error));
252				return 1;
253			}
254		}
255
256		// flush the output stream
257		error = outputStream->Flush();
258		if (error != B_OK) {
259			fprintf(stderr, "Error: Failed to flush output stream: %s\n",
260				strerror(error));
261			return 1;
262		}
263	}
264
265	return 0;
266}
267