1/*
2 * Copyright 2018-2023, Andrew Lindesay <apl@lindesay.co.nz>.
3 * Copyright 2013-2014, Stephan A��mus <superstippi@gmx.de>.
4 * Copyright 2013, Rene Gollent, rene@gollent.com.
5 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
6
7 * All rights reserved. Distributed under the terms of the MIT License.
8 *
9 * Note that this file included code earlier from `MainWindow.cpp` and
10 * copyrights have been latterly been carried across in 2021.
11 */
12
13
14#include "LocalPkgDataLoadProcess.h"
15
16#include <map>
17#include <vector>
18
19#include <AutoDeleter.h>
20#include <AutoLocker.h>
21#include <Autolock.h>
22#include <Catalog.h>
23#include <Roster.h>
24#include <StringList.h>
25
26#include "AppUtils.h"
27#include "HaikuDepotConstants.h"
28#include "Logger.h"
29#include "PackageInfo.h"
30#include "PackageManager.h"
31
32#include <package/Context.h>
33#include <package/manager/Exceptions.h>
34#include <package/manager/RepositoryBuilder.h>
35#include <package/PackageRoster.h>
36#include "package/RepositoryCache.h"
37#include <package/RefreshRepositoryRequest.h>
38#include <package/solver/SolverPackage.h>
39#include <package/solver/SolverResult.h>
40
41
42#undef B_TRANSLATION_CONTEXT
43#define B_TRANSLATION_CONTEXT "LocalPkgDataLoadProcess"
44
45
46using namespace BPackageKit;
47using namespace BPackageKit::BManager::BPrivate;
48
49
50typedef std::map<BString, PackageInfoRef> PackageInfoMap;
51
52
53/*!
54	\param packageInfoListener is assigned to each package model object.
55*/
56
57LocalPkgDataLoadProcess::LocalPkgDataLoadProcess(
58	PackageInfoListenerRef packageInfoListener,
59	Model *model, bool force)
60	:
61	AbstractProcess(),
62	fModel(model),
63	fForce(force),
64	fPackageInfoListener(packageInfoListener)
65{
66}
67
68
69LocalPkgDataLoadProcess::~LocalPkgDataLoadProcess()
70{
71}
72
73
74const char*
75LocalPkgDataLoadProcess::Name() const
76{
77	return "LocalPkgDataLoadProcess";
78}
79
80
81const char*
82LocalPkgDataLoadProcess::Description() const
83{
84	return B_TRANSLATE("Reading repository data");
85}
86
87
88/*! The contents of this method implementation have been 'lifted and shifted'
89    from MainWindow.cpp in order that the logic fits into the background
90    loading processes.  The code needs to be broken up into methods with some
91    sort of a state object carrying the state of the process.  As part of this,
92    better error handling and error reporting would also be advantageous.
93*/
94
95status_t
96LocalPkgDataLoadProcess::RunInternal()
97{
98	HDDEBUG("[%s] will refresh the package list", Name());
99	BPackageRoster roster;
100	BStringList repositoryNames;
101
102	status_t result = roster.GetRepositoryNames(repositoryNames);
103
104	if (result != B_OK)
105		return result;
106
107	std::vector<DepotInfoRef> depots(repositoryNames.CountStrings());
108	for (int32 i = 0; i < repositoryNames.CountStrings(); i++) {
109		const BString& repoName = repositoryNames.StringAt(i);
110		DepotInfoRef depotInfoRef = DepotInfoRef(
111			new(std::nothrow) DepotInfo(repoName), true);
112
113		if (!depotInfoRef.IsSet())
114			HDFATAL("unable to create new depot info - memory exhaustion");
115
116		BRepositoryConfig repoConfig;
117		status_t getRepositoryConfigStatus = roster.GetRepositoryConfig(
118			repoName, &repoConfig);
119
120		if (getRepositoryConfigStatus == B_OK) {
121			depotInfoRef->SetIdentifier(repoConfig.Identifier());
122			HDDEBUG("[%s] local repository [%s] identifier; [%s]",
123				Name(), repoName.String(), repoConfig.Identifier().String());
124		} else {
125			HDINFO("[%s] unable to obtain the repository config for local "
126				"repository '%s'; %s", Name(),
127				repoName.String(), strerror(getRepositoryConfigStatus));
128		}
129
130		depots[i] = depotInfoRef;
131	}
132
133	PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME);
134	try {
135		manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES
136			| PackageManager::B_ADD_REMOTE_REPOSITORIES);
137	} catch (BException& ex) {
138		BString message(B_TRANSLATE("An error occurred while "
139			"initializing the package manager: %message%"));
140		message.ReplaceFirst("%message%", ex.Message());
141		_NotifyError(message.String());
142		return B_ERROR;
143	}
144
145	BObjectList<BSolverPackage> packages;
146	result = manager.Solver()->FindPackages("",
147		BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME
148			| BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION
149			| BSolver::B_FIND_IN_PROVIDES,
150		packages);
151	if (result != B_OK) {
152		BString message(B_TRANSLATE("An error occurred while "
153			"obtaining the package list: %message%"));
154		message.ReplaceFirst("%message%", strerror(result));
155		_NotifyError(message.String());
156		return B_ERROR;
157	}
158
159	if (packages.IsEmpty())
160		return B_ERROR;
161
162	PackageInfoMap foundPackages;
163		// if a given package is installed locally, we will potentially
164		// get back multiple entries, one for each local installation
165		// location, and one for each remote repository the package
166		// is available in. The above map is used to ensure that in such
167		// cases we consolidate the information, rather than displaying
168		// duplicates
169	PackageInfoMap remotePackages;
170		// any package that we find in a remote repository goes in this map.
171		// this is later used to discern which packages came from a local
172		// installation only, as those must be handled a bit differently
173		// upon uninstallation, since we'd no longer be able to pull them
174		// down remotely.
175	BStringList systemFlaggedPackages;
176		// any packages flagged as a system package are added to this list.
177		// such packages cannot be uninstalled, nor can any of their deps.
178	PackageInfoMap systemInstalledPackages;
179		// any packages installed in system are added to this list.
180		// This is later used for dependency resolution of the actual
181		// system packages in order to compute the list of protected
182		// dependencies indicated above.
183
184	for (int32 i = 0; i < packages.CountItems(); i++) {
185		BSolverPackage* package = packages.ItemAt(i);
186		const BPackageInfo& repoPackageInfo = package->Info();
187		const BString repositoryName = package->Repository()->Name();
188		PackageInfoRef modelInfo;
189		PackageInfoMap::iterator it = foundPackages.find(
190			repoPackageInfo.Name());
191		if (it != foundPackages.end())
192			modelInfo.SetTo(it->second);
193		else {
194			// Add new package info
195			modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo),
196				true);
197
198			if (!modelInfo.IsSet())
199				return B_ERROR;
200
201			foundPackages[repoPackageInfo.Name()] = modelInfo;
202		}
203
204		// The package list here considers those packages that are installed
205		// in the system as well as those that exist in remote repositories.
206		// It is better if the 'depot name' is from the remote repository
207		// because then it will be possible to perform a rating on it later.
208
209		if (modelInfo->DepotName().IsEmpty()
210			|| modelInfo->DepotName() == REPOSITORY_NAME_SYSTEM
211			|| modelInfo->DepotName() == REPOSITORY_NAME_INSTALLED) {
212			modelInfo->SetDepotName(repositoryName);
213		}
214
215		modelInfo->AddListener(fPackageInfoListener);
216
217		BSolverRepository* repository = package->Repository();
218		BPackageManager::RemoteRepository* remoteRepository =
219			dynamic_cast<BPackageManager::RemoteRepository*>(repository);
220
221		if (remoteRepository != NULL) {
222
223			std::vector<DepotInfoRef>::iterator it;
224
225			for (it = depots.begin(); it != depots.end(); it++) {
226				if ((*it)->Identifier() == remoteRepository->Config().Identifier()) {
227					break;
228				}
229			}
230
231			if (it == depots.end()) {
232				HDDEBUG("pkg [%s] repository [%s] not recognized --> ignored",
233					modelInfo->Name().String(), repositoryName.String());
234			} else {
235				(*it)->AddPackage(modelInfo);
236				HDTRACE("pkg [%s] assigned to [%s]",
237					modelInfo->Name().String(), repositoryName.String());
238			}
239
240			remotePackages[modelInfo->Name()] = modelInfo;
241		} else {
242			if (repository == static_cast<const BSolverRepository*>(
243					manager.SystemRepository())) {
244				modelInfo->AddInstallationLocation(
245					B_PACKAGE_INSTALLATION_LOCATION_SYSTEM);
246				if (!modelInfo->IsSystemPackage()) {
247					systemInstalledPackages[repoPackageInfo.FileName()]
248						= modelInfo;
249				}
250			} else if (repository == static_cast<const BSolverRepository*>(
251					manager.HomeRepository())) {
252				modelInfo->AddInstallationLocation(
253					B_PACKAGE_INSTALLATION_LOCATION_HOME);
254			}
255		}
256
257		if (modelInfo->IsSystemPackage())
258			systemFlaggedPackages.Add(repoPackageInfo.FileName());
259	}
260
261	BAutolock lock(fModel->Lock());
262
263	if (fForce)
264		fModel->Clear();
265
266	// filter remote packages from the found list
267	// any packages remaining will be locally installed packages
268	// that weren't acquired from a repository
269	for (PackageInfoMap::iterator it = remotePackages.begin();
270			it != remotePackages.end(); it++) {
271		foundPackages.erase(it->first);
272	}
273
274	if (!foundPackages.empty()) {
275		BString repoName = B_TRANSLATE("Local");
276		DepotInfoRef depotInfoRef(new(std::nothrow) DepotInfo(repoName), true);
277
278		if (!depotInfoRef.IsSet())
279			HDFATAL("unable to create a new depot info - memory exhaustion");
280
281		depots.push_back(depotInfoRef);
282
283		for (PackageInfoMap::iterator it = foundPackages.begin();
284				it != foundPackages.end(); ++it) {
285			depotInfoRef->AddPackage(it->second);
286		}
287	}
288
289	{
290		std::vector<DepotInfoRef>::iterator it;
291		for (it = depots.begin(); it != depots.end(); it++)
292			fModel->MergeOrAddDepot(*it);
293	}
294
295	// compute the OS package dependencies
296	try {
297		// create the solver
298		BSolver* solver;
299		status_t error = BSolver::Create(solver);
300		if (error != B_OK)
301			throw BFatalErrorException(error, "Failed to create solver.");
302
303		ObjectDeleter<BSolver> solverDeleter(solver);
304		BPath systemPath;
305		error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath);
306		if (error != B_OK) {
307			throw BFatalErrorException(error,
308				"Unable to retrieve system packages directory.");
309		}
310
311		// add the "installed" repository with the given packages
312		BSolverRepository installedRepository;
313		{
314			BRepositoryBuilder installedRepositoryBuilder(installedRepository,
315				REPOSITORY_NAME_INSTALLED);
316			for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) {
317				BPath packagePath(systemPath);
318				packagePath.Append(systemFlaggedPackages.StringAt(i));
319				installedRepositoryBuilder.AddPackage(packagePath.Path());
320			}
321			installedRepositoryBuilder.AddToSolver(solver, true);
322		}
323
324		// add system repository
325		BSolverRepository systemRepository;
326		{
327			BRepositoryBuilder systemRepositoryBuilder(systemRepository,
328				REPOSITORY_NAME_SYSTEM);
329			for (PackageInfoMap::iterator it = systemInstalledPackages.begin();
330					it != systemInstalledPackages.end(); it++) {
331				BPath packagePath(systemPath);
332				packagePath.Append(it->first);
333				systemRepositoryBuilder.AddPackage(packagePath.Path());
334			}
335			systemRepositoryBuilder.AddToSolver(solver, false);
336		}
337
338		// solve
339		error = solver->VerifyInstallation();
340		if (error != B_OK) {
341			throw BFatalErrorException(error, "Failed to compute packages to "
342				"install.");
343		}
344
345		BSolverResult solverResult;
346		error = solver->GetResult(solverResult);
347		if (error != B_OK) {
348			throw BFatalErrorException(error, "Failed to retrieve system "
349				"package dependency list.");
350		}
351
352		for (int32 i = 0; const BSolverResultElement* element
353				= solverResult.ElementAt(i); i++) {
354			BSolverPackage* package = element->Package();
355			if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) {
356				PackageInfoMap::iterator it = systemInstalledPackages.find(
357					package->Info().FileName());
358				if (it != systemInstalledPackages.end())
359					it->second->SetSystemDependency(true);
360			}
361		}
362	} catch (BFatalErrorException& ex) {
363		HDERROR("Fatal exception occurred while resolving system dependencies: "
364			"%s, details: %s", strerror(ex.Error()), ex.Details().String());
365	} catch (BNothingToDoException&) {
366		// do nothing
367	} catch (BException& ex) {
368		HDERROR("Exception occurred while resolving system dependencies: %s",
369			ex.Message().String());
370	} catch (...) {
371		HDERROR("Unknown exception occurred while resolving system "
372			"dependencies.");
373	}
374
375	HDDEBUG("did refresh the package list");
376
377	return B_OK;
378}
379
380
381void
382LocalPkgDataLoadProcess::_NotifyError(const BString& messageText) const
383{
384	HDERROR("an error has arisen loading data of packages from local : %s",
385		messageText.String());
386	AppUtils::NotifySimpleError(
387		B_TRANSLATE("Local repository load error"),
388		messageText);
389}
390