xref: /haiku/src/add-ons/tracker/zipomatic/ZipperThread.cpp (revision e711e6e42fd7ec3111ba9dc2324fa8efedd6674b)
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 = B_OK;
391 		status = send_signal(fZipProcess, SIGINT);
392 		WaitOnExternalZip();
393 		return status;
394 	}
395 
396 	return status;
397 }
398 
399 
400 status_t
401 ZipperThread::WaitOnExternalZip()
402 {
403 	thread_info info;
404 	status_t status = get_thread_info(fZipProcess, &info);
405 
406 	if (status == B_OK && !strcmp(info.name, "zip"))
407 		return wait_for_thread(fZipProcess, &status);
408 
409 	return status;
410 }
411 
412 
413 status_t
414 ZipperThread::_SelectInTracker(int32 tryNumber)
415 {
416 	// work in progress - unreliable - not ready to be used
417 
418 	entry_ref parentRef;
419 	BEntry entry(&fOutputEntryRef);
420 
421 	if (!entry.Exists())
422 		return B_FILE_NOT_FOUND;
423 
424 	entry.GetParent(&entry);
425 	entry.GetRef(&parentRef);
426 
427 	BMessenger trackerMessenger("application/x-vnd.Be-TRAK");
428 	if (!trackerMessenger.IsValid())
429 		return B_ERROR;
430 
431 	BMessage request;
432 	BMessage reply;
433 	status_t status;
434 
435 	if (tryNumber == 0) {
436 		request.MakeEmpty();
437 		request.what = B_REFS_RECEIVED;
438 		request.AddRef("refs", &parentRef);
439 		trackerMessenger.SendMessage(&request, &reply);
440 	}
441 
442 	if (tryNumber > 20)
443 		return B_ERROR;
444 
445 	snooze(200000);
446 
447 	// find out the number of Tracker windows
448 	request.MakeEmpty();
449 	request.what = B_COUNT_PROPERTIES;
450 	request.AddSpecifier("Window");
451 	reply.MakeEmpty();
452 
453 	status = trackerMessenger.SendMessage(&request, &reply);
454 	if (status != B_OK)
455 		return status;
456 
457 	int32 windowCount;
458 	status = reply.FindInt32("result", &windowCount);
459 	if (status != B_OK)
460 		return status;
461 
462 	// find a likely parent window
463 	bool foundWindow = false;
464 	int32 index = 0;
465 	for (; index < windowCount; index++) {
466 		request.MakeEmpty();
467 		request.what = B_GET_PROPERTY;
468 		request.AddSpecifier("Path");
469 		request.AddSpecifier("Poses");
470 		request.AddSpecifier("Window", index);
471 		reply.MakeEmpty();
472 
473 		status = trackerMessenger.SendMessage(&request, &reply);
474 		if (status != B_OK)
475 			continue;
476 
477 		entry_ref windowRef;
478 		status = reply.FindRef("result", &windowRef);
479 		if (status != B_OK)
480 			continue;
481 
482 		if (windowRef == parentRef) {
483 			foundWindow = true;
484 			break;
485 		}
486 	}
487 
488 	if (!foundWindow)
489 		return _SelectInTracker(tryNumber + 1);
490 
491 	// find entry_ref in window - a newly opened window might
492 	// be filling and the entry_ref perhaps not there yet?
493 	request.MakeEmpty();
494 	request.what = B_GET_PROPERTY;
495 	request.AddSpecifier("Entry");
496 	request.AddSpecifier("Poses");
497 	request.AddSpecifier("Window", index);
498 	reply.MakeEmpty();
499 
500 	status = trackerMessenger.SendMessage(&request, &reply);
501 	if (status != B_OK)
502 		return _SelectInTracker(tryNumber + 1);
503 
504 	bool foundRef = false;
505 	entry_ref ref;
506 	for (int32 m = 0;; m++) {
507 		status = reply.FindRef("result", m, &ref);
508 		if (status != B_OK)
509 			break;
510 		if (ref == fOutputEntryRef)
511 			foundRef = true;
512 	}
513 
514 	// if entry_ref not found in window, start over
515 	if (!foundRef)
516 		return _SelectInTracker(tryNumber + 1);
517 
518 	// select archive file in Tracker window
519 	request.MakeEmpty();
520 	request.what = B_SET_PROPERTY;
521 	request.AddRef("data", &fOutputEntryRef);
522 	request.AddSpecifier("Selection");
523 	request.AddSpecifier("Poses");
524 	request.AddSpecifier("Window", index);
525 	reply.MakeEmpty();
526 
527 	status = trackerMessenger.SendMessage(&request, &reply);
528 
529 	return status;
530 }
531 
532