xref: /haiku/src/apps/packageinstaller/PackageView.cpp (revision adb0d19d561947362090081e81d90dde59142026)
1 /*
2  * Copyright (c) 2007, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Author:
6  *		Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
7  */
8 
9 
10 #include "InstalledPackageInfo.h"
11 #include "PackageImageViewer.h"
12 #include "PackageTextViewer.h"
13 #include "PackageView.h"
14 
15 #include <Alert.h>
16 #include <Button.h>
17 #include <Directory.h>
18 #include <FindDirectory.h>
19 #include <MenuItem.h>
20 #include <Path.h>
21 #include <PopUpMenu.h>
22 #include <ScrollView.h>
23 #include <TextView.h>
24 #include <Volume.h>
25 #include <VolumeRoster.h>
26 #include <Window.h>
27 
28 #include <GroupLayout.h>
29 #include <GroupLayoutBuilder.h>
30 #include <GroupView.h>
31 
32 #include <fs_info.h>
33 #include <stdio.h> // For debugging
34 
35 // Macro reserved for later localization
36 #define T(x) x
37 
38 const float kMaxDescHeight = 125.0f;
39 const uint32 kSeparatorIndex = 3;
40 
41 
42 
43 static void
44 convert_size(uint64 size, char *buffer, uint32 n)
45 {
46 	if (size < 1024)
47 		snprintf(buffer, n, "%llu bytes", size);
48 	else if (size < 1024 * 1024)
49 		snprintf(buffer, n, "%.1f KiB", size / 1024.0f);
50 	else if (size < 1024 * 1024 * 1024)
51 		snprintf(buffer, n, "%.1f MiB", size / (1024.0f*1024.0f));
52 	else
53 		snprintf(buffer, n, "%.1f GiB", size / (1024.0f*1024.0f*1024.0f));
54 }
55 
56 
57 
58 // #pragma mark -
59 
60 
61 PackageView::PackageView(BRect frame, const entry_ref *ref)
62 	:	BView(frame, "package_view", B_FOLLOW_NONE, 0),
63 		//BView("package_view", B_WILL_DRAW, new BGroupLayout(B_HORIZONTAL)),
64 	fOpenPanel(new BFilePanel(B_OPEN_PANEL, NULL, NULL,
65 		B_DIRECTORY_NODE, false)),
66 	fInfo(ref)
67 {
68 	_InitView();
69 
70 	// Check whether the package has been successfuly parsed
71 	status_t ret = fInfo.InitCheck();
72 	if (ret == B_OK)
73 		_InitProfiles();
74 
75 	ResizeTo(Bounds().Width(), fInstall->Frame().bottom + 4);
76 }
77 
78 
79 PackageView::~PackageView()
80 {
81 	delete fOpenPanel;
82 }
83 
84 
85 void
86 PackageView::AttachedToWindow()
87 {
88 	status_t ret = fInfo.InitCheck();
89 	if (ret != B_OK && ret != B_NO_INIT) {
90 		BAlert *warning = new BAlert(T("parsing_failed"),
91 				T("The package file is not readable.\nOne of the possible "
92 				"reasons for this might be that the requested file is not a valid "
93 				"BeOS .pkg package."), T("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
94 				B_WARNING_ALERT);
95 		warning->Go();
96 
97 		Window()->PostMessage(B_QUIT_REQUESTED);
98 		return;
99 	}
100 
101 	// Set the window title
102 	BWindow *parent = Window();
103 	BString title;
104 	BString name = fInfo.GetName();
105 	if (name.CountChars() == 0) {
106 		title = T("Package installer");
107 	}
108 	else {
109 		title = T("Install ");
110 		title += name;
111 	}
112 	parent->SetTitle(title.String());
113 	fInstall->SetTarget(this);
114 
115 	fOpenPanel->SetTarget(BMessenger(this));
116 	fInstallTypes->SetTargetForItems(this);
117 
118 	if (fInfo.InitCheck() == B_OK) {
119 		// If the package is valid, we can set up the default group and all
120 		// other things. If not, then the application will close just after
121 		// attaching the view to the window
122 		_GroupChanged(0);
123 
124 		fStatusWindow = new PackageStatus(T("Installation progress"));
125 
126 		// Show the splash screen, if present
127 		BMallocIO *image = fInfo.GetSplashScreen();
128 		if (image) {
129 			PackageImageViewer *imageViewer = new PackageImageViewer(image);
130 			imageViewer->Go();
131 		}
132 
133 		// Show the disclaimer/info text popup, if present
134 		BString disclaimer = fInfo.GetDisclaimer();
135 		if (disclaimer.Length() != 0) {
136 	  		PackageTextViewer *text = new PackageTextViewer(disclaimer.String());
137 			int32 selection = text->Go();
138 			// The user didn't accept our disclaimer, this means we cannot continue.
139 	  		if (selection == 0) {
140 				BWindow *parent = Window();
141 				if (parent && parent->Lock())
142 					parent->Quit();
143   			}
144 		}
145 	}
146 }
147 
148 
149 void
150 PackageView::MessageReceived(BMessage *msg)
151 {
152 	switch (msg->what) {
153 		case P_MSG_INSTALL:
154 		{
155 			fInstall->SetEnabled(false);
156 			fStatusWindow->Show();
157 			BAlert *notify;
158 			status_t ret = Install();
159 			if (ret == B_OK) {
160 				notify = new BAlert("installation_success",
161 					T("The package you requested has been successfully installed "
162 						"on your system."), T("OK"));
163 
164 				notify->Go();
165 				fStatusWindow->Hide();
166 
167 				BWindow *parent = Window();
168 				if (parent && parent->Lock())
169 					parent->Quit();
170 			}
171 			else if (ret == B_FILE_EXISTS)
172 				notify = new BAlert("installation_aborted",
173 					T("The installation of the package has been aborted."), T("OK"));
174 			else {
175 				notify = new BAlert("installation_failed", // TODO: Review this
176 					T("The requested package failed to install on your system. This "
177 						"might be a problem with the target package file. Please consult "
178 						"this issue with the package distributor."), T("OK"), NULL,
179 					NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
180 				fprintf(stderr, "Error while installing the package : %s\n", strerror(ret));
181 			}
182 			notify->Go();
183 			fStatusWindow->Hide();
184 			fInstall->SetEnabled(true);
185 
186 			break;
187 		}
188 		case P_MSG_PATH_CHANGED:
189 		{
190 			BString path;
191 			if (msg->FindString("path", &path) == B_OK) {
192 				fCurrentPath.SetTo(path.String());
193 			}
194 			break;
195 		}
196 		case P_MSG_OPEN_PANEL:
197 			fOpenPanel->Show();
198 			break;
199 		case P_MSG_GROUP_CHANGED:
200 		{
201 			int32 index;
202 			if (msg->FindInt32("index", &index) == B_OK) {
203 				_GroupChanged(index);
204 			}
205 			break;
206 		}
207 		case B_REFS_RECEIVED:
208 		{
209 			entry_ref ref;
210 			if (msg->FindRef("refs", &ref) == B_OK) {
211 				BPath path(&ref);
212 
213 				BMenuItem * item = fDestField->MenuItem();
214 				dev_t device = dev_for_path(path.Path());
215 				BVolume volume(device);
216 				if (volume.InitCheck() != B_OK)
217 					break;
218 
219 				BString name = path.Path();
220 				char sizeString[32];
221 
222 				convert_size(volume.FreeBytes(), sizeString, 32);
223 				name << " (" << sizeString << " free)";
224 
225 				item->SetLabel(name.String());
226 				fCurrentPath.SetTo(path.Path());
227 			}
228 			break;
229 		}
230 		case B_SIMPLE_DATA:
231 			if (msg->WasDropped()) {
232 				uint32 type;
233 				int32 count;
234 				status_t ret = msg->GetInfo("refs", &type, &count);
235 				// Check whether the message means someone dropped a file
236 				// to our view
237 				if (ret == B_OK && type == B_REF_TYPE) {
238 					// If it is, send it along with the refs to the application
239 					msg->what = B_REFS_RECEIVED;
240 					be_app->PostMessage(msg);
241 				}
242 			}
243 		default:
244 			BView::MessageReceived(msg);
245 			break;
246 	}
247 }
248 
249 
250 status_t
251 PackageView::Install()
252 {
253 	pkg_profile *type = static_cast<pkg_profile *>(fInfo.GetProfile(fCurrentType));
254 	uint32 n = type->items.CountItems();
255 
256 	fStatusWindow->Reset(n + 4);
257 
258 	fStatusWindow->StageStep(1, "Preparing package");
259 
260 	InstalledPackageInfo packageInfo(fInfo.GetName(), fInfo.GetVersion());
261 
262 	status_t err = packageInfo.InitCheck();
263 	err = B_ENTRY_NOT_FOUND;
264 	if (err == B_OK) {
265 		// The package is already installed, inform the user
266 		BAlert *reinstall = new BAlert("reinstall",
267 			T("The given package seems to be already installed on your system. "
268 				"Would you like to uninstall the existing one and continue the "
269 				"installation?"), T("Yes"), T("No"));
270 
271 		if (reinstall->Go() == 0) {
272 			// Uninstall the package
273 			err = packageInfo.Uninstall();
274 			if (err != B_OK) {
275 				fprintf(stderr, "Error on uninstall\n");
276 				return err;
277 			}
278 
279 			err = packageInfo.SetTo(fInfo.GetName(), fInfo.GetVersion(), true);
280 			if (err != B_OK) {
281 				fprintf(stderr, "Error on SetTo\n");
282 				return err;
283 			}
284 		}
285 		else {
286 			// Abort the installation
287 			return B_FILE_EXISTS;
288 		}
289 	}
290 	else if (err == B_ENTRY_NOT_FOUND) {
291 		err = packageInfo.SetTo(fInfo.GetName(), fInfo.GetVersion(), true);
292 		if (err != B_OK) {
293 			fprintf(stderr, "Error on SetTo\n");
294 			return err;
295 		}
296 	}
297 	else if (fStatusWindow->Stopped())
298 		return B_FILE_EXISTS;
299 	else {
300 		fprintf(stderr, "returning on error\n");
301 		return err;
302 	}
303 
304 	fStatusWindow->StageStep(1, "Installing files and directories");
305 
306 	// Install files and directories
307 	PackageItem *iter;
308 	BPath installedTo;
309 	uint32 i;
310 	BString label;
311 
312 	packageInfo.SetName(fInfo.GetName());
313 	// TODO: Here's a small problem, since right now it's not quite sure
314 	//		which description is really used as such. The one displayed on
315 	//		the installer is mostly package installation description, but
316 	//		most people use it for describing the application in more detail
317 	//		then in the short description.
318 	//		For now, we'll use the short description if possible.
319 	BString description = fInfo.GetShortDescription();
320 	if (description.Length() <= 0)
321 		description = fInfo.GetDescription();
322 	packageInfo.SetDescription(description.String());
323 	packageInfo.SetSpaceNeeded(type->space_needed);
324 
325 	for (i = 0; i < n; i++) {
326 		iter = static_cast<PackageItem *>(type->items.ItemAt(i));
327 		err = iter->WriteToPath(fCurrentPath.Path(), &installedTo);
328 		if (err != B_OK) {
329 			fprintf(stderr, "Error while writing path %s\n", fCurrentPath.Path());
330 			return err;
331 		}
332 		if (fStatusWindow->Stopped())
333 			return B_FILE_EXISTS;
334 		label = "";
335 		label << (uint32)(i + 1) << " of " << (uint32)n;
336 		fStatusWindow->StageStep(1, NULL, label.String());
337 
338 		packageInfo.AddItem(installedTo.Path());
339 	}
340 
341 	fStatusWindow->StageStep(1, "Finishing installation", "");
342 
343 	err = packageInfo.Save();
344 	if (err != B_OK)
345 		return err;
346 
347 	fStatusWindow->StageStep(1, "Done");
348 
349 	return B_OK;
350 }
351 
352 
353 /*
354 void
355 PackageView::_InitView()
356 {
357 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
358 
359 	BTextView *description = new BTextView(BRect(0, 0, 20, 20), "description",
360 		BRect(4, 4, 16, 16), B_FOLLOW_NONE, B_WILL_DRAW);
361 	description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
362 	description->SetText(fInfo.GetDescription());
363 	description->MakeEditable(false);
364 	description->MakeSelectable(false);
365 
366 	fInstallTypes = new BPopUpMenu("none");
367 
368 	BMenuField *installType = new BMenuField("install_type",
369 		T("Installation type:"), fInstallTypes, 0);
370 	installType->SetAlignment(B_ALIGN_RIGHT);
371 	installType->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_MIDDLE));
372 
373 	fInstallDesc = new BTextView(BRect(0, 0, 10, 10), "install_desc",
374 		BRect(2, 2, 8, 8), B_FOLLOW_NONE, B_WILL_DRAW);
375 	fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
376 	fInstallDesc->MakeEditable(false);
377 	fInstallDesc->MakeSelectable(false);
378 	fInstallDesc->SetText(T("No installation type selected"));
379 	fInstallDesc->TextHeight(0, fInstallDesc->TextLength());
380 
381 	fInstall = new BButton("install_button", T("Install"),
382 			new BMessage(P_MSG_INSTALL));
383 
384 	BView *installField = BGroupLayoutBuilder(B_VERTICAL, 5.0f)
385 		.AddGroup(B_HORIZONTAL)
386 			.Add(installType)
387 			.AddGlue()
388 		.End()
389 		.Add(fInstallDesc);
390 
391 	BBox *installBox = new BBox("install_box");
392 	installBox->AddChild(installField);
393 
394 	BView *root = BGroupLayoutBuilder(B_VERTICAL, 3.0f)
395 		.Add(description)
396 		.Add(installBox)
397 		.AddGroup(B_HORIZONTAL)
398 			.AddGlue()
399 			.Add(fInstall)
400 		.End();
401 
402 	AddChild(root);
403 
404 	fInstall->MakeDefault(true);
405 }*/
406 
407 
408 void
409 PackageView::_InitView()
410 {
411 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
412 
413 	BRect rect = Bounds();
414 	BTextView *description = new BTextView(rect, "description",
415 		rect.InsetByCopy(5, 5), B_FOLLOW_NONE, B_WILL_DRAW);
416 	description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
417 	description->SetText(fInfo.GetDescription());
418 	description->MakeEditable(false);
419 	description->MakeSelectable(false);
420 
421 	float length = description->TextHeight(0, description->TextLength()) + 5;
422 	if (length > kMaxDescHeight) {
423 		// Set a scroller for the description.
424 		description->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, kMaxDescHeight);
425 		BScrollView *scroller = new BScrollView("desciption_view", description,
426 				B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, B_NO_BORDER);
427 
428 		AddChild(scroller);
429 		rect = scroller->Frame();
430 	}
431 	else {
432 		description->ResizeTo(rect.Width(), length);
433 		AddChild(description);
434 		rect = description->Frame();
435 	}
436 
437 	rect.top = rect.bottom + 2;
438 	rect.bottom += 100;
439 	BBox *installBox = new BBox(rect.InsetByCopy(2, 2), "install_box");
440 
441 	fInstallTypes = new BPopUpMenu("none");
442 
443 	BMenuField *installType = new BMenuField(BRect(2, 2, 100, 50), "install_type",
444 		T("Installation type:"), fInstallTypes, false);
445 	installType->SetDivider(installType->StringWidth(installType->Label()) + 8);
446 	installType->SetAlignment(B_ALIGN_RIGHT);
447 	installType->ResizeToPreferred();
448 
449 	installBox->AddChild(installType);
450 
451 	rect = installBox->Bounds().InsetBySelf(4, 4);
452 	rect.top = installType->Frame().bottom;
453 	fInstallDesc = new BTextView(rect, "install_desc",
454 		BRect(2, 2, rect.Width() - 2, rect.Height() - 2), B_FOLLOW_NONE,
455 		B_WILL_DRAW);
456 	fInstallDesc->MakeEditable(false);
457 	fInstallDesc->MakeSelectable(false);
458 	fInstallDesc->SetText(T("No installation type selected"));
459 	fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
460 
461 	fInstallDesc->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, 60);
462 	BScrollView *scroller = new BScrollView("desciption_view", fInstallDesc,
463 				B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, B_NO_BORDER);
464 
465 	installBox->ResizeTo(installBox->Bounds().Width(),
466 		scroller->Frame().bottom + 10);
467 
468 	installBox->AddChild(scroller);
469 
470 	AddChild(installBox);
471 
472 	fDestination = new BPopUpMenu("none");
473 
474 	rect = installBox->Frame();
475 	rect.top = rect.bottom + 5;
476 	rect.bottom += 35;
477 	fDestField = new BMenuField(rect, "install_to", T("Install to:"),
478 			fDestination, false);
479 	fDestField->SetDivider(fDestField->StringWidth(fDestField->Label()) + 8);
480 	fDestField->SetAlignment(B_ALIGN_RIGHT);
481 	fDestField->ResizeToPreferred();
482 
483 	AddChild(fDestField);
484 
485 	fInstall = new BButton(rect, "install_button", T("Install"),
486 			new BMessage(P_MSG_INSTALL));
487 	fInstall->ResizeToPreferred();
488 	AddChild(fInstall);
489 	fInstall->MoveTo(Bounds().Width() - fInstall->Bounds().Width() - 10, rect.top + 2);
490 	fInstall->MakeDefault(true);
491 }
492 
493 
494 void
495 PackageView::_InitProfiles()
496 {
497 	// Set all profiles
498 	int i = 0, num = fInfo.GetProfileCount();
499 	pkg_profile *prof;
500 	BMenuItem *item = 0;
501 	char sizeString[32];
502 	BString name = "";
503 	BMessage *message;
504 
505 	if (num > 0) { // Add the default item
506 		prof = fInfo.GetProfile(0);
507 		convert_size(prof->space_needed, sizeString, 32);
508 		name << prof->name << " (" << sizeString << ")";
509 
510 		message = new BMessage(P_MSG_GROUP_CHANGED);
511 		message->AddInt32("index", 0);
512 		item = new BMenuItem(name.String(), message);
513 		fInstallTypes->AddItem(item);
514 		item->SetMarked(true);
515 		fCurrentType = 0;
516 	}
517 
518 	for (i = 1; i < num; i++) {
519 		prof = fInfo.GetProfile(i);
520 
521 		if (prof) {
522 			convert_size(prof->space_needed, sizeString, 32);
523 			name = prof->name;
524 			name << " (" << sizeString << ")";
525 
526 			message = new BMessage(P_MSG_GROUP_CHANGED);
527 			message->AddInt32("index", i);
528 			item = new BMenuItem(name.String(), message);
529 			fInstallTypes->AddItem(item);
530 		}
531 		else
532 			fInstallTypes->AddSeparatorItem();
533 	}
534 }
535 
536 
537 status_t
538 PackageView::_GroupChanged(int32 index)
539 {
540 	if (index < 0)
541 		return B_ERROR;
542 
543 	BMenuItem *iter;
544 	int32 i, num = fDestination->CountItems();
545 
546 	// Clear the choice list
547 	for (i = 0;i < num;i++) {
548 		iter = fDestination->RemoveItem((int32)0);
549 		delete iter;
550 	}
551 
552 	fCurrentType = index;
553 	pkg_profile *prof = fInfo.GetProfile(index);
554 	BString test;
555 	fInstallDesc->SetText(prof->description.String());
556 
557 	if (prof) {
558 		BMenuItem *item = 0;
559 		BPath path;
560 		BMessage *temp;
561 		BVolume volume;
562 
563 		if (prof->path_type == P_INSTALL_PATH) {
564 			dev_t device;
565 			BString name;
566 			char sizeString[32];
567 
568 			if (find_directory(B_BEOS_APPS_DIRECTORY, &path) == B_OK) {
569 				device = dev_for_path(path.Path());
570 				if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) {
571 					temp = new BMessage(P_MSG_PATH_CHANGED);
572 					temp->AddString("path", BString(path.Path()));
573 
574 					convert_size(volume.FreeBytes(), sizeString, 32);
575 					name = path.Path();
576 					name << " (" << sizeString << " free)";
577 					item = new BMenuItem(name.String(), temp);
578 					item->SetTarget(this);
579 					fDestination->AddItem(item);
580 				}
581 			}
582 			if (find_directory(B_APPS_DIRECTORY, &path) == B_OK) {
583 				device = dev_for_path(path.Path());
584 				if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) {
585 					temp = new BMessage(P_MSG_PATH_CHANGED);
586 					temp->AddString("path", BString(path.Path()));
587 
588 					convert_size(volume.FreeBytes(), sizeString, 32);
589 					name = path.Path();
590 					name << " (" << sizeString << " free)";
591 					item = new BMenuItem(name.String(), temp);
592 					item->SetTarget(this);
593 					fDestination->AddItem(item);
594 				}
595 			}
596 
597 			if (item) {
598 				item->SetMarked(true);
599 				fCurrentPath.SetTo(path.Path());
600 			}
601 			fDestination->AddSeparatorItem();
602 
603 			item = new BMenuItem("Other" B_UTF8_ELLIPSIS, new BMessage(P_MSG_OPEN_PANEL));
604 			item->SetTarget(this);
605 			fDestination->AddItem(item);
606 
607 			fDestField->SetEnabled(true);
608 		}
609 		else if (prof->path_type == P_USER_PATH) {
610 			BString name;
611 			char sizeString[32], volumeName[B_FILE_NAME_LENGTH];
612 			BVolumeRoster roster;
613 			BDirectory mountPoint;
614 
615 			while (roster.GetNextVolume(&volume) != B_BAD_VALUE) {
616 				if (volume.IsReadOnly() ||
617 						volume.GetRootDirectory(&mountPoint) != B_OK)
618 					continue;
619 
620 				if (path.SetTo(&mountPoint, NULL) != B_OK)
621 					continue;
622 
623 				temp = new BMessage(P_MSG_PATH_CHANGED);
624 				temp->AddString("path", BString(path.Path()));
625 
626 				convert_size(volume.FreeBytes(), sizeString, 32);
627 				volume.GetName(volumeName);
628 				name = volumeName;
629 				name << " (" << sizeString << " free)";
630 				item = new BMenuItem(name.String(), temp);
631 				item->SetTarget(this);
632 				fDestination->AddItem(item);
633 			}
634 
635 			fDestField->SetEnabled(true);
636 		}
637 		else
638 			fDestField->SetEnabled(false);
639 	}
640 
641 	return B_OK;
642 }
643 
644