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