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 * Note that this file has been re-factored from `PackageManager.cpp` and
13 * authors have been carried across in 2021.
14 */
15
16
17#include "InstallPackageProcess.h"
18
19#include <algorithm>
20
21#include <AutoLocker.h>
22#include <Catalog.h>
23#include <Locker.h>
24
25#include <package/manager/Exceptions.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/PackageReader.h>
31#include <package/solver/SolverPackage.h>
32
33#include "AppUtils.h"
34#include "Logger.h"
35#include "PackageManager.h"
36
37
38#undef B_TRANSLATION_CONTEXT
39#define B_TRANSLATION_CONTEXT "InstallPackageProcess"
40
41using namespace BPackageKit;
42using namespace BPackageKit::BPrivate;
43using namespace BPackageKit::BManager::BPrivate;
44
45using BPackageKit::BSolver;
46using BPackageKit::BSolverPackage;
47using BPackageKit::BSolverRepository;
48using BPackageKit::BHPKG::BNoErrorOutput;
49using BPackageKit::BHPKG::BPackageContentHandler;
50using BPackageKit::BHPKG::BPackageEntry;
51using BPackageKit::BHPKG::BPackageEntryAttribute;
52using BPackageKit::BHPKG::BPackageInfoAttributeValue;
53using BPackageKit::BHPKG::BPackageReader;
54
55
56class DownloadProgress {
57public:
58	DownloadProgress(BString packageName, float progress)
59		:
60		fPackageName(packageName),
61		fProgress(progress)
62	{
63	}
64
65	virtual ~DownloadProgress()
66	{
67	}
68
69	BString PackageName() const
70	{
71		return fPackageName;
72	}
73
74	float Progress() const
75	{
76		return fProgress;
77	}
78
79private:
80	BString	fPackageName;
81	float	fProgress;
82};
83
84
85InstallPackageProcess::InstallPackageProcess(
86	PackageInfoRef package, Model* model)
87	:
88	AbstractPackageProcess(package, model),
89	fLastDownloadUpdate(0)
90{
91	fDescription = B_TRANSLATE("Installing \"%PackageName%\"");
92	fDescription.ReplaceAll("%PackageName%", package->Name());
93}
94
95
96InstallPackageProcess::~InstallPackageProcess()
97{
98}
99
100
101const char*
102InstallPackageProcess::Name() const
103{
104	return "InstallPackageProcess";
105}
106
107
108const char*
109InstallPackageProcess::Description() const
110{
111	return fDescription;
112}
113
114
115float
116InstallPackageProcess::Progress()
117{
118	if (ProcessState() == PROCESS_RUNNING && !fDownloadProgresses.empty()) {
119		AutoLocker<BLocker> locker(&fLock);
120		float sum = 0.0;
121		std::vector<DownloadProgress>::const_iterator it;
122		for (it = fDownloadProgresses.begin();
123				it != fDownloadProgresses.end(); it++) {
124			DownloadProgress downloadProgress = *it;
125			sum += downloadProgress.Progress();
126		}
127		if (sum > 0.0)
128			return sum / (float) fDownloadProgresses.size();
129	}
130	return kProgressIndeterminate;
131}
132
133
134status_t
135InstallPackageProcess::RunInternal()
136{
137	fPackageManager->Init(BPackageManager::B_ADD_INSTALLED_REPOSITORIES
138		| BPackageManager::B_ADD_REMOTE_REPOSITORIES
139		| BPackageManager::B_REFRESH_REPOSITORIES);
140	PackageInfoRef ref(fPackage);
141	PackageState state = ref->State();
142	ref->SetState(PENDING);
143
144	fPackageManager->SetCurrentActionPackage(ref, true);
145	fPackageManager->AddProgressListener(this);
146
147	BString packageName;
148	if (ref->IsLocalFile())
149		packageName = ref->LocalFilePath();
150	else
151		packageName = ref->Name();
152
153	const char* packageNameString = packageName.String();
154	try {
155		fPackageManager->Install(&packageNameString, 1);
156	} catch (BFatalErrorException& ex) {
157		BString errorString;
158		errorString.SetToFormat(
159			"Fatal error occurred while installing package %s: "
160			"%s (%s)\n", packageNameString, ex.Message().String(),
161			ex.Details().String());
162		AppUtils::NotifySimpleError(B_TRANSLATE("Fatal error"), errorString,
163			B_STOP_ALERT);
164		_SetDownloadedPackagesState(NONE);
165		ref->SetState(state);
166		return ex.Error();
167	} catch (BAbortedByUserException& ex) {
168		HDINFO("Installation of package %s is aborted by user: %s",
169			packageNameString, ex.Message().String());
170		_SetDownloadedPackagesState(NONE);
171		ref->SetState(state);
172		return B_OK;
173	} catch (BNothingToDoException& ex) {
174		HDINFO("Nothing to do while installing package %s: %s",
175			packageNameString, ex.Message().String());
176		return B_OK;
177	} catch (BException& ex) {
178		HDERROR("Exception occurred while installing package %s: %s",
179			packageNameString, ex.Message().String());
180		_SetDownloadedPackagesState(NONE);
181		ref->SetState(state);
182		return B_ERROR;
183	}
184
185	fPackageManager->RemoveProgressListener(this);
186
187	_SetDownloadedPackagesState(ACTIVATED);
188
189	return B_OK;
190}
191
192
193// #pragma mark - DownloadProgressListener
194
195
196void
197InstallPackageProcess::DownloadProgressChanged(
198	const char* packageName, float progress)
199{
200	bigtime_t now = system_time();
201	if (now - fLastDownloadUpdate < 250000 && progress != 1.0)
202		return;
203	fLastDownloadUpdate = now;
204	BString simplePackageName;
205	if (_DeriveSimplePackageName(packageName, simplePackageName) != B_OK) {
206		HDERROR("malformed canonical package name [%s]", packageName);
207		return;
208	}
209	PackageInfoRef ref(FindPackageByName(simplePackageName));
210	if (ref.IsSet()) {
211		ref->SetDownloadProgress(progress);
212		_SetDownloadProgress(simplePackageName, progress);
213	} else {
214		HDERROR("unable to find the package info for simple package name [%s]",
215			simplePackageName.String());
216	}
217}
218
219
220void
221InstallPackageProcess::DownloadProgressComplete(const char* packageName)
222{
223	BString simplePackageName;
224	if (_DeriveSimplePackageName(packageName, simplePackageName) != B_OK) {
225		HDERROR("malformed canonical package name [%s]", packageName);
226		return;
227	}
228	_SetDownloadProgress(simplePackageName, 1.0);
229	PackageInfoRef ref(FindPackageByName(simplePackageName));
230	if (!ref.IsSet()) {
231		HDERROR("unable to find the package info for simple package name [%s]",
232			simplePackageName.String());
233		return;
234	}
235	ref->SetDownloadProgress(1.0);
236	fDownloadedPackages.insert(ref);
237}
238
239
240void
241InstallPackageProcess::ConfirmedChanges(
242	BPackageManager::InstalledRepository& repository)
243{
244	BPackageManager::InstalledRepository::PackageList& activationList =
245		repository.PackagesToActivate();
246
247	BSolverPackage* package = NULL;
248	for (int32 i = 0; (package = activationList.ItemAt(i)); i++) {
249		PackageInfoRef ref(FindPackageByName(package->Info().Name()));
250		if (ref.IsSet())
251			ref->SetState(PENDING);
252	}
253}
254
255
256void
257InstallPackageProcess::_SetDownloadedPackagesState(PackageState state)
258{
259	for (PackageInfoSet::iterator it = fDownloadedPackages.begin();
260			it != fDownloadedPackages.end(); ++it) {
261		(*it)->SetState(state);
262	}
263}
264
265
266static bool
267_IsDownloadProgressBefore(const DownloadProgress& dp1,
268	const DownloadProgress& dp2)
269{
270	return dp1.PackageName().Compare(dp2.PackageName()) < 0;
271}
272
273
274/*!	This method will extract the plain package name from the canonical
275 */
276
277/*static*/ status_t
278InstallPackageProcess::_DeriveSimplePackageName(const BString& canonicalForm,
279	BString& simplePackageName)
280{
281	int32 hypenIndex = canonicalForm.FindFirst('-');
282	if (hypenIndex <= 0)
283		return B_BAD_DATA;
284	simplePackageName.SetTo(canonicalForm);
285	simplePackageName.Truncate(hypenIndex);
286	return B_OK;
287}
288
289
290void
291InstallPackageProcess::_SetDownloadProgress(const BString& simplePackageName,
292	float progress)
293{
294	AutoLocker<BLocker> locker(&fLock);
295	DownloadProgress downloadProgress(simplePackageName, progress);
296	std::vector<DownloadProgress>::iterator itInsertionPt
297		= std::lower_bound(
298			fDownloadProgresses.begin(), fDownloadProgresses.end(),
299			downloadProgress, &_IsDownloadProgressBefore);
300
301	if (itInsertionPt != fDownloadProgresses.end()) {
302		if ((*itInsertionPt).PackageName() == simplePackageName) {
303			itInsertionPt = fDownloadProgresses.erase(itInsertionPt);
304		}
305	}
306
307	fDownloadProgresses.insert(itInsertionPt, downloadProgress);
308	_NotifyChanged();
309}
310