xref: /haiku/src/apps/haikudepot/server/LocalPkgDataLoadProcess.cpp (revision 82bfaa954dcfd90582fb2c1a0e918971eea57091)
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 
46 using namespace BPackageKit;
47 using namespace BPackageKit::BManager::BPrivate;
48 
49 
50 typedef std::map<BString, PackageInfoRef> PackageInfoMap;
51 
52 
53 /*!
54 	\param packageInfoListener is assigned to each package model object.
55 */
56 
57 LocalPkgDataLoadProcess::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 
69 LocalPkgDataLoadProcess::~LocalPkgDataLoadProcess()
70 {
71 }
72 
73 
74 const char*
75 LocalPkgDataLoadProcess::Name() const
76 {
77 	return "LocalPkgDataLoadProcess";
78 }
79 
80 
81 const char*
82 LocalPkgDataLoadProcess::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 
95 status_t
96 LocalPkgDataLoadProcess::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 
381 void
382 LocalPkgDataLoadProcess::_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