1 /* 2 * Copyright 2007-2009, 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("Continue"), T("Abort")); 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 folders"); 305 306 // Install files and directories 307 PackageItem *iter; 308 ItemState state; 309 uint32 i; 310 int32 choice; 311 BString label; 312 313 packageInfo.SetName(fInfo.GetName()); 314 // TODO: Here's a small problem, since right now it's not quite sure 315 // which description is really used as such. The one displayed on 316 // the installer is mostly package installation description, but 317 // most people use it for describing the application in more detail 318 // then in the short description. 319 // For now, we'll use the short description if possible. 320 BString description = fInfo.GetShortDescription(); 321 if (description.Length() <= 0) 322 description = fInfo.GetDescription(); 323 packageInfo.SetDescription(description.String()); 324 packageInfo.SetSpaceNeeded(type->space_needed); 325 326 fItemExistsPolicy = P_EXISTS_NONE; 327 328 for (i = 0; i < n; i++) { 329 state.Reset(fItemExistsPolicy); // Reset the current item state 330 iter = static_cast<PackageItem *>(type->items.ItemAt(i)); 331 332 err = iter->WriteToPath(fCurrentPath.Path(), &state); 333 if (err == B_FILE_EXISTS) { 334 // Writing to path failed because path already exists - ask the user 335 // what to do and retry the writing process 336 choice = _ItemExists(*iter, state.destination); 337 if (choice != P_EXISTS_ABORT) { 338 state.policy = choice; 339 err = iter->WriteToPath(fCurrentPath.Path(), &state); 340 } 341 } 342 343 if (err != B_OK) { 344 fprintf(stderr, "Error while writing path %s\n", fCurrentPath.Path()); 345 return err; 346 } 347 348 if (fStatusWindow->Stopped()) 349 return B_FILE_EXISTS; 350 label = ""; 351 label << (uint32)(i + 1) << " of " << (uint32)n; 352 fStatusWindow->StageStep(1, NULL, label.String()); 353 354 packageInfo.AddItem(state.destination.Path()); 355 } 356 357 fStatusWindow->StageStep(1, "Finishing installation", ""); 358 359 err = packageInfo.Save(); 360 if (err != B_OK) 361 return err; 362 363 fStatusWindow->StageStep(1, "Done"); 364 365 return B_OK; 366 } 367 368 369 /* 370 void 371 PackageView::_InitView() 372 { 373 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 374 375 BTextView *description = new BTextView(BRect(0, 0, 20, 20), "description", 376 BRect(4, 4, 16, 16), B_FOLLOW_NONE, B_WILL_DRAW); 377 description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 378 description->SetText(fInfo.GetDescription()); 379 description->MakeEditable(false); 380 description->MakeSelectable(false); 381 382 fInstallTypes = new BPopUpMenu("none"); 383 384 BMenuField *installType = new BMenuField("install_type", 385 T("Installation type:"), fInstallTypes, 0); 386 installType->SetAlignment(B_ALIGN_RIGHT); 387 installType->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_MIDDLE)); 388 389 fInstallDesc = new BTextView(BRect(0, 0, 10, 10), "install_desc", 390 BRect(2, 2, 8, 8), B_FOLLOW_NONE, B_WILL_DRAW); 391 fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 392 fInstallDesc->MakeEditable(false); 393 fInstallDesc->MakeSelectable(false); 394 fInstallDesc->SetText(T("No installation type selected")); 395 fInstallDesc->TextHeight(0, fInstallDesc->TextLength()); 396 397 fInstall = new BButton("install_button", T("Install"), 398 new BMessage(P_MSG_INSTALL)); 399 400 BView *installField = BGroupLayoutBuilder(B_VERTICAL, 5.0f) 401 .AddGroup(B_HORIZONTAL) 402 .Add(installType) 403 .AddGlue() 404 .End() 405 .Add(fInstallDesc); 406 407 BBox *installBox = new BBox("install_box"); 408 installBox->AddChild(installField); 409 410 BView *root = BGroupLayoutBuilder(B_VERTICAL, 3.0f) 411 .Add(description) 412 .Add(installBox) 413 .AddGroup(B_HORIZONTAL) 414 .AddGlue() 415 .Add(fInstall) 416 .End(); 417 418 AddChild(root); 419 420 fInstall->MakeDefault(true); 421 }*/ 422 423 424 void 425 PackageView::_InitView() 426 { 427 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 428 429 BRect rect = Bounds(); 430 BTextView *description = new BTextView(rect, "description", 431 rect.InsetByCopy(5, 5), B_FOLLOW_NONE, B_WILL_DRAW); 432 description->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 433 description->SetText(fInfo.GetDescription()); 434 description->MakeEditable(false); 435 description->MakeSelectable(false); 436 437 float length = description->TextHeight(0, description->TextLength()) + 5; 438 if (length > kMaxDescHeight) { 439 // Set a scroller for the description. 440 description->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, kMaxDescHeight); 441 BScrollView *scroller = new BScrollView("desciption_view", description, 442 B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, B_NO_BORDER); 443 444 AddChild(scroller); 445 rect = scroller->Frame(); 446 } 447 else { 448 description->ResizeTo(rect.Width(), length); 449 AddChild(description); 450 rect = description->Frame(); 451 } 452 453 rect.top = rect.bottom + 2; 454 rect.bottom += 100; 455 BBox *installBox = new BBox(rect.InsetByCopy(2, 2), "install_box"); 456 457 fInstallTypes = new BPopUpMenu("none"); 458 459 BMenuField *installType = new BMenuField(BRect(2, 2, 100, 50), "install_type", 460 T("Installation type:"), fInstallTypes, false); 461 installType->SetDivider(installType->StringWidth(installType->Label()) + 8); 462 installType->SetAlignment(B_ALIGN_RIGHT); 463 installType->ResizeToPreferred(); 464 465 installBox->AddChild(installType); 466 467 rect = installBox->Bounds().InsetBySelf(4, 4); 468 rect.top = installType->Frame().bottom; 469 fInstallDesc = new BTextView(rect, "install_desc", 470 BRect(2, 2, rect.Width() - 2, rect.Height() - 2), B_FOLLOW_NONE, 471 B_WILL_DRAW); 472 fInstallDesc->MakeEditable(false); 473 fInstallDesc->MakeSelectable(false); 474 fInstallDesc->SetText(T("No installation type selected")); 475 fInstallDesc->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 476 477 fInstallDesc->ResizeTo(rect.Width() - B_V_SCROLL_BAR_WIDTH, 60); 478 BScrollView *scroller = new BScrollView("desciption_view", fInstallDesc, 479 B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS, false, true, B_NO_BORDER); 480 481 installBox->ResizeTo(installBox->Bounds().Width(), 482 scroller->Frame().bottom + 10); 483 484 installBox->AddChild(scroller); 485 486 AddChild(installBox); 487 488 fDestination = new BPopUpMenu("none"); 489 490 rect = installBox->Frame(); 491 rect.top = rect.bottom + 5; 492 rect.bottom += 35; 493 fDestField = new BMenuField(rect, "install_to", T("Install to:"), 494 fDestination, false); 495 fDestField->SetDivider(fDestField->StringWidth(fDestField->Label()) + 8); 496 fDestField->SetAlignment(B_ALIGN_RIGHT); 497 fDestField->ResizeToPreferred(); 498 499 AddChild(fDestField); 500 501 fInstall = new BButton(rect, "install_button", T("Install"), 502 new BMessage(P_MSG_INSTALL)); 503 fInstall->ResizeToPreferred(); 504 AddChild(fInstall); 505 fInstall->MoveTo(Bounds().Width() - fInstall->Bounds().Width() - 10, rect.top + 2); 506 fInstall->MakeDefault(true); 507 } 508 509 510 void 511 PackageView::_InitProfiles() 512 { 513 // Set all profiles 514 int i = 0, num = fInfo.GetProfileCount(); 515 pkg_profile *prof; 516 BMenuItem *item = 0; 517 char sizeString[32]; 518 BString name = ""; 519 BMessage *message; 520 521 if (num > 0) { // Add the default item 522 prof = fInfo.GetProfile(0); 523 convert_size(prof->space_needed, sizeString, 32); 524 name << prof->name << " (" << sizeString << ")"; 525 526 message = new BMessage(P_MSG_GROUP_CHANGED); 527 message->AddInt32("index", 0); 528 item = new BMenuItem(name.String(), message); 529 fInstallTypes->AddItem(item); 530 item->SetMarked(true); 531 fCurrentType = 0; 532 } 533 534 for (i = 1; i < num; i++) { 535 prof = fInfo.GetProfile(i); 536 537 if (prof) { 538 convert_size(prof->space_needed, sizeString, 32); 539 name = prof->name; 540 name << " (" << sizeString << ")"; 541 542 message = new BMessage(P_MSG_GROUP_CHANGED); 543 message->AddInt32("index", i); 544 item = new BMenuItem(name.String(), message); 545 fInstallTypes->AddItem(item); 546 } 547 else 548 fInstallTypes->AddSeparatorItem(); 549 } 550 } 551 552 553 int32 554 PackageView::_ItemExists(PackageItem &item, BPath &path) 555 { 556 int32 choice = P_EXISTS_NONE; 557 558 switch (fItemExistsPolicy) { 559 case P_EXISTS_OVERWRITE: 560 choice = P_EXISTS_OVERWRITE; 561 break; 562 563 case P_EXISTS_SKIP: 564 choice = P_EXISTS_SKIP; 565 break; 566 567 case P_EXISTS_ASK: 568 case P_EXISTS_NONE: 569 { 570 BString alertString = T("The "); 571 572 alertString << item.ItemKind() << T(" named \'") << path.Leaf() << "\' "; 573 alertString << T("already exists in the given path.\nReplace the file with " 574 "the one from this package or skip it?"); 575 576 BAlert *alert = new BAlert(T("file_exists"), alertString.String(), 577 T("Replace"), T("Skip"), T("Abort")); 578 579 choice = alert->Go(); 580 switch (choice) { 581 case 0: 582 choice = P_EXISTS_OVERWRITE; 583 break; 584 case 1: 585 choice = P_EXISTS_SKIP; 586 break; 587 default: 588 return P_EXISTS_ABORT; 589 } 590 591 if (fItemExistsPolicy == P_EXISTS_NONE) { 592 // TODO: Maybe add 'No, but ask again' type of choice as well? 593 alertString = T("Do you want to remember this decision for the rest of " 594 "this installation?\nAll existing files will be "); 595 alertString << ((choice == P_EXISTS_OVERWRITE) 596 ? T("replaced?") : T("skipped?")); 597 598 alert = new BAlert(T("policy_decision"), alertString.String(), 599 T("Yes"), T("No")); 600 601 int32 decision = alert->Go(); 602 if (decision == 0) 603 fItemExistsPolicy = choice; 604 else 605 fItemExistsPolicy = P_EXISTS_ASK; 606 } 607 break; 608 } 609 } 610 611 return choice; 612 } 613 614 615 status_t 616 PackageView::_GroupChanged(int32 index) 617 { 618 if (index < 0) 619 return B_ERROR; 620 621 BMenuItem *iter; 622 int32 i, num = fDestination->CountItems(); 623 624 // Clear the choice list 625 for (i = 0;i < num;i++) { 626 iter = fDestination->RemoveItem((int32)0); 627 delete iter; 628 } 629 630 fCurrentType = index; 631 pkg_profile *prof = fInfo.GetProfile(index); 632 BString test; 633 fInstallDesc->SetText(prof->description.String()); 634 635 if (prof) { 636 BMenuItem *item = 0; 637 BPath path; 638 BMessage *temp; 639 BVolume volume; 640 641 if (prof->path_type == P_INSTALL_PATH) { 642 dev_t device; 643 BString name; 644 char sizeString[32]; 645 646 if (find_directory(B_BEOS_APPS_DIRECTORY, &path) == B_OK) { 647 device = dev_for_path(path.Path()); 648 if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) { 649 temp = new BMessage(P_MSG_PATH_CHANGED); 650 temp->AddString("path", BString(path.Path())); 651 652 convert_size(volume.FreeBytes(), sizeString, 32); 653 name = path.Path(); 654 name << " (" << sizeString << " free)"; 655 item = new BMenuItem(name.String(), temp); 656 item->SetTarget(this); 657 fDestination->AddItem(item); 658 } 659 } 660 if (find_directory(B_APPS_DIRECTORY, &path) == B_OK) { 661 device = dev_for_path(path.Path()); 662 if (volume.SetTo(device) == B_OK && !volume.IsReadOnly()) { 663 temp = new BMessage(P_MSG_PATH_CHANGED); 664 temp->AddString("path", BString(path.Path())); 665 666 convert_size(volume.FreeBytes(), sizeString, 32); 667 name = path.Path(); 668 name << " (" << sizeString << " free)"; 669 item = new BMenuItem(name.String(), temp); 670 item->SetTarget(this); 671 fDestination->AddItem(item); 672 } 673 } 674 675 if (item) { 676 item->SetMarked(true); 677 fCurrentPath.SetTo(path.Path()); 678 } 679 fDestination->AddSeparatorItem(); 680 681 item = new BMenuItem("Other" B_UTF8_ELLIPSIS, new BMessage(P_MSG_OPEN_PANEL)); 682 item->SetTarget(this); 683 fDestination->AddItem(item); 684 685 fDestField->SetEnabled(true); 686 } 687 else if (prof->path_type == P_USER_PATH) { 688 BString name; 689 char sizeString[32], volumeName[B_FILE_NAME_LENGTH]; 690 BVolumeRoster roster; 691 BDirectory mountPoint; 692 693 while (roster.GetNextVolume(&volume) != B_BAD_VALUE) { 694 if (volume.IsReadOnly() || 695 volume.GetRootDirectory(&mountPoint) != B_OK) 696 continue; 697 698 if (path.SetTo(&mountPoint, NULL) != B_OK) 699 continue; 700 701 temp = new BMessage(P_MSG_PATH_CHANGED); 702 temp->AddString("path", BString(path.Path())); 703 704 convert_size(volume.FreeBytes(), sizeString, 32); 705 volume.GetName(volumeName); 706 name = volumeName; 707 name << " (" << sizeString << " free)"; 708 item = new BMenuItem(name.String(), temp); 709 item->SetTarget(this); 710 fDestination->AddItem(item); 711 } 712 713 fDestField->SetEnabled(true); 714 } 715 else 716 fDestField->SetEnabled(false); 717 } 718 719 return B_OK; 720 } 721 722