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