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
UpdateManager(BPackageInstallationLocation location,bool verbose)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
~UpdateManager()60 UpdateManager::~UpdateManager()
61 {
62 }
63
64
65 void
CheckNetworkConnection()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
GetUpdateType()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
CheckRepositories()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
JobFailed(BSupportKit::BJob * job)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
JobAborted(BSupportKit::BJob * job)137 UpdateManager::JobAborted(BSupportKit::BJob* job)
138 {
139 if (fVerbose)
140 puts("Job aborted");
141 }
142
143
144 void
FinalUpdate(const char * header,const char * text)145 UpdateManager::FinalUpdate(const char* header, const char* text)
146 {
147 _FinalUpdate(header, text);
148 }
149
150
151 void
HandleProblems()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
ConfirmChanges(bool fromMostSpecific)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
Warn(status_t error,const char * format,...)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
ProgressPackageDownloadStarted(const char * packageName)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
ProgressPackageDownloadActive(const char * packageName,float completionValue,off_t bytes,off_t totalBytes)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
ProgressPackageDownloadComplete(const char * packageName)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
ProgressPackageChecksumStarted(const char * title)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
ProgressPackageChecksumComplete(const char * title)348 UpdateManager::ProgressPackageChecksumComplete(const char* title)
349 {
350 if (fVerbose)
351 puts("done.");
352 }
353
354
355 void
ProgressStartApplyingChanges(InstalledRepository & repository)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
ProgressTransactionCommitted(InstalledRepository & repository,const BCommitTransactionResult & result)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
ProgressApplyingChangesDone(InstalledRepository & repository)409 UpdateManager::ProgressApplyingChangesDone(InstalledRepository& repository)
410 {
411 if (fVerbose)
412 printf("[%s] Done.\n", repository.Name().String());
413 }
414
415
416 void
_PrintResult(InstalledRepository & installationRepository,int32 & upgradeCount,int32 & installCount,int32 & uninstallCount)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
_UpdateStatusWindow(const char * header,const char * detail)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
_UpdateDownloadProgress(const char * header,const char * packageName,float percentageComplete)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
_FinalUpdate(const char * header,const char * text)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
_SetCurrentStep(int32 step)566 UpdateManager::_SetCurrentStep(int32 step)
567 {
568 fCurrentStep = step;
569 }
570