xref: /haiku/src/apps/softwareupdater/UpdateManager.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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