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