1 /* 2 * Copyright 2013, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Ingo Weinhold <ingo_weinhold@gmx.de> 7 */ 8 9 10 #include <errno.h> 11 #include <getopt.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <sys/ioctl.h> 15 #include <unistd.h> 16 17 #include <algorithm> 18 #include <set> 19 20 #include <Locale.h> 21 22 #include <package/solver/SolverPackage.h> 23 #include <TextTable.h> 24 25 #include "Command.h" 26 #include "PackageManager.h" 27 #include "pkgman.h" 28 29 30 // TODO: internationalization! 31 // The table code doesn't support full-width characters yet. 32 33 34 using namespace BPackageKit; 35 36 37 static const char* const kShortUsage = 38 " %command% ( <search-string> | --all | -a )\n" 39 " Searches for packages matching <search-string>.\n"; 40 41 static const char* const kLongUsage = 42 "Usage: %program% %command% ( <search-string> | --all | -a )\n" 43 "Searches for packages matching <search-string>.\n" 44 "\n" 45 "Options:\n" 46 " -a, --all\n" 47 " List all packages. Specified instead of <search-string>.\n" 48 " --debug <level>\n" 49 " Print debug output. <level> should be between 0 (no debug output,\n" 50 " the default) and 10 (most debug output).\n" 51 " -D, --details\n" 52 " Print more details. Matches in each installation location and each\n" 53 " repository will be listed individually with their version.\n" 54 " -i, --installed-only\n" 55 " Only find installed packages.\n" 56 " -u, --uninstalled-only\n" 57 " Only find not installed packages.\n" 58 " -r, --requirements\n" 59 " Search packages with <search-string> as requirements.\n" 60 " -s <scope>, --search-scope=<scope>\n" 61 " Search for packages containing <search-string> only on the given scope.\n" 62 " <scope> must be either \"name\" or \"full\"." 63 "\n" 64 "Status flags in non-detailed listings:\n" 65 " S - installed in system with a matching version in a repository\n" 66 " s - installed in system without a matching version in a repository\n" 67 " H - installed in home with a matching version in a repository\n" 68 " h - installed in home without a matching version in a repository\n" 69 " v - multiple different versions available in repositories\n" 70 "\n"; 71 72 73 DEFINE_COMMAND(SearchCommand, "search", kShortUsage, kLongUsage, 74 COMMAND_CATEGORY_PACKAGES) 75 76 77 static int 78 get_terminal_width() 79 { 80 int fd = fileno(stdout); 81 struct winsize windowSize; 82 if (isatty(fd) == 1 && ioctl(fd, TIOCGWINSZ, &windowSize) == 0) 83 return windowSize.ws_col; 84 85 return INT_MAX; 86 } 87 88 89 struct PackageComparator { 90 PackageComparator(const BSolverRepository* systemRepository, 91 const BSolverRepository* homeRepository) 92 : 93 fSystemRepository(systemRepository), 94 fHomeRepository(homeRepository) 95 { 96 BLocale::Default()->GetCollator(&fCollator); 97 fCollator.SetNumericSorting(true); 98 } 99 100 int operator()(const BSolverPackage* a, const BSolverPackage* b) const 101 { 102 int cmp = fCollator.Compare(a->Name().String(), b->Name().String()); 103 if (cmp != 0) 104 return cmp; 105 106 // Names are equal. Sort by installation location and then by repository 107 // name. 108 if (a->Repository() == b->Repository()) 109 return 0; 110 111 if (a->Repository() == fSystemRepository) 112 return -1; 113 if (b->Repository() == fSystemRepository) 114 return 1; 115 if (a->Repository() == fHomeRepository) 116 return -1; 117 if (b->Repository() == fHomeRepository) 118 return 1; 119 120 return a->Repository()->Name().Compare(b->Repository()->Name()); 121 } 122 123 private: 124 const BSolverRepository* fSystemRepository; 125 const BSolverRepository* fHomeRepository; 126 127 BCollator fCollator; 128 }; 129 130 131 static int 132 compare_packages(const BSolverPackage* a, const BSolverPackage* b, 133 void* comparator) 134 { 135 return (*(PackageComparator*)comparator)(a, b); 136 } 137 138 139 int 140 SearchCommand::Execute(int argc, const char* const* argv) 141 { 142 bool installedOnly = false; 143 bool uninstalledOnly = false; 144 bool nameOnly = false; 145 bool fullSearch = false; 146 bool listAll = false; 147 bool details = false; 148 bool requirements = false; 149 150 while (true) { 151 static struct option sLongOptions[] = { 152 { "all", no_argument, 0, 'a' }, 153 { "debug", required_argument, 0, OPTION_DEBUG }, 154 { "details", no_argument, 0, 'D' }, 155 { "help", no_argument, 0, 'h' }, 156 { "installed-only", no_argument, 0, 'i' }, 157 { "uninstalled-only", no_argument, 0, 'u' }, 158 { "requirements", no_argument, 0, 'r' }, 159 { "search-scope", required_argument, NULL, 's' }, 160 { 0, 0, 0, 0 } 161 }; 162 163 opterr = 0; // don't print errors 164 int c = getopt_long(argc, (char**)argv, "aDhiurs:", sLongOptions, NULL); 165 if (c == -1) 166 break; 167 168 if (fCommonOptions.HandleOption(c)) 169 continue; 170 171 switch (c) { 172 case 'a': 173 listAll = true; 174 break; 175 176 case 'D': 177 details = true; 178 break; 179 180 case 'h': 181 PrintUsageAndExit(false); 182 break; 183 184 case 'i': 185 installedOnly = true; 186 uninstalledOnly = false; 187 break; 188 189 case 'u': 190 uninstalledOnly = true; 191 installedOnly = false; 192 break; 193 194 case 'r': 195 requirements = true; 196 break; 197 198 case 's': 199 if (strcmp(optarg, "name") == 0) 200 nameOnly = true; 201 else if (strcmp(optarg, "full") == 0) 202 fullSearch = true; 203 else 204 fprintf(stderr, "Warning: Invalid search scope (%s). Using default.\n", 205 optarg); 206 break; 207 208 default: 209 PrintUsageAndExit(true); 210 break; 211 } 212 } 213 214 // The remaining argument is the search string. Ignored when --all has been 215 // specified. 216 if ((!listAll && argc != optind + 1) || (listAll && requirements)) 217 PrintUsageAndExit(true); 218 219 const char* searchString = listAll ? "" : argv[optind++]; 220 221 // create the solver 222 PackageManager packageManager(B_PACKAGE_INSTALLATION_LOCATION_HOME); 223 packageManager.SetDebugLevel(fCommonOptions.DebugLevel()); 224 packageManager.Init( 225 (!uninstalledOnly ? PackageManager::B_ADD_INSTALLED_REPOSITORIES : 0) 226 | (!installedOnly ? PackageManager::B_ADD_REMOTE_REPOSITORIES : 0)); 227 228 uint32 flags = BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME 229 | BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_PROVIDES; 230 231 if (nameOnly) 232 flags = BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME; 233 234 if (fullSearch) 235 flags = BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME 236 | BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION 237 | BSolver::B_FIND_IN_PROVIDES; 238 239 if (requirements) 240 flags = BSolver::B_FIND_IN_REQUIRES; 241 242 // search 243 BObjectList<BSolverPackage> packages; 244 status_t error = packageManager.Solver()->FindPackages(searchString, 245 flags, packages); 246 if (error != B_OK) 247 DIE(error, "searching packages failed"); 248 249 if (packages.IsEmpty()) { 250 printf("No matching packages found.\n"); 251 return 0; 252 } 253 254 // sort packages by name and installation location/repository 255 const BSolverRepository* systemRepository 256 = static_cast<const BSolverRepository*>( 257 packageManager.SystemRepository()); 258 const BSolverRepository* homeRepository 259 = static_cast<const BSolverRepository*>( 260 packageManager.HomeRepository()); 261 PackageComparator comparator(systemRepository, homeRepository); 262 packages.SortItems(&compare_packages, &comparator); 263 264 // print table 265 TextTable table; 266 267 if (details) { 268 table.AddColumn("Repository"); 269 table.AddColumn("Name"); 270 table.AddColumn("Version"); 271 table.AddColumn("Arch"); 272 273 int32 packageCount = packages.CountItems(); 274 for (int32 i = 0; i < packageCount; i++) { 275 BSolverPackage* package = packages.ItemAt(i); 276 277 BString repository = ""; 278 if (package->Repository() == systemRepository) 279 repository = "<system>"; 280 else if (package->Repository() == homeRepository) 281 repository = "<home>"; 282 else 283 repository = package->Repository()->Name(); 284 285 table.SetTextAt(i, 0, repository); 286 table.SetTextAt(i, 1, package->Name()); 287 table.SetTextAt(i, 2, package->Version().ToString()); 288 table.SetTextAt(i, 3, package->Info().ArchitectureName()); 289 } 290 } else { 291 table.AddColumn("Status"); 292 table.AddColumn("Name"); 293 table.AddColumn("Description", B_ALIGN_LEFT, true); 294 295 int32 packageCount = packages.CountItems(); 296 for (int32 i = 0; i < packageCount;) { 297 // find the next group of equally named packages 298 int32 groupStart = i; 299 std::set<BPackageVersion> versions; 300 BSolverPackage* systemPackage = NULL; 301 BSolverPackage* homePackage = NULL; 302 while (i < packageCount) { 303 BSolverPackage* package = packages.ItemAt(i); 304 if (i > groupStart 305 && package->Name() != packages.ItemAt(groupStart)->Name()) { 306 break; 307 } 308 309 if (package->Repository() == systemRepository) 310 systemPackage = package; 311 else if (package->Repository() == homeRepository) 312 homePackage = package; 313 else 314 versions.insert(package->Version()); 315 316 i++; 317 } 318 319 // add a table row for the group 320 BString status; 321 if (systemPackage != NULL) { 322 status << (versions.find(systemPackage->Version()) 323 != versions.end() ? 'S' : 's'); 324 } 325 if (homePackage != NULL) { 326 status << (versions.find(homePackage->Version()) 327 != versions.end() ? 'H' : 'h'); 328 } 329 if (versions.size() > 1) 330 status << 'v'; 331 332 int32 rowIndex = table.CountRows(); 333 BSolverPackage* package = packages.ItemAt(groupStart); 334 table.SetTextAt(rowIndex, 0, status); 335 table.SetTextAt(rowIndex, 1, package->Name()); 336 table.SetTextAt(rowIndex, 2, package->Info().Summary()); 337 } 338 } 339 340 table.Print(get_terminal_width()); 341 342 return 0; 343 } 344