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