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