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