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