108ce0d439427d854cb8a1f7ed2eafcf365263c2
[lms] / lightmediascanner / src / lib / lightmediascanner.c
1 /**
2  * Copyright (C) 2007 by INdT
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (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  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <dlfcn.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "lightmediascanner.h"
31 #include "lightmediascanner_private.h"
32 #include "lightmediascanner_plugin.h"
33
34 #define DEFAULT_SLAVE_TIMEOUT 1000
35 #define DEFAULT_COMMIT_INTERVAL 100
36
37 static int
38 _parser_load(struct parser *p, const char *so_path)
39 {
40     lms_plugin_t *(*plugin_open)(void);
41     char *errmsg;
42
43     memset(p, 0, sizeof(*p));
44
45     p->dl_handle = dlopen(so_path, RTLD_NOW | RTLD_LOCAL);
46     errmsg = dlerror();
47     if (errmsg) {
48         fprintf(stderr, "ERROR: could not dlopen() %s\n", errmsg);
49         return -1;
50     }
51
52     plugin_open = dlsym(p->dl_handle, "lms_plugin_open");
53     errmsg = dlerror();
54     if (errmsg) {
55         fprintf(stderr, "ERROR: could not find plugin entry point %s\n",
56                 errmsg);
57         return -2;
58     }
59
60     p->so_path = strdup(so_path);
61     if (!p->so_path) {
62         perror("strdup");
63         return -3;
64     }
65
66     p->plugin = plugin_open();
67     if (!p->plugin) {
68         fprintf(stderr, "ERROR: plugin \"%s\" failed to init.\n", so_path);
69         return -4;
70     }
71
72     return 0;
73 }
74
75 static int
76 _parser_unload(struct parser *p)
77 {
78     int r;
79
80     r = 0;
81     if (p->plugin) {
82         if (p->plugin->close(p->plugin) != 0) {
83             fprintf(stderr, "ERROR: plugin \"%s\" failed to deinit.\n",
84                     p->so_path);
85             r -= 1;
86         }
87     }
88
89     if (p->dl_handle) {
90         char *errmsg;
91
92         dlclose(p->dl_handle);
93         errmsg = dlerror();
94         if (errmsg) {
95             fprintf(stderr, "ERROR: could not dlclose() plugin \"%s\": %s\n",
96                     errmsg, p->so_path);
97             r -= 1;
98         }
99     }
100
101     if (p->so_path)
102         free(p->so_path);
103
104     return r;
105 }
106
107
108 /***********************************************************************
109  * Public API.
110  ***********************************************************************/
111 /**
112  * Create new Light Media Scanner instance.
113  *
114  * @param db_path path to database file.
115  * @return allocated data on success or NULL on failure.
116  * @ingroup LMS_API
117  */
118 lms_t *
119 lms_new(const char *db_path)
120 {
121     lms_t *lms;
122
123     lms = calloc(1, sizeof(lms_t));
124     if (!lms) {
125         perror("calloc");
126         return NULL;
127     }
128
129     lms->cs_conv = lms_charset_conv_new();
130     if (!lms->cs_conv) {
131         free(lms);
132         return NULL;
133     }
134
135     lms->commit_interval = DEFAULT_COMMIT_INTERVAL;
136     lms->slave_timeout = DEFAULT_SLAVE_TIMEOUT;
137     lms->db_path = strdup(db_path);
138     if (!lms->db_path) {
139         perror("strdup");
140         lms_charset_conv_free(lms->cs_conv);
141         free(lms);
142         return NULL;
143     }
144
145     return lms;
146 }
147
148 /**
149  * Free existing Light Media Scanner instance.
150  *
151  * @param lms previously allocated Light Media Scanner instance.
152  *
153  * @return On success 0 is returned.
154  * @ingroup LMS_API
155  */
156 int
157 lms_free(lms_t *lms)
158 {
159     int i;
160
161     if (!lms)
162         return 0;
163
164     if (lms->is_processing)
165         return -1;
166
167     if (lms->parsers) {
168         for (i = 0; i < lms->n_parsers; i++)
169             _parser_unload(lms->parsers + i);
170
171         free(lms->parsers);
172     }
173
174     free(lms->db_path);
175     lms_charset_conv_free(lms->cs_conv);
176     free(lms);
177     return 0;
178 }
179
180 /**
181  * Add parser plugin given it's shared object path.
182  *
183  * @param lms previously allocated Light Media Scanner instance.
184  * @param so_path path to shared object (usable by dlopen(3)).
185  *
186  * @return On success the LMS handle to plugin is returned, NULL on error.
187  * @ingroup LMS_API
188  */
189 lms_plugin_t *
190 lms_parser_add(lms_t *lms, const char *so_path)
191 {
192     struct parser *parser;
193
194     if (!lms)
195         return NULL;
196
197     if (!so_path)
198         return NULL;
199
200     if (lms->is_processing) {
201         fprintf(stderr, "ERROR: do not add parsers while it's processing.\n");
202         return NULL;
203     }
204
205     lms->parsers = realloc(lms->parsers,
206                            (lms->n_parsers + 1) * sizeof(struct parser));
207     if (!lms->parsers) {
208         perror("realloc");
209         return NULL;
210     }
211
212     parser = lms->parsers + lms->n_parsers;
213     if (_parser_load(parser, so_path) != 0) {
214         _parser_unload(parser);
215         return NULL;
216     }
217
218     lms->n_parsers++;
219     return parser->plugin;
220 }
221
222 /**
223  * Add parser plugin given it's name.
224  *
225  * This will look at default plugin path by the file named @p name (plus
226  * the required shared object extension).
227  *
228  * @param lms previously allocated Light Media Scanner instance.
229  * @param name plugin name.
230  *
231  * @return On success the LMS handle to plugin is returned, NULL on error.
232  * @ingroup LMS_API
233  */
234 lms_plugin_t *
235 lms_parser_find_and_add(lms_t *lms, const char *name)
236 {
237     char so_path[PATH_MAX];
238
239     if (!lms)
240         return NULL;
241     if (!name)
242         return NULL;
243
244     snprintf(so_path, sizeof(so_path), "%s/%s.so", PLUGINSDIR, name);
245     return lms_parser_add(lms, so_path);
246 }
247
248 int
249 lms_parser_del_int(lms_t *lms, int i)
250 {
251     struct parser *parser;
252
253     parser = lms->parsers + i;
254     _parser_unload(parser);
255     lms->n_parsers--;
256
257     if (lms->n_parsers == 0) {
258         free(lms->parsers);
259         lms->parsers = NULL;
260         return 0;
261     } else {
262         int dif;
263
264         dif = lms->n_parsers - i;
265         if (dif)
266             lms->parsers = memmove(parser, parser + 1,
267                                    dif * sizeof(struct parser));
268
269         lms->parsers = realloc(lms->parsers,
270                                lms->n_parsers * sizeof(struct parser));
271         if (!lms->parsers) {
272             lms->n_parsers = 0;
273             return -1;
274         }
275
276         return 0;
277     }
278 }
279
280 /**
281  * Delete previously added parser, making it unavailable for future operations.
282  *
283  * @param lms previously allocated Light Media Scanner instance.
284  *
285  * @return On success 0 is returned.
286  * @ingroup LMS_API
287  */
288 int
289 lms_parser_del(lms_t *lms, lms_plugin_t *handle)
290 {
291     int i;
292
293     if (!lms)
294         return -1;
295     if (!handle)
296         return -2;
297     if (!lms->parsers)
298         return -3;
299     if (lms->is_processing) {
300         fprintf(stderr, "ERROR: do not del parsers while it's processing.\n");
301         return -4;
302     }
303
304     for (i = 0; i < lms->n_parsers; i++)
305         if (lms->parsers[i].plugin == handle)
306             return lms_parser_del_int(lms, i);
307
308     return -3;
309 }
310
311 /**
312  * Checks if Light Media Scanner is being used in a processing operation lile
313  * lms_process() or lms_check().
314  *
315  * @param lms previously allocated Light Media Scanner instance.
316  *
317  * @return 1 if it is processing, 0 if it's not, -1 on error.
318  * @ingroup LMS_API
319  */
320 int
321 lms_is_processing(const lms_t *lms)
322 {
323     if (!lms) {
324         fprintf(stderr, "ERROR: lms_is_processing(NULL)\n");
325         return -1;
326     }
327
328     return lms->is_processing;
329 }
330
331 /**
332  * Get the database path given at creation time.
333  *
334  * @param lms previously allocated Light Media Scanner instance.
335  *
336  * @return path to database.
337  * @ingroup LMS_API
338  */
339 const char *
340 lms_get_db_path(const lms_t *lms)
341 {
342     if (!lms) {
343         fprintf(stderr, "ERROR: lms_get_db_path(NULL)\n");
344         return NULL;
345     }
346
347     return lms->db_path;
348 }
349
350 /**
351  * Get the maximum amount of milliseconds the slave can take to serve one file.
352  *
353  * If a slave takes more than this amount of milliseconds, it will be killed
354  * and the scanner will continue with the next file.
355  *
356  * @param lms previously allocated Light Media Scanner instance.
357  *
358  * @return -1 on error or time in milliseconds otherwise.
359  * @ingroup LMS_API
360  */
361 int
362 lms_get_slave_timeout(const lms_t *lms)
363 {
364     if (!lms) {
365         fprintf(stderr, "ERROR: lms_get_slave_timeout(NULL)\n");
366         return -1;
367     }
368
369     return lms->slave_timeout;
370 }
371
372 /**
373  * Set the maximum amount of milliseconds the slave can take to serve one file.
374  *
375  * If a slave takes more than this amount of milliseconds, it will be killed
376  * and the scanner will continue with the next file.
377  *
378  * @param lms previously allocated Light Media Scanner instance.
379  * @param ms time in milliseconds.
380  * @ingroup LMS_API
381  */
382 void lms_set_slave_timeout(lms_t *lms, int ms)
383 {
384     if (!lms) {
385         fprintf(stderr, "ERROR: lms_set_slave_timeout(NULL, %d)\n", ms);
386         return;
387     }
388
389     lms->slave_timeout = ms;
390 }
391
392 /**
393  * Get the number of files served between database transactions.
394  *
395  * This is used as an optimization to database access: doing database commits
396  * take some time and can slow things down too much, so you can choose to just
397  * commit after some number of files are processed, this is the commit_interval.
398  *
399  * @param lms previously allocated Light Media Scanner instance.
400  * @return (unsigned int)-1 on error, value otherwise.
401  * @ingroup LMS_API
402  */
403 unsigned int
404 lms_get_commit_interval(const lms_t *lms)
405 {
406     if (!lms) {
407         fprintf(stderr, "ERROR: lms_get_commit_interval(NULL)\n");
408         return (unsigned int)-1;
409     }
410
411     return lms->commit_interval;
412 }
413
414 /**
415  * Set the number of files served between database transactions.
416  *
417  * This is used as an optimization to database access: doing database commits
418  * take some time and can slow things down too much, so you can choose to just
419  * commit after @p transactions files are processed.
420  *
421  * @param lms previously allocated Light Media Scanner instance.
422  * @param transactions number of files (transactions) to process between
423  *        commits.
424  * @ingroup LMS_API
425  */
426 void
427 lms_set_commit_interval(lms_t *lms, unsigned int transactions)
428 {
429     if (!lms) {
430         fprintf(stderr, "ERROR: lms_set_commit_interval(NULL, %u)\n",
431                 transactions);
432         return;
433     }
434
435     lms->commit_interval = transactions;
436 }
437
438 /**
439  * Register a new charset encoding to be used.
440  *
441  * All database text strings are in UTF-8, so one needs to register new
442  * encodings in order to convert to it.
443  *
444  * @param lms previously allocated Light Media Scanner instance.
445  * @param charset charset name as understood by iconv_open(3).
446  *
447  * @return On success 0 is returned.
448  * @ingroup LMS_API
449  */
450 int
451 lms_charset_add(lms_t *lms, const char *charset)
452 {
453     if (!lms) {
454         fprintf(stderr, "ERROR: lms_charset_add(NULL)\n");
455         return -1;
456     }
457
458     return lms_charset_conv_add(lms->cs_conv, charset);
459 }
460
461 /**
462  * Forget about registered charset encoding.
463  *
464  * All database text strings are in UTF-8, so one needs to register new
465  * encodings in order to convert to it.
466  *
467  * @param lms previously allocated Light Media Scanner instance.
468  * @param charset charset name as understood by iconv_open(3).
469  *
470  * @return On success 0 is returned.
471  * @ingroup LMS_API
472  */
473 int
474 lms_charset_del(lms_t *lms, const char *charset)
475 {
476     if (!lms) {
477         fprintf(stderr, "ERROR: lms_charset_del(NULL)\n");
478         return -1;
479     }
480
481     return lms_charset_conv_del(lms->cs_conv, charset);
482 }