xref: /haiku/src/add-ons/tracker/zipomatic/ZipperThread.cpp (revision 23d878482ed22e55dad6d1fca1df7bea42eb157c)
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 	Quit();
224 }
225 
226 
227 void
228 ZipperThread::ExecuteUnitFailed(status_t status)
229 {
230 	if (status == EOF) {
231 		// thread has finished, been quit or killed, we don't know
232 		_SendMessageToWindow(ZIPPO_THREAD_EXIT);
233 	} else {
234 		// explicit error - communicate error to Window
235 		_SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR);
236 	}
237 
238 	Quit();
239 }
240 
241 
242 void
243 ZipperThread::ThreadShutdownFailed(status_t status)
244 {
245 	fprintf(stderr, "ZipperThread::ThreadShutdownFailed(): %s\n",
246 		strerror(status));
247 }
248 
249 
250 void
251 ZipperThread::_MakeShellSafe(BString* string)
252 {
253 	string->CharacterEscape("\"$`", '\\');
254 	string->Prepend("\"");
255 	string->Append("\"");
256 }
257 
258 
259 thread_id
260 ZipperThread::_PipeCommand(int argc, const char** argv, int& in, int& out,
261 	int& err, const char** envp)
262 {
263 	static BLocker lock;
264 
265 	if (lock.Lock()) {
266 		// This function was originally written by Peter Folk
267 		// <pfolk@uni.uiuc.edu> and published in the BeDevTalk FAQ
268 		// http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209
269 
270 		thread_id thread;
271 
272 		// Save current FDs
273 		int oldIn = dup(STDIN_FILENO);
274 		int oldOut = dup(STDOUT_FILENO);
275 		int oldErr = dup(STDERR_FILENO);
276 
277 		int inPipe[2], outPipe[2], errPipe[2];
278 
279 		// Create new pipe FDs as stdin, stdout, stderr
280 		if (pipe(inPipe) < 0)
281 			goto err1;
282 		if (pipe(outPipe) < 0)
283 			goto err2;
284 		if (pipe(errPipe) < 0)
285 			goto err3;
286 
287 		errno = 0;
288 
289 		// replace old stdin/stderr/stdout
290 		dup2(inPipe[0], STDIN_FILENO);
291 		close(inPipe[0]);
292 		dup2(outPipe[1], STDOUT_FILENO);
293 		close(outPipe[1]);
294 		dup2(errPipe[1], STDERR_FILENO);
295 		close(errPipe[1]);
296 
297 		if (errno == 0) {
298 			in = inPipe[1];		// Write to in, appears on cmd's stdin
299 			out = outPipe[0];	// Read from out, taken from cmd's stdout
300 			err = errPipe[0];	// Read from err, taken from cmd's stderr
301 
302 			// execute command
303 			thread = load_image(argc, argv, envp);
304 		} else {
305 			thread = errno;
306 		}
307 
308 		// Restore old FDs
309 		dup2(oldIn, STDIN_FILENO);
310 		close(oldIn);
311 		dup2(oldOut, STDOUT_FILENO);
312 		close(oldOut);
313 		dup2(oldErr, STDERR_FILENO);
314 		close(oldErr);
315 
316 		lock.Unlock();
317 		return thread;
318 
319 	err3:
320 		close(outPipe[0]);
321 		close(outPipe[1]);
322 	err2:
323 		close(inPipe[0]);
324 		close(inPipe[1]);
325 	err1:
326 		close(oldIn);
327 		close(oldOut);
328 		close(oldErr);
329 
330 		lock.Unlock();
331 		return errno;
332 	} else {
333 		return B_ERROR;
334 	}
335 }
336 
337 
338 void
339 ZipperThread::_SendMessageToWindow(uint32 what, const char* name,
340 	const char* value)
341 {
342 	BMessage msg(what);
343 	if (name != NULL && value != NULL)
344 		msg.AddString(name, value);
345 
346 	fWindowMessenger.SendMessage(&msg);
347 }
348 
349 
350 status_t
351 ZipperThread::SuspendExternalZip()
352 {
353 	thread_info info;
354 	status_t status = get_thread_info(fZipProcess, &info);
355 
356 	if (status == B_OK && !strcmp(info.name, "zip"))
357 		return suspend_thread(fZipProcess);
358 
359 	return status;
360 }
361 
362 
363 status_t
364 ZipperThread::ResumeExternalZip()
365 {
366 	thread_info info;
367 	status_t status = get_thread_info(fZipProcess, &info);
368 
369 	if (status == B_OK && !strcmp(info.name, "zip"))
370 		return resume_thread(fZipProcess);
371 
372 	return status;
373 }
374 
375 
376 status_t
377 ZipperThread::InterruptExternalZip()
378 {
379 	thread_info info;
380 	status_t status = get_thread_info(fZipProcess, &info);
381 
382 	if (status == B_OK && !strcmp(info.name, "zip")) {
383 		status = B_OK;
384 		status = send_signal(fZipProcess, SIGINT);
385 		WaitOnExternalZip();
386 		return status;
387 	}
388 
389 	return status;
390 }
391 
392 
393 status_t
394 ZipperThread::WaitOnExternalZip()
395 {
396 	thread_info info;
397 	status_t status = get_thread_info(fZipProcess, &info);
398 
399 	if (status == B_OK && !strcmp(info.name, "zip"))
400 		return wait_for_thread(fZipProcess, &status);
401 
402 	return status;
403 }
404 
405 
406 status_t
407 ZipperThread::_SelectInTracker(int32 tryNumber)
408 {
409 	// work in progress - unreliable - not ready to be used
410 
411 	entry_ref parentRef;
412 	BEntry entry(&fOutputEntryRef);
413 
414 	if (!entry.Exists())
415 		return B_FILE_NOT_FOUND;
416 
417 	entry.GetParent(&entry);
418 	entry.GetRef(&parentRef);
419 
420 	BMessenger trackerMessenger("application/x-vnd.Be-TRAK");
421 	if (!trackerMessenger.IsValid())
422 		return B_ERROR;
423 
424 	BMessage request;
425 	BMessage reply;
426 	status_t status;
427 
428 	if (tryNumber == 0) {
429 		request.MakeEmpty();
430 		request.what = B_REFS_RECEIVED;
431 		request.AddRef("refs", &parentRef);
432 		trackerMessenger.SendMessage(&request, &reply);
433 	}
434 
435 	if (tryNumber > 20)
436 		return B_ERROR;
437 
438 	snooze(200000);
439 
440 	// find out the number of Tracker windows
441 	request.MakeEmpty();
442 	request.what = B_COUNT_PROPERTIES;
443 	request.AddSpecifier("Window");
444 	reply.MakeEmpty();
445 
446 	status = trackerMessenger.SendMessage(&request, &reply);
447 	if (status != B_OK)
448 		return status;
449 
450 	int32 windowCount;
451 	status = reply.FindInt32("result", &windowCount);
452 	if (status != B_OK)
453 		return status;
454 
455 	// find a likely parent window
456 	bool foundWindow = false;
457 	int32 index = 0;
458 	for (; index < windowCount; index++) {
459 		request.MakeEmpty();
460 		request.what = B_GET_PROPERTY;
461 		request.AddSpecifier("Path");
462 		request.AddSpecifier("Poses");
463 		request.AddSpecifier("Window", index);
464 		reply.MakeEmpty();
465 
466 		status = trackerMessenger.SendMessage(&request, &reply);
467 		if (status != B_OK)
468 			continue;
469 
470 		entry_ref windowRef;
471 		status = reply.FindRef("result", &windowRef);
472 		if (status != B_OK)
473 			continue;
474 
475 		if (windowRef == parentRef) {
476 			foundWindow = true;
477 			break;
478 		}
479 	}
480 
481 	if (!foundWindow)
482 		return _SelectInTracker(tryNumber + 1);
483 
484 	// find entry_ref in window - a newly opened window might
485 	// be filling and the entry_ref perhaps not there yet?
486 	request.MakeEmpty();
487 	request.what = B_GET_PROPERTY;
488 	request.AddSpecifier("Entry");
489 	request.AddSpecifier("Poses");
490 	request.AddSpecifier("Window", index);
491 	reply.MakeEmpty();
492 
493 	status = trackerMessenger.SendMessage(&request, &reply);
494 	if (status != B_OK)
495 		return _SelectInTracker(tryNumber + 1);
496 
497 	bool foundRef = false;
498 	entry_ref ref;
499 	for (int32 m = 0;; m++) {
500 		status = reply.FindRef("result", m, &ref);
501 		if (status != B_OK)
502 			break;
503 		if (ref == fOutputEntryRef)
504 			foundRef = true;
505 	}
506 
507 	// if entry_ref not found in window, start over
508 	if (!foundRef)
509 		return _SelectInTracker(tryNumber + 1);
510 
511 	// select archive file in Tracker window
512 	request.MakeEmpty();
513 	request.what = B_SET_PROPERTY;
514 	request.AddRef("data", &fOutputEntryRef);
515 	request.AddSpecifier("Selection");
516 	request.AddSpecifier("Poses");
517 	request.AddSpecifier("Window", index);
518 	reply.MakeEmpty();
519 
520 	status = trackerMessenger.SendMessage(&request, &reply);
521 
522 	return status;
523 }
524 
525