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
35using namespace BPackageKit;
36using namespace BPackageKit::BManager::BPrivate;
37
38#undef B_TRANSLATION_CONTEXT
39#define B_TRANSLATION_CONTEXT "UpdateManager"
40
41
42UpdateManager::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
60UpdateManager::~UpdateManager()
61{
62}
63
64
65void
66UpdateManager::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
85update_type
86UpdateManager::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
99void
100UpdateManager::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
122void
123UpdateManager::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
136void
137UpdateManager::JobAborted(BSupportKit::BJob* job)
138{
139	if (fVerbose)
140		puts("Job aborted");
141}
142
143
144void
145UpdateManager::FinalUpdate(const char* header, const char* text)
146{
147	_FinalUpdate(header, text);
148}
149
150
151void
152UpdateManager::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
167void
168UpdateManager::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
203void
204UpdateManager::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
234void
235UpdateManager::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
248void
249UpdateManager::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
313void
314UpdateManager::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
335void
336UpdateManager::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
347void
348UpdateManager::ProgressPackageChecksumComplete(const char* title)
349{
350	if (fVerbose)
351		puts("done.");
352}
353
354
355void
356UpdateManager::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
368void
369UpdateManager::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
408void
409UpdateManager::ProgressApplyingChangesDone(InstalledRepository& repository)
410{
411	if (fVerbose)
412		printf("[%s] Done.\n", repository.Name().String());
413}
414
415
416void
417UpdateManager::_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
509void
510UpdateManager::_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
527void
528UpdateManager::_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(
539		B_TRANSLATE_COMMENT("%i of %i", "Do not translate %i"),
540		fPackageDownloadsCount,
541		fPackageDownloadsTotal);
542	BMessage message(kMsgProgressUpdate);
543	if (header != NULL)
544		message.AddString(kKeyHeader, header);
545	message.AddString(kKeyPackageName, packageName);
546	message.AddString(kKeyPackageCount, packageCount.String());
547	message.AddFloat(kKeyPercentage, percentageComplete);
548	fStatusWindow->PostMessage(&message);
549}
550
551
552void
553UpdateManager::_FinalUpdate(const char* header, const char* text)
554{
555	if (!fStatusWindow->IsFront()) {
556		BNotification notification(B_INFORMATION_NOTIFICATION);
557		notification.SetGroup("SoftwareUpdater");
558		notification.SetTitle(header);
559		notification.SetContent(text);
560		notification.Send();
561	}
562
563	fStatusWindow->FinalUpdate(header, text);
564}
565
566
567void
568UpdateManager::_SetCurrentStep(int32 step)
569{
570	fCurrentStep = step;
571}
572