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