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