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