xref: /haiku/src/add-ons/tracker/zipomatic/ZipperThread.cpp (revision 12dba4e70f831d6d27a7f769cc9dab19c19a155d)
1 /*
2  * Copyright 2003-2009, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Jonas Sundström, jonas@kirilla.com
7  *		Peter Folk <pfolk@uni.uiuc.edu>
8  */
9 
10 
11 #include "ZipperThread.h"
12 
13 #include <errno.h>
14 #include <signal.h>
15 #include <string.h>
16 #include <unistd.h>
17 
18 #include <Catalog.h>
19 #include <FindDirectory.h>
20 #include <Locale.h>
21 #include <Locker.h>
22 #include <Message.h>
23 #include <Path.h>
24 #include <Volume.h>
25 
26 #include "ZipOMaticMisc.h"
27 #include "ZipOMaticWindow.h"
28 
29 
30 #undef B_TRANSLATION_CONTEXT
31 #define B_TRANSLATION_CONTEXT "file:ZipperThread.cpp"
32 
33 
34 ZipperThread::ZipperThread(BMessage* refsMessage, BWindow* window)
35 	:
36 	GenericThread("ZipperThread", B_NORMAL_PRIORITY, refsMessage),
37 	fWindowMessenger(window),
38 	fZipProcess(-1),
39 	fStdIn(-1),
40 	fStdOut(-1),
41 	fStdErr(-1),
42 	fOutputFile(NULL)
43 {
44 	fThreadDataStore = new BMessage(*refsMessage);
45 }
46 
47 
48 ZipperThread::~ZipperThread()
49 {
50 }
51 
52 
53 status_t
54 ZipperThread::ThreadStartup()
55 {
56 	type_code type = B_REF_TYPE;
57 	int32 refCount = 0;
58 	entry_ref ref;
59 	entry_ref lastRef;
60 	bool sameFolder = true;
61 
62 	status_t status = fThreadDataStore->GetInfo("refs", &type, &refCount);
63 	if (status != B_OK || refCount < 1) {
64 		_SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR);
65 		Quit();
66 		return B_ERROR;
67 	}
68 
69 	for	(int index = 0; index < refCount; index++) {
70 		fThreadDataStore->FindRef("refs", index, &ref);
71 
72 		if (index > 0) {
73 			if (lastRef.directory != ref.directory) {
74 				sameFolder = false;
75 				break;
76 			}
77 		}
78 		lastRef = ref;
79 	}
80 
81 	entry_ref dirRef;
82 	bool gotDirRef = false;
83 
84 	status = fThreadDataStore->FindRef("dir_ref", 0, &dirRef);
85 	if (status == B_OK) {
86 		BEntry dirEntry(&dirRef);
87 		BNode dirNode(&dirRef);
88 
89 		if (dirEntry.InitCheck() == B_OK
90 			&& dirEntry.Exists()
91 			&& dirNode.InitCheck() == B_OK
92 			&& dirNode.IsDirectory())
93 			gotDirRef = true;
94 	}
95 
96 	if (gotDirRef) {
97 		BEntry entry(&dirRef);
98 		BPath path;
99 		entry.GetPath(&path);
100 		chdir(path.Path());
101 	} else if (sameFolder) {
102 		BEntry entry(&lastRef);
103 		BPath path;
104 		entry.GetParent(&entry);
105 		entry.GetPath(&path);
106 		chdir(path.Path());
107 	} else {
108 		BPath path;
109 		if (find_directory(B_DESKTOP_DIRECTORY, &path) == B_OK)
110 			chdir(path.Path());
111 	}
112 
113 	BString archiveName;
114 
115 	if (refCount > 1)
116 		archiveName = B_TRANSLATE("Archive");
117 	else
118 		archiveName = lastRef.name;
119 
120 	int index = 1;
121 	for (;; index++) {
122 		BString tryName = archiveName;
123 
124 		if (index != 1)
125 			tryName << " " << index;
126 
127 		tryName << ".zip";
128 
129 		BEntry entry(tryName.String());
130 		if (!entry.Exists()) {
131 			archiveName = tryName;
132 			entry.GetRef(&fOutputEntryRef);
133 			break;
134 		}
135 	}
136 
137 	int32 argc = refCount + 3;
138 	const char** argv = new const char* [argc + 1];
139 
140 	argv[0] = strdup("/bin/zip");
141 	argv[1] = strdup("-ry");
142 	argv[2] = strdup(archiveName.String());
143 
144 	for (int index = 0; index < refCount; index++) {
145 		fThreadDataStore->FindRef("refs", index, &ref);
146 
147 		if (gotDirRef || sameFolder) {
148 			argv[3 + index]	= strdup(ref.name);
149 		} else {
150 			BPath path(&ref);
151 			BString file = path.Path();
152 			argv[3 + index]	= strdup(path.Path());
153 		}
154 	}
155 
156 	argv[argc] = NULL;
157 
158 	fZipProcess = _PipeCommand(argc, argv, fStdIn, fStdOut, fStdErr);
159 
160 	delete [] argv;
161 
162 	if (fZipProcess < 0)
163 		return fZipProcess;
164 
165 	resume_thread(fZipProcess);
166 
167 	fOutputFile = fdopen(fStdOut, "r");
168 	if (fOutputFile == NULL)
169 		return errno;
170 
171 	_SendMessageToWindow(ZIPPO_TASK_DESCRIPTION, "archive_filename",
172 		archiveName.String());
173 	_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output",
174 		B_TRANSLATE("Preparing to archive"));
175 
176 	return B_OK;
177 }
178 
179 
180 status_t
181 ZipperThread::ExecuteUnit()
182 {
183 	char buffer[4096];
184 
185 	char* output = fgets(buffer, sizeof(buffer) - 1, fOutputFile);
186 	if (output == NULL)
187 		return EOF;
188 
189 	char* newLine = strrchr(output, '\n');
190 	if (newLine != NULL)
191 		*newLine = '\0';
192 
193 	if (!strncmp("  a", output, 3)) {
194 		output[2] = 'A';
195 		_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output + 2);
196 	} else if (!strncmp("up", output, 2)) {
197 		output[0] = 'U';
198 		_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output);
199 	} else {
200 		_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output);
201 	}
202 
203 	return B_OK;
204 }
205 
206 
207 status_t
208 ZipperThread::ThreadShutdown()
209 {
210 	close(fStdIn);
211 	close(fStdOut);
212 	close(fStdErr);
213 
214 	// _SelectInTracker();
215 
216 	return B_OK;
217 }
218 
219 
220 void
221 ZipperThread::ThreadStartupFailed(status_t status)
222 {
223 	fprintf(stderr, "ZipperThread::ThreadStartupFailed(): %s\n",
224 		strerror(status));
225 	_SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR);
226 
227 	Quit();
228 }
229 
230 
231 void
232 ZipperThread::ExecuteUnitFailed(status_t status)
233 {
234 	if (status == EOF) {
235 		fprintf(stderr, "ZipperThread::ExecuteUnitFailed(): EOF\n");
236 		// thread has finished, been quit or killed, we don't know
237 		_SendMessageToWindow(ZIPPO_THREAD_EXIT);
238 	} else {
239 		fprintf(stderr, "ZipperThread::ExecuteUnitFailed(): %s\n",
240 			strerror(status));
241 		// explicit error - communicate error to Window
242 		_SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR);
243 	}
244 
245 	Quit();
246 }
247 
248 
249 void
250 ZipperThread::ThreadShutdownFailed(status_t status)
251 {
252 	fprintf(stderr, "ZipperThread::ThreadShutdownFailed(): %s\n",
253 		strerror(status));
254 }
255 
256 
257 void
258 ZipperThread::_MakeShellSafe(BString* string)
259 {
260 	string->CharacterEscape("\"$`", '\\');
261 	string->Prepend("\"");
262 	string->Append("\"");
263 }
264 
265 
266 thread_id
267 ZipperThread::_PipeCommand(int argc, const char** argv, int& in, int& out,
268 	int& err, const char** envp)
269 {
270 	static BLocker lock;
271 
272 	if (lock.Lock()) {
273 		// This function was originally written by Peter Folk
274 		// <pfolk@uni.uiuc.edu> and published in the BeDevTalk FAQ
275 		// http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209
276 
277 		thread_id thread;
278 
279 		// Save current FDs
280 		int oldIn = dup(STDIN_FILENO);
281 		int oldOut = dup(STDOUT_FILENO);
282 		int oldErr = dup(STDERR_FILENO);
283 
284 		int inPipe[2], outPipe[2], errPipe[2];
285 
286 		// Create new pipe FDs as stdin, stdout, stderr
287 		if (pipe(inPipe) < 0)
288 			goto err1;
289 		if (pipe(outPipe) < 0)
290 			goto err2;
291 		if (pipe(errPipe) < 0)
292 			goto err3;
293 
294 		errno = 0;
295 
296 		// replace old stdin/stderr/stdout
297 		dup2(inPipe[0], STDIN_FILENO);
298 		close(inPipe[0]);
299 		dup2(outPipe[1], STDOUT_FILENO);
300 		close(outPipe[1]);
301 		dup2(errPipe[1], STDERR_FILENO);
302 		close(errPipe[1]);
303 
304 		if (errno == 0) {
305 			in = inPipe[1];		// Write to in, appears on cmd's stdin
306 			out = outPipe[0];	// Read from out, taken from cmd's stdout
307 			err = errPipe[0];	// Read from err, taken from cmd's stderr
308 
309 			// execute command
310 			thread = load_image(argc, argv, envp);
311 		} else {
312 			thread = errno;
313 		}
314 
315 		// Restore old FDs
316 		dup2(oldIn, STDIN_FILENO);
317 		close(oldIn);
318 		dup2(oldOut, STDOUT_FILENO);
319 		close(oldOut);
320 		dup2(oldErr, STDERR_FILENO);
321 		close(oldErr);
322 
323 		lock.Unlock();
324 		return thread;
325 
326 	err3:
327 		close(outPipe[0]);
328 		close(outPipe[1]);
329 	err2:
330 		close(inPipe[0]);
331 		close(inPipe[1]);
332 	err1:
333 		close(oldIn);
334 		close(oldOut);
335 		close(oldErr);
336 
337 		lock.Unlock();
338 		return errno;
339 	} else {
340 		return B_ERROR;
341 	}
342 }
343 
344 
345 void
346 ZipperThread::_SendMessageToWindow(uint32 what, const char* name,
347 	const char* value)
348 {
349 	BMessage msg(what);
350 	if (name != NULL && value != NULL)
351 		msg.AddString(name, value);
352 
353 	fWindowMessenger.SendMessage(&msg);
354 }
355 
356 
357 status_t
358 ZipperThread::SuspendExternalZip()
359 {
360 	thread_info info;
361 	status_t status = get_thread_info(fZipProcess, &info);
362 
363 	if (status == B_OK && !strcmp(info.name, "zip"))
364 		return suspend_thread(fZipProcess);
365 
366 	return status;
367 }
368 
369 
370 status_t
371 ZipperThread::ResumeExternalZip()
372 {
373 	thread_info info;
374 	status_t status = get_thread_info(fZipProcess, &info);
375 
376 	if (status == B_OK && !strcmp(info.name, "zip"))
377 		return resume_thread(fZipProcess);
378 
379 	return status;
380 }
381 
382 
383 status_t
384 ZipperThread::InterruptExternalZip()
385 {
386 	thread_info info;
387 	status_t status = get_thread_info(fZipProcess, &info);
388 
389 	if (status == B_OK && !strcmp(info.name, "zip")) {
390 		status = send_signal(fZipProcess, SIGINT);
391 		WaitOnExternalZip();
392 		return status;
393 	}
394 
395 	return status;
396 }
397 
398 
399 status_t
400 ZipperThread::WaitOnExternalZip()
401 {
402 	thread_info info;
403 	status_t status = get_thread_info(fZipProcess, &info);
404 
405 	if (status == B_OK && !strcmp(info.name, "zip"))
406 		return wait_for_thread(fZipProcess, &status);
407 
408 	return status;
409 }
410 
411 
412 status_t
413 ZipperThread::_SelectInTracker(int32 tryNumber)
414 {
415 	// work in progress - unreliable - not ready to be used
416 
417 	entry_ref parentRef;
418 	BEntry entry(&fOutputEntryRef);
419 
420 	if (!entry.Exists())
421 		return B_FILE_NOT_FOUND;
422 
423 	entry.GetParent(&entry);
424 	entry.GetRef(&parentRef);
425 
426 	BMessenger trackerMessenger("application/x-vnd.Be-TRAK");
427 	if (!trackerMessenger.IsValid())
428 		return B_ERROR;
429 
430 	BMessage request;
431 	BMessage reply;
432 	status_t status;
433 
434 	if (tryNumber == 0) {
435 		request.MakeEmpty();
436 		request.what = B_REFS_RECEIVED;
437 		request.AddRef("refs", &parentRef);
438 		trackerMessenger.SendMessage(&request, &reply);
439 	}
440 
441 	if (tryNumber > 20)
442 		return B_ERROR;
443 
444 	snooze(200000);
445 
446 	// find out the number of Tracker windows
447 	request.MakeEmpty();
448 	request.what = B_COUNT_PROPERTIES;
449 	request.AddSpecifier("Window");
450 	reply.MakeEmpty();
451 
452 	status = trackerMessenger.SendMessage(&request, &reply);
453 	if (status != B_OK)
454 		return status;
455 
456 	int32 windowCount;
457 	status = reply.FindInt32("result", &windowCount);
458 	if (status != B_OK)
459 		return status;
460 
461 	// find a likely parent window
462 	bool foundWindow = false;
463 	int32 index = 0;
464 	for (; index < windowCount; index++) {
465 		request.MakeEmpty();
466 		request.what = B_GET_PROPERTY;
467 		request.AddSpecifier("Path");
468 		request.AddSpecifier("Poses");
469 		request.AddSpecifier("Window", index);
470 		reply.MakeEmpty();
471 
472 		status = trackerMessenger.SendMessage(&request, &reply);
473 		if (status != B_OK)
474 			continue;
475 
476 		entry_ref windowRef;
477 		status = reply.FindRef("result", &windowRef);
478 		if (status != B_OK)
479 			continue;
480 
481 		if (windowRef == parentRef) {
482 			foundWindow = true;
483 			break;
484 		}
485 	}
486 
487 	if (!foundWindow)
488 		return _SelectInTracker(tryNumber + 1);
489 
490 	// find entry_ref in window - a newly opened window might
491 	// be filling and the entry_ref perhaps not there yet?
492 	request.MakeEmpty();
493 	request.what = B_GET_PROPERTY;
494 	request.AddSpecifier("Entry");
495 	request.AddSpecifier("Poses");
496 	request.AddSpecifier("Window", index);
497 	reply.MakeEmpty();
498 
499 	status = trackerMessenger.SendMessage(&request, &reply);
500 	if (status != B_OK)
501 		return _SelectInTracker(tryNumber + 1);
502 
503 	bool foundRef = false;
504 	entry_ref ref;
505 	for (int32 m = 0;; m++) {
506 		status = reply.FindRef("result", m, &ref);
507 		if (status != B_OK)
508 			break;
509 		if (ref == fOutputEntryRef)
510 			foundRef = true;
511 	}
512 
513 	// if entry_ref not found in window, start over
514 	if (!foundRef)
515 		return _SelectInTracker(tryNumber + 1);
516 
517 	// select archive file in Tracker window
518 	request.MakeEmpty();
519 	request.what = B_SET_PROPERTY;
520 	request.AddRef("data", &fOutputEntryRef);
521 	request.AddSpecifier("Selection");
522 	request.AddSpecifier("Poses");
523 	request.AddSpecifier("Window", index);
524 	reply.MakeEmpty();
525 
526 	status = trackerMessenger.SendMessage(&request, &reply);
527 
528 	return status;
529 }
530 
531