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 struct stat s = {}; 393 if (entry.GetStat(&s) == B_OK) 394 return s.st_size; 395 else { 396 HDDEBUG("unable to get the size of local file [%s]", path.Path()); 397 } 398 } 399 else { 400 HDDEBUG("unable to get the local file of package [%s]", package->Name().String()); 401 } 402 return 0; 403 } 404 405 406 void 407 LocalPkgDataLoadProcess::_NotifyError(const BString& messageText) const 408 { 409 HDERROR("an error has arisen loading data of packages from local : %s", 410 messageText.String()); 411 AppUtils::NotifySimpleError( 412 B_TRANSLATE("Local repository load error"), 413 messageText); 414 } 415