1 /* 2 * Copyright 2013-2024, 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 */ 10 11 12 #include <StringForSize.h> 13 #include <StringForRate.h> 14 // Must be first, or the BPrivate namespaces are confused 15 16 #include "PackageManager.h" 17 18 #include <InterfaceDefs.h> 19 20 #include <sys/ioctl.h> 21 #include <unistd.h> 22 23 #include <package/CommitTransactionResult.h> 24 #include <package/DownloadFileRequest.h> 25 #include <package/RefreshRepositoryRequest.h> 26 #include <package/solver/SolverPackage.h> 27 #include <package/solver/SolverProblem.h> 28 #include <package/solver/SolverProblemSolution.h> 29 30 #include "pkgman.h" 31 32 33 using namespace BPackageKit::BPrivate; 34 35 36 PackageManager::PackageManager(BPackageInstallationLocation location, 37 bool interactive) 38 : 39 BPackageManager(location, &fClientInstallationInterface, this), 40 BPackageManager::UserInteractionHandler(), 41 fDecisionProvider(interactive), 42 fClientInstallationInterface(), 43 fInteractive(interactive) 44 { 45 } 46 47 48 PackageManager::~PackageManager() 49 { 50 } 51 52 53 void 54 PackageManager::SetInteractive(bool interactive) 55 { 56 fInteractive = interactive; 57 fDecisionProvider.SetInteractive(interactive); 58 } 59 60 61 void 62 PackageManager::JobFailed(BSupportKit::BJob* job) 63 { 64 BString error = job->ErrorString(); 65 if (error.Length() > 0) { 66 error.ReplaceAll("\n", "\n*** "); 67 fprintf(stderr, "%s", error.String()); 68 } 69 } 70 71 72 void 73 PackageManager::HandleProblems() 74 { 75 printf("Encountered problems:\n"); 76 77 int32 problemCount = fSolver->CountProblems(); 78 for (int32 i = 0; i < problemCount; i++) { 79 // print problem and possible solutions 80 BSolverProblem* problem = fSolver->ProblemAt(i); 81 printf("problem %" B_PRId32 ": %s\n", i + 1, 82 problem->ToString().String()); 83 84 int32 solutionCount = problem->CountSolutions(); 85 for (int32 k = 0; k < solutionCount; k++) { 86 const BSolverProblemSolution* solution = problem->SolutionAt(k); 87 printf(" solution %" B_PRId32 ":\n", k + 1); 88 int32 elementCount = solution->CountElements(); 89 for (int32 l = 0; l < elementCount; l++) { 90 const BSolverProblemSolutionElement* element 91 = solution->ElementAt(l); 92 printf(" - %s\n", element->ToString().String()); 93 } 94 } 95 96 if (!fInteractive) 97 continue; 98 99 // let the user choose a solution 100 printf("Please select a solution, skip the problem for now or quit.\n"); 101 for (;;) { 102 if (solutionCount > 1) 103 printf("select [1...%" B_PRId32 "/s/q]: ", solutionCount); 104 else 105 printf("select [1/s/q]: "); 106 107 char buffer[32]; 108 if (fgets(buffer, sizeof(buffer), stdin) == NULL 109 || strcmp(buffer, "q\n") == 0) { 110 exit(1); 111 } 112 113 if (strcmp(buffer, "s\n") == 0) 114 break; 115 116 char* end; 117 long selected = strtol(buffer, &end, 0); 118 if (end == buffer || *end != '\n' || selected < 1 119 || selected > solutionCount) { 120 printf("*** invalid input\n"); 121 continue; 122 } 123 124 status_t error = fSolver->SelectProblemSolution(problem, 125 problem->SolutionAt(selected - 1)); 126 if (error != B_OK) 127 DIE(error, "failed to set solution"); 128 break; 129 } 130 } 131 132 if (problemCount > 0 && !fInteractive) 133 exit(1); 134 } 135 136 137 void 138 PackageManager::ConfirmChanges(bool fromMostSpecific) 139 { 140 printf("The following changes will be made:\n"); 141 142 int32 count = fInstalledRepositories.CountItems(); 143 if (fromMostSpecific) { 144 for (int32 i = count - 1; i >= 0; i--) 145 _PrintResult(*fInstalledRepositories.ItemAt(i)); 146 } else { 147 for (int32 i = 0; i < count; i++) 148 _PrintResult(*fInstalledRepositories.ItemAt(i)); 149 } 150 151 if (!fDecisionProvider.YesNoDecisionNeeded(BString(), "Continue?", "yes", 152 "no", "yes")) { 153 exit(1); 154 } 155 } 156 157 158 void 159 PackageManager::Warn(status_t error, const char* format, ...) 160 { 161 va_list args; 162 va_start(args, format); 163 vfprintf(stderr, format, args); 164 va_end(args); 165 166 if (error == B_OK) 167 printf("\n"); 168 else 169 printf(": %s\n", strerror(error)); 170 } 171 172 173 void 174 PackageManager::ProgressPackageDownloadStarted(const char* packageName) 175 { 176 fShowProgress = isatty(STDOUT_FILENO); 177 fLastBytes = 0; 178 fLastRateCalcTime = system_time(); 179 fDownloadRate = 0; 180 181 if (fShowProgress) { 182 char percentString[32]; 183 fNumberFormat.FormatPercent(percentString, sizeof(percentString), 0.0); 184 // Make sure there is enough space for '100 %' percent format 185 printf("%6s", percentString); 186 } 187 } 188 189 190 void 191 PackageManager::ProgressPackageDownloadActive(const char* packageName, 192 float completionPercentage, off_t bytes, off_t totalBytes) 193 { 194 if (bytes == totalBytes) 195 fLastBytes = totalBytes; 196 if (!fShowProgress) 197 return; 198 199 // Do not update if nothing changed in the last 500ms 200 if (bytes <= fLastBytes || (system_time() - fLastRateCalcTime) < 500000) 201 return; 202 203 const bigtime_t time = system_time(); 204 if (time != fLastRateCalcTime) { 205 fDownloadRate = (bytes - fLastBytes) * 1000000 206 / (time - fLastRateCalcTime); 207 } 208 fLastRateCalcTime = time; 209 fLastBytes = bytes; 210 211 // Build the current file progress percentage and size string 212 BString leftStr; 213 BString rightStr; 214 215 int width = 70; 216 struct winsize winSize; 217 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winSize) == 0) 218 width = std::min(winSize.ws_col - 2, 78); 219 220 if (width < 30) { 221 // Not much space for anything, just draw a percentage 222 fNumberFormat.FormatPercent(leftStr, completionPercentage); 223 } else { 224 BString dataString; 225 fNumberFormat.FormatPercent(dataString, completionPercentage); 226 // Make sure there is enough space for '100 %' percent format 227 leftStr.SetToFormat("%6s %s", dataString.String(), packageName); 228 229 char byteBuffer[32]; 230 char totalBuffer[32]; 231 char rateBuffer[32]; 232 rightStr.SetToFormat("%s/%s %s ", 233 string_for_size(bytes, byteBuffer, sizeof(byteBuffer)), 234 string_for_size(totalBytes, totalBuffer, sizeof(totalBuffer)), 235 fDownloadRate == 0 ? "--.-" : 236 string_for_rate(fDownloadRate, rateBuffer, sizeof(rateBuffer))); 237 238 if (leftStr.CountChars() + rightStr.CountChars() >= width) 239 { 240 // The full string does not fit! Try to make a shorter one. 241 leftStr.ReplaceLast(".hpkg", ""); 242 leftStr.TruncateChars(width - rightStr.CountChars() - 2); 243 leftStr.Append(B_UTF8_ELLIPSIS " "); 244 } 245 246 int extraSpace = width - leftStr.CountChars() - rightStr.CountChars(); 247 248 leftStr.Append(' ', extraSpace); 249 leftStr.Append(rightStr); 250 } 251 252 const int progChars = leftStr.CountBytes(0, 253 (int)(width * completionPercentage)); 254 255 // Set bg to green, fg to white, and print progress bar. 256 // Then reset colors and print rest of text 257 // And finally remove any stray chars at the end of the line 258 printf("\r\x1B[42;37m%.*s\x1B[0m%s\x1B[K", progChars, leftStr.String(), 259 leftStr.String() + progChars); 260 261 // Force terminal to update when the line is complete, to avoid flickering 262 // because of updates at random times 263 fflush(stdout); 264 } 265 266 267 void 268 PackageManager::ProgressPackageDownloadComplete(const char* packageName) 269 { 270 if (fShowProgress) { 271 // Erase the line, return to the start, and reset colors 272 printf("\r\33[2K\r\x1B[0m"); 273 } 274 275 char byteBuffer[32]; 276 char percentString[32]; 277 fNumberFormat.FormatPercent(percentString, sizeof(percentString), 1.0); 278 // Make sure there is enough space for '100 %' percent format 279 printf("%6s %s [%s]\n", percentString, packageName, 280 string_for_size(fLastBytes, byteBuffer, sizeof(byteBuffer))); 281 fflush(stdout); 282 } 283 284 285 void 286 PackageManager::ProgressPackageChecksumStarted(const char* title) 287 { 288 printf("%s...", title); 289 } 290 291 292 void 293 PackageManager::ProgressPackageChecksumComplete(const char* title) 294 { 295 printf("done.\n"); 296 } 297 298 299 void 300 PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository) 301 { 302 printf("[%s] Applying changes ...\n", repository.Name().String()); 303 } 304 305 306 void 307 PackageManager::ProgressTransactionCommitted(InstalledRepository& repository, 308 const BCommitTransactionResult& result) 309 { 310 const char* repositoryName = repository.Name().String(); 311 312 int32 issueCount = result.CountIssues(); 313 for (int32 i = 0; i < issueCount; i++) { 314 const BTransactionIssue* issue = result.IssueAt(i); 315 if (issue->PackageName().IsEmpty()) { 316 printf("[%s] warning: %s\n", repositoryName, 317 issue->ToString().String()); 318 } else { 319 printf("[%s] warning: package %s: %s\n", repositoryName, 320 issue->PackageName().String(), issue->ToString().String()); 321 } 322 } 323 324 printf("[%s] Changes applied. Old activation state backed up in \"%s\"\n", 325 repositoryName, result.OldStateDirectory().String()); 326 printf("[%s] Cleaning up ...\n", repositoryName); 327 } 328 329 330 void 331 PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository) 332 { 333 printf("[%s] Done.\n", repository.Name().String()); 334 335 if (BPackageRoster().IsRebootNeeded()) 336 printf("A reboot is necessary to complete the installation process.\n"); 337 } 338 339 340 void 341 PackageManager::_PrintResult(InstalledRepository& installationRepository) 342 { 343 if (!installationRepository.HasChanges()) 344 return; 345 346 printf(" in %s:\n", installationRepository.Name().String()); 347 348 PackageList& packagesToActivate 349 = installationRepository.PackagesToActivate(); 350 PackageList& packagesToDeactivate 351 = installationRepository.PackagesToDeactivate(); 352 353 BStringList upgradedPackages; 354 BStringList upgradedPackageVersions; 355 for (int32 i = 0; 356 BSolverPackage* installPackage = packagesToActivate.ItemAt(i); 357 i++) { 358 for (int32 j = 0; 359 BSolverPackage* uninstallPackage = packagesToDeactivate.ItemAt(j); 360 j++) { 361 if (installPackage->Info().Name() == uninstallPackage->Info().Name()) { 362 upgradedPackages.Add(installPackage->Info().Name()); 363 upgradedPackageVersions.Add(uninstallPackage->Info().Version().ToString()); 364 break; 365 } 366 } 367 } 368 369 for (int32 i = 0; BSolverPackage* package = packagesToActivate.ItemAt(i); 370 i++) { 371 BString repository; 372 if (dynamic_cast<MiscLocalRepository*>(package->Repository()) != NULL) 373 repository = "local file"; 374 else 375 repository.SetToFormat("repository %s", package->Repository()->Name().String()); 376 377 int position = upgradedPackages.IndexOf(package->Info().Name()); 378 if (position >= 0) { 379 printf(" upgrade package %s-%s to %s from %s\n", 380 package->Info().Name().String(), 381 upgradedPackageVersions.StringAt(position).String(), 382 package->Info().Version().ToString().String(), 383 repository.String()); 384 } else { 385 printf(" install package %s-%s from %s\n", 386 package->Info().Name().String(), 387 package->Info().Version().ToString().String(), 388 repository.String()); 389 } 390 } 391 392 for (int32 i = 0; BSolverPackage* package = packagesToDeactivate.ItemAt(i); 393 i++) { 394 if (upgradedPackages.HasString(package->Info().Name())) 395 continue; 396 printf(" uninstall package %s\n", package->VersionedName().String()); 397 } 398 // TODO: Print file/download sizes. Unfortunately our package infos don't 399 // contain the file size. Which is probably correct. The file size (and possibly 400 // other information) should, however, be provided by the repository cache in 401 // some way. Extend BPackageInfo? Create a BPackageFileInfo? 402 } 403