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