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
get_terminal_width()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 {
PackageComparatorPackageComparator90 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
operator ()PackageComparator100 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
compare_packages(const BSolverPackage * a,const BSolverPackage * b,void * comparator)132 compare_packages(const BSolverPackage* a, const BSolverPackage* b,
133 void* comparator)
134 {
135 return (*(PackageComparator*)comparator)(a, b);
136 }
137
138
139 int
Execute(int argc,const char * const * argv)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