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