xref: /haiku/src/apps/haikudepot/server/LocalPkgDataLoadProcess.cpp (revision ca7a630e20b634baf7f70bb0a799becd4117e04f)
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<DepotInfo> depots(repositoryNames.CountStrings());
102  	for (int32 i = 0; i < repositoryNames.CountStrings(); i++) {
103  		const BString& repoName = repositoryNames.StringAt(i);
104  		DepotInfo depotInfo = DepotInfo(repoName);
105 
106  		BRepositoryConfig repoConfig;
107  		status_t getRepositoryConfigStatus = roster.GetRepositoryConfig(
108  			repoName, &repoConfig);
109 
110  		if (getRepositoryConfigStatus == B_OK) {
111  			depotInfo.SetURL(repoConfig.Identifier());
112  			HDDEBUG("[%s] local repository [%s] identifier; [%s]",
113  				Name(), repoName.String(), repoConfig.Identifier().String())
114  		} else {
115  			HDINFO("[%s] unable to obtain the repository config for local "
116  				"repository '%s'; %s", Name(),
117  				repoName.String(), strerror(getRepositoryConfigStatus))
118  		}
119 
120  		depots[i] = depotInfo;
121  	}
122 
123  	PackageManager manager(B_PACKAGE_INSTALLATION_LOCATION_HOME);
124  	try {
125  		manager.Init(PackageManager::B_ADD_INSTALLED_REPOSITORIES
126  			| PackageManager::B_ADD_REMOTE_REPOSITORIES);
127 	} catch (BException& ex) {
128  		BString message(B_TRANSLATE("An error occurred while "
129  			"initializing the package manager: %message%"));
130  		message.ReplaceFirst("%message%", ex.Message());
131  		_NotifyError(message.String());
132  		return B_ERROR;
133  	}
134 
135  	BObjectList<BSolverPackage> packages;
136  	result = manager.Solver()->FindPackages("",
137  		BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME
138  			| BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION
139  			| BSolver::B_FIND_IN_PROVIDES,
140  		packages);
141  	if (result != B_OK) {
142  		BString message(B_TRANSLATE("An error occurred while "
143  			"obtaining the package list: %message%"));
144  		message.ReplaceFirst("%message%", strerror(result));
145  		_NotifyError(message.String());
146  		return B_ERROR;
147  	}
148 
149  	if (packages.IsEmpty())
150  		return B_ERROR;
151 
152  	PackageInfoMap foundPackages;
153  		// if a given package is installed locally, we will potentially
154  		// get back multiple entries, one for each local installation
155  		// location, and one for each remote repository the package
156  		// is available in. The above map is used to ensure that in such
157  		// cases we consolidate the information, rather than displaying
158  		// duplicates
159  	PackageInfoMap remotePackages;
160  		// any package that we find in a remote repository goes in this map.
161  		// this is later used to discern which packages came from a local
162  		// installation only, as those must be handled a bit differently
163  		// upon uninstallation, since we'd no longer be able to pull them
164  		// down remotely.
165  	BStringList systemFlaggedPackages;
166  		// any packages flagged as a system package are added to this list.
167  		// such packages cannot be uninstalled, nor can any of their deps.
168  	PackageInfoMap systemInstalledPackages;
169  		// any packages installed in system are added to this list.
170  		// This is later used for dependency resolution of the actual
171  		// system packages in order to compute the list of protected
172  		// dependencies indicated above.
173 
174  	for (int32 i = 0; i < packages.CountItems(); i++) {
175  		BSolverPackage* package = packages.ItemAt(i);
176  		const BPackageInfo& repoPackageInfo = package->Info();
177  		const BString repositoryName = package->Repository()->Name();
178  		PackageInfoRef modelInfo;
179  		PackageInfoMap::iterator it = foundPackages.find(
180  			repoPackageInfo.Name());
181  		if (it != foundPackages.end())
182  			modelInfo.SetTo(it->second);
183  		else {
184  			// Add new package info
185  			modelInfo.SetTo(new(std::nothrow) PackageInfo(repoPackageInfo),
186  				true);
187 
188  			if (modelInfo.Get() == NULL)
189  				return B_ERROR;
190 
191  			foundPackages[repoPackageInfo.Name()] = modelInfo;
192  		}
193 
194  		// The package list here considers those packages that are installed
195  		// in the system as well as those that exist in remote repositories.
196  		// It is better if the 'depot name' is from the remote repository
197  		// because then it will be possible to perform a rating on it later.
198 
199  		if (modelInfo->DepotName().IsEmpty()
200  			|| modelInfo->DepotName() == REPOSITORY_NAME_SYSTEM
201  			|| modelInfo->DepotName() == REPOSITORY_NAME_INSTALLED) {
202  			modelInfo->SetDepotName(repositoryName);
203  		}
204 
205  		modelInfo->AddListener(fPackageInfoListener);
206 
207  		BSolverRepository* repository = package->Repository();
208  		BPackageManager::RemoteRepository* remoteRepository =
209  			dynamic_cast<BPackageManager::RemoteRepository*>(repository);
210 
211  		if (remoteRepository != NULL) {
212 
213  			std::vector<DepotInfo>::iterator it;
214 
215  			for (it = depots.begin(); it != depots.end(); it++) {
216  				if (RepositoryUrlUtils::EqualsNormalized(
217  					it->URL(), remoteRepository->Config().Identifier())) {
218  					break;
219  				}
220  			}
221 
222  			if (it == depots.end()) {
223  				HDDEBUG("pkg [%s] repository [%s] not recognized --> ignored",
224  					modelInfo->Name().String(), repositoryName.String())
225  			} else {
226  				it->AddPackage(modelInfo);
227  				HDTRACE("pkg [%s] assigned to [%s]",
228  					modelInfo->Name().String(), repositoryName.String());
229  			}
230 
231  			remotePackages[modelInfo->Name()] = modelInfo;
232  		} else {
233  			if (repository == static_cast<const BSolverRepository*>(
234  					manager.SystemRepository())) {
235  				modelInfo->AddInstallationLocation(
236  					B_PACKAGE_INSTALLATION_LOCATION_SYSTEM);
237  				if (!modelInfo->IsSystemPackage()) {
238  					systemInstalledPackages[repoPackageInfo.FileName()]
239  						= modelInfo;
240  				}
241  			} else if (repository == static_cast<const BSolverRepository*>(
242  					manager.HomeRepository())) {
243  				modelInfo->AddInstallationLocation(
244  					B_PACKAGE_INSTALLATION_LOCATION_HOME);
245  			}
246  		}
247 
248  		if (modelInfo->IsSystemPackage())
249  			systemFlaggedPackages.Add(repoPackageInfo.FileName());
250  	}
251 
252  	BAutolock lock(fModel->Lock());
253 
254  	if (fForce)
255  		fModel->Clear();
256 
257  	// filter remote packages from the found list
258  	// any packages remaining will be locally installed packages
259  	// that weren't acquired from a repository
260  	for (PackageInfoMap::iterator it = remotePackages.begin();
261  			it != remotePackages.end(); it++) {
262  		foundPackages.erase(it->first);
263  	}
264 
265  	if (!foundPackages.empty()) {
266  		BString repoName = B_TRANSLATE("Local");
267  		depots.push_back(DepotInfo(repoName));
268 
269  		for (PackageInfoMap::iterator it = foundPackages.begin();
270  				it != foundPackages.end(); ++it) {
271  			depots.back().AddPackage(it->second);
272  		}
273  	}
274 
275  	{
276  		std::vector<DepotInfo>::iterator it;
277 
278  		for (it = depots.begin(); it != depots.end(); it++) {
279  			if (fModel->HasDepot(it->Name()))
280  				fModel->SyncDepot(*it);
281  			else
282  				fModel->AddDepot(*it);
283  		}
284  	}
285 
286  	// compute the OS package dependencies
287  	try {
288  		// create the solver
289  		BSolver* solver;
290  		status_t error = BSolver::Create(solver);
291  		if (error != B_OK)
292  			throw BFatalErrorException(error, "Failed to create solver.");
293 
294  		ObjectDeleter<BSolver> solverDeleter(solver);
295  		BPath systemPath;
296  		error = find_directory(B_SYSTEM_PACKAGES_DIRECTORY, &systemPath);
297  		if (error != B_OK) {
298  			throw BFatalErrorException(error,
299  				"Unable to retrieve system packages directory.");
300  		}
301 
302  		// add the "installed" repository with the given packages
303  		BSolverRepository installedRepository;
304  		{
305  			BRepositoryBuilder installedRepositoryBuilder(installedRepository,
306  				REPOSITORY_NAME_INSTALLED);
307  			for (int32 i = 0; i < systemFlaggedPackages.CountStrings(); i++) {
308  				BPath packagePath(systemPath);
309  				packagePath.Append(systemFlaggedPackages.StringAt(i));
310  				installedRepositoryBuilder.AddPackage(packagePath.Path());
311  			}
312  			installedRepositoryBuilder.AddToSolver(solver, true);
313  		}
314 
315  		// add system repository
316  		BSolverRepository systemRepository;
317  		{
318  			BRepositoryBuilder systemRepositoryBuilder(systemRepository,
319  				REPOSITORY_NAME_SYSTEM);
320  			for (PackageInfoMap::iterator it = systemInstalledPackages.begin();
321  					it != systemInstalledPackages.end(); it++) {
322  				BPath packagePath(systemPath);
323  				packagePath.Append(it->first);
324  				systemRepositoryBuilder.AddPackage(packagePath.Path());
325  			}
326  			systemRepositoryBuilder.AddToSolver(solver, false);
327  		}
328 
329  		// solve
330  		error = solver->VerifyInstallation();
331  		if (error != B_OK) {
332  			throw BFatalErrorException(error, "Failed to compute packages to "
333  				"install.");
334  		}
335 
336  		BSolverResult solverResult;
337  		error = solver->GetResult(solverResult);
338  		if (error != B_OK) {
339  			throw BFatalErrorException(error, "Failed to retrieve system "
340  				"package dependency list.");
341  		}
342 
343  		for (int32 i = 0; const BSolverResultElement* element
344  				= solverResult.ElementAt(i); i++) {
345  			BSolverPackage* package = element->Package();
346  			if (element->Type() == BSolverResultElement::B_TYPE_INSTALL) {
347  				PackageInfoMap::iterator it = systemInstalledPackages.find(
348  					package->Info().FileName());
349  				if (it != systemInstalledPackages.end())
350  					it->second->SetSystemDependency(true);
351  			}
352  		}
353 	} catch (BFatalErrorException& ex) {
354  		HDERROR("Fatal exception occurred while resolving system dependencies: "
355  			"%s, details: %s", strerror(ex.Error()), ex.Details().String())
356 	} catch (BNothingToDoException&) {
357  		// do nothing
358 	} catch (BException& ex) {
359  		HDERROR("Exception occurred while resolving system dependencies: %s",
360  			ex.Message().String())
361  	} catch (...) {
362  		HDERROR("Unknown exception occurred while resolving system "
363  			"dependencies.")
364  	}
365 
366  	HDDEBUG("did refresh the package list")
367 
368  	return B_OK;
369 }
370 
371 
372 void
373 LocalPkgDataLoadProcess::_NotifyError(const BString& messageText) const
374 {
375 	HDERROR("an error has arisen loading data of packages from local : %s",
376 		messageText.String())
377 	AppUtils::NotifySimpleError(
378 		B_TRANSLATE("Local repository load error"),
379 		messageText);
380 }
381