search-client: added a simple search client plugin.
[profile/ivi/speech-recognition.git] / src / plugins / search-client / search-plugin.c
1 /*
2  * Copyright (c) 2012, 2013, Intel Corporation
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *   * Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *   * Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  *   * Neither the name of Intel Corporation nor the names of its contributors
14  *     may be used to endorse or promote products derived from this software
15  *     without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <errno.h>
31
32 #include <murphy/common/debug.h>
33
34 #include "src/daemon/plugin.h"
35 #include "src/daemon/client.h"
36
37 #define SEARCH_NAME    "search-client"
38 #define SEARCH_DESCR   "A trivial search plugin for SRS."
39 #define SEARCH_AUTHORS "Krisztian Litkey <kli@iki.fi>"
40 #define SEARCH_VERSION "0.0.1"
41
42 #define DICTIONARY     "general"
43 #define COMMAND        "chromium-browser \"http://google.com/search?q=__url__\""
44
45 typedef struct {
46     srs_plugin_t *self;                  /* us */
47     srs_client_t *c;                     /* our client instance */
48     const char   *dict;                  /* dictionary to use */
49     const char   *cmd;                   /* search command template */
50 } search_t;
51
52
53
54 static int focus_cb(srs_client_t *c, srs_voice_focus_t focus)
55 {
56     srs_context_t *srs = c->srs;
57     search_t      *sch = c->user_data;
58     const char    *state;
59
60     switch (focus) {
61     case SRS_VOICE_FOCUS_NONE:      state = "none";      break;
62     case SRS_VOICE_FOCUS_SHARED:    state = "shared";    break;
63     case SRS_VOICE_FOCUS_EXCLUSIVE: state = "exclusive"; break;
64     default:                        state = "unknown";
65     }
66
67     mrp_debug("search plugin focus is now: %s", state);
68
69     return TRUE;
70 }
71
72
73 static int url_encode(char *buf, size_t size, int ntoken, char **tokens)
74 {
75     unsigned char *p, *s, L, H;
76     int            l, n, i;
77
78     p = (unsigned char *)buf;
79     l = size;
80
81     for (i = 0; i < ntoken; i++) {
82         if (l <= 0) {
83         overflow:
84             errno = EOVERFLOW;
85             return -1;
86         }
87
88         if (i != 0) {
89             *p++ = '+';
90             l--;
91         }
92
93         s = (unsigned char *)tokens[i];
94
95         while (*s && l > 0) {
96             switch (*s) {
97             case '0'...'9':
98             case 'A'...'Z':
99             case 'a'...'z':
100             case '-':
101             case '_':
102             case '.':
103             case '~':
104                 *p++ = *s++;
105                 l--;
106                 break;
107             default:
108                 if (l <= 3)
109                     goto overflow;
110
111                 H = (*s & 0xf0) >> 4;
112                 L =  *s & 0x0f;
113
114                 if (0 <= H && H <= 9) H += '0';
115                 else                  H += 'A' - 10;
116                 if (0 <= L && L <= 9) L += '0';
117                 else                  L += 'A' - 10;
118
119                 p[0] = '%';
120                 p[1] = H;
121                 p[2] = L;
122
123                 p += 3;
124                 l -= 3;
125                 s++;
126             }
127         }
128     }
129
130     if (l <= 0)
131         goto overflow;
132
133     *p = '\0';
134
135     return size - l;
136 }
137
138
139
140 static int command_cb(srs_client_t *c, int idx, int ntoken, char **tokens,
141                       uint32_t *start, uint32_t *end, srs_audiobuf_t *audio)
142 {
143     search_t *sch = (search_t *)c->user_data;
144     char      qry[1024], cmd[8192];
145     int       l;
146
147     MRP_UNUSED(sch);
148
149     MRP_UNUSED(idx);
150     MRP_UNUSED(start);
151     MRP_UNUSED(end);
152     MRP_UNUSED(audio);
153
154     mrp_debug("got search plugin command...");
155
156     tokens += 2;
157     ntoken -= 2;
158
159     if (url_encode(qry, sizeof(qry), ntoken, tokens) > 0) {
160         mrp_log_info("*** search query: '%s'", qry);
161
162         l = snprintf(cmd, sizeof(cmd), sch->cmd, qry);
163
164         if (l < sizeof(cmd)) {
165             mrp_log_info("*** executing '%s'", cmd);
166             system(cmd);
167         }
168     }
169     else
170         mrp_log_error("*** URL encoding failed");
171
172     return TRUE;
173 }
174
175 static int create_search(srs_plugin_t *plugin)
176 {
177     search_t *sch;
178
179     mrp_debug("creating search plugin");
180
181     sch = mrp_allocz(sizeof(*sch));
182
183     if (sch != NULL) {
184         plugin->plugin_data = sch;
185         return TRUE;
186     }
187     else
188         return FALSE;
189 }
190
191
192 static int config_search(srs_plugin_t *plugin, srs_cfg_t *settings)
193 {
194     static char    cmdbuf[1024];
195     search_t      *sch = (search_t *)plugin->plugin_data;
196     srs_context_t *srs = plugin->srs;
197     srs_cfg_t     *cfg = srs->settings;
198     const char    *dict, *cmd;
199     char          *p, *q;
200     int            l, n, nobg;
201
202     mrp_debug("configure search plugin");
203
204     dict = srs_get_string_config(cfg, "search.dictionary", DICTIONARY);
205     cmd  = srs_get_string_config(cfg, "search.command"   , COMMAND);
206
207     q = (char *)cmd + strlen(cmd);
208     while (q > cmd && (*q == ' ' || *q == '\t'))
209         q--;
210     if (*q != '&')
211         nobg = TRUE;
212     else
213         nobg = FALSE;
214
215     if ((q = strstr(cmd, "__url__")) == NULL) {
216         mrp_log_error("Invalid search command '%s', has no __url__ tag.", cmd);
217         return FALSE;
218     }
219
220     p = cmdbuf;
221     l = sizeof(cmdbuf);
222     n = q - cmd;
223
224     n  = snprintf(p, l, "%*.*s", n, n, cmd);
225     p += n;
226     l -= n;
227
228     if (l <= 0) {
229     overflow:
230         mrp_log_error("Invalid search command '%s', too long.", cmd);
231         return FALSE;
232     }
233
234     n  = snprintf(p, l, "%%s");
235     p += n;
236     l -= n;
237
238     if (l <= 0)
239         goto overflow;
240
241     q += strlen("__url__");
242     n  = snprintf(p, l, "%s%s", q, nobg ? "&" : "");
243     p += n;
244     l -= n;
245
246     if (l <= 0)
247         goto overflow;
248
249     sch->cmd  = cmdbuf;
250     sch->dict = dict;
251
252     mrp_log_info("search plugin dictionary: '%s'", sch->dict);
253     mrp_log_info("search plugin command: '%s'", sch->cmd);
254
255     return TRUE;
256 }
257
258
259 static int start_search(srs_plugin_t *plugin)
260 {
261     static srs_client_ops_t ops = {
262         .notify_focus   = focus_cb,
263         .notify_command = command_cb
264     };
265
266     char           cmd1[1024], cmd2[1024];
267     search_t      *sch    = plugin->plugin_data;
268     char          *cmds[2]= { &cmd1[0], &cmd2[0] };
269     int            ncmd   = (int)MRP_ARRAY_SIZE(cmds);
270     srs_context_t *srs    = plugin->srs;
271
272     srs_client_t  *c;
273     const char    *name, *appcls, *id;
274
275     mrp_debug("starting search plugin");
276
277     name   = "search";
278     appcls = "player";
279     id     = "search";
280
281     snprintf(cmd1, sizeof(cmd1), "search for __push_dict__(%s) *", sch->dict);
282     snprintf(cmd2, sizeof(cmd2), "google for __push_dict__(%s) *", sch->dict);
283
284     c = client_create(srs, SRS_CLIENT_TYPE_BUILTIN,
285                       name, appcls, cmds, ncmd, id, &ops, sch);
286
287     if (c == NULL)
288         return FALSE;
289
290     sch->self = plugin;
291     sch->c    = c;
292
293     client_request_focus(c, SRS_VOICE_FOCUS_SHARED);
294
295     return TRUE;
296 }
297
298
299 static void stop_search(srs_plugin_t *plugin)
300 {
301     search_t *sch = (search_t *)plugin->plugin_data;
302
303     mrp_debug("stop search plugin");
304
305     client_destroy(sch->c);
306     sch->c = NULL;
307
308     return;
309 }
310
311
312 static void destroy_search(srs_plugin_t *plugin)
313 {
314     srs_context_t *srs = plugin->srs;
315     search_t      *sch = (search_t *)plugin->plugin_data;
316
317     mrp_debug("destroy search plugin");
318
319     mrp_free(sch);
320 }
321
322
323 SRS_DECLARE_PLUGIN(SEARCH_NAME, SEARCH_DESCR, SEARCH_AUTHORS, SEARCH_VERSION,
324                    create_search, config_search, start_search, stop_search,
325                    destroy_search)