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