xref: /haiku/src/bin/pkgman/command_search.cpp (revision 072d3935c2497638e9c2502f574c133caeba9d3d)
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