1 /* 2 * Copyright 2013-2019, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel Dörfler <axeld@pinc-software.de> 7 * Rene Gollent <rene@gollent.com> 8 * Ingo Weinhold <ingo_weinhold@gmx.de> 9 * Brian Hill <supernova@tycho.email> 10 * Jacob Secunda 11 */ 12 13 14 #include "UpdateManager.h" 15 16 #include <sys/ioctl.h> 17 #include <unistd.h> 18 19 #include <Alert.h> 20 #include <Application.h> 21 #include <Catalog.h> 22 #include <Message.h> 23 #include <Messenger.h> 24 #include <NetworkInterface.h> 25 #include <NetworkRoster.h> 26 #include <Notification.h> 27 #include <Roster.h> 28 29 #include <package/manager/Exceptions.h> 30 #include <package/solver/SolverPackage.h> 31 32 #include "constants.h" 33 #include "ProblemWindow.h" 34 35 using namespace BPackageKit; 36 using namespace BPackageKit::BManager::BPrivate; 37 38 #undef B_TRANSLATION_CONTEXT 39 #define B_TRANSLATION_CONTEXT "UpdateManager" 40 41 42 UpdateManager::UpdateManager(BPackageInstallationLocation location, 43 bool verbose) 44 : 45 BPackageManager(location, &fClientInstallationInterface, this), 46 BPackageManager::UserInteractionHandler(), 47 fClientInstallationInterface(), 48 fStatusWindow(new SoftwareUpdaterWindow()), 49 fStatusWindowMessenger(fStatusWindow), 50 fProblemWindow(NULL), 51 fProblemWindowMessenger(), 52 fCurrentStep(ACTION_STEP_INIT), 53 fChangesConfirmed(false), 54 fVerbose(verbose) 55 { 56 _SetCurrentStep(ACTION_STEP_START); 57 } 58 59 60 UpdateManager::~UpdateManager() 61 { 62 } 63 64 65 void 66 UpdateManager::CheckNetworkConnection() 67 { 68 BNetworkRoster& roster = BNetworkRoster::Default(); 69 BNetworkInterface interface; 70 uint32 cookie = 0; 71 while (roster.GetNextInterface(&cookie, interface) == B_OK) { 72 uint32 flags = interface.Flags(); 73 if ((flags & IFF_LOOPBACK) == 0 74 && (flags & (IFF_UP | IFF_LINK)) == (IFF_UP | IFF_LINK)) { 75 return; 76 } 77 } 78 79 // No network connection detected, cannot continue 80 throw BException(B_TRANSLATE_COMMENT( 81 "No active network connection was found", "Error message")); 82 } 83 84 85 update_type 86 UpdateManager::GetUpdateType() 87 { 88 int32 action = USER_SELECTION_NEEDED; 89 if (fStatusWindowMessenger.IsValid()) { 90 BMessage message(kMsgGetUpdateType); 91 BMessage reply; 92 fStatusWindowMessenger.SendMessage(&message, &reply); 93 reply.FindInt32(kKeyAlertResult, &action); 94 } 95 return (update_type)action; 96 } 97 98 99 void 100 UpdateManager::CheckRepositories() 101 { 102 int32 count = fOtherRepositories.CountItems(); 103 if (fVerbose) 104 printf("Remote repositories available: %" B_PRId32 "\n", count); 105 if (count == 0) { 106 if (fStatusWindowMessenger.IsValid()) { 107 BMessage message(kMsgNoRepositories); 108 BMessage reply; 109 fStatusWindowMessenger.SendMessage(&message, &reply); 110 int32 result; 111 reply.FindInt32(kKeyAlertResult, &result); 112 if (result == 1) 113 be_roster->Launch("application/x-vnd.Haiku-Repositories"); 114 } 115 be_app->PostMessage(kMsgFinalQuit); 116 throw BException(B_TRANSLATE_COMMENT( 117 "No remote repositories are available", "Error message")); 118 } 119 } 120 121 122 void 123 UpdateManager::JobFailed(BSupportKit::BJob* job) 124 { 125 if (!fVerbose) 126 return; 127 128 BString error = job->ErrorString(); 129 if (error.Length() > 0) { 130 error.ReplaceAll("\n", "\n*** "); 131 fprintf(stderr, "%s", error.String()); 132 } 133 } 134 135 136 void 137 UpdateManager::JobAborted(BSupportKit::BJob* job) 138 { 139 if (fVerbose) 140 puts("Job aborted"); 141 } 142 143 144 void 145 UpdateManager::FinalUpdate(const char* header, const char* text) 146 { 147 _FinalUpdate(header, text); 148 } 149 150 151 void 152 UpdateManager::HandleProblems() 153 { 154 if (fProblemWindow == NULL) { 155 fProblemWindow = new ProblemWindow; 156 fProblemWindowMessenger.SetTo(fProblemWindow); 157 } 158 159 ProblemWindow::SolverPackageSet installPackages; 160 ProblemWindow::SolverPackageSet uninstallPackages; 161 if (!fProblemWindow->Go(fSolver,installPackages, uninstallPackages)) 162 throw BAbortedByUserException(); 163 fProblemWindow->Hide(); 164 } 165 166 167 void 168 UpdateManager::ConfirmChanges(bool fromMostSpecific) 169 { 170 if (fVerbose) 171 puts("The following changes will be made:"); 172 173 int32 count = fInstalledRepositories.CountItems(); 174 int32 upgradeCount = 0; 175 int32 installCount = 0; 176 int32 uninstallCount = 0; 177 178 if (fromMostSpecific) { 179 for (int32 i = count - 1; i >= 0; i--) 180 _PrintResult(*fInstalledRepositories.ItemAt(i), upgradeCount, 181 installCount, uninstallCount); 182 } else { 183 for (int32 i = 0; i < count; i++) 184 _PrintResult(*fInstalledRepositories.ItemAt(i), upgradeCount, 185 installCount, uninstallCount); 186 } 187 188 if (fVerbose) 189 printf("Upgrade count=%" B_PRId32 ", Install count=%" B_PRId32 190 ", Uninstall count=%" B_PRId32 "\n", 191 upgradeCount, installCount, uninstallCount); 192 193 fChangesConfirmed = fStatusWindow->ConfirmUpdates(); 194 if (!fChangesConfirmed) 195 throw BAbortedByUserException(); 196 197 _SetCurrentStep(ACTION_STEP_DOWNLOAD); 198 fPackageDownloadsTotal = upgradeCount + installCount; 199 fPackageDownloadsCount = 1; 200 } 201 202 203 void 204 UpdateManager::Warn(status_t error, const char* format, ...) 205 { 206 char buffer[256]; 207 va_list args; 208 va_start(args, format); 209 vsnprintf(buffer, sizeof(buffer), format, args); 210 va_end(args); 211 212 if (fVerbose) { 213 fputs(buffer, stderr); 214 if (error == B_OK) 215 puts(""); 216 else 217 printf(": %s\n", strerror(error)); 218 } 219 220 if (fStatusWindow != NULL) { 221 if (fStatusWindow->UserCancelRequested()) 222 throw BAbortedByUserException(); 223 fStatusWindow->ShowWarningAlert(buffer); 224 } else { 225 BString text("SoftwareUpdater:\n"); 226 text.Append(buffer); 227 BAlert* alert = new BAlert("warning", text, B_TRANSLATE("OK"), NULL, 228 NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 229 alert->Go(NULL); 230 } 231 } 232 233 234 void 235 UpdateManager::ProgressPackageDownloadStarted(const char* packageName) 236 { 237 if (fCurrentStep == ACTION_STEP_DOWNLOAD) { 238 BString header(B_TRANSLATE("Downloading packages")); 239 _UpdateDownloadProgress(header.String(), packageName, 0.0); 240 fNewDownloadStarted = false; 241 } 242 243 if (fVerbose) 244 printf("Downloading %s...\n", packageName); 245 } 246 247 248 void 249 UpdateManager::ProgressPackageDownloadActive(const char* packageName, 250 float completionValue, off_t bytes, off_t totalBytes) 251 { 252 if (fCurrentStep == ACTION_STEP_DOWNLOAD) { 253 // Fix a bug where a 100% completion percentage gets sent at the start 254 // of a package download 255 if (!fNewDownloadStarted) { 256 if (completionValue > 0 && completionValue < 1) 257 fNewDownloadStarted = true; 258 else 259 completionValue = 0.0; 260 } 261 _UpdateDownloadProgress(NULL, packageName, completionValue * 100.0); 262 } 263 264 if (fVerbose) { 265 static const char* progressChars[] = { 266 "\xE2\x96\x8F", 267 "\xE2\x96\x8E", 268 "\xE2\x96\x8D", 269 "\xE2\x96\x8C", 270 "\xE2\x96\x8B", 271 "\xE2\x96\x8A", 272 "\xE2\x96\x89", 273 "\xE2\x96\x88", 274 }; 275 276 int width = 70; 277 278 struct winsize winSize; 279 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winSize) == 0 280 && winSize.ws_col < 77) { 281 // We need 7 characters for the percent display 282 width = winSize.ws_col - 7; 283 } 284 285 int position; 286 int ipart = (int)(completionValue * width); 287 int fpart = (int)(((completionValue * width) - ipart) * 8); 288 289 fputs("\r", stdout); // return to the beginning of the line 290 291 for (position = 0; position < width; position++) { 292 if (position < ipart) { 293 // This part is fully downloaded, show a full block 294 fputs(progressChars[7], stdout); 295 } else if (position > ipart) { 296 // This part is not downloaded, show a space 297 fputs(" ", stdout); 298 } else { 299 // This part is partially downloaded 300 fputs(progressChars[fpart], stdout); 301 } 302 } 303 304 // Also print the progress percentage 305 printf(" %3d%%", (int)(completionValue * 100)); 306 307 fflush(stdout); 308 } 309 310 } 311 312 313 void 314 UpdateManager::ProgressPackageDownloadComplete(const char* packageName) 315 { 316 if (fCurrentStep == ACTION_STEP_DOWNLOAD) { 317 _UpdateDownloadProgress(NULL, packageName, 100.0); 318 fPackageDownloadsCount++; 319 } 320 321 if (fVerbose) { 322 // Overwrite the progress bar with whitespace 323 fputs("\r", stdout); 324 struct winsize w; 325 ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); 326 for (int i = 0; i < (w.ws_col); i++) 327 fputs(" ", stdout); 328 fputs("\r\x1b[1A", stdout); // Go to previous line. 329 330 printf("Downloading %s...done.\n", packageName); 331 } 332 } 333 334 335 void 336 UpdateManager::ProgressPackageChecksumStarted(const char* title) 337 { 338 // Repository checksums 339 if (fCurrentStep == ACTION_STEP_START) 340 _UpdateStatusWindow(NULL, title); 341 342 if (fVerbose) 343 printf("%s...", title); 344 } 345 346 347 void 348 UpdateManager::ProgressPackageChecksumComplete(const char* title) 349 { 350 if (fVerbose) 351 puts("done."); 352 } 353 354 355 void 356 UpdateManager::ProgressStartApplyingChanges(InstalledRepository& repository) 357 { 358 _SetCurrentStep(ACTION_STEP_APPLY); 359 BString header(B_TRANSLATE("Applying changes")); 360 BString detail(B_TRANSLATE("Packages are being updated")); 361 fStatusWindow->UpdatesApplying(header.String(), detail.String()); 362 363 if (fVerbose) 364 printf("[%s] Applying changes ...\n", repository.Name().String()); 365 } 366 367 368 void 369 UpdateManager::ProgressTransactionCommitted(InstalledRepository& repository, 370 const BCommitTransactionResult& result) 371 { 372 _SetCurrentStep(ACTION_STEP_COMPLETE); 373 BString header(B_TRANSLATE("Updates completed")); 374 375 BString detail; 376 if (BPackageRoster().IsRebootNeeded()) { 377 detail = B_TRANSLATE("A reboot is necessary to complete the " 378 "update process."); 379 fStatusWindow->PostMessage(kMsgShowReboot); 380 } else { 381 detail = B_TRANSLATE("Updates have been successfully installed."); 382 } 383 384 _FinalUpdate(header.String(), detail.String()); 385 386 if (fVerbose) { 387 const char* repositoryName = repository.Name().String(); 388 389 int32 issueCount = result.CountIssues(); 390 for (int32 i = 0; i < issueCount; i++) { 391 const BTransactionIssue* issue = result.IssueAt(i); 392 if (issue->PackageName().IsEmpty()) { 393 printf("[%s] warning: %s\n", repositoryName, 394 issue->ToString().String()); 395 } else { 396 printf("[%s] warning: package %s: %s\n", repositoryName, 397 issue->PackageName().String(), issue->ToString().String()); 398 } 399 } 400 401 printf("[%s] Changes applied. Old activation state backed up in \"%s\"\n", 402 repositoryName, result.OldStateDirectory().String()); 403 printf("[%s] Cleaning up ...\n", repositoryName); 404 } 405 } 406 407 408 void 409 UpdateManager::ProgressApplyingChangesDone(InstalledRepository& repository) 410 { 411 if (fVerbose) 412 printf("[%s] Done.\n", repository.Name().String()); 413 } 414 415 416 void 417 UpdateManager::_PrintResult(InstalledRepository& installationRepository, 418 int32& upgradeCount, int32& installCount, int32& uninstallCount) 419 { 420 if (!installationRepository.HasChanges()) 421 return; 422 423 if (fVerbose) 424 printf(" in %s:\n", installationRepository.Name().String()); 425 426 PackageList& packagesToActivate 427 = installationRepository.PackagesToActivate(); 428 PackageList& packagesToDeactivate 429 = installationRepository.PackagesToDeactivate(); 430 431 BStringList upgradedPackages; 432 BStringList upgradedPackageVersions; 433 for (int32 i = 0; 434 BSolverPackage* installPackage = packagesToActivate.ItemAt(i); 435 i++) { 436 for (int32 j = 0; 437 BSolverPackage* uninstallPackage = packagesToDeactivate.ItemAt(j); 438 j++) { 439 if (installPackage->Info().Name() == uninstallPackage->Info().Name()) { 440 upgradedPackages.Add(installPackage->Info().Name()); 441 upgradedPackageVersions.Add(uninstallPackage->Info().Version().ToString()); 442 break; 443 } 444 } 445 } 446 447 for (int32 i = 0; BSolverPackage* package = packagesToActivate.ItemAt(i); 448 i++) { 449 BString repository; 450 if (dynamic_cast<MiscLocalRepository*>(package->Repository()) != NULL) 451 repository = "local file"; 452 else 453 repository.SetToFormat("repository %s", 454 package->Repository()->Name().String()); 455 456 int position = upgradedPackages.IndexOf(package->Info().Name()); 457 if (position >= 0) { 458 if (fVerbose) 459 printf(" upgrade package %s-%s to %s from %s\n", 460 package->Info().Name().String(), 461 upgradedPackageVersions.StringAt(position).String(), 462 package->Info().Version().ToString().String(), 463 repository.String()); 464 fStatusWindow->AddPackageInfo(PACKAGE_UPDATE, 465 package->Info().Name().String(), 466 upgradedPackageVersions.StringAt(position).String(), 467 package->Info().Version().ToString().String(), 468 package->Info().Summary().String(), 469 package->Repository()->Name().String(), 470 package->Info().FileName().String()); 471 upgradeCount++; 472 } else { 473 if (fVerbose) 474 printf(" install package %s-%s from %s\n", 475 package->Info().Name().String(), 476 package->Info().Version().ToString().String(), 477 repository.String()); 478 fStatusWindow->AddPackageInfo(PACKAGE_INSTALL, 479 package->Info().Name().String(), 480 NULL, 481 package->Info().Version().ToString().String(), 482 package->Info().Summary().String(), 483 package->Repository()->Name().String(), 484 package->Info().FileName().String()); 485 installCount++; 486 } 487 } 488 489 BStringList uninstallList; 490 for (int32 i = 0; BSolverPackage* package = packagesToDeactivate.ItemAt(i); 491 i++) { 492 if (upgradedPackages.HasString(package->Info().Name())) 493 continue; 494 if (fVerbose) 495 printf(" uninstall package %s\n", 496 package->VersionedName().String()); 497 fStatusWindow->AddPackageInfo(PACKAGE_UNINSTALL, 498 package->Info().Name().String(), 499 package->Info().Version().ToString(), 500 NULL, 501 package->Info().Summary().String(), 502 package->Repository()->Name().String(), 503 package->Info().FileName().String()); 504 uninstallCount++; 505 } 506 } 507 508 509 void 510 UpdateManager::_UpdateStatusWindow(const char* header, const char* detail) 511 { 512 if (header == NULL && detail == NULL) 513 return; 514 515 if (fStatusWindow->UserCancelRequested()) 516 throw BAbortedByUserException(); 517 518 BMessage message(kMsgTextUpdate); 519 if (header != NULL) 520 message.AddString(kKeyHeader, header); 521 if (detail != NULL) 522 message.AddString(kKeyDetail, detail); 523 fStatusWindow->PostMessage(&message); 524 } 525 526 527 void 528 UpdateManager::_UpdateDownloadProgress(const char* header, 529 const char* packageName, float percentageComplete) 530 { 531 if (packageName == NULL) 532 return; 533 534 if (fStatusWindow->UserCancelRequested()) 535 throw BAbortedByUserException(); 536 537 BString packageCount; 538 packageCount.SetToFormat(B_TRANSLATE_COMMENT("%i of %i", "Do not translate %i"), 539 (int)fPackageDownloadsCount, (int)fPackageDownloadsTotal); 540 BMessage message(kMsgProgressUpdate); 541 if (header != NULL) 542 message.AddString(kKeyHeader, header); 543 message.AddString(kKeyPackageName, packageName); 544 message.AddString(kKeyPackageCount, packageCount.String()); 545 message.AddFloat(kKeyPercentage, percentageComplete); 546 fStatusWindow->PostMessage(&message); 547 } 548 549 550 void 551 UpdateManager::_FinalUpdate(const char* header, const char* text) 552 { 553 if (!fStatusWindow->IsFront()) { 554 BNotification notification(B_INFORMATION_NOTIFICATION); 555 notification.SetGroup("SoftwareUpdater"); 556 notification.SetTitle(header); 557 notification.SetContent(text); 558 notification.Send(); 559 } 560 561 fStatusWindow->FinalUpdate(header, text); 562 } 563 564 565 void 566 UpdateManager::_SetCurrentStep(int32 step) 567 { 568 fCurrentStep = step; 569 } 570