]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/spotlight_module.c
Function tracker_to_unix_path() is static
[netatalk.git] / etc / afpd / spotlight_module.c
1 /*
2   Copyright (c) 2012 Frank Lahm <franklahm@gmail.com>
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 2 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 */
14
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif /* HAVE_CONFIG_H */
18
19 #include <string.h>
20 #include <locale.h>
21
22 #include <gio/gio.h>
23 #include <tracker-sparql.h>
24 #include <libtracker-miner/tracker-miner.h>
25
26 #include <atalk/util.h>
27 #include <atalk/errchk.h>
28 #include <atalk/logger.h>
29 #include <atalk/unix.h>
30
31 #include "spotlight.h"
32
33 #define MAX_SL_RESULTS 20
34
35 static TrackerSparqlConnection *connection;
36 static TrackerMinerManager *manager;
37
38 static char *tracker_to_unix_path(const char *uri)
39 {
40     EC_INIT;
41     GFile *f = NULL;
42     char *path = NULL;
43
44     EC_NULL_LOG( f = g_file_new_for_uri(uri) );
45     EC_NULL_LOG( path = g_file_get_path(f) );
46
47 EC_CLEANUP:
48     if (f)
49         g_object_unref(f);
50     if (ret != 0)
51         return NULL;
52     return path;
53 }
54
55 static int sl_mod_init(void *p)
56 {
57     EC_INIT;
58     GError *error = NULL;
59     const char *msg = p;
60
61     LOG(log_info, logtype_sl, "sl_mod_init: %s", msg);
62
63     g_type_init();
64     setenv("DBUS_SESSION_BUS_ADDRESS", "unix:path=/tmp/spotlight.ipc", 1);
65     setenv("TRACKER_SPARQL_BACKEND", "bus", 1);
66
67 #ifdef DEBUG
68     setenv("TRACKER_VERBOSITY", "3", 1);
69     dup2(type_configs[logtype_sl].fd, 1);
70     dup2(type_configs[logtype_sl].fd, 2);
71 #endif
72
73     become_root();
74     connection = tracker_sparql_connection_get(NULL, &error);
75     manager = tracker_miner_manager_new_full(FALSE, &error);
76     unbecome_root();
77
78     if (!connection) {
79         LOG(log_error, logtype_sl, "Couldn't obtain a direct connection to the Tracker store: %s",
80             error ? error->message : "unknown error");
81         g_clear_error(&error);
82         EC_FAIL;
83     }
84
85     if (!manager) {
86         LOG(log_error, logtype_sl, "Couldn't connect to Tracker miner");
87         g_clear_error(&error);
88         EC_FAIL;
89     }
90
91 EC_CLEANUP:
92     EC_EXIT;
93 }
94
95 /*!
96  * Return talloced query from query string
97  * *=="query*"
98  */
99 static const gchar *map_spotlight_to_sparql_query(slq_t *slq)
100 {
101     EC_INIT;
102     const gchar *sparql_query;
103     const char *sparql_query_format;
104     const char *slquery = slq->slq_qstring;
105     char *word, *p;
106
107     LOG(log_debug, logtype_sl, "query_word_from_sl_query: \"%s\"", slquery);
108
109     if ((word = strstr(slquery, "*==")) || (word = strstr(slquery, "kMDItemTextContent=="))) {
110         /* Full text search */
111         sparql_query_format = "SELECT nie:url(?uri) WHERE {?uri nie:url ?url . FILTER(fn:starts-with(?url, 'file://%s')) . ?uri fts:match '%s'}";
112         EC_NULL_LOG( word = strchr(word, '"') );
113         word++;
114         EC_NULL( word = dalloc_strdup(slq, word) );
115         EC_NULL_LOG( p = strchr(word, '"') );
116         *p = 0;
117         sparql_query = talloc_asprintf(slq, sparql_query_format, slq->slq_vol->v_path, word);
118         LOG(log_debug, logtype_sl, "query_word_from_sl_query: \"%s\"", sparql_query);
119     } else if ((word = strstr(slquery, "kMDItemDisplayName=="))) {
120         /* Filename search */
121         sparql_query_format = "SELECT ?url WHERE { ?x nie:url ?url ; nfo:fileName ?name FILTER(fn:starts-with(?url, 'file://%s/') && regex(?name, '%s')) }";
122         EC_NULL_LOG( word = strchr(word, '"') );
123         word++;
124         EC_NULL( word = dalloc_strdup(slq, word) );
125         EC_NULL_LOG( p = strchr(word, '"') );
126         if (*(p-1) == '*')
127             *(p-1) = 0;
128         else
129             *p = 0;
130         sparql_query = talloc_asprintf(slq, sparql_query_format, slq->slq_vol->v_path, word);
131         LOG(log_debug, logtype_sl, "query_word_from_sl_query: \"%s\"", sparql_query);
132     } else {
133         EC_FAIL_LOG("Can't parse SL query: '%s'", slquery);
134     }
135
136 EC_CLEANUP:
137     if (ret != 0)
138         sparql_query = NULL;
139     return sparql_query;
140 }
141
142 static void tracker_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
143 {
144     slq_t *slq = user_data;
145     TrackerSparqlCursor *cursor;
146     GError *error = NULL;
147
148     LOG(log_debug, logtype_sl, "tracker_cb");
149
150     cursor = tracker_sparql_connection_query_finish(connection, res, &error);
151
152     if (error) {
153         LOG(log_error, logtype_sl, "sl_mod_fetch_result: Couldn't query the Tracker Store: '%s'",
154             error ? error->message : "unknown error");
155         g_clear_error(&error);
156         return;
157     }
158
159     slq->slq_tracker_cursor = cursor;
160 }
161
162 static int sl_mod_start_search(void *p)
163 {
164     EC_INIT;
165     slq_t *slq = p; 
166     const gchar *sparql_query;
167     GError *error = NULL;
168
169     LOG(log_debug, logtype_sl, "sl_mod_start_search: Spotlight query string: \"%s\"", slq->slq_qstring);
170
171     EC_NULL_LOG( sparql_query = map_spotlight_to_sparql_query(slq) );
172     LOG(log_debug, logtype_sl, "sl_mod_start_search: SPARQL query: \"%s\"", sparql_query);
173
174 #if 0
175     /* Start the async query */
176     tracker_sparql_connection_query_async(connection, sparql_query, NULL, tracker_cb, slq);
177 #endif
178
179     become_root();
180     slq->slq_tracker_cursor = tracker_sparql_connection_query(connection, sparql_query, NULL, &error);
181     unbecome_root();
182
183     if (error) {
184         LOG(log_error, logtype_sl, "Couldn't query the Tracker Store: '%s'",
185             error ? error->message : "unknown error");
186         g_clear_error(&error);
187         EC_FAIL;
188     }
189     slq->slq_state = SLQ_STATE_RUNNING;
190
191 EC_CLEANUP:
192     EC_EXIT;
193 }
194
195 static int add_filemeta(sl_array_t *reqinfo, sl_array_t *fm_array, cnid_t id, const char *path)
196 {
197     EC_INIT;
198     sl_array_t *meta;
199     sl_nil_t nil = 0;
200     int i, metacount;
201
202     if ((metacount = talloc_array_length(reqinfo->dd_talloc_array)) == 0) {
203         dalloc_add_copy(fm_array, &nil, sl_nil_t);
204         goto EC_CLEANUP;
205     }
206
207     LOG(log_debug, logtype_sl, "add_filemeta: metadata count: %d", metacount);
208
209     meta = talloc_zero(fm_array, sl_array_t);
210
211     for (i = 0; i < metacount; i++) {
212         if (STRCMP(reqinfo->dd_talloc_array[i], ==, "kMDItemDisplayName")) {
213             char *p, *name;
214             if ((p = strrchr(path, '/'))) {
215                 name = dalloc_strdup(meta, p + 1);
216                 dalloc_add(meta, name, "char *");
217             }
218         } else {
219             dalloc_add_copy(meta, &nil, sl_nil_t);
220         }
221     }
222
223     dalloc_add(fm_array, meta, sl_array_t);
224
225 EC_CLEANUP:
226     EC_EXIT;
227 }
228
229 static int sl_mod_fetch_result(void *p)
230 {
231     EC_INIT;
232     slq_t *slq = p;
233     GError *error = NULL;
234     int i = 0;
235     cnid_t did, id;
236     const gchar *uri;
237     char *path;
238     sl_cnids_t *cnids;
239     sl_filemeta_t *fm;
240     sl_array_t *fm_array;
241     sl_nil_t nil;
242     uint64_t uint64;
243     gboolean qres, firstmatch = true;
244
245     if (!slq->slq_tracker_cursor) {
246         LOG(log_debug, logtype_sl, "sl_mod_fetch_result: no results found");
247         goto EC_CLEANUP;
248     }
249
250     /* Prepare CNIDs */
251     cnids = talloc_zero(slq->slq_reply, sl_cnids_t);
252     cnids->ca_cnids = talloc_zero(cnids, DALLOC_CTX);
253     cnids->ca_unkn1 = 0xadd;
254     cnids->ca_context = slq->slq_ctx2;
255
256     /* Prepare FileMeta */
257     fm = talloc_zero(slq->slq_reply, sl_filemeta_t);
258     fm_array = talloc_zero(fm, sl_array_t);
259     dalloc_add(fm, fm_array, sl_array_t);
260
261     LOG(log_debug, logtype_sl, "sl_mod_fetch_result: now interating Tracker results cursor");
262
263     while ((slq->slq_state == SLQ_STATE_RUNNING) && (i <= MAX_SL_RESULTS)) {
264         become_root();
265         qres = tracker_sparql_cursor_next(slq->slq_tracker_cursor, NULL, &error);
266         unbecome_root();
267
268         if (!qres)
269             break;
270
271         if (firstmatch) {
272             /* For some reason the list of results always starts with a nil entry */
273             dalloc_add_copy(fm_array, &nil, sl_nil_t);
274             firstmatch = false;
275         }
276
277         become_root();
278         uri = tracker_sparql_cursor_get_string(slq->slq_tracker_cursor, 0, NULL);
279         unbecome_root();
280
281         EC_NULL_LOG( path = tracker_to_unix_path(uri) );
282
283         if ((id = cnid_for_path(slq->slq_vol->v_cdb, slq->slq_vol->v_path, path, &did)) == CNID_INVALID) {
284             LOG(log_error, logtype_sl, "sl_mod_fetch_result: cnid_for_path error");
285             goto loop_cleanup;
286         }
287         LOG(log_debug, logtype_sl, "Result %d: CNID: %" PRIu32 ", path: \"%s\"", i, ntohl(id), path);
288
289         uint64 = ntohl(id);
290         dalloc_add_copy(cnids->ca_cnids, &uint64, uint64_t);
291         add_filemeta(slq->slq_reqinfo, fm_array, id, path);
292
293     loop_cleanup:
294         g_free(path);
295         i++;
296    }
297
298     if (error) {
299         LOG(log_error, logtype_sl, "Couldn't query the Tracker Store: '%s'",
300             error ? error->message : "unknown error");
301         g_clear_error (&error);
302         EC_FAIL;
303     }
304
305     if (i < MAX_SL_RESULTS)
306         slq->slq_state = SLQ_STATE_DONE;
307
308     uint64 = (i > 0) ? 35 : 0; /* OS X AFP server returns 35 here if results are found */
309     dalloc_add_copy(slq->slq_reply, &uint64, uint64_t);
310     dalloc_add(slq->slq_reply, cnids, sl_cnids_t);
311     dalloc_add(slq->slq_reply, fm, sl_filemeta_t);
312
313 EC_CLEANUP:
314     if (ret != 0) {
315         if (slq->slq_tracker_cursor) {
316             g_object_unref(slq->slq_tracker_cursor);
317             slq->slq_tracker_cursor = NULL;
318         }
319     }
320     EC_EXIT;
321 }
322
323 /* Free ressources allocated in this module */
324 static int sl_mod_close_query(void *p)
325 {
326     EC_INIT;
327     slq_t *slq = p;
328
329     if (slq->slq_tracker_cursor) {
330         g_object_unref(slq->slq_tracker_cursor);
331         slq->slq_tracker_cursor = NULL;
332     }
333
334 EC_CLEANUP:
335     EC_EXIT;
336 }
337
338 static int sl_mod_error(void *p)
339 {
340     EC_INIT;
341     slq_t *slq = p;
342
343     if (!slq)
344         goto EC_CLEANUP;
345
346     if (slq->slq_tracker_cursor) {
347         g_object_unref(slq->slq_tracker_cursor);
348         slq->slq_tracker_cursor = NULL;
349     }
350
351 EC_CLEANUP:
352     EC_EXIT;
353 }
354
355 static int sl_mod_index_file(const void *p)
356 {
357 #ifdef HAVE_TRACKER_MINER
358     EC_INIT;
359     const char *f = p;
360
361     if (!f)
362         goto EC_CLEANUP;
363
364     GError *error = NULL;
365     GFile *file = NULL;
366
367     file = g_file_new_for_commandline_arg(f);
368
369     become_root();
370     tracker_miner_manager_index_file(manager, file, &error);
371     unbecome_root();
372
373     if (error)
374         LOG(log_error, logtype_sl, "sl_mod_index_file(\"%s\"): indexing failed", f);
375     else
376         LOG(log_debug, logtype_sl, "sl_mod_index_file(\"%s\"): indexing file was successful", f);
377
378 EC_CLEANUP:
379     if (file)
380         g_object_unref(file);
381     EC_EXIT;
382 #else
383     return 0;
384 #endif
385 }
386
387 struct sl_module_export sl_mod = {
388     SL_MODULE_VERSION,
389     sl_mod_init,
390     sl_mod_start_search,
391     sl_mod_fetch_result,
392     sl_mod_close_query,
393     sl_mod_error,
394     sl_mod_index_file
395 };