xref: /haiku/src/bin/pkgman/command_search.cpp (revision da4dbfa47a47beb355289f3dd685797cee69ab77)
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
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 {
90 	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 
100 	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
132 compare_packages(const BSolverPackage* a, const BSolverPackage* b,
133 	void* comparator)
134 {
135 	return (*(PackageComparator*)comparator)(a, b);
136 }
137 
138 
139 int
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