xref: /haiku/src/apps/installer/UnzipEngine.cpp (revision 25a7b01d15612846f332751841da3579db313082)
1 /*
2  * Copyright 2009, Stephan Aßmus <superstippi@gmx.de>
3  *  All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "UnzipEngine.h"
7 
8 #include <new>
9 
10 #include <stdio.h>
11 #include <string.h>
12 
13 #include <Directory.h>
14 #include <Entry.h>
15 #include <File.h>
16 #include <Node.h>
17 #include <Path.h>
18 #include <String.h>
19 
20 #include "CommandPipe.h"
21 #include "SemaphoreLocker.h"
22 #include "ProgressReporter.h"
23 
24 
25 using std::nothrow;
26 
27 
UnzipEngine(ProgressReporter * reporter,sem_id cancelSemaphore)28 UnzipEngine::UnzipEngine(ProgressReporter* reporter,
29 		sem_id cancelSemaphore)
30 	:
31 	fPackage(""),
32 	fRetrievingListing(false),
33 
34 	fBytesToUncompress(0),
35 	fBytesUncompressed(0),
36 	fLastBytesUncompressed(0),
37 	fItemsToUncompress(0),
38 	fItemsUncompressed(0),
39 	fLastItemsUncompressed(0),
40 
41 	fProgressReporter(reporter),
42 	fCancelSemaphore(cancelSemaphore)
43 {
44 }
45 
46 
~UnzipEngine()47 UnzipEngine::~UnzipEngine()
48 {
49 }
50 
51 
52 status_t
SetTo(const char * pathToPackage,const char * destinationFolder)53 UnzipEngine::SetTo(const char* pathToPackage, const char* destinationFolder)
54 {
55 	fPackage = pathToPackage;
56 	fDestinationFolder = destinationFolder;
57 
58 	fEntrySizeMap.Clear();
59 
60 	fBytesToUncompress = 0;
61 	fBytesUncompressed = 0;
62 	fLastBytesUncompressed = 0;
63 	fItemsToUncompress = 0;
64 	fItemsUncompressed = 0;
65 	fLastItemsUncompressed = 0;
66 
67 	BPrivate::BCommandPipe commandPipe;
68 	status_t ret = commandPipe.AddArg("unzip");
69 	if (ret == B_OK)
70 		ret = commandPipe.AddArg("-l");
71 	if (ret == B_OK)
72 		ret = commandPipe.AddArg(fPackage.String());
73 	if (ret != B_OK)
74 		return ret;
75 
76 	// Launch the unzip thread and start reading the stdout and stderr output.
77 	FILE* stdOutAndErrPipe = NULL;
78 	thread_id unzipThread = commandPipe.PipeInto(&stdOutAndErrPipe);
79 	if (unzipThread < 0)
80 		return (status_t)unzipThread;
81 
82 	fRetrievingListing = true;
83 	ret = commandPipe.ReadLines(stdOutAndErrPipe, this);
84 	fRetrievingListing = false;
85 
86 	printf("%" B_PRIu64 " items in %" B_PRIdOFF " bytes\n", fItemsToUncompress,
87 		fBytesToUncompress);
88 
89 	return ret;
90 }
91 
92 
93 status_t
UnzipPackage()94 UnzipEngine::UnzipPackage()
95 {
96 	if (fItemsToUncompress == 0)
97 		return B_NO_INIT;
98 
99 	BPrivate::BCommandPipe commandPipe;
100 	status_t ret = commandPipe.AddArg("unzip");
101 	if (ret == B_OK)
102 		ret = commandPipe.AddArg("-o");
103 	if (ret == B_OK)
104 		ret = commandPipe.AddArg(fPackage.String());
105 	if (ret == B_OK)
106 		ret = commandPipe.AddArg("-d");
107 	if (ret == B_OK)
108 		ret = commandPipe.AddArg(fDestinationFolder.String());
109 	if (ret != B_OK) {
110 		fprintf(stderr, "Faild to construct argument list for unzip "
111 			"process: %s\n", strerror(ret));
112 		return ret;
113 	}
114 
115 	// Launch the unzip thread and start reading the stdout and stderr output.
116 	FILE* stdOutAndErrPipe = NULL;
117 	thread_id unzipThread = commandPipe.PipeInto(&stdOutAndErrPipe);
118 	if (unzipThread < 0)
119 		return (status_t)unzipThread;
120 
121 	ret = commandPipe.ReadLines(stdOutAndErrPipe, this);
122 	if (ret != B_OK) {
123 		fprintf(stderr, "Piping the unzip process failed: %s\n",
124 			strerror(ret));
125 		return ret;
126 	}
127 
128 	// Add the contents of a potentially existing .OptionalPackageDescription
129 	// to the COPYRIGHTS attribute of AboutSystem.
130 	BPath descriptionPath(fDestinationFolder.String(),
131 		".OptionalPackageDescription");
132 	ret = descriptionPath.InitCheck();
133 	if (ret != B_OK) {
134 		fprintf(stderr, "Failed to construct path to "
135 			".OptionalPackageDescription: %s\n", strerror(ret));
136 		return ret;
137 	}
138 
139 	BEntry descriptionEntry(descriptionPath.Path());
140 	if (!descriptionEntry.Exists())
141 		return B_OK;
142 
143 	BFile descriptionFile(&descriptionEntry, B_READ_ONLY);
144 	ret = descriptionFile.InitCheck();
145 	if (ret != B_OK) {
146 		fprintf(stderr, "Failed to construct file to "
147 			".OptionalPackageDescription: %s\n", strerror(ret));
148 		return ret;
149 	}
150 
151 	BPath aboutSystemPath(fDestinationFolder.String(),
152 		"system/apps/AboutSystem");
153 	ret = aboutSystemPath.InitCheck();
154 	if (ret != B_OK) {
155 		fprintf(stderr, "Failed to construct path to AboutSystem: %s\n",
156 			strerror(ret));
157 		return ret;
158 	}
159 
160 	BNode aboutSystemNode(aboutSystemPath.Path());
161 	ret = aboutSystemNode.InitCheck();
162 	if (ret != B_OK) {
163 		fprintf(stderr, "Failed to construct node to AboutSystem: %s\n",
164 			strerror(ret));
165 		return ret;
166 	}
167 
168 	const char* kCopyrightsAttrName = "COPYRIGHTS";
169 
170 	BString copyrightAttr;
171 	ret = aboutSystemNode.ReadAttrString(kCopyrightsAttrName, &copyrightAttr);
172 	if (ret != B_OK && ret != B_ENTRY_NOT_FOUND) {
173 		fprintf(stderr, "Failed to read current COPYRIGHTS attribute from "
174 			"AboutSystem: %s\n", strerror(ret));
175 		return ret;
176 	}
177 
178 	// Append the contents of the current optional package description to
179 	// the existing COPYRIGHTS attribute from AboutSystem
180 	size_t bufferSize = 2048;
181 	char buffer[bufferSize + 1];
182 	buffer[bufferSize] = '\0';
183 	while (true) {
184 		ssize_t read = descriptionFile.Read(buffer, bufferSize);
185 		if (read > 0) {
186 			int32 length = copyrightAttr.Length();
187 			if (read < (ssize_t)bufferSize)
188 				buffer[read] = '\0';
189 			int32 bufferLength = strlen(buffer);
190 				// Should be "read", but maybe we have a zero in the
191 				// buffer in which case the next check would be fooled.
192 			copyrightAttr << buffer;
193 			if (copyrightAttr.Length() != length + bufferLength) {
194 				fprintf(stderr, "Failed to append buffer to COPYRIGHTS "
195 					"attribute.\n");
196 				return B_NO_MEMORY;
197 			}
198 		} else
199 			break;
200 	}
201 
202 	if (copyrightAttr[copyrightAttr.Length() - 1] != '\n')
203 		copyrightAttr << '\n\n';
204 	else
205 		copyrightAttr << '\n';
206 
207 	ret = aboutSystemNode.WriteAttrString(kCopyrightsAttrName, &copyrightAttr);
208 	if (ret != B_OK && ret != B_ENTRY_NOT_FOUND) {
209 		fprintf(stderr, "Failed to read current COPYRIGHTS attribute from "
210 			"AboutSystem: %s\n", strerror(ret));
211 		return ret;
212 	}
213 
214 	// Don't leave the .OptionalPackageDescription behind.
215 	descriptionFile.Unset();
216 	descriptionEntry.Remove();
217 
218 	return B_OK;
219 }
220 
221 
222 // #pragma mark -
223 
224 
225 bool
IsCanceled()226 UnzipEngine::IsCanceled()
227 {
228 	if (fCancelSemaphore < 0)
229 		return false;
230 
231 	SemaphoreLocker locker(fCancelSemaphore);
232 	return !locker.IsLocked();
233 }
234 
235 
236 status_t
ReadLine(const BString & line)237 UnzipEngine::ReadLine(const BString& line)
238 {
239 	if (fRetrievingListing)
240 		return _ReadLineListing(line);
241 	else
242 		return _ReadLineExtract(line);
243 }
244 
245 
246 status_t
_ReadLineListing(const BString & line)247 UnzipEngine::_ReadLineListing(const BString& line)
248 {
249 	static const char* kListingFormat = "%llu  %s %s   %s\n";
250 
251 	const char* string = line.String();
252 	while (string[0] == ' ')
253 		string++;
254 
255 	uint64 bytes;
256 	char date[16];
257 	char time[16];
258 	char path[1024];
259 	if (sscanf(string, kListingFormat, &bytes, &date, &time, &path) == 4) {
260 		fBytesToUncompress += bytes;
261 
262 		BString itemPath(path);
263 		BString itemName(path);
264 		int leafPos = itemPath.FindLast('/');
265 		if (leafPos >= 0)
266 			itemName = itemPath.String() + leafPos + 1;
267 
268 		// We check if the target folder exists and don't increment
269 		// the item count in that case. Unzip won't report on folders that did
270 		// not need to be created. This may mess up our current item count.
271 		uint32 itemCount = 1;
272 		if (bytes == 0 && itemName.Length() == 0) {
273 			// a folder?
274 			BPath destination(fDestinationFolder.String());
275 			if (destination.Append(itemPath.String()) == B_OK) {
276 				BEntry test(destination.Path());
277 				if (test.Exists() && test.IsDirectory()) {
278 //					printf("ignoring %s\n", itemPath.String());
279 					itemCount = 0;
280 				}
281 			}
282 		}
283 
284 		fItemsToUncompress += itemCount;
285 
286 //		printf("item %s with %llu bytes to %s\n", itemName.String(),
287 //			bytes, itemPath.String());
288 
289 		fEntrySizeMap.Put(itemName.String(), bytes);
290 	} else {
291 //		printf("listing not understood: %s", string);
292 	}
293 
294 	return B_OK;
295 }
296 
297 
298 status_t
_ReadLineExtract(const BString & line)299 UnzipEngine::_ReadLineExtract(const BString& line)
300 {
301 	const char* kCreatingFormat = "   creating:";
302 	const char* kInflatingFormat = "  inflating:";
303 	const char* kLinkingFormat = "    linking:";
304 	if (line.FindFirst(kCreatingFormat) == 0
305 		|| line.FindFirst(kInflatingFormat) == 0
306 		|| line.FindFirst(kLinkingFormat) == 0) {
307 
308 		fItemsUncompressed++;
309 
310 		BString itemPath;
311 
312 		int pos = line.FindLast(" -> ");
313 		if (pos > 0)
314 			line.CopyInto(itemPath, 13, pos - 13);
315 		else
316 			line.CopyInto(itemPath, 13, line.CountChars() - 14);
317 
318 		itemPath.Trim();
319 		pos = itemPath.FindLast('/');
320 		BString itemName = itemPath.String() + pos + 1;
321 		itemPath.Truncate(pos);
322 
323 		off_t bytes = 0;
324 		if (fEntrySizeMap.ContainsKey(itemName.String())) {
325 			bytes = fEntrySizeMap.Get(itemName.String());
326 			fBytesUncompressed += bytes;
327 		}
328 
329 //		printf("%llu extracted %s to %s (%llu)\n", fItemsUncompressed,
330 //			itemName.String(), itemPath.String(), bytes);
331 
332 		_UpdateProgress(itemName.String(), itemPath.String());
333 	} else {
334 //		printf("ignored: '%s'\n", line.String());
335 	}
336 
337 	return B_OK;
338 }
339 
340 
341 void
_UpdateProgress(const char * item,const char * targetFolder)342 UnzipEngine::_UpdateProgress(const char* item, const char* targetFolder)
343 {
344 	if (fProgressReporter == NULL)
345 		return;
346 
347 	uint64 items = 0;
348 	if (fLastItemsUncompressed < fItemsUncompressed) {
349 		items = fItemsUncompressed - fLastItemsUncompressed;
350 		fLastItemsUncompressed = fItemsUncompressed;
351 	}
352 
353 	off_t bytes = 0;
354 	if (fLastBytesUncompressed < fBytesUncompressed) {
355 		bytes = fBytesUncompressed - fLastBytesUncompressed;
356 		fLastBytesUncompressed = fBytesUncompressed;
357 	}
358 
359 	fProgressReporter->ItemsWritten(items, bytes, item, targetFolder);
360 }
361