xref: /haiku/src/apps/installer/WorkerThread.cpp (revision fce4895d1884da5ae6fb299d23c735c598e690b1)
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 <set>
13 #include <string>
14 #include <strings.h>
15 
16 #include <Alert.h>
17 #include <Autolock.h>
18 #include <Catalog.h>
19 #include <Directory.h>
20 #include <DiskDeviceVisitor.h>
21 #include <DiskDeviceTypes.h>
22 #include <FindDirectory.h>
23 #include <fs_index.h>
24 #include <Locale.h>
25 #include <Menu.h>
26 #include <MenuItem.h>
27 #include <Message.h>
28 #include <Messenger.h>
29 #include <Path.h>
30 #include <String.h>
31 #include <VolumeRoster.h>
32 
33 #include "AutoLocker.h"
34 #include "CopyEngine.h"
35 #include "InstallerDefs.h"
36 #include "PackageViews.h"
37 #include "PartitionMenuItem.h"
38 #include "ProgressReporter.h"
39 #include "StringForSize.h"
40 #include "UnzipEngine.h"
41 
42 
43 #define B_TRANSLATION_CONTEXT "InstallProgress"
44 
45 
46 //#define COPY_TRACE
47 #ifdef COPY_TRACE
48 #define CALLED() 		printf("CALLED %s\n",__PRETTY_FUNCTION__)
49 #define ERR2(x, y...)	fprintf(stderr, "WorkerThread: "x" %s\n", y, strerror(err))
50 #define ERR(x)			fprintf(stderr, "WorkerThread: "x" %s\n", strerror(err))
51 #else
52 #define CALLED()
53 #define ERR(x)
54 #define ERR2(x, y...)
55 #endif
56 
57 const char BOOT_PATH[] = "/boot";
58 
59 const uint32 MSG_START_INSTALLING = 'eSRT';
60 
61 
62 class SourceVisitor : public BDiskDeviceVisitor {
63 public:
64 	SourceVisitor(BMenu* menu);
65 	virtual bool Visit(BDiskDevice* device);
66 	virtual bool Visit(BPartition* partition, int32 level);
67 
68 private:
69 	BMenu* fMenu;
70 };
71 
72 
73 class TargetVisitor : public BDiskDeviceVisitor {
74 public:
75 	TargetVisitor(BMenu* menu);
76 	virtual bool Visit(BDiskDevice* device);
77 	virtual bool Visit(BPartition* partition, int32 level);
78 
79 private:
80 	BMenu* fMenu;
81 };
82 
83 
84 // #pragma mark - WorkerThread
85 
86 
87 class WorkerThread::EntryFilter : public CopyEngine::EntryFilter {
88 public:
89 	EntryFilter(const char* sourceDirectory)
90 		:
91 		fIgnorePaths(),
92 		fSourceDevice(-1)
93 	{
94 		try {
95 			fIgnorePaths.insert(kPackagesDirectoryPath);
96 			fIgnorePaths.insert(kSourcesDirectoryPath);
97 			fIgnorePaths.insert("rr_moved");
98 			fIgnorePaths.insert("boot.catalog");
99 			fIgnorePaths.insert("haiku-boot-floppy.image");
100 			fIgnorePaths.insert("system/var/swap");
101 			fIgnorePaths.insert("system/var/shared_memory");
102 
103 			fPackageFSRootPaths.insert("system");
104 			fPackageFSRootPaths.insert("home/config");
105 		} catch (std::bad_alloc&) {
106 		}
107 
108 		struct stat st;
109 		if (stat(sourceDirectory, &st) == 0)
110 			fSourceDevice = st.st_dev;
111 	}
112 
113 	virtual bool ShouldCopyEntry(const BEntry& entry, const char* path,
114 		const struct stat& statInfo, int32 level) const
115 	{
116 		if (fIgnorePaths.find(path) != fIgnorePaths.end()) {
117 			printf("ignoring '%s'.\n", path);
118 			return false;
119 		}
120 
121 		if (statInfo.st_dev != fSourceDevice) {
122 			// Allow that only for the root of the packagefs mounts, since
123 			// those contain directories that shine through from the
124 			// underlying volume.
125 			if (fPackageFSRootPaths.find(path) == fPackageFSRootPaths.end())
126 				return false;
127 		}
128 
129 		return true;
130 	}
131 
132 	virtual bool ShouldClobberFolder(const BEntry& entry, const char* path,
133 		const struct stat& statInfo, int32 level) const
134 	{
135 		if (level == 2 && S_ISDIR(statInfo.st_mode)
136 				&& strncmp("system/", path, 7) == 0
137 				&& strcmp("system/settings", path) != 0) {
138 			// Replace everything in "system" besides "settings"
139 			printf("clobbering '%s'.\n", path);
140 			return true;
141 		}
142 		return false;
143 	}
144 
145 private:
146 	typedef std::set<std::string> StringSet;
147 
148 			StringSet			fIgnorePaths;
149 			StringSet			fPackageFSRootPaths;
150 			dev_t				fSourceDevice;
151 };
152 
153 
154 // #pragma mark - WorkerThread
155 
156 
157 WorkerThread::WorkerThread(const BMessenger& owner)
158 	:
159 	BLooper("copy_engine"),
160 	fOwner(owner),
161 	fPackages(NULL),
162 	fSpaceRequired(0),
163 	fCancelSemaphore(-1)
164 {
165 	Run();
166 }
167 
168 
169 void
170 WorkerThread::MessageReceived(BMessage* message)
171 {
172 	CALLED();
173 
174 	switch (message->what) {
175 		case MSG_START_INSTALLING:
176 			_PerformInstall(message->GetInt32("source", -1),
177 				message->GetInt32("target", -1));
178 			break;
179 
180 		case MSG_WRITE_BOOT_SECTOR:
181 		{
182 			int32 id;
183 			if (message->FindInt32("id", &id) != B_OK) {
184 				_SetStatusMessage(B_TRANSLATE("Boot sector not written "
185 					"because of an internal error."));
186 				break;
187 			}
188 
189 			// TODO: Refactor with _PerformInstall()
190 			BPath targetDirectory;
191 			BDiskDevice device;
192 			BPartition* partition;
193 
194 			if (fDDRoster.GetPartitionWithID(id, &device, &partition) == B_OK) {
195 				if (!partition->IsMounted()) {
196 					if (partition->Mount() < B_OK) {
197 						_SetStatusMessage(B_TRANSLATE("The partition can't be "
198 							"mounted. Please choose a different partition."));
199 						break;
200 					}
201 				}
202 				if (partition->GetMountPoint(&targetDirectory) != B_OK) {
203 					_SetStatusMessage(B_TRANSLATE("The mount point could not "
204 						"be retrieved."));
205 					break;
206 				}
207 			} else if (fDDRoster.GetDeviceWithID(id, &device) == B_OK) {
208 				if (!device.IsMounted()) {
209 					if (device.Mount() < B_OK) {
210 						_SetStatusMessage(B_TRANSLATE("The disk can't be "
211 							"mounted. Please choose a different disk."));
212 						break;
213 					}
214 				}
215 				if (device.GetMountPoint(&targetDirectory) != B_OK) {
216 					_SetStatusMessage(B_TRANSLATE("The mount point could not "
217 						"be retrieved."));
218 					break;
219 				}
220 			}
221 
222 			_LaunchFinishScript(targetDirectory);
223 			// TODO: Get error from executing script!
224 			_SetStatusMessage(
225 				B_TRANSLATE("Boot sector successfully written."));
226 		}
227 		default:
228 			BLooper::MessageReceived(message);
229 	}
230 }
231 
232 
233 
234 
235 void
236 WorkerThread::ScanDisksPartitions(BMenu *srcMenu, BMenu *targetMenu)
237 {
238 	// NOTE: This is actually executed in the window thread.
239 	BDiskDevice device;
240 	BPartition *partition = NULL;
241 
242 	printf("\nScanDisksPartitions source partitions begin\n");
243 	SourceVisitor srcVisitor(srcMenu);
244 	fDDRoster.VisitEachMountedPartition(&srcVisitor, &device, &partition);
245 
246 	printf("\nScanDisksPartitions target partitions begin\n");
247 	TargetVisitor targetVisitor(targetMenu);
248 	fDDRoster.VisitEachPartition(&targetVisitor, &device, &partition);
249 }
250 
251 
252 void
253 WorkerThread::SetPackagesList(BList *list)
254 {
255 	// Executed in window thread.
256 	BAutolock _(this);
257 
258 	delete fPackages;
259 	fPackages = list;
260 }
261 
262 
263 void
264 WorkerThread::StartInstall(partition_id sourcePartitionID,
265 	partition_id targetPartitionID)
266 {
267 	// Executed in window thread.
268 	BMessage message(MSG_START_INSTALLING);
269 	message.AddInt32("source", sourcePartitionID);
270 	message.AddInt32("target", targetPartitionID);
271 
272 	PostMessage(&message, this);
273 }
274 
275 
276 void
277 WorkerThread::WriteBootSector(BMenu* targetMenu)
278 {
279 	// Executed in window thread.
280 	CALLED();
281 
282 	PartitionMenuItem* item = (PartitionMenuItem*)targetMenu->FindMarked();
283 	if (item == NULL) {
284 		ERR("bad menu items\n");
285 		return;
286 	}
287 
288 	BMessage message(MSG_WRITE_BOOT_SECTOR);
289 	message.AddInt32("id", item->ID());
290 	PostMessage(&message, this);
291 }
292 
293 
294 // #pragma mark -
295 
296 
297 void
298 WorkerThread::_LaunchInitScript(BPath &path)
299 {
300 	BPath bootPath;
301 	find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath);
302 	BString command("/bin/sh ");
303 	command += bootPath.Path();
304 	command += "/InstallerInitScript ";
305 	command += "\"";
306 	command += path.Path();
307 	command += "\"";
308 	_SetStatusMessage(B_TRANSLATE("Starting Installation."));
309 	system(command.String());
310 }
311 
312 
313 void
314 WorkerThread::_LaunchFinishScript(BPath &path)
315 {
316 	BPath bootPath;
317 	find_directory(B_BEOS_BOOT_DIRECTORY, &bootPath);
318 	BString command("/bin/sh ");
319 	command += bootPath.Path();
320 	command += "/InstallerFinishScript ";
321 	command += "\"";
322 	command += path.Path();
323 	command += "\"";
324 	_SetStatusMessage(B_TRANSLATE("Finishing Installation."));
325 	system(command.String());
326 }
327 
328 
329 status_t
330 WorkerThread::_PerformInstall(partition_id sourcePartitionID,
331 	partition_id targetPartitionID)
332 {
333 	CALLED();
334 
335 	BPath targetDirectory;
336 	BPath srcDirectory;
337 	BPath trashPath;
338 	BPath testPath;
339 	BDirectory targetDir;
340 	BDiskDevice device;
341 	BPartition* partition;
342 	BVolume targetVolume;
343 	status_t err = B_OK;
344 	int32 entries = 0;
345 	entry_ref testRef;
346 	const char* mountError = B_TRANSLATE("The disk can't be mounted. Please "
347 		"choose a different disk.");
348 
349 	if (sourcePartitionID < 0 || targetPartitionID < 0) {
350 		ERR("bad source or target partition ID\n");
351 		return _InstallationError(err);
352 	}
353 
354 	// check if target is initialized
355 	// ask if init or mount as is
356 	if (fDDRoster.GetPartitionWithID(targetPartitionID, &device,
357 			&partition) == B_OK) {
358 		if (!partition->IsMounted()) {
359 			if ((err = partition->Mount()) < B_OK) {
360 				_SetStatusMessage(mountError);
361 				ERR("BPartition::Mount");
362 				return _InstallationError(err);
363 			}
364 		}
365 		if ((err = partition->GetVolume(&targetVolume)) != B_OK) {
366 			ERR("BPartition::GetVolume");
367 			return _InstallationError(err);
368 		}
369 		if ((err = partition->GetMountPoint(&targetDirectory)) != B_OK) {
370 			ERR("BPartition::GetMountPoint");
371 			return _InstallationError(err);
372 		}
373 	} else if (fDDRoster.GetDeviceWithID(targetPartitionID, &device) == B_OK) {
374 		if (!device.IsMounted()) {
375 			if ((err = device.Mount()) < B_OK) {
376 				_SetStatusMessage(mountError);
377 				ERR("BDiskDevice::Mount");
378 				return _InstallationError(err);
379 			}
380 		}
381 		if ((err = device.GetVolume(&targetVolume)) != B_OK) {
382 			ERR("BDiskDevice::GetVolume");
383 			return _InstallationError(err);
384 		}
385 		if ((err = device.GetMountPoint(&targetDirectory)) != B_OK) {
386 			ERR("BDiskDevice::GetMountPoint");
387 			return _InstallationError(err);
388 		}
389 	} else
390 		return _InstallationError(err);  // shouldn't happen
391 
392 	// check if target has enough space
393 	if (fSpaceRequired > 0 && targetVolume.FreeBytes() < fSpaceRequired) {
394 		BAlert* alert = new BAlert("", B_TRANSLATE("The destination disk may "
395 			"not have enough space. Try choosing a different disk or choose "
396 			"to not install optional items."),
397 			B_TRANSLATE("Try installing anyway"), B_TRANSLATE("Cancel"), 0,
398 			B_WIDTH_AS_USUAL, B_STOP_ALERT);
399 		alert->SetShortcut(1, B_ESCAPE);
400 		if (alert->Go() != 0)
401 			return _InstallationError(err);
402 	}
403 
404 	if (fDDRoster.GetPartitionWithID(sourcePartitionID, &device, &partition)
405 			== B_OK) {
406 		if ((err = partition->GetMountPoint(&srcDirectory)) != B_OK) {
407 			ERR("BPartition::GetMountPoint");
408 			return _InstallationError(err);
409 		}
410 	} else if (fDDRoster.GetDeviceWithID(sourcePartitionID, &device) == B_OK) {
411 		if ((err = device.GetMountPoint(&srcDirectory)) != B_OK) {
412 			ERR("BDiskDevice::GetMountPoint");
413 			return _InstallationError(err);
414 		}
415 	} else
416 		return _InstallationError(err); // shouldn't happen
417 
418 	// check not installing on itself
419 	if (strcmp(srcDirectory.Path(), targetDirectory.Path()) == 0) {
420 		_SetStatusMessage(B_TRANSLATE("You can't install the contents of a "
421 			"disk onto itself. Please choose a different disk."));
422 		return _InstallationError(err);
423 	}
424 
425 	// check not installing on boot volume
426 	if (strncmp(BOOT_PATH, targetDirectory.Path(), strlen(BOOT_PATH)) == 0) {
427 		BAlert* alert = new BAlert("", B_TRANSLATE("Are you sure you want to "
428 			"install onto the current boot disk? The Installer will have to "
429 			"reboot your machine if you proceed."), B_TRANSLATE("OK"),
430 			B_TRANSLATE("Cancel"), 0, B_WIDTH_AS_USUAL, B_STOP_ALERT);
431 		alert->SetShortcut(1, B_ESCAPE);
432 		if (alert->Go() != 0) {
433 			_SetStatusMessage("Installation stopped.");
434 			return _InstallationError(err);
435 		}
436 	}
437 
438 	// check if target volume's trash dir has anything in it
439 	// (target volume w/ only an empty trash dir is considered
440 	// an empty volume)
441 	if (find_directory(B_TRASH_DIRECTORY, &trashPath, false,
442 		&targetVolume) == B_OK && targetDir.SetTo(trashPath.Path()) == B_OK) {
443 			while (targetDir.GetNextRef(&testRef) == B_OK) {
444 				// Something in the Trash
445 				entries++;
446 				break;
447 			}
448 	}
449 
450 	targetDir.SetTo(targetDirectory.Path());
451 
452 	// check if target volume otherwise has any entries
453 	while (entries == 0 && targetDir.GetNextRef(&testRef) == B_OK) {
454 		if (testPath.SetTo(&testRef) == B_OK && testPath != trashPath)
455 			entries++;
456 	}
457 
458 	if (entries != 0) {
459 		BAlert* alert = new BAlert("", B_TRANSLATE("The target volume is not "
460 			"empty. Are you sure you want to install anyway?\n\nNote: The "
461 			"'system' folder will be a clean copy from the source volume but "
462 			"will retain its settings folder, all other folders will be "
463 			"merged, whereas files and links that exist on both the source "
464 			"and target volume will be overwritten with the source volume "
465 			"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.
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