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