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 "\n" 59 "Status flags in non-detailed listings:\n" 60 " S - installed in system with a matching version in a repository\n" 61 " s - installed in system without a matching version in a repository\n" 62 " H - installed in home with a matching version in a repository\n" 63 " h - installed in home without a matching version in a repository\n" 64 " v - multiple different versions available in repositories\n" 65 "\n"; 66 67 68 DEFINE_COMMAND(SearchCommand, "search", kShortUsage, kLongUsage, 69 COMMAND_CATEGORY_PACKAGES) 70 71 72 static int 73 get_terminal_width() 74 { 75 int fd = fileno(stdout); 76 struct winsize windowSize; 77 if (isatty(fd) == 1 && ioctl(fd, TIOCGWINSZ, &windowSize) == 0) 78 return windowSize.ws_col; 79 80 return INT_MAX; 81 } 82 83 84 struct PackageComparator { 85 PackageComparator(const BSolverRepository* systemRepository, 86 const BSolverRepository* homeRepository) 87 : 88 fSystemRepository(systemRepository), 89 fHomeRepository(homeRepository) 90 { 91 } 92 93 int operator()(const BSolverPackage* a, const BSolverPackage* b) const 94 { 95 int cmp = a->Name().Compare(b->Name()); 96 if (cmp != 0) 97 return cmp; 98 99 // Names are equal. Sort by installation location and then by repository 100 // name. 101 if (a->Repository() == b->Repository()) 102 return 0; 103 104 if (a->Repository() == fSystemRepository) 105 return -1; 106 if (b->Repository() == fSystemRepository) 107 return 1; 108 if (a->Repository() == fHomeRepository) 109 return -1; 110 if (b->Repository() == fHomeRepository) 111 return 1; 112 113 return a->Repository()->Name().Compare(b->Repository()->Name()); 114 } 115 116 private: 117 const BSolverRepository* fSystemRepository; 118 const BSolverRepository* fHomeRepository; 119 }; 120 121 122 static int 123 compare_packages(const BSolverPackage* a, const BSolverPackage* b, 124 void* comparator) 125 { 126 return (*(PackageComparator*)comparator)(a, b); 127 } 128 129 130 int 131 SearchCommand::Execute(int argc, const char* const* argv) 132 { 133 bool installedOnly = false; 134 bool uninstalledOnly = false; 135 bool listAll = false; 136 bool details = false; 137 bool requirements = false; 138 139 while (true) { 140 static struct option sLongOptions[] = { 141 { "all", no_argument, 0, 'a' }, 142 { "debug", required_argument, 0, OPTION_DEBUG }, 143 { "details", no_argument, 0, 'D' }, 144 { "help", no_argument, 0, 'h' }, 145 { "installed-only", no_argument, 0, 'i' }, 146 { "uninstalled-only", no_argument, 0, 'u' }, 147 { "requirements", no_argument, 0, 'r' }, 148 { 0, 0, 0, 0 } 149 }; 150 151 opterr = 0; // don't print errors 152 int c = getopt_long(argc, (char**)argv, "aDhiur", sLongOptions, NULL); 153 if (c == -1) 154 break; 155 156 if (fCommonOptions.HandleOption(c)) 157 continue; 158 159 switch (c) { 160 case 'a': 161 listAll = true; 162 break; 163 164 case 'D': 165 details = true; 166 break; 167 168 case 'h': 169 PrintUsageAndExit(false); 170 break; 171 172 case 'i': 173 installedOnly = true; 174 uninstalledOnly = false; 175 break; 176 177 case 'u': 178 uninstalledOnly = true; 179 installedOnly = false; 180 break; 181 182 case 'r': 183 requirements = true; 184 break; 185 186 default: 187 PrintUsageAndExit(true); 188 break; 189 } 190 } 191 192 // The remaining argument is the search string. Ignored when --all has been 193 // specified. 194 if ((!listAll && argc != optind + 1) || (listAll && requirements)) 195 PrintUsageAndExit(true); 196 197 const char* searchString = listAll ? "" : argv[optind++]; 198 199 // create the solver 200 PackageManager packageManager(B_PACKAGE_INSTALLATION_LOCATION_HOME); 201 packageManager.SetDebugLevel(fCommonOptions.DebugLevel()); 202 packageManager.Init( 203 (!uninstalledOnly ? PackageManager::B_ADD_INSTALLED_REPOSITORIES : 0) 204 | (!installedOnly ? PackageManager::B_ADD_REMOTE_REPOSITORIES : 0)); 205 206 uint32 flags = BSolver::B_FIND_CASE_INSENSITIVE | BSolver::B_FIND_IN_NAME 207 | BSolver::B_FIND_IN_SUMMARY | BSolver::B_FIND_IN_DESCRIPTION 208 | BSolver::B_FIND_IN_PROVIDES; 209 if (requirements) 210 flags = BSolver::B_FIND_IN_REQUIRES; 211 // search 212 BObjectList<BSolverPackage> packages; 213 status_t error = packageManager.Solver()->FindPackages(searchString, 214 flags, packages); 215 if (error != B_OK) 216 DIE(error, "searching packages failed"); 217 218 if (packages.IsEmpty()) { 219 printf("No matching packages found.\n"); 220 return 0; 221 } 222 223 // sort packages by name and installation location/repository 224 const BSolverRepository* systemRepository 225 = static_cast<const BSolverRepository*>( 226 packageManager.SystemRepository()); 227 const BSolverRepository* homeRepository 228 = static_cast<const BSolverRepository*>( 229 packageManager.HomeRepository()); 230 PackageComparator comparator(systemRepository, homeRepository); 231 packages.SortItems(&compare_packages, &comparator); 232 233 // print table 234 TextTable table; 235 236 if (details) { 237 table.AddColumn("Repository"); 238 table.AddColumn("Name"); 239 table.AddColumn("Version"); 240 table.AddColumn("Arch"); 241 242 int32 packageCount = packages.CountItems(); 243 for (int32 i = 0; i < packageCount; i++) { 244 BSolverPackage* package = packages.ItemAt(i); 245 246 BString repository = ""; 247 if (package->Repository() == systemRepository) 248 repository = "<system>"; 249 else if (package->Repository() == homeRepository) 250 repository = "<home>"; 251 else 252 repository = package->Repository()->Name(); 253 254 table.SetTextAt(i, 0, repository); 255 table.SetTextAt(i, 1, package->Name()); 256 table.SetTextAt(i, 2, package->Version().ToString()); 257 table.SetTextAt(i, 3, package->Info().ArchitectureName()); 258 } 259 } else { 260 table.AddColumn("Status"); 261 table.AddColumn("Name"); 262 table.AddColumn("Description", B_ALIGN_LEFT, true); 263 264 int32 packageCount = packages.CountItems(); 265 for (int32 i = 0; i < packageCount;) { 266 // find the next group of equally named packages 267 int32 groupStart = i; 268 std::set<BPackageVersion> versions; 269 BSolverPackage* systemPackage = NULL; 270 BSolverPackage* homePackage = NULL; 271 while (i < packageCount) { 272 BSolverPackage* package = packages.ItemAt(i); 273 if (i > groupStart 274 && package->Name() != packages.ItemAt(groupStart)->Name()) { 275 break; 276 } 277 278 if (package->Repository() == systemRepository) 279 systemPackage = package; 280 else if (package->Repository() == homeRepository) 281 homePackage = package; 282 else 283 versions.insert(package->Version()); 284 285 i++; 286 } 287 288 // add a table row for the group 289 BString status; 290 if (systemPackage != NULL) { 291 status << (versions.find(systemPackage->Version()) 292 != versions.end() ? 'S' : 's'); 293 } 294 if (homePackage != NULL) { 295 status << (versions.find(homePackage->Version()) 296 != versions.end() ? 'H' : 'h'); 297 } 298 if (versions.size() > 1) 299 status << 'v'; 300 301 int32 rowIndex = table.CountRows(); 302 BSolverPackage* package = packages.ItemAt(groupStart); 303 table.SetTextAt(rowIndex, 0, status); 304 table.SetTextAt(rowIndex, 1, package->Name()); 305 table.SetTextAt(rowIndex, 2, package->Info().Summary()); 306 } 307 } 308 309 table.Print(get_terminal_width()); 310 311 return 0; 312 } 313