f439d9430540bda18db92712ccb9e3d14a814c5f
[platform/upstream/lightmediascanner.git] / src / bin / lightmediascannerd.c
1 #include "lightmediascanner.h"
2 #include <gio/gio.h>
3 #include <glib-unix.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <stdio.h>
7 #include <sqlite3.h>
8
9 static char *db_path = NULL;
10 static char **charsets = NULL;
11 static GHashTable *categories = NULL;
12 static int commit_interval = 100;
13 static int slave_timeout = 60;
14 static int delete_older_than = 30;
15 static gboolean vacuum = FALSE;
16 static gboolean startup_scan = FALSE;
17 static gboolean omit_scan_progress = FALSE;
18
19 static GDBusNodeInfo *introspection_data = NULL;
20
21 static const char BUS_PATH[] = "/org/lightmediascanner/Scanner1";
22 static const char BUS_IFACE[] = "org.lightmediascanner.Scanner1";
23
24 static const char introspection_xml[] =
25     "<node>"
26     "  <interface name=\"org.lightmediascanner.Scanner1\">"
27     "    <property name=\"DataBasePath\" type=\"s\" access=\"read\" />"
28     "    <property name=\"IsScanning\" type=\"b\" access=\"read\" />"
29     "    <property name=\"WriteLocked\" type=\"b\" access=\"read\" />"
30     "    <property name=\"UpdateID\" type=\"t\" access=\"read\" />"
31     "    <property name=\"Categories\" type=\"a{sv}\" access=\"read\" />"
32     "    <method name=\"Scan\">"
33     "      <arg direction=\"in\" type=\"a{sv}\" name=\"specification\" />"
34     "    </method>"
35     "    <method name=\"Stop\" />"
36     "    <method name=\"RequestWriteLock\" />"
37     "    <method name=\"ReleaseWriteLock\" />"
38     "    <signal name=\"ScanProgress\">"
39     "      <arg type=\"s\" name=\"Category\" />"
40     "      <arg type=\"s\" name=\"Path\" />"
41     "      <arg type=\"t\" name=\"UpToDate\" />"
42     "      <arg type=\"t\" name=\"Processed\" />"
43     "      <arg type=\"t\" name=\"Deleted\" />"
44     "      <arg type=\"t\" name=\"Skipped\" />"
45     "      <arg type=\"t\" name=\"Errors\" />"
46     "    </signal>"
47     "  </interface>"
48     "</node>";
49
50 typedef struct scanner_category
51 {
52     char *category;
53     GArray *parsers;
54     GArray *dirs;
55 } scanner_category_t;
56
57 typedef struct scanner_pending
58 {
59     char *category;
60     GList *paths;
61 } scanner_pending_t;
62
63 typedef struct scan_progress {
64     GDBusConnection *conn;
65     gchar *category;
66     gchar *path;
67     guint64 uptodate;
68     guint64 processed;
69     guint64 deleted;
70     guint64 skipped;
71     guint64 errors;
72     time_t last_report_time;
73     gint updated;
74 } scan_progress_t;
75
76 /* Scan progress signals will be issued if the time since last
77  * emission is greated than SCAN_PROGRESS_UPDATE_TIMEOUT _and_ number
78  * of items is greater than the SCAN_PROGRESS_UPDATE_COUNT.
79  *
80  * Be warned that D-Bus signal will wake-up the dbus-daemon (unless
81  * k-dbus) and all listener clients, which may hurt scan performance,
82  * thus we keep these good enough for GUI to look responsive while
83  * conservative to not hurt performance.
84  *
85  * Note that at after a path is scanned (check/progress) the signal is
86  * emitted even if count or timeout didn't match.
87  */
88 #define SCAN_PROGRESS_UPDATE_TIMEOUT 1 /* in seconds */
89 #define SCAN_PROGRESS_UPDATE_COUNT  50 /* in number of items */
90
91 #define SCAN_MOUNTPOINTS_TIMEOUT 1 /* in seconds */
92
93 typedef struct scanner {
94     GDBusConnection *conn;
95     char *write_lock;
96     unsigned write_lock_name_watcher;
97     GDBusMethodInvocation *pending_stop;
98     GList *pending_scan; /* of scanner_pending_t, see scanner_thread_work */
99     GThread *thread; /* see scanner_thread_work */
100     unsigned cleanup_thread_idler; /* see scanner_thread_work */
101     scan_progress_t *scan_progress;
102     struct {
103         GIOChannel *channel;
104         unsigned watch;
105         unsigned timer;
106         GList *paths;
107         GList *pending;
108     } mounts;
109     guint64 update_id;
110     struct {
111         unsigned idler; /* not a flag, but g_source tag */
112         unsigned is_scanning : 1;
113         unsigned write_locked : 1;
114         unsigned update_id : 1;
115         unsigned categories: 1;
116     } changed_props;
117 } scanner_t;
118
119 static scanner_category_t *
120 scanner_category_new(const char *category)
121 {
122     scanner_category_t *sc = g_new0(scanner_category_t, 1);
123
124     sc->category = g_strdup(category);
125     sc->parsers = g_array_new(TRUE, TRUE, sizeof(char *));
126     sc->dirs = g_array_new(TRUE, TRUE, sizeof(char *));
127
128     return sc;
129 }
130
131 static void
132 scanner_category_destroy(scanner_category_t *sc)
133 {
134     char **itr;
135
136     for (itr = (char **)sc->parsers->data; *itr != NULL; itr++)
137         g_free(*itr);
138     for (itr = (char **)sc->dirs->data; *itr != NULL; itr++)
139         g_free(*itr);
140
141     g_array_free(sc->parsers, TRUE);
142     g_array_free(sc->dirs, TRUE);
143     g_free(sc->category);
144
145     g_free(sc);
146 }
147
148 static void scanner_release_write_lock(scanner_t *scanner);
149
150 static void
151 scanner_write_lock_vanished(GDBusConnection *conn, const char *name, gpointer data)
152 {
153     scanner_t *scanner = data;
154     g_warning("Write lock holder %s vanished, release lock\n", name);
155     scanner_release_write_lock(scanner);
156 }
157
158 static guint64
159 get_update_id(void)
160 {
161     const char sql[] = "SELECT version FROM lms_internal WHERE tab='update_id'";
162     sqlite3 *db;
163     sqlite3_stmt *stmt;
164     int ret;
165     guint64 update_id = 0;
166
167     ret = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READONLY, NULL);
168     if (ret != SQLITE_OK) {
169         g_warning("Couldn't open '%s': %s", db_path, sqlite3_errmsg(db));
170         goto end;
171     }
172
173     if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
174         g_warning("Couldn't get update_id from %s: %s",
175                   db_path, sqlite3_errmsg(db));
176         goto end;
177     }
178
179     ret = sqlite3_step(stmt);
180     if (ret ==  SQLITE_DONE)
181         update_id = 0;
182     else if (ret == SQLITE_ROW)
183         update_id = sqlite3_column_int(stmt, 0);
184     else
185         g_warning("Couldn't run SQL to get update_id, ret=%d: %s",
186                   ret, sqlite3_errmsg(db));
187
188     sqlite3_reset(stmt);
189     sqlite3_finalize(stmt);
190
191 end:
192     sqlite3_close(db);
193
194     g_debug("update id: %llu", (unsigned long long)update_id);
195     return update_id;
196 }
197
198 static void
199 do_delete_old(void)
200 {
201     const char sql[] = "DELETE FROM files WHERE dtime > 0 and dtime <= ?";
202     sqlite3 *db;
203     sqlite3_stmt *stmt;
204     gint64 dtime;
205     int ret;
206
207     ret = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READWRITE, NULL);
208     if (ret != SQLITE_OK) {
209         g_warning("Couldn't open '%s': %s", db_path, sqlite3_errmsg(db));
210         goto end;
211     }
212
213     if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
214         g_warning("Couldn't prepare delete old from %s: %s",
215                   db_path, sqlite3_errmsg(db));
216         goto end;
217     }
218
219     dtime = (gint64)time(NULL) - delete_older_than * (24 * 60 * 60);
220     if (sqlite3_bind_int64(stmt, 1, dtime) != SQLITE_OK) {
221         g_warning("Couldn't bind delete old dtime '%"G_GINT64_FORMAT
222                   "'from %s: %s", dtime, db_path, sqlite3_errmsg(db));
223         goto cleanup;
224     }
225
226     ret = sqlite3_step(stmt);
227     if (ret != SQLITE_DONE)
228         g_warning("Couldn't run SQL delete old dtime '%"G_GINT64_FORMAT
229                   "', ret=%d: %s", dtime, ret, sqlite3_errmsg(db));
230
231 cleanup:
232     sqlite3_reset(stmt);
233     sqlite3_finalize(stmt);
234
235 end:
236     sqlite3_close(db);
237 }
238
239 static void
240 do_vacuum(void)
241 {
242     const char sql[] = "VACUUM";
243     sqlite3 *db;
244     sqlite3_stmt *stmt;
245     int ret;
246
247     ret = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READWRITE, NULL);
248     if (ret != SQLITE_OK) {
249         g_warning("Couldn't open '%s': %s", db_path, sqlite3_errmsg(db));
250         goto end;
251     }
252
253     if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
254         g_warning("Couldn't vacuum from %s: %s",
255                   db_path, sqlite3_errmsg(db));
256         goto end;
257     }
258
259     ret = sqlite3_step(stmt);
260     if (ret != SQLITE_DONE)
261         g_warning("Couldn't run SQL VACUUM, ret=%d: %s",
262                   ret, sqlite3_errmsg(db));
263
264     sqlite3_reset(stmt);
265     sqlite3_finalize(stmt);
266
267 end:
268     sqlite3_close(db);
269 }
270
271 static gboolean
272 check_write_locked(const scanner_t *scanner)
273 {
274     return scanner->write_lock != NULL || scanner->thread != NULL;
275 }
276
277 static void
278 category_variant_foreach(gpointer key, gpointer value, gpointer user_data)
279 {
280     scanner_category_t *sc = value;
281     GVariantBuilder *builder = user_data;
282     GVariantBuilder *sub, *darr, *parr;
283     char **itr;
284
285     darr = g_variant_builder_new(G_VARIANT_TYPE("as"));
286     for (itr = (char **)sc->dirs->data; *itr != NULL; itr++)
287         g_variant_builder_add(darr, "s", *itr);
288
289     parr = g_variant_builder_new(G_VARIANT_TYPE("as"));
290     for (itr = (char **)sc->parsers->data; *itr != NULL; itr++)
291         g_variant_builder_add(parr, "s", *itr);
292
293     sub = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
294     g_variant_builder_add(sub, "{sv}", "dirs", g_variant_builder_end(darr));
295     g_variant_builder_add(sub, "{sv}", "parsers", g_variant_builder_end(parr));
296
297     g_variant_builder_add(builder, "{sv}", sc->category,
298                           g_variant_builder_end(sub));
299
300     g_variant_builder_unref(sub);
301     g_variant_builder_unref(parr);
302     g_variant_builder_unref(darr);
303 }
304
305 static GVariant *
306 categories_get_variant(void)
307 {
308     GVariantBuilder *builder;
309     GVariant *variant;
310
311     builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
312
313     g_hash_table_foreach(categories, category_variant_foreach, builder);
314
315     variant = g_variant_builder_end(builder);
316     g_variant_builder_unref(builder);
317
318     return variant;
319 }
320
321 static gboolean
322 scanner_dbus_props_changed(gpointer data)
323 {
324     GVariantBuilder *builder;
325     GError *error = NULL;
326     scanner_t *scanner = data;
327     guint64 update_id = 0;
328
329     if (!check_write_locked(scanner))
330         update_id = get_update_id();
331     if (update_id > 0 && update_id != scanner->update_id) {
332         scanner->changed_props.update_id = TRUE;
333         scanner->update_id = update_id;
334     }
335
336     builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
337
338     if (scanner->changed_props.is_scanning) {
339         scanner->changed_props.is_scanning = FALSE;
340         g_variant_builder_add(builder, "{sv}", "IsScanning",
341                               g_variant_new_boolean(scanner->thread != NULL));
342     }
343     if (scanner->changed_props.write_locked) {
344         scanner->changed_props.write_locked = FALSE;
345         g_variant_builder_add(
346             builder, "{sv}", "WriteLocked",
347             g_variant_new_boolean(check_write_locked(scanner)));
348     }
349     if (scanner->changed_props.update_id) {
350         scanner->changed_props.update_id = FALSE;
351         g_variant_builder_add(builder, "{sv}", "UpdateID",
352                               g_variant_new_uint64(scanner->update_id));
353     }
354     if (scanner->changed_props.categories) {
355         scanner->changed_props.categories = FALSE;
356         g_variant_builder_add(builder, "{sv}", "Categories",
357                               categories_get_variant());
358     }
359
360     g_dbus_connection_emit_signal(scanner->conn,
361                                   NULL,
362                                   BUS_PATH,
363                                   "org.freedesktop.DBus.Properties",
364                                   "PropertiesChanged",
365                                   g_variant_new("(sa{sv}as)",
366                                                 BUS_IFACE, builder, NULL),
367                                   &error);
368     g_variant_builder_unref(builder);
369     g_assert_no_error(error);
370
371     scanner->changed_props.idler = 0;
372     return FALSE;
373 }
374
375 static void
376 scanner_write_lock_changed(scanner_t *scanner)
377 {
378     if (scanner->changed_props.idler == 0)
379         scanner->changed_props.idler = g_idle_add(scanner_dbus_props_changed,
380                                                   scanner);
381
382     scanner->changed_props.write_locked = TRUE;
383 }
384
385 static void
386 scanner_acquire_write_lock(scanner_t *scanner, const char *sender)
387 {
388     g_debug("acquired write lock for %s", sender);
389     scanner->write_lock = g_strdup(sender);
390     scanner->write_lock_name_watcher = g_bus_watch_name_on_connection(
391         scanner->conn, sender, G_BUS_NAME_WATCHER_FLAGS_NONE,
392         NULL, scanner_write_lock_vanished, scanner, NULL);
393
394     scanner_write_lock_changed(scanner);
395 }
396
397 static void
398 scanner_release_write_lock(scanner_t *scanner)
399 {
400     g_debug("release write lock previously owned by %s", scanner->write_lock);
401
402     g_free(scanner->write_lock);
403     scanner->write_lock = NULL;
404
405     g_bus_unwatch_name(scanner->write_lock_name_watcher);
406     scanner->write_lock_name_watcher = 0;
407
408     scanner_write_lock_changed(scanner);
409 }
410
411 static void
412 scanner_pending_free(scanner_pending_t *pending)
413 {
414     g_list_free_full(pending->paths, g_free);
415     g_free(pending->category);
416     g_free(pending);
417 }
418
419 static scanner_pending_t *
420 scanner_pending_get_or_add(scanner_t *scanner, const char *category)
421 {
422     scanner_pending_t *pending;
423     GList *n, *nlast = NULL;
424
425     g_assert(scanner->thread == NULL);
426
427     for (n = scanner->pending_scan; n != NULL; n = n->next) {
428         nlast = n;
429         pending = n->data;
430         if (strcmp(pending->category, category) == 0)
431             return pending;
432     }
433
434     pending = g_new0(scanner_pending_t, 1);
435     pending->category = g_strdup(category);
436     pending->paths = NULL;
437
438     /* I can't believe there is no g_list_insert_after() :-( */
439     n = g_list_alloc();
440     n->data = pending;
441     n->next = NULL;
442     n->prev = nlast;
443
444     if (nlast)
445         nlast->next = n;
446     else
447         scanner->pending_scan = n;
448
449     return pending;
450 }
451
452 /* NOTE:  assumes array was already validated for duplicates/restrictions */
453 static void
454 scanner_pending_add_all(scanner_pending_t *pending, const GArray *arr)
455 {
456     char **itr;
457
458
459     for (itr = (char **)arr->data; *itr != NULL; itr++)
460         pending->paths = g_list_prepend(pending->paths, g_strdup(*itr));
461
462     pending->paths = g_list_reverse(pending->paths);
463 }
464
465 static gboolean
466 scanner_category_allows_path(const GArray *restrictions, const char *path)
467 {
468     const char * const *itr;
469     for (itr = (const char *const*)restrictions->data; *itr != NULL; itr++) {
470         if (g_str_has_prefix(path, *itr))
471             return TRUE;
472     }
473     return FALSE;
474 }
475
476 static void
477 scanner_pending_add(scanner_pending_t *pending, const GArray *restrictions, const char *path)
478 {
479     GList *n, *nlast;
480
481     for (n = pending->paths; n != NULL; n = n->next) {
482         const char *other = n->data;
483         if (g_str_has_prefix(path, other)) {
484             g_debug("Path already in pending scan in category %s: %s (%s)",
485                     pending->category, path, other);
486             return;
487         }
488     }
489
490
491     if (restrictions && (!scanner_category_allows_path(restrictions, path))) {
492         g_warning("Path is outside of category %s directories: %s",
493                   pending->category, path);
494         return;
495     }
496
497     nlast = NULL;
498     for (n = pending->paths; n != NULL; ) {
499         char *other = n->data;
500
501         nlast = n;
502
503         if (!g_str_has_prefix(other, path))
504             n = n->next;
505         else {
506             GList *tmp;
507
508             g_debug("Path covers previous pending scan in category %s, "
509                     "replace %s (%s)",
510                     pending->category, other, path);
511
512             tmp = n->next;
513             nlast = n->next ? n->next : n->prev;
514
515             pending->paths = g_list_delete_link(pending->paths, n);
516             g_free(other);
517
518             n = tmp;
519         }
520     }
521
522     g_debug("New scan path for category %s: %s", pending->category, path);
523
524     /* I can't believe there is no g_list_insert_after() :-( */
525     n = g_list_alloc();
526     n->data = g_strdup(path);
527     n->next = NULL;
528     n->prev = nlast;
529
530     if (nlast)
531         nlast->next = n;
532     else
533         pending->paths = n;
534 }
535
536 static void
537 scan_params_all(gpointer key, gpointer value, gpointer user_data)
538 {
539     scanner_pending_t *pending;
540     scanner_category_t *sc = value;
541     scanner_t *scanner = user_data;
542
543     pending = scanner_pending_get_or_add(scanner, sc->category);
544     scanner_pending_add_all(pending, sc->dirs);
545 }
546
547 static void
548 dbus_scanner_scan_params_set(scanner_t *scanner, GVariant *params)
549 {
550     GVariantIter *itr;
551     GVariant *el;
552     char *cat;
553     gboolean empty = TRUE;
554
555     g_variant_get(params, "(a{sv})", &itr);
556     while (g_variant_iter_loop(itr, "{sv}", &cat, &el)) {
557         scanner_category_t *sc;
558         scanner_pending_t *pending;
559         GVariantIter *subitr;
560         char *path;
561         gboolean nodirs = TRUE;
562
563         sc = g_hash_table_lookup(categories, cat);
564         if (!sc) {
565             g_warning("Unexpected scan category: %s, skipped.", cat);
566             continue;
567         }
568
569         pending = scanner_pending_get_or_add(scanner, cat);
570         empty = FALSE;
571
572         g_variant_get(el, "as", &subitr);
573         while (g_variant_iter_loop(subitr, "s", &path)) {
574             scanner_pending_add(pending, sc->dirs, path);
575             nodirs = FALSE;
576         }
577
578         if (nodirs)
579             scanner_pending_add_all(pending, sc->dirs);
580     }
581     g_variant_iter_free(itr);
582
583     if (empty)
584         g_hash_table_foreach(categories, scan_params_all, scanner);
585 }
586
587 static void
588 scanner_is_scanning_changed(scanner_t *scanner)
589 {
590     if (scanner->changed_props.idler == 0)
591         scanner->changed_props.idler = g_idle_add(scanner_dbus_props_changed,
592                                                   scanner);
593
594     scanner->changed_props.is_scanning = TRUE;
595 }
596
597 static gboolean
598 report_scan_progress(gpointer data)
599 {
600     scan_progress_t *sp = data;
601     GError *error = NULL;
602
603     g_dbus_connection_emit_signal(sp->conn,
604                                   NULL,
605                                   BUS_PATH,
606                                   BUS_IFACE,
607                                   "ScanProgress",
608                                   g_variant_new("(ssttttt)",
609                                                 sp->category,
610                                                 sp->path,
611                                                 sp->uptodate,
612                                                 sp->processed,
613                                                 sp->deleted,
614                                                 sp->skipped,
615                                                 sp->errors),
616                                   &error);
617     g_assert_no_error(error);
618
619     return FALSE;
620 }
621
622 static gboolean
623 report_scan_progress_and_free(gpointer data)
624 {
625     scan_progress_t *sp = data;
626
627     if (sp->updated)
628         report_scan_progress(sp);
629
630     g_object_unref(sp->conn);
631     g_free(sp->category);
632     g_free(sp->path);
633     g_free(sp);
634     return FALSE;
635 }
636
637 static void
638 scan_progress_cb(lms_t *lms, const char *path, int pathlen, lms_progress_status_t status, void *data)
639 {
640     const scanner_t *scanner = data;
641     scan_progress_t *sp = scanner->scan_progress;
642
643     if (scanner->pending_stop)
644         lms_stop_processing(lms);
645
646     if (!sp)
647         return;
648
649     switch (status) {
650     case LMS_PROGRESS_STATUS_UP_TO_DATE:
651         sp->uptodate++;
652         break;
653     case LMS_PROGRESS_STATUS_PROCESSED:
654         sp->processed++;
655         break;
656     case LMS_PROGRESS_STATUS_DELETED:
657         sp->deleted++;
658         break;
659     case LMS_PROGRESS_STATUS_KILLED:
660     case LMS_PROGRESS_STATUS_ERROR_PARSE:
661     case LMS_PROGRESS_STATUS_ERROR_COMM:
662         sp->errors++;
663         break;
664     case LMS_PROGRESS_STATUS_SKIPPED:
665         sp->skipped++;
666         break;
667     }
668
669     sp->updated++;
670     if (sp->updated > SCAN_PROGRESS_UPDATE_COUNT) {
671         time_t now = time(NULL);
672         if (sp->last_report_time + SCAN_PROGRESS_UPDATE_TIMEOUT < now) {
673             sp->last_report_time = now;
674             sp->updated = 0;
675             g_idle_add(report_scan_progress, sp);
676         }
677     }
678 }
679
680 static lms_t *
681 setup_lms(const char *category, const scanner_t *scanner)
682 {
683     scanner_category_t *sc;
684     char **itr;
685     lms_t *lms;
686
687     sc = g_hash_table_lookup(categories, category);
688     if (!sc) {
689         g_error("Unknown category %s", category);
690         return NULL;
691     }
692
693     if (sc->parsers->len == 0) {
694         g_warning("No parsers for category %s", category);
695         return NULL;
696     }
697
698     lms = lms_new(db_path);
699     if (!lms) {
700         g_warning("Failed to create lms");
701         return NULL;
702     }
703
704     lms_set_commit_interval(lms, commit_interval);
705     lms_set_slave_timeout(lms, slave_timeout * 1000);
706
707     if (charsets) {
708         for (itr = charsets; *itr != NULL; itr++)
709             if (lms_charset_add(lms, *itr) != 0)
710                 g_warning("Couldn't add charset: %s", *itr);
711     }
712
713     for (itr = (char **)sc->parsers->data; *itr != NULL; itr++) {
714         const char *parser = *itr;
715         lms_plugin_t *plugin;
716
717         if (parser[0] == '/')
718             plugin = lms_parser_add(lms, parser);
719         else
720             plugin = lms_parser_find_and_add(lms, parser);
721
722         if (!plugin)
723             g_warning("Couldn't add parser: %s", parser);
724     }
725
726     lms_set_progress_callback(lms, scan_progress_cb, scanner, NULL);
727
728     return lms;
729 }
730
731 static void scan_mountpoints(scanner_t *scanner);
732
733 static gboolean
734 scanner_thread_cleanup(gpointer data)
735 {
736     scanner_t *scanner = data;
737     gpointer ret;
738
739     g_debug("cleanup scanner work thread");
740     ret = g_thread_join(scanner->thread);
741     g_assert(ret == scanner);
742
743     scanner->thread = NULL;
744     scanner->cleanup_thread_idler = 0;
745
746     if (scanner->pending_stop) {
747         g_dbus_method_invocation_return_value(scanner->pending_stop, NULL);
748         g_object_unref(scanner->pending_stop);
749         scanner->pending_stop =  NULL;
750     }
751
752     g_list_free_full(scanner->pending_scan,
753                      (GDestroyNotify)scanner_pending_free);
754     scanner->pending_scan = NULL;
755
756     if (scanner->mounts.pending && !scanner->mounts.timer)
757         scan_mountpoints(scanner);
758     else {
759         scanner_is_scanning_changed(scanner);
760         scanner_write_lock_changed(scanner);
761     }
762
763     return FALSE;
764 }
765
766 /*
767  * Note on thread usage and locks (or lack of locks):
768  *
769  * The main thread is responsible for launching the worker thread,
770  * setting 'scanner->thread' pointer, which is later checked *ONLY* by
771  * main thread. When the thread is done, it will notify the main
772  * thread with scanner_thread_cleanup() so it can unset the pointer
773  * and do whatever it needs, so 'scanner->thread' is exclusively
774  * managed by main thread.
775  *
776  * The other shared data 'scanner->pending_scan' is managed by the
777  * main thread only when 'scanner->thread' is unset. If there is a
778  * worker thread the main thread should never touch that list, thus
779  * there is *NO NEED FOR LOCKS*.
780  *
781  * The thread will stop its work by checking 'scanner->pending_stop',
782  * this is also done without a lock as there is no need for such thing
783  * given above. The stop is also voluntary and it can happen on a
784  * second iteration of work.
785  */
786 static gpointer
787 scanner_thread_work(gpointer data)
788 {
789     GList *lst;
790     scanner_t *scanner = data;
791
792     g_debug("started scanner thread");
793
794     lst = scanner->pending_scan;
795     scanner->pending_scan = NULL;
796     while (lst) {
797         scanner_pending_t *pending;
798         lms_t *lms;
799
800         if (scanner->pending_stop)
801             break;
802
803         pending = lst->data;
804         lst = g_list_delete_link(lst, lst);
805
806         g_debug("scan category: %s", pending->category);
807         lms = setup_lms(pending->category, scanner);
808         if (lms) {
809             while (pending->paths) {
810                 char *path;
811                 scan_progress_t *sp = NULL;
812
813                 if (scanner->pending_stop)
814                     break;
815
816                 path = pending->paths->data;
817                 pending->paths = g_list_delete_link(pending->paths,
818                                                     pending->paths);
819
820                 g_debug("scan category %s, path %s", pending->category, path);
821
822                 if (!omit_scan_progress) {
823                     sp = g_new0(scan_progress_t, 1);
824                     sp->conn = g_object_ref(scanner->conn);
825                     sp->category = g_strdup(pending->category);
826                     sp->path = g_strdup(path);
827                     scanner->scan_progress = sp;
828                 }
829
830                 if (!scanner->pending_stop)
831                     lms_check(lms, path);
832                 if (!scanner->pending_stop &&
833                     g_file_test(path, G_FILE_TEST_EXISTS))
834                     lms_process(lms, path);
835
836                 if (sp)
837                     g_idle_add(report_scan_progress_and_free, sp);
838
839                 g_free(path);
840             }
841             lms_free(lms);
842         }
843
844         scanner_pending_free(pending);
845     }
846
847     g_debug("finished scanner thread");
848
849     if (delete_older_than >= 0) {
850         g_debug("Delete from DB files with dtime older than %d days.",
851                 delete_older_than);
852         do_delete_old();
853     }
854
855     if (vacuum) {
856         GTimer *timer = g_timer_new();
857
858         g_debug("Starting SQL VACUUM...");
859         g_timer_start(timer);
860         do_vacuum();
861         g_timer_stop(timer);
862         g_debug("Finished VACUUM in %0.3f seconds.",
863                 g_timer_elapsed(timer, NULL));
864         g_timer_destroy(timer);
865     }
866
867     scanner->cleanup_thread_idler = g_idle_add(scanner_thread_cleanup, scanner);
868
869     return scanner;
870 }
871
872 static void
873 do_scan(scanner_t *scanner)
874 {
875     scanner->thread = g_thread_new("scanner", scanner_thread_work, scanner);
876
877     scanner_is_scanning_changed(scanner);
878     scanner_write_lock_changed(scanner);
879 }
880
881 static void
882 dbus_scanner_scan(GDBusMethodInvocation *inv, scanner_t *scanner, GVariant *params)
883 {
884     if (scanner->thread) {
885         g_dbus_method_invocation_return_dbus_error(
886             inv, "org.lightmediascanner.AlreadyScanning",
887             "Scanner was already scanning.");
888         return;
889     }
890
891     if (scanner->write_lock) {
892         g_dbus_method_invocation_return_dbus_error(
893             inv, "org.lightmediascanner.WriteLocked",
894             "Data Base has a write lock for another process.");
895         return;
896     }
897
898     dbus_scanner_scan_params_set(scanner, params);
899
900     do_scan(scanner);
901
902     g_dbus_method_invocation_return_value(inv, NULL);
903 }
904
905 static void
906 dbus_scanner_stop(GDBusMethodInvocation *inv, scanner_t *scanner)
907 {
908     if (!scanner->thread) {
909         g_dbus_method_invocation_return_dbus_error(
910             inv, "org.lightmediascanner.NotScanning",
911             "Scanner was already stopped.");
912         return;
913     }
914     if (scanner->pending_stop) {
915         g_dbus_method_invocation_return_dbus_error(
916             inv, "org.lightmediascanner.AlreadyStopping",
917             "Scanner was already being stopped.");
918         return;
919     }
920
921     scanner->pending_stop = g_object_ref(inv);
922 }
923
924 static void
925 dbus_scanner_request_write_lock(GDBusMethodInvocation *inv, scanner_t *scanner, const char *sender)
926 {
927     if (check_write_locked(scanner)) {
928         if (scanner->write_lock && strcmp(scanner->write_lock, sender) == 0)
929             g_dbus_method_invocation_return_value(inv, NULL);
930         else if (scanner->write_lock)
931             g_dbus_method_invocation_return_dbus_error(
932                 inv, "org.lightmediascanner.AlreadyLocked",
933                 "Scanner is already locked");
934         else
935             g_dbus_method_invocation_return_dbus_error(
936                 inv, "org.lightmediascanner.IsScanning",
937                 "Scanner is scanning and can't grant a write lock");
938         return;
939     }
940
941     scanner_acquire_write_lock(scanner, sender);
942     g_dbus_method_invocation_return_value(inv, NULL);
943 }
944
945 static void
946 dbus_scanner_release_write_lock(GDBusMethodInvocation *inv, scanner_t *scanner, const char *sender)
947 {
948     if (!scanner->write_lock || strcmp(scanner->write_lock, sender) != 0) {
949         g_dbus_method_invocation_return_dbus_error(
950             inv, "org.lightmediascanner.NotLocked",
951             "Scanner was not locked by you.");
952         return;
953     }
954
955     scanner_release_write_lock(scanner);
956     g_dbus_method_invocation_return_value(inv, NULL);
957 }
958
959 static void
960 scanner_method_call(GDBusConnection *conn, const char *sender, const char *opath, const char *iface, const char *method, GVariant *params, GDBusMethodInvocation *inv, gpointer data)
961 {
962     scanner_t *scanner = data;
963
964     if (strcmp(method, "Scan") == 0)
965         dbus_scanner_scan(inv, scanner, params);
966     else if (strcmp(method, "Stop") == 0)
967         dbus_scanner_stop(inv, scanner);
968     else if (strcmp(method, "RequestWriteLock") == 0)
969         dbus_scanner_request_write_lock(inv, scanner, sender);
970     else if (strcmp(method, "ReleaseWriteLock") == 0)
971         dbus_scanner_release_write_lock(inv, scanner, sender);
972 }
973
974 static void
975 scanner_update_id_changed(scanner_t *scanner)
976 {
977     if (scanner->changed_props.idler == 0)
978         scanner->changed_props.idler = g_idle_add(scanner_dbus_props_changed,
979                                                   scanner);
980
981     scanner->changed_props.update_id = TRUE;
982 }
983
984 static void
985 scanner_categories_changed(scanner_t *scanner)
986 {
987     if (scanner->changed_props.idler == 0)
988         scanner->changed_props.idler = g_idle_add(scanner_dbus_props_changed,
989                                                   scanner);
990
991     scanner->changed_props.categories = TRUE;
992 }
993
994 static GVariant *
995 scanner_get_prop(GDBusConnection *conn, const char *sender, const char *opath, const char *iface, const char *prop,  GError **error, gpointer data)
996 {
997     scanner_t *scanner = data;
998     GVariant *ret;
999
1000     if (strcmp(prop, "DataBasePath") == 0)
1001         ret = g_variant_new_string(db_path);
1002     else if (strcmp(prop, "IsScanning") == 0)
1003         ret = g_variant_new_boolean(scanner->thread != NULL);
1004     else if (strcmp(prop, "WriteLocked") == 0)
1005         ret = g_variant_new_boolean(check_write_locked(scanner));
1006     else if (strcmp(prop, "UpdateID") == 0) {
1007         guint64 update_id = 0;
1008
1009         if (!check_write_locked(scanner))
1010             update_id = get_update_id();
1011         if (update_id > 0 && update_id != scanner->update_id) {
1012             scanner->update_id = update_id;
1013             scanner_update_id_changed(scanner);
1014         }
1015         ret = g_variant_new_uint64(scanner->update_id);
1016     } else if (strcmp(prop, "Categories") == 0)
1017         ret = categories_get_variant();
1018     else
1019         ret = NULL;
1020
1021     return ret;
1022 }
1023
1024 struct scanner_should_monitor_data {
1025     const char *mountpoint;
1026     gboolean should_monitor;
1027 };
1028
1029 static void
1030 category_should_monitor(gpointer key, gpointer value, gpointer user_data)
1031 {
1032     const scanner_category_t *sc = value;
1033     struct scanner_should_monitor_data *data = user_data;
1034
1035     if (data->should_monitor)
1036         return;
1037
1038     if (scanner_category_allows_path(sc->dirs, data->mountpoint)) {
1039         data->should_monitor = TRUE;
1040         return;
1041     }
1042 }
1043
1044 static gboolean
1045 should_monitor(const char *mountpoint)
1046 {
1047     struct scanner_should_monitor_data data = {mountpoint, FALSE};
1048     g_hash_table_foreach(categories, category_should_monitor, &data);
1049     return data.should_monitor;
1050 }
1051
1052 static GList *
1053 scanner_mounts_parse(scanner_t *scanner)
1054 {
1055     GList *paths = NULL;
1056     GIOStatus status;
1057     GError *error = NULL;
1058
1059     status = g_io_channel_seek_position(scanner->mounts.channel, 0, G_SEEK_SET,
1060                                         &error);
1061     if (error) {
1062         g_warning("Couldn't rewind mountinfo channel: %s", error->message);
1063         g_free(error);
1064         return NULL;
1065     }
1066
1067     do {
1068         gchar *str = NULL;
1069         gsize len = 0;
1070         int mount_id, parent_id, major, minor;
1071         char root[1024], mountpoint[1024];
1072
1073         status = g_io_channel_read_line(scanner->mounts.channel, &str, NULL,
1074                                         &len, &error);
1075         switch (status) {
1076         case G_IO_STATUS_AGAIN:
1077             continue;
1078         case G_IO_STATUS_NORMAL:
1079             str[len] = '\0';
1080             break;
1081         case G_IO_STATUS_ERROR:
1082             g_warning("Couldn't read line of mountinfo: %s", error->message);
1083             g_free(error);
1084         case G_IO_STATUS_EOF:
1085             return g_list_sort(paths, (GCompareFunc)strcmp);
1086         }
1087
1088         if (sscanf(str, "%d %d %d:%d %1023s %1023s", &mount_id, &parent_id,
1089                    &major, &minor, root, mountpoint) != 6)
1090             g_warning("Error parsing mountinfo line: %s", str);
1091         else {
1092             char *mp = g_strcompress(mountpoint);
1093
1094             if (!should_monitor(mp)) {
1095                 g_debug("Ignored mountpoint: %s. Not in any category.", mp);
1096                 g_free(mp);
1097             } else {
1098                 g_debug("Got mountpoint: %s", mp);
1099                 paths = g_list_prepend(paths, mp);
1100             }
1101         }
1102
1103         g_free(str);
1104     } while (TRUE);
1105 }
1106
1107 static void
1108 scanner_mount_pending_add_or_delete(scanner_t *scanner, gchar *mountpoint)
1109 {
1110     GList *itr;
1111
1112     for (itr = scanner->mounts.pending; itr != NULL; itr = itr->next) {
1113         if (strcmp(itr->data, mountpoint) == 0) {
1114             g_free(mountpoint);
1115             return;
1116         }
1117     }
1118
1119     scanner->mounts.pending = g_list_prepend(scanner->mounts.pending,
1120                                              mountpoint);
1121 }
1122
1123 static void
1124 category_scan_pending_mountpoints(gpointer key, gpointer value, gpointer user_data)
1125 {
1126     scanner_pending_t *pending = NULL;
1127     scanner_category_t *sc = value;
1128     scanner_t *scanner = user_data;
1129     GList *n;
1130
1131     for (n = scanner->mounts.pending; n != NULL; n = n->next) {
1132         const char *mountpoint = n->data;
1133
1134         if (scanner_category_allows_path(sc->dirs, mountpoint)) {
1135             if (!pending)
1136                 pending = scanner_pending_get_or_add(scanner, sc->category);
1137
1138             scanner_pending_add(pending, NULL, mountpoint);
1139         }
1140     }
1141 }
1142
1143 static void
1144 scan_mountpoints(scanner_t *scanner)
1145 {
1146     g_assert(scanner->thread == NULL);
1147
1148     g_hash_table_foreach(categories, category_scan_pending_mountpoints,
1149                          scanner);
1150     g_list_free_full(scanner->mounts.pending, g_free);
1151     scanner->mounts.pending = NULL;
1152
1153     do_scan(scanner);
1154 }
1155
1156 static gboolean
1157 on_scan_mountpoints_timeout(gpointer data)
1158 {
1159     scanner_t *scanner = data;
1160
1161     if (!scanner->thread)
1162         scan_mountpoints(scanner);
1163
1164     scanner->mounts.timer = 0;
1165     return FALSE;
1166 }
1167
1168 static gboolean
1169 on_mounts_changed(GIOChannel *source, GIOCondition cond, gpointer data)
1170 {
1171     scanner_t *scanner = data;
1172     GList *oldpaths = scanner->mounts.paths;
1173     GList *newpaths = scanner_mounts_parse(scanner);
1174     GList *o, *n;
1175     GList *current = NULL;
1176
1177     for (o = oldpaths, n = newpaths; o != NULL && n != NULL;) {
1178         int r = strcmp(o->data, n->data);
1179         if (r == 0) {
1180             current = g_list_prepend(current, o->data);
1181             g_free(n->data);
1182             o = o->next;
1183             n = n->next;
1184         } else if (r < 0) { /* removed */
1185             scanner_mount_pending_add_or_delete(scanner, o->data);
1186             o = o->next;
1187         } else { /* added (goes to both pending and current) */
1188             current = g_list_prepend(current, g_strdup(n->data));
1189             scanner_mount_pending_add_or_delete(scanner, n->data);
1190             n = n->next;
1191         }
1192     }
1193
1194     for (; o != NULL; o = o->next)
1195         scanner_mount_pending_add_or_delete(scanner, o->data);
1196     for (; n != NULL; n = n->next) {
1197         current = g_list_prepend(current, g_strdup(n->data));
1198         scanner_mount_pending_add_or_delete(scanner, n->data);
1199     }
1200
1201     scanner->mounts.paths = g_list_reverse(current);
1202
1203     g_list_free(oldpaths);
1204     g_list_free(newpaths);
1205
1206     if (scanner->mounts.timer) {
1207         g_source_remove(scanner->mounts.timer);
1208         scanner->mounts.timer = 0;
1209     }
1210     if (scanner->mounts.pending) {
1211         scanner->mounts.timer = g_timeout_add(SCAN_MOUNTPOINTS_TIMEOUT * 1000,
1212                                               on_scan_mountpoints_timeout,
1213                                               scanner);
1214     }
1215
1216     return TRUE;
1217 }
1218
1219 static void
1220 scanner_destroyed(gpointer data)
1221 {
1222     scanner_t *scanner = data;
1223
1224     g_free(scanner->write_lock);
1225
1226     if (scanner->mounts.timer)
1227         g_source_remove(scanner->mounts.timer);
1228     if (scanner->mounts.watch)
1229         g_source_remove(scanner->mounts.watch);
1230     if (scanner->mounts.channel)
1231         g_io_channel_unref(scanner->mounts.channel);
1232     g_list_free_full(scanner->mounts.paths, g_free);
1233     g_list_free_full(scanner->mounts.pending, g_free);
1234
1235     if (scanner->write_lock_name_watcher)
1236         g_bus_unwatch_name(scanner->write_lock_name_watcher);
1237
1238     if (scanner->thread) {
1239         g_warning("Shutdown while scanning, wait...");
1240         g_thread_join(scanner->thread);
1241     }
1242
1243     if (scanner->cleanup_thread_idler) {
1244         g_source_remove(scanner->cleanup_thread_idler);
1245         scanner_thread_cleanup(scanner);
1246     }
1247
1248     if (scanner->changed_props.idler) {
1249         g_source_remove(scanner->changed_props.idler);
1250         scanner_dbus_props_changed(scanner);
1251     }
1252
1253     g_assert(scanner->thread == NULL);
1254     g_assert(scanner->pending_scan == NULL);
1255     g_assert(scanner->cleanup_thread_idler == 0);
1256     g_assert(scanner->pending_stop == NULL);
1257     g_assert(scanner->changed_props.idler == 0);
1258
1259     g_free(scanner);
1260 }
1261
1262 static const GDBusInterfaceVTable scanner_vtable = {
1263     scanner_method_call,
1264     scanner_get_prop,
1265     NULL
1266 };
1267
1268 static void
1269 on_name_acquired(GDBusConnection *conn, const gchar *name, gpointer data)
1270 {
1271     GDBusInterfaceInfo *iface;
1272     GError *error = NULL;
1273     unsigned id;
1274     scanner_t *scanner;
1275
1276     scanner = g_new0(scanner_t, 1);
1277     g_assert(scanner != NULL);
1278     scanner->conn = conn;
1279     scanner->pending_scan = NULL;
1280     scanner->update_id = get_update_id();
1281
1282     iface = g_dbus_node_info_lookup_interface(introspection_data, BUS_IFACE);
1283
1284     id = g_dbus_connection_register_object(conn,
1285                                            BUS_PATH,
1286                                            iface,
1287                                            &scanner_vtable,
1288                                            scanner,
1289                                            scanner_destroyed,
1290                                            NULL);
1291     g_assert(id > 0);
1292
1293     scanner->mounts.channel = g_io_channel_new_file("/proc/self/mountinfo",
1294                                                     "r", &error);
1295     if (error) {
1296       g_warning("No /proc/self/mountinfo file: %s. Disabled mount monitoring.",
1297                 error->message);
1298       g_error_free(error);
1299     } else {
1300         scanner->mounts.watch = g_io_add_watch(scanner->mounts.channel,
1301                                                G_IO_ERR,
1302                                                on_mounts_changed, scanner);
1303         scanner->mounts.paths = scanner_mounts_parse(scanner);
1304     }
1305
1306     if (startup_scan) {
1307         g_debug("Do startup scan");
1308         g_hash_table_foreach(categories, scan_params_all, scanner);
1309         do_scan(scanner);
1310     }
1311
1312     scanner_update_id_changed(scanner);
1313     scanner_write_lock_changed(scanner);
1314     scanner_is_scanning_changed(scanner);
1315     scanner_categories_changed(scanner);
1316
1317     g_debug("Acquired name org.lightmediascanner and registered object");
1318 }
1319
1320 static gboolean
1321 str_array_find(const GArray *arr, const char *str)
1322 {
1323     char **itr;
1324     for (itr = (char **)arr->data; *itr; itr++)
1325         if (strcmp(*itr, str) == 0)
1326             return TRUE;
1327
1328     return FALSE;
1329 }
1330
1331 static scanner_category_t *
1332 scanner_category_get_or_add(const char *category)
1333 {
1334     scanner_category_t *sc = g_hash_table_lookup(categories, category);
1335     if (sc)
1336         return sc;
1337
1338     sc = scanner_category_new(category);
1339     g_hash_table_insert(categories, sc->category, sc);
1340     return sc;
1341 }
1342
1343 static void
1344 scanner_category_add_parser(scanner_category_t *sc, const char *parser)
1345 {
1346     char *p;
1347
1348     if (str_array_find(sc->parsers, parser))
1349         return;
1350
1351     p = g_strdup(parser);
1352     g_array_append_val(sc->parsers, p);
1353 }
1354
1355 static void
1356 scanner_category_add_dir(scanner_category_t *sc, const char *dir)
1357 {
1358     char *p;
1359
1360     if (str_array_find(sc->dirs, dir))
1361         return;
1362
1363     p = g_strdup(dir);
1364     g_array_append_val(sc->dirs, p);
1365 }
1366
1367 static int
1368 populate_categories(void *data, const char *path)
1369 {
1370     struct lms_parser_info *info;
1371     const char * const *itr;
1372     long do_parsers = (long)data;
1373
1374     info = lms_parser_info(path);
1375     if (!info)
1376         return 1;
1377
1378     if (strcmp(info->name, "dummy") == 0)
1379         goto end;
1380
1381     if (!info->categories)
1382         goto end;
1383
1384
1385     for (itr = info->categories; *itr != NULL; itr++) {
1386         scanner_category_t *sc;
1387
1388         if (strcmp(*itr, "all") == 0)
1389             continue;
1390
1391         sc = scanner_category_get_or_add(*itr);
1392
1393         if (do_parsers)
1394             scanner_category_add_parser(sc, path);
1395     }
1396
1397 end:
1398     lms_parser_info_free(info);
1399
1400     return 1;
1401 }
1402
1403 static int
1404 populate_category_all_parsers(void *data, const char *path)
1405 {
1406     struct lms_parser_info *info;
1407     scanner_category_t *sc = data;
1408
1409     info = lms_parser_info(path);
1410     if (!info)
1411         return 1;
1412
1413     if (strcmp(info->name, "dummy") != 0)
1414         scanner_category_add_parser(sc, path);
1415
1416     lms_parser_info_free(info);
1417     return 1;
1418 }
1419
1420 static int
1421 populate_category_parsers(void *data, const char *path, const struct lms_parser_info *info)
1422 {
1423     scanner_category_t *sc = data;
1424
1425     if (strcmp(info->name, "dummy") != 0)
1426         scanner_category_add_parser(sc, path);
1427
1428     return 1;
1429 }
1430
1431 static void
1432 _populate_parser_internal(const char *category, const char *parser)
1433 {
1434     scanner_category_t *sc = scanner_category_get_or_add(category);
1435
1436     if (strcmp(parser, "all") == 0)
1437         lms_parsers_list(populate_category_all_parsers, sc);
1438     else if (strcmp(parser, "all-category") == 0)
1439         lms_parsers_list_by_category(sc->category, populate_category_parsers,
1440                                      sc);
1441     else
1442         scanner_category_add_parser(sc, parser);
1443 }
1444
1445 static void
1446 populate_parser_foreach(gpointer key, gpointer value, gpointer user_data)
1447 {
1448     const char *category = key;
1449     const char *parser = user_data;
1450     _populate_parser_internal(category, parser);
1451 }
1452
1453 static void
1454 populate_parser(const char *category, const char *parser)
1455 {
1456     if (!category)
1457         g_hash_table_foreach(categories, populate_parser_foreach,
1458                              (gpointer)parser);
1459     else
1460         _populate_parser_internal(category, parser);
1461 }
1462
1463 static void
1464 _populate_dir_internal(const char *category, const char *dir)
1465 {
1466     scanner_category_t *sc = scanner_category_get_or_add(category);
1467
1468     if (strcmp(dir, "defaults") != 0)
1469         scanner_category_add_dir(sc, dir);
1470     else {
1471         struct {
1472             const char *cat;
1473             const char *path;
1474         } *itr, defaults[] = {
1475             {"audio", g_get_user_special_dir(G_USER_DIRECTORY_MUSIC)},
1476             {"video", g_get_user_special_dir(G_USER_DIRECTORY_VIDEOS)},
1477             {"picture", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES)},
1478             {"multimedia", g_get_user_special_dir(G_USER_DIRECTORY_MUSIC)},
1479             {"multimedia", g_get_user_special_dir(G_USER_DIRECTORY_VIDEOS)},
1480             {"multimedia", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES)},
1481             {NULL, NULL}
1482         };
1483         for (itr = defaults; itr->cat != NULL; itr++) {
1484             if (strcmp(itr->cat, category) == 0)
1485                 scanner_category_add_dir(sc, itr->path);
1486         }
1487     }
1488 }
1489
1490 static void
1491 populate_dir_foreach(gpointer key, gpointer value, gpointer user_data)
1492 {
1493     const char *category = key;
1494     const char *dir = user_data;
1495     _populate_dir_internal(category, dir);
1496 }
1497
1498 static void
1499 populate_dir(const char *category, const char *dir)
1500 {
1501     if (!category)
1502         g_hash_table_foreach(categories, populate_dir_foreach,
1503                              (gpointer)dir);
1504     else
1505         _populate_dir_internal(category, dir);
1506 }
1507
1508 static void
1509 debug_categories(gpointer key, gpointer value, gpointer user_data)
1510 {
1511     const scanner_category_t *sc = value;
1512     const char * const *itr;
1513
1514     g_debug("category: %s", sc->category);
1515
1516     if (sc->parsers->len) {
1517         for (itr = (const char * const *)sc->parsers->data; *itr != NULL; itr++)
1518             g_debug("  parser: %s", *itr);
1519     } else
1520             g_debug("  parser: <none>");
1521
1522     if (sc->dirs->len) {
1523         for (itr = (const char * const *)sc->dirs->data; *itr != NULL; itr++)
1524             g_debug("  dir...: %s", *itr);
1525     } else
1526             g_debug("  dir...: <none>");
1527 }
1528
1529 static gboolean
1530 on_sig_term(gpointer data)
1531 {
1532     GMainLoop *loop = data;
1533
1534     g_debug("got SIGTERM, exit.");
1535     g_main_loop_quit(loop);
1536     return FALSE;
1537 }
1538
1539 static gboolean
1540 on_sig_int(gpointer data)
1541 {
1542     GMainLoop *loop = data;
1543
1544     g_debug("got SIGINT, exit.");
1545     g_main_loop_quit(loop);
1546     return FALSE;
1547 }
1548
1549 int
1550 main(int argc, char *argv[])
1551 {
1552     int ret = EXIT_SUCCESS;
1553     unsigned id;
1554     GMainLoop *loop;
1555     GError *error = NULL;
1556     GOptionContext *opt_ctx;
1557     char **parsers = NULL;
1558     char **dirs = NULL;
1559     GOptionEntry opt_entries[] = {
1560         {"db-path", 'p', 0, G_OPTION_ARG_FILENAME, &db_path,
1561          "Path to LightMediaScanner SQLit3 data base, "
1562          "defaults to \"~/.config/lightmediascannerd/db.sqlite3\".",
1563          "PATH"},
1564         {"commit-interval", 'c', 0, G_OPTION_ARG_INT, &commit_interval,
1565          "Execute SQL COMMIT after NUMBER files are processed, "
1566          "defaults to 100.",
1567          "NUMBER"},
1568         {"slave-timeout", 't', 0, G_OPTION_ARG_INT, &slave_timeout,
1569          "Number of seconds to wait for slave to reply, otherwise kills it. "
1570          "Defaults to 60.",
1571          "SECONDS"},
1572         {"delete-older-than", 'd', 0, G_OPTION_ARG_INT, &delete_older_than,
1573          "Delete from database files that have 'dtime' older than the given "
1574          "number of DAYS. If not specified LightMediaScanner will keep the "
1575          "files in the database even if they are gone from the file system "
1576          "and if they appear again and have the same 'mtime' and 'size' "
1577          "it will be restored ('dtime' set to 0) without the need to parse "
1578          "the file again (much faster). This is useful for removable media. "
1579          "Use a negative number to disable this behavior. "
1580          "Defaults to 30.",
1581          "DAYS"},
1582         {"vacuum", 'V', 0, G_OPTION_ARG_NONE, &vacuum,
1583          "Execute SQL VACUUM after every scan.", NULL},
1584         {"startup-scan", 'S', 0, G_OPTION_ARG_NONE, &startup_scan,
1585          "Execute full scan on startup.", NULL},
1586         {"omit-scan-progress", 0, 0, G_OPTION_ARG_NONE, &omit_scan_progress,
1587          "Omit the ScanProgress signal during scans. This will avoid the "
1588          "overhead of D-Bus signal emission and may slightly improve the "
1589          "performance, but will make the listener user-interfaces less "
1590          "responsive as they won't be able to tell the user what is happening.",
1591          NULL},
1592         {"charset", 'C', 0, G_OPTION_ARG_STRING_ARRAY, &charsets,
1593          "Extra charset to use. (Multiple use)", "CHARSET"},
1594         {"parser", 'P', 0, G_OPTION_ARG_STRING_ARRAY, &parsers,
1595          "Parsers to use, defaults to all. Format is 'category:parsername' or "
1596          "'parsername' to apply parser to all categories. The special "
1597          "parsername 'all' declares all known parsers, while 'all-category' "
1598          "declares all parsers of that category. If one parser is provided, "
1599          "then no defaults are used, you can pre-populate all categories "
1600          "with their parsers by using --parser=all-category.",
1601          "CATEGORY:PARSER"},
1602         {"directory", 'D', 0, G_OPTION_ARG_STRING_ARRAY, &dirs,
1603          "Directories to use, defaults to FreeDesktop.Org standard. "
1604          "Format is 'category:directory' or 'path' to "
1605          "apply directory to all categories. The special directory "
1606          "'defaults' declares all directories used by default for that "
1607          "category. If one directory is provided, then no defaults are used, "
1608          "you can pre-populate all categories with their directories by "
1609          "using --directory=defaults.",
1610          "CATEGORY:DIRECTORY"},
1611         {NULL, 0, 0, 0, NULL, NULL, NULL}
1612     };
1613
1614     opt_ctx = g_option_context_new(
1615         "\nLightMediaScanner D-Bus daemon.\n\n"
1616         "Usually there is no need to declare options, defaults should "
1617         "be good. However one may want the defaults and in addition to scan "
1618         "everything in an USB with:\n\n"
1619         "\tlightmediascannerd --directory=defaults --parser=all-category "
1620         "--directory=usb:/media/usb --parser=usb:all");
1621     g_option_context_add_main_entries(opt_ctx, opt_entries,
1622                                       "lightmediascannerd");
1623     if (!g_option_context_parse(opt_ctx, &argc, &argv, &error)) {
1624         g_option_context_free(opt_ctx);
1625         g_error("Option parsing failed: %s\n", error->message);
1626         g_error_free(error);
1627         return EXIT_FAILURE;
1628     }
1629
1630     g_option_context_free(opt_ctx);
1631
1632     categories = g_hash_table_new_full(
1633         g_str_hash, g_str_equal, NULL,
1634         (GDestroyNotify)scanner_category_destroy);
1635
1636     if (!db_path)
1637         db_path = g_strdup_printf("%s/lightmediascannerd/db.sqlite3",
1638                                   g_get_user_config_dir());
1639     if (!g_file_test(db_path, G_FILE_TEST_EXISTS)) {
1640         char *dname = g_path_get_dirname(db_path);
1641         if (!g_file_test(dname, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
1642             if (g_mkdir_with_parents(dname, 0755) != 0) {
1643                 g_error("Couldn't create directory %s", dname);
1644                 g_free(dname);
1645                 ret = EXIT_FAILURE;
1646                 goto end_options;
1647             }
1648         }
1649         g_free(dname);
1650     }
1651
1652     if (!parsers)
1653         lms_parsers_list(populate_categories, (void *)1L);
1654     else {
1655         char **itr;
1656
1657         lms_parsers_list(populate_categories, (void *)0L);
1658
1659         for (itr = parsers; *itr != NULL; itr++) {
1660             char *sep = strchr(*itr, ':');
1661             const char *path;
1662             if (!sep)
1663                 path = *itr;
1664             else {
1665                 path = sep + 1;
1666                 *sep = '\0';
1667             }
1668
1669             if (path[0] != '\0')
1670                 populate_parser(sep ? *itr : NULL, path);
1671
1672             if (sep)
1673                 *sep = ':';
1674         }
1675     }
1676
1677     if (!dirs)
1678         populate_dir(NULL, "defaults");
1679     else {
1680         char **itr;
1681
1682         for (itr = dirs; *itr != NULL; itr++) {
1683             char *sep = strchr(*itr, ':');
1684             const char *path;
1685             if (!sep)
1686                 path = *itr;
1687             else {
1688                 path = sep + 1;
1689                 *sep = '\0';
1690             }
1691
1692             if (path[0] != '\0')
1693                 populate_dir(sep ? *itr : NULL, path);
1694
1695             if (sep)
1696                 *sep = ':';
1697         }
1698     }
1699
1700     g_debug("db-path: %s", db_path);
1701     g_debug("commit-interval: %d files", commit_interval);
1702     g_debug("slave-timeout: %d seconds", slave_timeout);
1703     g_debug("delete-older-than: %d days", delete_older_than);
1704
1705     if (charsets) {
1706         char *tmp = g_strjoinv(", ", charsets);
1707         g_debug("charsets: %s", tmp);
1708         g_free(tmp);
1709     } else
1710         g_debug("charsets: <none>");
1711
1712     g_hash_table_foreach(categories, debug_categories, NULL);
1713
1714     introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
1715     g_assert(introspection_xml != NULL);
1716
1717     id = g_bus_own_name(G_BUS_TYPE_SESSION, "org.lightmediascanner",
1718                         G_BUS_NAME_OWNER_FLAGS_NONE,
1719                         NULL, on_name_acquired, NULL, NULL, NULL);
1720
1721     g_debug("starting main loop");
1722
1723     loop = g_main_loop_new(NULL, FALSE);
1724     g_unix_signal_add(SIGTERM, on_sig_term, loop);
1725     g_unix_signal_add(SIGINT, on_sig_int, loop);
1726     g_main_loop_run(loop);
1727
1728     g_debug("main loop is finished");
1729
1730     g_bus_unown_name(id);
1731     g_main_loop_unref(loop);
1732
1733     g_dbus_node_info_unref(introspection_data);
1734
1735 end_options:
1736     g_free(db_path);
1737     g_strfreev(charsets);
1738     g_strfreev(parsers);
1739     g_strfreev(dirs);
1740
1741     if (categories)
1742         g_hash_table_destroy(categories);
1743
1744     return ret;
1745 }