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