xref: /haiku/src/apps/installer/WorkerThread.cpp (revision ba6f7c8c42038755c9017ae014859d016b072580)
1 /*
2  * Copyright 2009, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2005-2008, Jérôme DUVAL.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 #include "WorkerThread.h"
8 
9 #include <errno.h>
10 #include <stdio.h>
11 
12 #include <Alert.h>
13 #include <Autolock.h>
14 #include <Catalog.h>
15 #include <Directory.h>
16 #include <DiskDeviceVisitor.h>
17 #include <DiskDeviceTypes.h>
18 #include <FindDirectory.h>
19 #include <fs_index.h>
20 #include <Locale.h>
21 #include <Menu.h>
22 #include <MenuItem.h>
23 #include <Message.h>
24 #include <Messenger.h>
25 #include <Path.h>
26 #include <String.h>
27 #include <VolumeRoster.h>
28 
29 #include "AutoLocker.h"
30 #include "CopyEngine.h"
31 #include "InstallerDefs.h"
32 #include "PackageViews.h"
33 #include "PartitionMenuItem.h"
34 #include "ProgressReporter.h"
35 #include "StringForSize.h"
36 #include "UnzipEngine.h"
37 
38 
39 #define B_TRANSLATION_CONTEXT "InstallProgress"
40 
41 
42 //#define COPY_TRACE
43 #ifdef COPY_TRACE
44 #define CALLED() 		printf("CALLED %s\n",__PRETTY_FUNCTION__)
45 #define ERR2(x, y...)	fprintf(stderr, "WorkerThread: "x" %s\n", y, strerror(err))
46 #define ERR(x)			fprintf(stderr, "WorkerThread: "x" %s\n", strerror(err))
47 #else
48 #define CALLED()
49 #define ERR(x)
50 #define ERR2(x, y...)
51 #endif
52 
53 const char BOOT_PATH[] = "/boot";
54 
55 const uint32 MSG_START_INSTALLING = 'eSRT';
56 
57 
58 class SourceVisitor : public BDiskDeviceVisitor {
59 public:
60 	SourceVisitor(BMenu* menu);
61 	virtual bool Visit(BDiskDevice* device);
62 	virtual bool Visit(BPartition* partition, int32 level);
63 
64 private:
65 	BMenu* fMenu;
66 };
67 
68 
69 class TargetVisitor : public BDiskDeviceVisitor {
70 public:
71 	TargetVisitor(BMenu* menu);
72 	virtual bool Visit(BDiskDevice* device);
73 	virtual bool Visit(BPartition* partition, int32 level);
74 
75 private:
76 	BMenu* fMenu;
77 };
78 
79 
80 // #pragma mark - WorkerThread
81 
82 
83 class WorkerThread::EntryFilter : public CopyEngine::EntryFilter {
84 public:
85 	EntryFilter(const char* sourceDirectory)
86 	{
87 		// init BEntry pointing to /var
88 		// There is no other way to retrieve the path to the var folder
89 		// on the source volume. Using find_directory() with
90 		// B_COMMON_VAR_DIRECTORY will only ever get the var folder on the
91 		// current /boot volume regardless of the volume of "source", which
92 		// makes sense, since passing a volume is meant to folders that are
93 		// volume specific, like "trash".
94 		BPath path(sourceDirectory);
95 		if (path.Append(kSwapFilePath) == B_OK)
96 			fSwapFileEntry.SetTo(path.Path());
97 		else
98 			fSwapFileEntry.Unset();
99 	}
100 
101 	virtual bool ShouldCopyEntry(const BEntry& entry, const char* path,
102 		const struct stat& statInfo, int32 level) const
103 	{
104 		if (level == 1 && S_ISDIR(statInfo.st_mode)) {
105 			if (strcmp(kPackagesDirectoryPath, path) == 0) {
106 				printf("ignoring '%s'.\n", path);
107 				return false;
108 			}
109 			if (strcmp(kSourcesDirectoryPath, path) == 0) {
110 				printf("ignoring '%s'.\n", path);
111 				return false;
112 			}
113 			if (strcmp("rr_moved", path) == 0) {
114 				printf("ignoring '%s'.\n", path);
115 				return false;
116 			}
117 		}
118 		if (level == 1 && S_ISREG(statInfo.st_mode)) {
119 			if (strcmp("boot.catalog", path) == 0) {
120 				printf("ignoring '%s'.\n", path);
121 				return false;
122 			}
123 			if (strcmp("haiku-boot-floppy.image", path) == 0) {
124 				printf("ignoring '%s'.\n", path);
125 				return false;
126 			}
127 		}
128 		if (fSwapFileEntry == entry) {
129 			// current location of var
130 			printf("ignoring swap file\n");
131 			return false;
132 		}
133 		return true;
134 	}
135 
136 	virtual bool ShouldClobberFolder(const BEntry& entry, const char* path,
137 		const struct stat& statInfo, int32 level) const
138 	{
139 		if (level == 1 && S_ISDIR(statInfo.st_mode)) {
140 			if (strcmp("system", path) == 0) {
141 				printf("clobbering '%s'.\n", path);
142 				return true;
143 			}
144 		}
145 		return false;
146 	}
147 
148 private:
149 	// TODO: Should be made into a list of BEntris to be ignored, perhaps.
150 	// settable by method...
151 	BEntry fSwapFileEntry;
152 };
153 
154 
155 // #pragma mark - WorkerThread
156 
157 
158 WorkerThread::WorkerThread(const BMessenger& owner)
159 	:
160 	BLooper("copy_engine"),
161 	fOwner(owner),
162 	fPackages(NULL),
163 	fSpaceRequired(0),
164 	fCancelSemaphore(-1)
165 {
166 	Run();
167 }
168 
169 
170 void
171 WorkerThread::MessageReceived(BMessage* message)
172 {
173 	CALLED();
174 
175 	switch (message->what) {
176 		case MSG_START_INSTALLING:
177 			_PerformInstall(message->GetInt32("source", -1),
178 				message->GetInt32("target", -1));
179 			break;
180 
181 		case MSG_WRITE_BOOT_SECTOR:
182 		{
183 			int32 id;
184 			if (message->FindInt32("id", &id) != B_OK) {
185 				_SetStatusMessage(B_TRANSLATE("Boot sector not written "
186 					"because of an internal error."));
187 				break;
188 			}
189 
190 			// TODO: Refactor with _PerformInstall()
191 			BPath targetDirectory;
192 			BDiskDevice device;
193 			BPartition* partition;
194 
195 			if (fDDRoster.GetPartitionWithID(id, &device, &partition) == B_OK) {
196 				if (!partition->IsMounted()) {
197 					if (partition->Mount() < B_OK) {
198 						_SetStatusMessage(B_TRANSLATE("The partition can't be "
199 							"mounted. Please choose a different partition."));
200 						break;
201 					}
202 				}
203 				if (partition->GetMountPoint(&targetDirectory) != B_OK) {
204 					_SetStatusMessage(B_TRANSLATE("The mount point could not "
205 						"be retrieved."));
206 					break;
207 				}
208 			} else if (fDDRoster.GetDeviceWithID(id, &device) == B_OK) {
209 				if (!device.IsMounted()) {
210 					if (device.Mount() < B_OK) {
211 						_SetStatusMessage(B_TRANSLATE("The disk can't be "
212 							"mounted. Please choose a different disk."));
213 						break;
214 					}
215 				}
216 				if (device.GetMountPoint(&targetDirectory) != B_OK) {
217 					_SetStatusMessage(B_TRANSLATE("The mount point could not "
218 						"be retrieved."));
219 					break;
220 				}
221 			}
222 
223 			_LaunchFinishScript(targetDirectory);
224 			// TODO: Get error from executing script!
225 			_SetStatusMessage(
226 				B_TRANSLATE("Boot sector successfully written."));
227 		}
228 		default:
229 			BLooper::MessageReceived(message);
230 	}
231 }
232 
233 
234 
235 
236 void
237 WorkerThread::ScanDisksPartitions(BMenu *srcMenu, BMenu *targetMenu)
238 {
239 	// NOTE: This is actually executed in the window thread.
240 	BDiskDevice device;
241 	BPartition *partition = NULL;
242 
243 	printf("\nScanDisksPartitions source partitions begin\n");
244 	SourceVisitor srcVisitor(srcMenu);
245 	fDDRoster.VisitEachMountedPartition(&srcVisitor, &device, &partition);
246 
247 	printf("\nScanDisksPartitions target partitions begin\n");
248 	TargetVisitor targetVisitor(targetMenu);
249 	fDDRoster.VisitEachPartition(&targetVisitor, &device, &partition);
250 }
251 
252 
253 void
254 WorkerThread::SetPackagesList(BList *list)
255 {
256 	// Executed in window thread.
257 	BAutolock _(this);
258 
259 	delete fPackages;
260 	fPackages = list;
261 }
262 
263 
264 void
265 WorkerThread::StartInstall(partition_id sourcePartitionID,
266 	partition_id targetPartitionID)
267 {
268 	// Executed in window thread.
269 	BMessage message(MSG_START_INSTALLING);
270 	message.AddInt32("source", sourcePartitionID);
271 	message.AddInt32("target", targetPartitionID);
272 
273 	PostMessage(&message, this);
274 }
275 
276 
277 void
278 WorkerThread::WriteBootSector(BMenu* targetMenu)
279 {
280 	// Executed in window thread.
281 	CALLED();
282 
283 	PartitionMenuItem* item = (PartitionMenuItem*)targetMenu->FindMarked();
284 	if (item == NULL) {
285 		ERR("bad menu items\n");
286 		return;
287 	}
288 
289 	BMessage message(MSG_WRITE_BOOT_SECTOR);
290 	message.AddInt32("id", item->ID());
291 	PostMessage(&message, this);
292 }
293 
294 
295 // #pragma mark -
296 
297 
298 void
299 WorkerThread::_LaunchInitScript(BPath &path)
300 {
301 	BPath bootPath;
302 	find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath);
303 	BString command("/bin/sh ");
304 	command += bootPath.Path();
305 	command += "/InstallerInitScript ";
306 	command += "\"";
307 	command += path.Path();
308 	command += "\"";
309 	_SetStatusMessage(B_TRANSLATE("Starting Installation."));
310 	system(command.String());
311 }
312 
313 
314 void
315 WorkerThread::_LaunchFinishScript(BPath &path)
316 {
317 	BPath bootPath;
318 	find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath);
319 	BString command("/bin/sh ");
320 	command += bootPath.Path();
321 	command += "/InstallerFinishScript ";
322 	command += "\"";
323 	command += path.Path();
324 	command += "\"";
325 	_SetStatusMessage(B_TRANSLATE("Finishing Installation."));
326 	system(command.String());
327 }
328 
329 
330 status_t
331 WorkerThread::_PerformInstall(partition_id sourcePartitionID,
332 	partition_id targetPartitionID)
333 {
334 	CALLED();
335 
336 	BPath targetDirectory;
337 	BPath srcDirectory;
338 	BPath trashPath;
339 	BPath testPath;
340 	BDirectory targetDir;
341 	BDiskDevice device;
342 	BPartition* partition;
343 	BVolume targetVolume;
344 	status_t err = B_OK;
345 	int32 entries = 0;
346 	entry_ref testRef;
347 	const char* mountError = B_TRANSLATE("The disk can't be mounted. Please "
348 		"choose a different disk.");
349 
350 	if (sourcePartitionID < 0 || targetPartitionID < 0) {
351 		ERR("bad source or target partition ID\n");
352 		return _InstallationError(err);
353 	}
354 
355 	// check if target is initialized
356 	// ask if init or mount as is
357 	if (fDDRoster.GetPartitionWithID(targetPartitionID, &device,
358 			&partition) == B_OK) {
359 		if (!partition->IsMounted()) {
360 			if ((err = partition->Mount()) < B_OK) {
361 				_SetStatusMessage(mountError);
362 				ERR("BPartition::Mount");
363 				return _InstallationError(err);
364 			}
365 		}
366 		if ((err = partition->GetVolume(&targetVolume)) != B_OK) {
367 			ERR("BPartition::GetVolume");
368 			return _InstallationError(err);
369 		}
370 		if ((err = partition->GetMountPoint(&targetDirectory)) != B_OK) {
371 			ERR("BPartition::GetMountPoint");
372 			return _InstallationError(err);
373 		}
374 	} else if (fDDRoster.GetDeviceWithID(targetPartitionID, &device) == B_OK) {
375 		if (!device.IsMounted()) {
376 			if ((err = device.Mount()) < B_OK) {
377 				_SetStatusMessage(mountError);
378 				ERR("BDiskDevice::Mount");
379 				return _InstallationError(err);
380 			}
381 		}
382 		if ((err = device.GetVolume(&targetVolume)) != B_OK) {
383 			ERR("BDiskDevice::GetVolume");
384 			return _InstallationError(err);
385 		}
386 		if ((err = device.GetMountPoint(&targetDirectory)) != B_OK) {
387 			ERR("BDiskDevice::GetMountPoint");
388 			return _InstallationError(err);
389 		}
390 	} else
391 		return _InstallationError(err);  // shouldn't happen
392 
393 	// check if target has enough space
394 	if (fSpaceRequired > 0 && targetVolume.FreeBytes() < fSpaceRequired) {
395 		BAlert* alert = new BAlert("", B_TRANSLATE("The destination disk may "
396 			"not have enough space. Try choosing a different disk or choose "
397 			"to not install optional items."),
398 			B_TRANSLATE("Try installing anyway"), B_TRANSLATE("Cancel"), 0,
399 			B_WIDTH_AS_USUAL, B_STOP_ALERT);
400 		alert->SetShortcut(1, B_ESCAPE);
401 		if (alert->Go() != 0)
402 			return _InstallationError(err);
403 	}
404 
405 	if (fDDRoster.GetPartitionWithID(sourcePartitionID, &device, &partition)
406 			== B_OK) {
407 		if ((err = partition->GetMountPoint(&srcDirectory)) != B_OK) {
408 			ERR("BPartition::GetMountPoint");
409 			return _InstallationError(err);
410 		}
411 	} else if (fDDRoster.GetDeviceWithID(sourcePartitionID, &device) == B_OK) {
412 		if ((err = device.GetMountPoint(&srcDirectory)) != B_OK) {
413 			ERR("BDiskDevice::GetMountPoint");
414 			return _InstallationError(err);
415 		}
416 	} else
417 		return _InstallationError(err); // shouldn't happen
418 
419 	// check not installing on itself
420 	if (strcmp(srcDirectory.Path(), targetDirectory.Path()) == 0) {
421 		_SetStatusMessage(B_TRANSLATE("You can't install the contents of a "
422 			"disk onto itself. Please choose a different disk."));
423 		return _InstallationError(err);
424 	}
425 
426 	// check not installing on boot volume
427 	if (strncmp(BOOT_PATH, targetDirectory.Path(), strlen(BOOT_PATH)) == 0) {
428 		BAlert* alert = new BAlert("", B_TRANSLATE("Are you sure you want to "
429 			"install onto the current boot disk? The Installer will have to "
430 			"reboot your machine if you proceed."), B_TRANSLATE("OK"),
431 			B_TRANSLATE("Cancel"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
432 		alert->SetShortcut(1, B_ESCAPE);
433 		if (alert->Go() != 0) {
434 			_SetStatusMessage("Installation stopped.");
435 			return _InstallationError(err);
436 		}
437 	}
438 
439 	// check if target volume's trash dir has anything in it
440 	// (target volume w/ only an empty trash dir is considered
441 	// an empty volume)
442 	if (find_directory(B_TRASH_DIRECTORY, &trashPath, false,
443 		&targetVolume) == B_OK && targetDir.SetTo(trashPath.Path()) == B_OK) {
444 			while (targetDir.GetNextRef(&testRef) == B_OK) {
445 				// Something in the Trash
446 				entries++;
447 				break;
448 			}
449 	}
450 
451 	targetDir.SetTo(targetDirectory.Path());
452 
453 	// check if target volume otherwise has any entries
454 	while (entries == 0 && targetDir.GetNextRef(&testRef) == B_OK) {
455 		if (testPath.SetTo(&testRef) == B_OK && testPath != trashPath)
456 			entries++;
457 	}
458 
459 	if (entries != 0) {
460 		BAlert* alert = new BAlert("", B_TRANSLATE("The target volume is not "
461 			"empty. Are you sure you want to install anyway?\n\nNote: The "
462 			"'system' folder will be a clean copy from the source volume, all "
463 			"other folders will be merged, whereas files and links that exist "
464 			"on both the source and target volume will be overwritten with "
465 			"the source volume version."),
466 			B_TRANSLATE("Install anyway"), B_TRANSLATE("Cancel"), 0,
467 			B_WIDTH_AS_USUAL, B_STOP_ALERT);
468 		alert->SetShortcut(1, B_ESCAPE);
469 		if (alert->Go() != 0) {
470 		// TODO: Would be cool to offer the option here to clean additional
471 		// folders at the user's choice (like /boot/common and /boot/develop).
472 			return _InstallationError(B_CANCELED);
473 		}
474 	}
475 
476 	// Begin actual installation
477 
478 	ProgressReporter reporter(fOwner, new BMessage(MSG_STATUS_MESSAGE));
479 	EntryFilter entryFilter(srcDirectory.Path());
480 	CopyEngine engine(&reporter, &entryFilter);
481 	BList unzipEngines;
482 
483 	_LaunchInitScript(targetDirectory);
484 
485 	// Create the default indices which should always be present on a proper
486 	// boot volume. We don't care if the source volume does not have them.
487 	// After all, the user might be re-installing to another drive and may
488 	// want problems fixed along the way...
489 	err = _CreateDefaultIndices(targetDirectory);
490 	if (err != B_OK)
491 		return _InstallationError(err);
492 	// Mirror all the indices which are present on the source volume onto
493 	// the target volume.
494 	err = _MirrorIndices(srcDirectory, targetDirectory);
495 	if (err != B_OK)
496 		return _InstallationError(err);
497 
498 	// Let the engine collect information for the progress bar later on
499 	engine.ResetTargets(srcDirectory.Path());
500 	err = engine.CollectTargets(srcDirectory.Path(), fCancelSemaphore);
501 	if (err != B_OK)
502 		return _InstallationError(err);
503 
504 	// Collect selected packages also
505 	if (fPackages) {
506 		BPath pkgRootDir(srcDirectory.Path(), kPackagesDirectoryPath);
507 		int32 count = fPackages->CountItems();
508 		for (int32 i = 0; i < count; i++) {
509 			Package *p = static_cast<Package*>(fPackages->ItemAt(i));
510 			BPath packageDir(pkgRootDir.Path(), p->Folder());
511 			err = engine.CollectTargets(packageDir.Path(), fCancelSemaphore);
512 			if (err != B_OK)
513 				return _InstallationError(err);
514 		}
515 	}
516 
517 	// collect information about all zip packages
518 	err = _ProcessZipPackages(srcDirectory.Path(), targetDirectory.Path(),
519 		&reporter, unzipEngines);
520 	if (err != B_OK)
521 		return _InstallationError(err);
522 
523 	reporter.StartTimer();
524 
525 	// copy source volume
526 	err = engine.CopyFolder(srcDirectory.Path(), targetDirectory.Path(),
527 		fCancelSemaphore);
528 	if (err != B_OK)
529 		return _InstallationError(err);
530 
531 	// copy selected packages
532 	if (fPackages) {
533 		BPath pkgRootDir(srcDirectory.Path(), kPackagesDirectoryPath);
534 		int32 count = fPackages->CountItems();
535 		for (int32 i = 0; i < count; i++) {
536 			Package *p = static_cast<Package*>(fPackages->ItemAt(i));
537 			BPath packageDir(pkgRootDir.Path(), p->Folder());
538 			err = engine.CopyFolder(packageDir.Path(), targetDirectory.Path(),
539 				fCancelSemaphore);
540 			if (err != B_OK)
541 				return _InstallationError(err);
542 		}
543 	}
544 
545 	// Extract all zip packages. If an error occured, delete the rest of
546 	// the engines, but stop extracting.
547 	for (int32 i = 0; i < unzipEngines.CountItems(); i++) {
548 		UnzipEngine* engine = reinterpret_cast<UnzipEngine*>(
549 			unzipEngines.ItemAtFast(i));
550 		if (err == B_OK)
551 			err = engine->UnzipPackage();
552 		delete engine;
553 	}
554 	if (err != B_OK)
555 		return _InstallationError(err);
556 
557 	_LaunchFinishScript(targetDirectory);
558 
559 	fOwner.SendMessage(MSG_INSTALL_FINISHED);
560 	return B_OK;
561 }
562 
563 
564 status_t
565 WorkerThread::_InstallationError(status_t error)
566 {
567 	BMessage statusMessage(MSG_RESET);
568 	if (error == B_CANCELED)
569 		_SetStatusMessage(B_TRANSLATE("Installation canceled."));
570 	else
571 		statusMessage.AddInt32("error", error);
572 	ERR("_PerformInstall failed");
573 	fOwner.SendMessage(&statusMessage);
574 	return error;
575 }
576 
577 
578 status_t
579 WorkerThread::_MirrorIndices(const BPath& sourceDirectory,
580 	const BPath& targetDirectory) const
581 {
582 	dev_t sourceDevice = dev_for_path(sourceDirectory.Path());
583 	if (sourceDevice < 0)
584 		return (status_t)sourceDevice;
585 	dev_t targetDevice = dev_for_path(targetDirectory.Path());
586 	if (targetDevice < 0)
587 		return (status_t)targetDevice;
588 	DIR* indices = fs_open_index_dir(sourceDevice);
589 	if (indices == NULL) {
590 		printf("%s: fs_open_index_dir(): (%d) %s\n", sourceDirectory.Path(),
591 			errno, strerror(errno));
592 		// Opening the index directory will fail for example on ISO-Live
593 		// CDs. The default indices have already been created earlier, so
594 		// we simply bail.
595 		return B_OK;
596 	}
597 	while (dirent* index = fs_read_index_dir(indices)) {
598 		if (strcmp(index->d_name, "name") == 0
599 			|| strcmp(index->d_name, "size") == 0
600 			|| strcmp(index->d_name, "last_modified") == 0) {
601 			continue;
602 		}
603 
604 		index_info info;
605 		if (fs_stat_index(sourceDevice, index->d_name, &info) != B_OK) {
606 			printf("Failed to mirror index %s: fs_stat_index(): (%d) %s\n",
607 				index->d_name, errno, strerror(errno));
608 			continue;
609 		}
610 
611 		uint32 flags = 0;
612 			// Flags are always 0 for the moment.
613 		if (fs_create_index(targetDevice, index->d_name, info.type, flags)
614 			!= B_OK) {
615 			if (errno == B_FILE_EXISTS)
616 				continue;
617 			printf("Failed to mirror index %s: fs_create_index(): (%d) %s\n",
618 				index->d_name, errno, strerror(errno));
619 			continue;
620 		}
621 	}
622 	fs_close_index_dir(indices);
623 	return B_OK;
624 }
625 
626 
627 status_t
628 WorkerThread::_CreateDefaultIndices(const BPath& targetDirectory) const
629 {
630 	dev_t targetDevice = dev_for_path(targetDirectory.Path());
631 	if (targetDevice < 0)
632 		return (status_t)targetDevice;
633 
634 	struct IndexInfo {
635 		const char* name;
636 		uint32_t	type;
637 	};
638 
639 	const IndexInfo defaultIndices[] = {
640 		{ "BEOS:APP_SIG", B_STRING_TYPE },
641 		{ "BEOS:LOCALE_LANGUAGE", B_STRING_TYPE },
642 		{ "BEOS:LOCALE_SIGNATURE", B_STRING_TYPE },
643 		{ "_trk/qrylastchange", B_INT32_TYPE },
644 		{ "_trk/recentQuery", B_INT32_TYPE },
645 		{ "be:deskbar_item_status", B_STRING_TYPE }
646 	};
647 
648 	uint32 flags = 0;
649 		// Flags are always 0 for the moment.
650 
651 	for (uint32 i = 0; i < sizeof(defaultIndices) / sizeof(IndexInfo); i++) {
652 		const IndexInfo& info = defaultIndices[i];
653 		if (fs_create_index(targetDevice, info.name, info.type, flags)
654 			!= B_OK) {
655 			if (errno == B_FILE_EXISTS)
656 				continue;
657 			printf("Failed to create index %s: fs_create_index(): (%d) %s\n",
658 				info.name, errno, strerror(errno));
659 			return errno;
660 		}
661 	}
662 
663 	return B_OK;
664 }
665 
666 
667 status_t
668 WorkerThread::_ProcessZipPackages(const char* sourcePath,
669 	const char* targetPath, ProgressReporter* reporter, BList& unzipEngines)
670 {
671 	// TODO: Put those in the optional packages list view
672 	// TODO: Implement mechanism to handle dependencies between these
673 	// packages. (Selecting one will auto-select others.)
674 	BPath pkgRootDir(sourcePath, kPackagesDirectoryPath);
675 	BDirectory directory(pkgRootDir.Path());
676 	BEntry entry;
677 	while (directory.GetNextEntry(&entry) == B_OK) {
678 		char name[B_FILE_NAME_LENGTH];
679 		if (entry.GetName(name) != B_OK)
680 			continue;
681 		int nameLength = strlen(name);
682 		if (nameLength <= 0)
683 			continue;
684 		char* nameExtension = name + nameLength - 4;
685 		if (strcasecmp(nameExtension, ".zip") != 0)
686 			continue;
687 		printf("found .zip package: %s\n", name);
688 
689 		UnzipEngine* unzipEngine = new(std::nothrow) UnzipEngine(reporter,
690 			fCancelSemaphore);
691 		if (unzipEngine == NULL || !unzipEngines.AddItem(unzipEngine)) {
692 			delete unzipEngine;
693 			return B_NO_MEMORY;
694 		}
695 		BPath path;
696 		entry.GetPath(&path);
697 		status_t ret = unzipEngine->SetTo(path.Path(), targetPath);
698 		if (ret != B_OK)
699 			return ret;
700 
701 		reporter->AddItems(unzipEngine->ItemsToUncompress(),
702 			unzipEngine->BytesToUncompress());
703 	}
704 
705 	return B_OK;
706 }
707 
708 
709 void
710 WorkerThread::_SetStatusMessage(const char *status)
711 {
712 	BMessage msg(MSG_STATUS_MESSAGE);
713 	msg.AddString("status", status);
714 	fOwner.SendMessage(&msg);
715 }
716 
717 
718 static void
719 make_partition_label(BPartition* partition, char* label, char* menuLabel,
720 	bool showContentType)
721 {
722 	char size[20];
723 	string_for_size(partition->Size(), size, sizeof(size));
724 
725 	BPath path;
726 	partition->GetPath(&path);
727 
728 	if (showContentType) {
729 		const char* type = partition->ContentType();
730 		if (type == NULL)
731 			type = B_TRANSLATE_COMMENT("Unknown Type", "Partition content type");
732 
733 		sprintf(label, "%s - %s [%s] (%s)", partition->ContentName(), size,
734 			path.Path(), type);
735 	} else {
736 		sprintf(label, "%s - %s [%s]", partition->ContentName(), size,
737 			path.Path());
738 	}
739 
740 	sprintf(menuLabel, "%s - %s", partition->ContentName(), size);
741 }
742 
743 
744 // #pragma mark - SourceVisitor
745 
746 
747 SourceVisitor::SourceVisitor(BMenu *menu)
748 	: fMenu(menu)
749 {
750 }
751 
752 bool
753 SourceVisitor::Visit(BDiskDevice *device)
754 {
755 	return Visit(device, 0);
756 }
757 
758 
759 bool
760 SourceVisitor::Visit(BPartition *partition, int32 level)
761 {
762 	BPath path;
763 	if (partition->GetPath(&path) == B_OK)
764 		printf("SourceVisitor::Visit(BPartition *) : %s\n", path.Path());
765 	printf("SourceVisitor::Visit(BPartition *) : %s\n",
766 		partition->ContentName());
767 
768 	if (partition->ContentType() == NULL)
769 		return false;
770 
771 	bool isBootPartition = false;
772 	if (partition->IsMounted()) {
773 		BPath mountPoint;
774 		partition->GetMountPoint(&mountPoint);
775 		isBootPartition = strcmp(BOOT_PATH, mountPoint.Path()) == 0;
776 	}
777 
778 	if (!isBootPartition
779 		&& strcmp(partition->ContentType(), kPartitionTypeBFS) != 0) {
780 		// Except only BFS partitions, except this is the boot partition
781 		// (ISO9660 with write overlay for example).
782 		return false;
783 	}
784 
785 	// TODO: We could probably check if this volume contains
786 	// the Haiku kernel or something. Does it make sense to "install"
787 	// from your BFS volume containing the music collection?
788 	// TODO: Then the check for BFS could also be removed above.
789 
790 	char label[255];
791 	char menuLabel[255];
792 	make_partition_label(partition, label, menuLabel, false);
793 	PartitionMenuItem* item = new PartitionMenuItem(partition->ContentName(),
794 		label, menuLabel, new BMessage(SOURCE_PARTITION), partition->ID());
795 	item->SetMarked(isBootPartition);
796 	fMenu->AddItem(item);
797 	return false;
798 }
799 
800 
801 // #pragma mark - TargetVisitor
802 
803 
804 TargetVisitor::TargetVisitor(BMenu *menu)
805 	: fMenu(menu)
806 {
807 }
808 
809 
810 bool
811 TargetVisitor::Visit(BDiskDevice *device)
812 {
813 	if (device->IsReadOnlyMedia())
814 		return false;
815 	return Visit(device, 0);
816 }
817 
818 
819 bool
820 TargetVisitor::Visit(BPartition *partition, int32 level)
821 {
822 	BPath path;
823 	if (partition->GetPath(&path) == B_OK)
824 		printf("TargetVisitor::Visit(BPartition *) : %s\n", path.Path());
825 	printf("TargetVisitor::Visit(BPartition *) : %s\n",
826 		partition->ContentName());
827 
828 	if (partition->ContentSize() < 20 * 1024 * 1024) {
829 		// reject partitions which are too small anyway
830 		// TODO: Could depend on the source size
831 		printf("  too small\n");
832 		return false;
833 	}
834 
835 	if (partition->CountChildren() > 0) {
836 		// Looks like an extended partition, or the device itself.
837 		// Do not accept this as target...
838 		printf("  no leaf partition\n");
839 		return false;
840 	}
841 
842 	// TODO: After running DriveSetup and doing another scan, it would
843 	// be great to pick the partition which just appeared!
844 
845 	bool isBootPartition = false;
846 	if (partition->IsMounted()) {
847 		BPath mountPoint;
848 		partition->GetMountPoint(&mountPoint);
849 		isBootPartition = strcmp(BOOT_PATH, mountPoint.Path()) == 0;
850 	}
851 
852 	// Only non-boot BFS partitions are valid targets, but we want to display the
853 	// other partitions as well, in order not to irritate the user.
854 	bool isValidTarget = isBootPartition == false
855 		&& partition->ContentType() != NULL
856 		&& strcmp(partition->ContentType(), kPartitionTypeBFS) == 0;
857 
858 	char label[255];
859 	char menuLabel[255];
860 	make_partition_label(partition, label, menuLabel, !isValidTarget);
861 	PartitionMenuItem* item = new PartitionMenuItem(partition->ContentName(),
862 		label, menuLabel, new BMessage(TARGET_PARTITION), partition->ID());
863 
864 	item->SetIsValidTarget(isValidTarget);
865 
866 
867 	fMenu->AddItem(item);
868 	return false;
869 }
870 
871