xref: /haiku/src/add-ons/tracker/zipomatic/ZipperThread.cpp (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
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 <unistd.h>
16 
17 #include <FindDirectory.h>
18 #include <Message.h>
19 #include <Path.h>
20 #include <Volume.h>
21 
22 #include "ZipOMaticMisc.h"
23 #include "ZipOMaticWindow.h"
24 
25 
26 ZipperThread::ZipperThread(BMessage* refsMessage, BWindow* window)
27 	:
28 	GenericThread("ZipperThread", B_NORMAL_PRIORITY, refsMessage),
29 	fWindowMessenger(window),
30 	fZipProcess(-1),
31 	fStdIn(-1),
32 	fStdOut(-1),
33 	fStdErr(-1),
34 	fOutputFile(NULL)
35 {
36 	fThreadDataStore = new BMessage(*refsMessage);
37 		// leak?
38 		// prevents bug with B_SIMPLE_DATA
39 		// (drag&drop messages)
40 }
41 
42 
43 ZipperThread::~ZipperThread()
44 {
45 }
46 
47 
48 status_t
49 ZipperThread::ThreadStartup()
50 {
51 	BString archiveName = "Archive.zip";
52 
53 	// do all refs have the same parent dir?
54 	type_code type = B_REF_TYPE;
55 	int32 refCount = 0;
56 	entry_ref ref;
57 	entry_ref lastRef;
58 	bool sameFolder = true;
59 
60 	status_t status = fThreadDataStore->GetInfo("refs", &type, &refCount);
61 	if (status != B_OK)
62 		return status;
63 
64 	for	(int index = 0; index < refCount; index++) {
65 		fThreadDataStore->FindRef("refs", index, &ref);
66 
67 		if (index > 0) {
68 			BEntry entry(&ref);
69 			if (entry.IsSymLink()) {
70 				entry.SetTo(&ref, true);
71 				entry_ref target;
72 				entry.GetRef(&target);
73 				if (lastRef.directory != target.directory) {
74 					sameFolder = false;
75 					break;
76 				}
77 			} else if (lastRef.directory != ref.directory) {
78 				sameFolder = false;
79 				break;
80 			}
81 		}
82 		lastRef = ref;
83 	}
84 
85 	// change active dir
86 	if (sameFolder) {
87 		BEntry entry(&lastRef);
88 		BPath path;
89 		entry.GetParent(&entry);
90 		entry.GetPath(&path);
91 		chdir(path.Path());
92 	} else {
93 		BPath path;
94 		if (find_directory(B_DESKTOP_DIRECTORY, &path) == B_OK)
95 			chdir(path.Path());
96 	}
97 
98 	// archive filename
99 	if (refCount == 1) {
100 		archiveName = lastRef.name;
101 		archiveName += ".zip";
102 	}
103 
104 	int32 argc = refCount + 3;
105 	const char** argv = new const char* [argc + 1];
106 
107 	argv[0] = strdup("/bin/zip");
108 	argv[1] = strdup("-ry");
109 	argv[2] = strdup(archiveName.String());
110 
111 	// files to zip
112 	for (int index = 0; index < refCount; index++) {
113 		fThreadDataStore->FindRef("refs", index, &ref);
114 
115 		if (sameFolder) {
116 			// just the file name
117 			argv[3 + index]	= strdup(ref.name);
118 		} else {
119 			// full path
120 			BPath path(&ref);
121 			BString file = path.Path();
122 			argv[3 + index]	= strdup(path.Path());
123 		}
124 	}
125 
126 	argv[argc] = NULL;
127 
128 	fZipProcess = _PipeCommand(argc, argv, fStdIn, fStdOut, fStdErr);
129 
130 	delete [] argv;
131 
132 	if (fZipProcess < 0)
133 		return fZipProcess;
134 
135 	resume_thread(fZipProcess);
136 
137 	fOutputFile = fdopen(fStdOut, "r");
138 	if (fOutputFile == NULL)
139 		return errno;
140 
141 	archiveName.Prepend("Creating archive: ");
142 
143 	_SendMessageToWindow(ZIPPO_TASK_DESCRIPTION, "archive_filename",
144 		archiveName.String());
145 	_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output",
146 		"Preparing to archive");
147 
148 	return B_OK;
149 }
150 
151 
152 status_t
153 ZipperThread::ExecuteUnit()
154 {
155 	// read output from /bin/zip
156 	// send it to window
157 	char buffer[4096];
158 
159 	char* output = fgets(buffer, sizeof(buffer) - 1, fOutputFile);
160 	if (output == NULL)
161 		return EOF;
162 
163 	char* newLine = strrchr(output, '\n');
164 	if (newLine != NULL)
165 		*newLine = '\0';
166 
167 	if (!strncmp("  a", output, 3)) {
168 		output[2] = 'A';
169 		_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output + 2);
170 	} else if (!strncmp("up", output, 2)) {
171 		output[0] = 'U';
172 		_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output);
173 	} else {
174 		_SendMessageToWindow(ZIPPO_LINE_OF_STDOUT, "zip_output", output);
175 	}
176 
177 	return B_OK;
178 }
179 
180 
181 status_t
182 ZipperThread::ThreadShutdown()
183 {
184 	close(fStdIn);
185 	close(fStdOut);
186 	close(fStdErr);
187 
188 	return B_OK;
189 }
190 
191 
192 void
193 ZipperThread::ThreadStartupFailed(status_t status)
194 {
195 	ErrorMessage("ZipperThread::ThreadStartupFailed() \n", status);
196 	Quit();
197 }
198 
199 
200 void
201 ZipperThread::ExecuteUnitFailed(status_t status)
202 {
203 	ErrorMessage("ZipperThread::ExecuteUnitFailed() \n", status);
204 
205 	if (status == EOF) {
206 		// thread has finished, been quit or killed, we don't know
207 		_SendMessageToWindow(ZIPPO_THREAD_EXIT);
208 	} else {
209 		// explicit error - communicate error to Window
210 		_SendMessageToWindow(ZIPPO_THREAD_EXIT_ERROR);
211 	}
212 
213 	Quit();
214 }
215 
216 
217 void
218 ZipperThread::ThreadShutdownFailed(status_t status)
219 {
220 	ErrorMessage("ZipperThread::ThreadShutdownFailed() \n", status);
221 }
222 
223 
224 void
225 ZipperThread::_MakeShellSafe(BString* string)
226 {
227 	string->CharacterEscape("\"$`", '\\');
228 	string->Prepend("\"");
229 	string->Append("\"");
230 }
231 
232 
233 thread_id
234 ZipperThread::_PipeCommand(int argc, const char** argv, int& in, int& out,
235 	int& err, const char** envp)
236 {
237 	// This function was originally written by Peter Folk <pfolk@uni.uiuc.edu>
238 	// and published in the BeDevTalk FAQ
239 	// http://www.abisoft.com/faq/BeDevTalk_FAQ.html#FAQ-209
240 
241 	thread_id thread;
242 
243 	// Save current FDs
244 	int oldIn = dup(STDIN_FILENO);
245 	int oldOut = dup(STDOUT_FILENO);
246 	int oldErr = dup(STDERR_FILENO);
247 
248 	int inPipe[2], outPipe[2], errPipe[2];
249 
250 	// Create new pipe FDs as stdin, stdout, stderr
251 	if (pipe(inPipe) < 0)
252 		goto err1;
253 	if (pipe(outPipe) < 0)
254 		goto err2;
255 	if (pipe(errPipe) < 0)
256 		goto err3;
257 
258 	errno = 0;
259 
260 	// replace old stdin/stderr/stdout
261 	dup2(inPipe[0], STDIN_FILENO);
262 	close(inPipe[0]);
263 	dup2(outPipe[1], STDOUT_FILENO);
264 	close(outPipe[1]);
265 	dup2(errPipe[1], STDERR_FILENO);
266 	close(errPipe[1]);
267 
268 	if (errno == 0) {
269 		in = inPipe[1];		// Write to in, appears on cmd's stdin
270 		out = outPipe[0];	// Read from out, taken from cmd's stdout
271 		err = errPipe[0];	// Read from err, taken from cmd's stderr
272 
273 		// execute command
274 		thread = load_image(argc, argv, envp);
275 	} else {
276 		thread = errno;
277 	}
278 
279 	// Restore old FDs
280 	dup2(oldIn, STDIN_FILENO);
281 	close(oldIn);
282 	dup2(oldOut, STDOUT_FILENO);
283 	close(oldOut);
284 	dup2(oldErr, STDERR_FILENO);
285 	close(oldErr);
286 	return thread;
287 
288 err3:
289 	close(outPipe[0]);
290 	close(outPipe[1]);
291 err2:
292 	close(inPipe[0]);
293 	close(inPipe[1]);
294 err1:
295 	close(oldIn);
296 	close(oldOut);
297 	close(oldErr);
298 	return errno;
299 }
300 
301 
302 void
303 ZipperThread::_SendMessageToWindow(uint32 what, const char* name,
304 	const char* value)
305 {
306 	BMessage msg(what);
307 	if (name != NULL && value != NULL)
308 		msg.AddString(name, value);
309 
310 	fWindowMessenger.SendMessage(&msg);
311 }
312 
313 
314 status_t
315 ZipperThread::SuspendExternalZip()
316 {
317 	thread_info info;
318 	status_t status = get_thread_info(fZipProcess, &info);
319 
320 	if (status == B_OK && !strcmp(info.name, "zip"))
321 		return suspend_thread(fZipProcess);
322 
323 	return status;
324 }
325 
326 
327 status_t
328 ZipperThread::ResumeExternalZip()
329 {
330 	thread_info info;
331 	status_t status = get_thread_info(fZipProcess, &info);
332 
333 	if (status == B_OK && !strcmp(info.name, "zip"))
334 		return resume_thread(fZipProcess);
335 
336 	return status;
337 }
338 
339 
340 status_t
341 ZipperThread::InterruptExternalZip()
342 {
343 	thread_info info;
344 	status_t status = get_thread_info(fZipProcess, &info);
345 
346 	if (status == B_OK && !strcmp(info.name, "zip")) {
347 		status = B_OK;
348 		status = send_signal(fZipProcess, SIGINT);
349 		WaitOnExternalZip();
350 		return status;
351 	}
352 
353 	return status;
354 }
355 
356 
357 status_t
358 ZipperThread::WaitOnExternalZip()
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 wait_for_thread(fZipProcess, &status);
365 
366 	return status;
367 }
368 
369