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