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