1/*
2 * Copyright 2013-2021, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Ingo Weinhold <ingo_weinhold@gmx.de>
7 * 		Stephan A��mus <superstippi@gmx.de>
8 * 		Rene Gollent <rene@gollent.com>
9 *		Julian Harnath <julian.harnath@rwth-aachen.de>
10 *		Andrew Lindesay <apl@lindesay.co.nz>
11 */
12
13
14#include "PackageManager.h"
15
16#include <Alert.h>
17#include <Catalog.h>
18#include <Entry.h>
19#include <FindDirectory.h>
20#include <Path.h>
21#include <Roster.h>
22
23#include <package/DownloadFileRequest.h>
24#include <package/manager/Exceptions.h>
25#include <package/RefreshRepositoryRequest.h>
26#include <package/hpkg/NoErrorOutput.h>
27#include <package/hpkg/PackageContentHandler.h>
28#include <package/hpkg/PackageEntry.h>
29#include <package/hpkg/PackageEntryAttribute.h>
30#include <package/hpkg/PackageInfoAttributeValue.h>
31#include <package/hpkg/PackageReader.h>
32#include <package/solver/SolverPackage.h>
33#include <package/solver/SolverProblem.h>
34#include <package/solver/SolverProblemSolution.h>
35
36#include "AutoDeleter.h"
37#include "AutoLocker.h"
38#include "Logger.h"
39#include "OpenPackageProcess.h"
40#include "PackageInfo.h"
41#include "ProblemWindow.h"
42#include "ResultWindow.h"
43
44
45#undef B_TRANSLATION_CONTEXT
46#define B_TRANSLATION_CONTEXT "PackageManager"
47
48
49using namespace BPackageKit;
50using namespace BPackageKit::BPrivate;
51using namespace BPackageKit::BManager::BPrivate;
52
53using BPackageKit::BRefreshRepositoryRequest;
54using BPackageKit::DownloadFileRequest;
55using BPackageKit::BSolver;
56using BPackageKit::BSolverPackage;
57using BPackageKit::BSolverRepository;
58using BPackageKit::BHPKG::BNoErrorOutput;
59using BPackageKit::BHPKG::BPackageContentHandler;
60using BPackageKit::BHPKG::BPackageEntry;
61using BPackageKit::BHPKG::BPackageEntryAttribute;
62using BPackageKit::BHPKG::BPackageInfoAttributeValue;
63using BPackageKit::BHPKG::BPackageReader;
64
65
66// #pragma mark - PackageProgressListener
67
68
69PackageProgressListener::~PackageProgressListener()
70{
71}
72
73
74void
75PackageProgressListener::DownloadProgressChanged(const char* packageName,
76	float progress)
77{
78}
79
80
81void
82PackageProgressListener::DownloadProgressComplete(const char* packageName)
83{
84}
85
86
87void
88PackageProgressListener::ConfirmedChanges(
89	BPackageManager::InstalledRepository& repository)
90{
91}
92
93
94void
95PackageProgressListener::StartApplyingChanges(
96	BPackageManager::InstalledRepository& repository)
97{
98}
99
100
101void
102PackageProgressListener::ApplyingChangesDone(
103	BPackageManager::InstalledRepository& repository)
104{
105}
106
107
108// #pragma mark - PackageManager
109
110
111PackageManager::PackageManager(BPackageInstallationLocation location)
112	:
113	BPackageManager(location, &fClientInstallationInterface, this),
114	BPackageManager::UserInteractionHandler(),
115	fDecisionProvider(),
116	fClientInstallationInterface(),
117	fProblemWindow(NULL),
118	fCurrentInstallPackage(NULL),
119	fCurrentUninstallPackage(NULL)
120{
121}
122
123
124PackageManager::~PackageManager()
125{
126	if (fProblemWindow != NULL)
127		fProblemWindow->PostMessage(B_QUIT_REQUESTED);
128}
129
130
131PackageState
132PackageManager::GetPackageState(const PackageInfo& package)
133{
134	// TODO: Fetch information from the PackageKit
135	return NONE;
136}
137
138
139void
140PackageManager::CollectPackageActions(PackageInfoRef package,
141		Collector<PackageActionRef>& actionList)
142{
143	if (package->IsSystemPackage() || package->IsSystemDependency())
144		return;
145
146	switch (package->State()) {
147		case ACTIVATED:
148		case INSTALLED:
149			_CollectPackageActionsForActivatedOrInstalled(package, actionList);
150			break;
151		case NONE:
152		case UNINSTALLED:
153			actionList.Add(_CreateInstallPackageAction(package));
154			break;
155		case DOWNLOADING:
156			HDINFO("no package actions for [%s] (downloading)",
157				package->Name().String());
158			break;
159		case PENDING:
160			HDINFO("no package actions for [%s] (pending)",
161				package->Name().String());
162			break;
163		default:
164			HDFATAL("unexpected status for package [%s]",
165				package->Name().String());
166			break;
167	}
168}
169
170
171void
172PackageManager::_CollectPackageActionsForActivatedOrInstalled(
173		PackageInfoRef package,
174		Collector<PackageActionRef>& actionList)
175{
176	actionList.Add(_CreateUninstallPackageAction(package));
177
178	// Add OpenPackageActions for each deskbar link found in the
179	// package
180	std::vector<DeskbarLink> foundLinks;
181	if (OpenPackageProcess::FindAppToLaunch(package, foundLinks)
182		&& foundLinks.size() < 4) {
183		std::vector<DeskbarLink>::const_iterator it;
184		for (it = foundLinks.begin(); it != foundLinks.end(); it++) {
185			const DeskbarLink& aLink = *it;
186			actionList.Add(_CreateOpenPackageAction(package, aLink));
187		}
188	}
189}
190
191
192PackageActionRef
193PackageManager::_CreateUninstallPackageAction(const PackageInfoRef& package)
194{
195	BString title = B_TRANSLATE("Uninstall %PackageTitle%");
196	title.ReplaceAll("%PackageTitle%", package->Title());
197
198	BMessage message(MSG_PKG_UNINSTALL);
199	message.AddString(KEY_TITLE, title);
200	message.AddString(KEY_PACKAGE_NAME, package->Name());
201
202	return PackageActionRef(new PackageAction(title, message));
203}
204
205
206PackageActionRef
207PackageManager::_CreateInstallPackageAction(const PackageInfoRef& package)
208{
209	BString title = B_TRANSLATE("Install %PackageTitle%");
210	title.ReplaceAll("%PackageTitle%", package->Title());
211
212	BMessage message(MSG_PKG_INSTALL);
213	message.AddString(KEY_TITLE, title);
214	message.AddString(KEY_PACKAGE_NAME, package->Name());
215
216	return PackageActionRef(new PackageAction(title, message));
217}
218
219
220PackageActionRef
221PackageManager::_CreateOpenPackageAction(const PackageInfoRef& package,
222	const DeskbarLink& link)
223{
224	BPath linkPath(link.Link());
225	BString title = B_TRANSLATE("Open %DeskbarLink%");
226	title.ReplaceAll("%DeskbarLink%", linkPath.Leaf());
227
228	BMessage deskbarLinkMessage;
229	if (link.Archive(&deskbarLinkMessage) != B_OK)
230		HDFATAL("unable to archive the deskbar link");
231
232	BMessage message(MSG_PKG_OPEN);
233	message.AddString(KEY_TITLE, title);
234	message.AddMessage(KEY_DESKBAR_LINK, &deskbarLinkMessage);
235	message.AddString(KEY_PACKAGE_NAME, package->Name());
236
237	return PackageActionRef(new PackageAction(title, message));
238}
239
240
241void
242PackageManager::SetCurrentActionPackage(PackageInfoRef package, bool install)
243{
244	BSolverPackage* solverPackage = _GetSolverPackage(package);
245	fCurrentInstallPackage = install ? solverPackage : NULL;
246	fCurrentUninstallPackage = install ? NULL : solverPackage;
247}
248
249
250status_t
251PackageManager::RefreshRepository(const BRepositoryConfig& repoConfig)
252{
253	status_t result;
254	try {
255		result = BPackageManager::RefreshRepository(repoConfig);
256	} catch (BFatalErrorException& ex) {
257		HDERROR("Fatal error occurred while refreshing repository: "
258			"%s (%s)", ex.Message().String(), ex.Details().String());
259		result = ex.Error();
260	} catch (BException& ex) {
261		HDERROR("Exception occurred while refreshing "
262			"repository: %s\n", ex.Message().String());
263		result = B_ERROR;
264	}
265
266	return result;
267}
268
269
270status_t
271PackageManager::DownloadPackage(const BString& fileURL,
272	const BEntry& targetEntry, const BString& checksum)
273{
274	status_t result;
275	try {
276		result = BPackageManager::DownloadPackage(fileURL, targetEntry,
277			checksum);
278	} catch (BFatalErrorException& ex) {
279		HDERROR("Fatal error occurred while downloading package: "
280			"%s: %s (%s)", fileURL.String(), ex.Message().String(),
281			ex.Details().String());
282		result = ex.Error();
283	} catch (BException& ex) {
284		HDERROR("Exception occurred while downloading package "
285			"%s: %s", fileURL.String(), ex.Message().String());
286		result = B_ERROR;
287	}
288
289	return result;
290}
291
292
293void
294PackageManager::AddProgressListener(PackageProgressListener* listener)
295{
296	fPackageProgressListeners.AddItem(listener);
297}
298
299
300void
301PackageManager::RemoveProgressListener(PackageProgressListener* listener)
302{
303	fPackageProgressListeners.RemoveItem(listener);
304}
305
306
307void
308PackageManager::HandleProblems()
309{
310	if (fProblemWindow == NULL)
311		fProblemWindow = new ProblemWindow;
312
313	ProblemWindow::SolverPackageSet installPackages;
314	ProblemWindow::SolverPackageSet uninstallPackages;
315	if (fCurrentInstallPackage != NULL)
316		installPackages.insert(fCurrentInstallPackage);
317
318	if (fCurrentUninstallPackage != NULL)
319		uninstallPackages.insert(fCurrentUninstallPackage);
320
321	if (!fProblemWindow->Go(fSolver,installPackages, uninstallPackages))
322		throw BAbortedByUserException();
323}
324
325
326void
327PackageManager::ConfirmChanges(bool fromMostSpecific)
328{
329	ResultWindow* window = new ResultWindow;
330	ObjectDeleter<ResultWindow> windowDeleter(window);
331
332	bool hasOtherChanges = false;
333	int32 count = fInstalledRepositories.CountItems();
334
335	if (fromMostSpecific) {
336		for (int32 i = count - 1; i >= 0; i--)
337			hasOtherChanges
338				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
339	} else {
340		for (int32 i = 0; i < count; i++)
341			hasOtherChanges
342				|= _AddResults(*fInstalledRepositories.ItemAt(i), window);
343	}
344
345	if (!hasOtherChanges) {
346		_NotifyChangesConfirmed();
347		return;
348	}
349
350	// show the window
351	if (windowDeleter.Detach()->Go() == 0)
352		throw BAbortedByUserException();
353
354	_NotifyChangesConfirmed();
355}
356
357
358void
359PackageManager::Warn(status_t error, const char* format, ...)
360{
361	// TODO: Show alert to user
362
363	va_list args;
364	va_start(args, format);
365	vfprintf(stderr, format, args);
366	va_end(args);
367
368	if (error == B_OK)
369		printf("\n");
370	else
371		printf(": %s\n", strerror(error));
372}
373
374
375void
376PackageManager::ProgressPackageDownloadStarted(const char* packageName)
377{
378	ProgressPackageDownloadActive(packageName, 0.0f, 0, 0);
379}
380
381
382void
383PackageManager::ProgressPackageDownloadActive(const char* packageName,
384	float completionPercentage, off_t bytes, off_t totalBytes)
385{
386	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
387		fPackageProgressListeners.ItemAt(i)->DownloadProgressChanged(
388			packageName, completionPercentage);
389	}
390}
391
392
393void
394PackageManager::ProgressPackageDownloadComplete(const char* packageName)
395{
396	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++) {
397		fPackageProgressListeners.ItemAt(i)->DownloadProgressComplete(
398			packageName);
399	}
400}
401
402
403void
404PackageManager::ProgressPackageChecksumStarted(const char* title)
405{
406	// TODO: implement
407}
408
409
410void
411PackageManager::ProgressPackageChecksumComplete(const char* title)
412{
413	// TODO: implement
414}
415
416
417void
418PackageManager::ProgressStartApplyingChanges(InstalledRepository& repository)
419{
420	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
421		fPackageProgressListeners.ItemAt(i)->StartApplyingChanges(repository);
422}
423
424
425void
426PackageManager::ProgressTransactionCommitted(InstalledRepository& repository,
427	const BCommitTransactionResult& result)
428{
429	// TODO: implement
430}
431
432
433void
434PackageManager::ProgressApplyingChangesDone(InstalledRepository& repository)
435{
436	for (int32 i = 0; i < fPackageProgressListeners.CountItems(); i++)
437		fPackageProgressListeners.ItemAt(i)->ApplyingChangesDone(repository);
438
439	if (BPackageRoster().IsRebootNeeded()) {
440		BString infoString(B_TRANSLATE("A reboot is necessary to complete the "
441			"installation process."));
442		BAlert* alert = new(std::nothrow) BAlert(B_TRANSLATE("Reboot required"),
443			infoString, B_TRANSLATE("Close"), NULL, NULL,
444			B_WIDTH_AS_USUAL, B_INFO_ALERT);
445		if (alert != NULL)
446			alert->Go();
447	}
448}
449
450
451bool
452PackageManager::_AddResults(InstalledRepository& repository,
453	ResultWindow* window)
454{
455	if (!repository.HasChanges())
456		return false;
457
458	ProblemWindow::SolverPackageSet installPackages;
459	ProblemWindow::SolverPackageSet uninstallPackages;
460	if (fCurrentInstallPackage != NULL)
461		installPackages.insert(fCurrentInstallPackage);
462
463	if (fCurrentUninstallPackage != NULL)
464		uninstallPackages.insert(fCurrentUninstallPackage);
465
466	return window->AddLocationChanges(repository.Name(),
467		repository.PackagesToActivate(), installPackages,
468		repository.PackagesToDeactivate(), uninstallPackages);
469}
470
471
472void
473PackageManager::_NotifyChangesConfirmed()
474{
475	int32 count = fInstalledRepositories.CountItems();
476	for (int32 i = 0; i < count; i++) {
477		for (int32 j = 0; j < fPackageProgressListeners.CountItems(); j++) {
478			fPackageProgressListeners.ItemAt(j)->ConfirmedChanges(
479				*fInstalledRepositories.ItemAt(i));
480		}
481	}
482}
483
484
485BSolverPackage*
486PackageManager::_GetSolverPackage(PackageInfoRef package)
487{
488	int32 flags = BSolver::B_FIND_IN_NAME;
489	if (package->State() == ACTIVATED || package->State() == INSTALLED)
490		flags |= BSolver::B_FIND_INSTALLED_ONLY;
491
492	BObjectList<BSolverPackage> packages;
493	status_t result = Solver()->FindPackages(package->Name(), flags, packages);
494	if (result == B_OK) {
495		for (int32 i = 0; i < packages.CountItems(); i++) {
496			BSolverPackage* solverPackage = packages.ItemAt(i);
497			if (solverPackage->Name() != package->Name())
498				continue;
499			else if (package->State() == NONE
500				&& dynamic_cast<BPackageManager::RemoteRepository*>(
501					solverPackage->Repository()) == NULL) {
502				continue;
503			}
504			return solverPackage;
505		}
506	}
507
508	return NULL;
509}
510