1/*
2 * lws-api-test-fts - lws full-text search api test
3 *
4 * Written in 2010-2019 by Andy Green <andy@warmcat.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 */
9
10#include <libwebsockets.h>
11#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
12#include <getopt.h>
13#endif
14#include <fcntl.h>
15
16#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
17static struct option options[] = {
18	{ "help",	no_argument,		NULL, 'h' },
19	{ "createindex", no_argument,		NULL, 'c' },
20	{ "index",	required_argument,	NULL, 'i' },
21	{ "debug",	required_argument,	NULL, 'd' },
22	{ "file",	required_argument,	NULL, 'f' },
23	{ "lines",	required_argument,	NULL, 'l' },
24	{ NULL, 0, 0, 0 }
25};
26#endif
27
28static const char *index_filepath = "/tmp/lws-fts-test-index";
29static char filepath[256];
30
31int main(int argc, char **argv)
32{
33	int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
34	int fd, fi, ft, createindex = 0, flags = LWSFTS_F_QUERY_AUTOCOMPLETE;
35	struct lws_fts_search_params params;
36	struct lws_fts_result *result;
37	struct lws_fts_file *jtf;
38	struct lws_fts *t;
39	char buf[16384];
40
41	do {
42#if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
43		n = getopt_long(argc, argv, "hd:i:cfl", options, NULL);
44#else
45       n = getopt(argc, argv, "hd:i:cfl");
46#endif
47		if (n < 0)
48			continue;
49		switch (n) {
50		case 'i':
51			strncpy(filepath, optarg, sizeof(filepath) - 1);
52			filepath[sizeof(filepath) - 1] = '\0';
53			index_filepath = filepath;
54			break;
55		case 'd':
56			logs = atoi(optarg);
57			break;
58		case 'c':
59			createindex = 1;
60			break;
61		case 'f':
62			flags &= ~LWSFTS_F_QUERY_AUTOCOMPLETE;
63			flags |= LWSFTS_F_QUERY_FILES;
64			break;
65		case 'l':
66			flags |= LWSFTS_F_QUERY_FILES |
67				 LWSFTS_F_QUERY_FILE_LINES;
68			break;
69		case 'h':
70			fprintf(stderr,
71				"Usage: %s [--createindex]"
72					"[--index=<index filepath>] "
73					"[-d <log bitfield>] file1 file2 \n",
74					argv[0]);
75			exit(1);
76		}
77	} while (n >= 0);
78
79	lws_set_log_level(logs, NULL);
80	lwsl_user("LWS API selftest: full-text search\n");
81
82	if (createindex) {
83
84		lwsl_notice("Creating index\n");
85
86		/*
87		 * create an index by shifting through argv and indexing each
88		 * file given there into a single combined index
89		 */
90
91		ft = open(index_filepath, O_CREAT | O_WRONLY | O_TRUNC, 0600);
92		if (ft < 0) {
93			lwsl_err("%s: can't open index %s\n", __func__,
94				 index_filepath);
95
96			goto bail;
97		}
98
99		t = lws_fts_create(ft);
100		if (!t) {
101			lwsl_err("%s: Unable to allocate trie\n", __func__);
102
103			goto bail1;
104		}
105
106		while (optind < argc) {
107
108			fi = lws_fts_file_index(t, argv[optind],
109						(int)strlen(argv[optind]), 1);
110			if (fi < 0) {
111				lwsl_err("%s: Failed to get file idx for %s\n",
112					 __func__, argv[optind]);
113
114				goto bail1;
115			}
116
117			fd = open(argv[optind], O_RDONLY);
118			if (fd < 0) {
119				lwsl_err("unable to open %s for read\n",
120						argv[optind]);
121				goto bail;
122			}
123
124			do {
125				int n = (int)read(fd, buf, sizeof(buf));
126
127				if (n <= 0)
128					break;
129
130				if (lws_fts_fill(t, (uint32_t)fi, buf, (size_t)n)) {
131					lwsl_err("%s: lws_fts_fill failed\n",
132						 __func__);
133					close(fd);
134
135					goto bail;
136				}
137
138			} while (1);
139
140			close(fd);
141			optind++;
142		}
143
144		if (lws_fts_serialize(t)) {
145			lwsl_err("%s: serialize failed\n", __func__);
146
147			goto bail;
148		}
149
150		lws_fts_destroy(&t);
151		close(ft);
152
153		return 0;
154	}
155
156	/*
157	 * shift through argv searching for each token
158	 */
159
160	jtf = lws_fts_open(index_filepath);
161	if (!jtf)
162		goto bail;
163
164	while (optind < argc) {
165
166		struct lws_fts_result_autocomplete *ac;
167		struct lws_fts_result_filepath *fp;
168		uint32_t *l, n;
169
170		memset(&params, 0, sizeof(params));
171
172		params.needle = argv[optind];
173		params.flags = flags;
174		params.max_autocomplete = 20;
175		params.max_files = 20;
176
177		result = lws_fts_search(jtf, &params);
178
179		if (!result) {
180			lwsl_err("%s: search failed\n", __func__);
181			lws_fts_close(jtf);
182			goto bail;
183		}
184
185		ac = result->autocomplete_head;
186		fp = result->filepath_head;
187
188		if (!ac)
189			lwsl_notice("%s: no autocomplete results\n", __func__);
190
191		while (ac) {
192			lwsl_notice("%s: AC %s: %d agg hits\n", __func__,
193				((char *)(ac + 1)), ac->instances);
194
195			ac = ac->next;
196		}
197
198		if (!fp)
199			lwsl_notice("%s: no filepath results\n", __func__);
200
201		while (fp) {
202			lwsl_notice("%s: %s: (%d lines) %d hits \n", __func__,
203				(((char *)(fp + 1)) + fp->matches_length),
204				fp->lines_in_file, fp->matches);
205
206			if (fp->matches_length) {
207				l = (uint32_t *)(fp + 1);
208				n = 0;
209				while ((int)n++ < fp->matches)
210					lwsl_notice(" %d\n", *l++);
211			}
212			fp = fp->next;
213		}
214
215		lwsac_free(&params.results_head);
216
217		optind++;
218	}
219
220	lws_fts_close(jtf);
221
222	return 0;
223
224bail1:
225	close(ft);
226bail:
227	lwsl_user("FAILED\n");
228
229	return 1;
230}
231