1 #include "lightmediascanner.h"
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;
19 static GDBusNodeInfo *introspection_data = NULL;
21 static const char BUS_PATH[] = "/org/lightmediascanner/Scanner1";
22 static const char BUS_IFACE[] = "org.lightmediascanner.Scanner1";
24 static const char introspection_xml[] =
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\" />"
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\" />"
50 typedef struct scanner_category
57 typedef struct scanner_pending
63 typedef struct scan_progress {
64 GDBusConnection *conn;
72 time_t last_report_time;
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.
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.
85 * Note that at after a path is scanned (check/progress) the signal is
86 * emitted even if count or timeout didn't match.
88 #define SCAN_PROGRESS_UPDATE_TIMEOUT 1 /* in seconds */
89 #define SCAN_PROGRESS_UPDATE_COUNT 50 /* in number of items */
91 #define SCAN_MOUNTPOINTS_TIMEOUT 1 /* in seconds */
93 typedef struct scanner {
94 GDBusConnection *conn;
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;
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;
119 static scanner_category_t *
120 scanner_category_new(const char *category)
122 scanner_category_t *sc = g_new0(scanner_category_t, 1);
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 *));
132 scanner_category_destroy(scanner_category_t *sc)
136 for (itr = (char **)sc->parsers->data; *itr != NULL; itr++)
138 for (itr = (char **)sc->dirs->data; *itr != NULL; itr++)
141 g_array_free(sc->parsers, TRUE);
142 g_array_free(sc->dirs, TRUE);
143 g_free(sc->category);
148 static void scanner_release_write_lock(scanner_t *scanner);
151 scanner_write_lock_vanished(GDBusConnection *conn, const char *name, gpointer data)
153 scanner_t *scanner = data;
154 g_warning("Write lock holder %s vanished, release lock\n", name);
155 scanner_release_write_lock(scanner);
161 const char sql[] = "SELECT version FROM lms_internal WHERE tab='update_id'";
165 guint64 update_id = 0;
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));
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));
179 ret = sqlite3_step(stmt);
180 if (ret == SQLITE_DONE)
182 else if (ret == SQLITE_ROW)
183 update_id = sqlite3_column_int(stmt, 0);
185 g_warning("Couldn't run SQL to get update_id, ret=%d: %s",
186 ret, sqlite3_errmsg(db));
189 sqlite3_finalize(stmt);
194 g_debug("update id: %llu", (unsigned long long)update_id);
201 const char sql[] = "DELETE FROM files WHERE dtime > 0 and dtime <= ?";
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));
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));
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));
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));
233 sqlite3_finalize(stmt);
242 const char sql[] = "VACUUM";
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));
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));
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));
265 sqlite3_finalize(stmt);
272 check_write_locked(const scanner_t *scanner)
274 return scanner->write_lock != NULL || scanner->thread != NULL;
278 category_variant_foreach(gpointer key, gpointer value, gpointer user_data)
280 scanner_category_t *sc = value;
281 GVariantBuilder *builder = user_data;
282 GVariantBuilder *sub, *darr, *parr;
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);
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);
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));
297 g_variant_builder_add(builder, "{sv}", sc->category,
298 g_variant_builder_end(sub));
300 g_variant_builder_unref(sub);
301 g_variant_builder_unref(parr);
302 g_variant_builder_unref(darr);
306 categories_get_variant(void)
308 GVariantBuilder *builder;
311 builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
313 g_hash_table_foreach(categories, category_variant_foreach, builder);
315 variant = g_variant_builder_end(builder);
316 g_variant_builder_unref(builder);
322 scanner_dbus_props_changed(gpointer data)
324 GVariantBuilder *builder;
325 GError *error = NULL;
326 scanner_t *scanner = data;
327 guint64 update_id = 0;
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;
336 builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
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));
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)));
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));
354 if (scanner->changed_props.categories) {
355 scanner->changed_props.categories = FALSE;
356 g_variant_builder_add(builder, "{sv}", "Categories",
357 categories_get_variant());
360 g_dbus_connection_emit_signal(scanner->conn,
363 "org.freedesktop.DBus.Properties",
365 g_variant_new("(sa{sv}as)",
366 BUS_IFACE, builder, NULL),
368 g_variant_builder_unref(builder);
369 g_assert_no_error(error);
371 scanner->changed_props.idler = 0;
376 scanner_write_lock_changed(scanner_t *scanner)
378 if (scanner->changed_props.idler == 0)
379 scanner->changed_props.idler = g_idle_add(scanner_dbus_props_changed,
382 scanner->changed_props.write_locked = TRUE;
386 scanner_acquire_write_lock(scanner_t *scanner, const char *sender)
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);
394 scanner_write_lock_changed(scanner);
398 scanner_release_write_lock(scanner_t *scanner)
400 g_debug("release write lock previously owned by %s", scanner->write_lock);
402 g_free(scanner->write_lock);
403 scanner->write_lock = NULL;
405 g_bus_unwatch_name(scanner->write_lock_name_watcher);
406 scanner->write_lock_name_watcher = 0;
408 scanner_write_lock_changed(scanner);
412 scanner_pending_free(scanner_pending_t *pending)
414 g_list_free_full(pending->paths, g_free);
415 g_free(pending->category);
419 static scanner_pending_t *
420 scanner_pending_get_or_add(scanner_t *scanner, const char *category)
422 scanner_pending_t *pending;
423 GList *n, *nlast = NULL;
425 g_assert(scanner->thread == NULL);
427 for (n = scanner->pending_scan; n != NULL; n = n->next) {
430 if (strcmp(pending->category, category) == 0)
434 pending = g_new0(scanner_pending_t, 1);
435 pending->category = g_strdup(category);
436 pending->paths = NULL;
438 /* I can't believe there is no g_list_insert_after() :-( */
447 scanner->pending_scan = n;
452 /* NOTE: assumes array was already validated for duplicates/restrictions */
454 scanner_pending_add_all(scanner_pending_t *pending, const GArray *arr)
459 for (itr = (char **)arr->data; *itr != NULL; itr++)
460 pending->paths = g_list_prepend(pending->paths, g_strdup(*itr));
462 pending->paths = g_list_reverse(pending->paths);
466 scanner_category_allows_path(const GArray *restrictions, const char *path)
468 const char * const *itr;
469 for (itr = (const char *const*)restrictions->data; *itr != NULL; itr++) {
470 if (g_str_has_prefix(path, *itr))
477 scanner_pending_add(scanner_pending_t *pending, const GArray *restrictions, const char *path)
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);
491 if (restrictions && (!scanner_category_allows_path(restrictions, path))) {
492 g_warning("Path is outside of category %s directories: %s",
493 pending->category, path);
498 for (n = pending->paths; n != NULL; ) {
499 char *other = n->data;
503 if (!g_str_has_prefix(other, path))
508 g_debug("Path covers previous pending scan in category %s, "
510 pending->category, other, path);
513 nlast = n->next ? n->next : n->prev;
515 pending->paths = g_list_delete_link(pending->paths, n);
522 g_debug("New scan path for category %s: %s", pending->category, path);
524 /* I can't believe there is no g_list_insert_after() :-( */
526 n->data = g_strdup(path);
537 scan_params_all(gpointer key, gpointer value, gpointer user_data)
539 scanner_pending_t *pending;
540 scanner_category_t *sc = value;
541 scanner_t *scanner = user_data;
543 pending = scanner_pending_get_or_add(scanner, sc->category);
544 scanner_pending_add_all(pending, sc->dirs);
548 dbus_scanner_scan_params_set(scanner_t *scanner, GVariant *params)
553 gboolean empty = TRUE;
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;
561 gboolean nodirs = TRUE;
563 sc = g_hash_table_lookup(categories, cat);
565 g_warning("Unexpected scan category: %s, skipped.", cat);
569 pending = scanner_pending_get_or_add(scanner, cat);
572 g_variant_get(el, "as", &subitr);
573 while (g_variant_iter_loop(subitr, "s", &path)) {
574 scanner_pending_add(pending, sc->dirs, path);
579 scanner_pending_add_all(pending, sc->dirs);
581 g_variant_iter_free(itr);
584 g_hash_table_foreach(categories, scan_params_all, scanner);
588 scanner_is_scanning_changed(scanner_t *scanner)
590 if (scanner->changed_props.idler == 0)
591 scanner->changed_props.idler = g_idle_add(scanner_dbus_props_changed,
594 scanner->changed_props.is_scanning = TRUE;
598 report_scan_progress(gpointer data)
600 scan_progress_t *sp = data;
601 GError *error = NULL;
603 g_dbus_connection_emit_signal(sp->conn,
608 g_variant_new("(ssttttt)",
617 g_assert_no_error(error);
623 report_scan_progress_and_free(gpointer data)
625 scan_progress_t *sp = data;
628 report_scan_progress(sp);
630 g_object_unref(sp->conn);
631 g_free(sp->category);
638 scan_progress_cb(lms_t *lms, const char *path, int pathlen, lms_progress_status_t status, void *data)
640 const scanner_t *scanner = data;
641 scan_progress_t *sp = scanner->scan_progress;
643 if (scanner->pending_stop)
644 lms_stop_processing(lms);
650 case LMS_PROGRESS_STATUS_UP_TO_DATE:
653 case LMS_PROGRESS_STATUS_PROCESSED:
656 case LMS_PROGRESS_STATUS_DELETED:
659 case LMS_PROGRESS_STATUS_KILLED:
660 case LMS_PROGRESS_STATUS_ERROR_PARSE:
661 case LMS_PROGRESS_STATUS_ERROR_COMM:
664 case LMS_PROGRESS_STATUS_SKIPPED:
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;
675 g_idle_add(report_scan_progress, sp);
681 setup_lms(const char *category, const scanner_t *scanner)
683 scanner_category_t *sc;
687 sc = g_hash_table_lookup(categories, category);
689 g_error("Unknown category %s", category);
693 if (sc->parsers->len == 0) {
694 g_warning("No parsers for category %s", category);
698 lms = lms_new(db_path);
700 g_warning("Failed to create lms");
704 lms_set_commit_interval(lms, commit_interval);
705 lms_set_slave_timeout(lms, slave_timeout * 1000);
708 for (itr = charsets; *itr != NULL; itr++)
709 if (lms_charset_add(lms, *itr) != 0)
710 g_warning("Couldn't add charset: %s", *itr);
713 for (itr = (char **)sc->parsers->data; *itr != NULL; itr++) {
714 const char *parser = *itr;
715 lms_plugin_t *plugin;
717 if (parser[0] == '/')
718 plugin = lms_parser_add(lms, parser);
720 plugin = lms_parser_find_and_add(lms, parser);
723 g_warning("Couldn't add parser: %s", parser);
726 lms_set_progress_callback(lms, scan_progress_cb, scanner, NULL);
731 static void scan_mountpoints(scanner_t *scanner);
734 scanner_thread_cleanup(gpointer data)
736 scanner_t *scanner = data;
739 g_debug("cleanup scanner work thread");
740 ret = g_thread_join(scanner->thread);
741 g_assert(ret == scanner);
743 scanner->thread = NULL;
744 scanner->cleanup_thread_idler = 0;
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;
752 g_list_free_full(scanner->pending_scan,
753 (GDestroyNotify)scanner_pending_free);
754 scanner->pending_scan = NULL;
756 if (scanner->mounts.pending && !scanner->mounts.timer)
757 scan_mountpoints(scanner);
759 scanner_is_scanning_changed(scanner);
760 scanner_write_lock_changed(scanner);
767 * Note on thread usage and locks (or lack of locks):
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.
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*.
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.
787 scanner_thread_work(gpointer data)
790 scanner_t *scanner = data;
792 g_debug("started scanner thread");
794 lst = scanner->pending_scan;
795 scanner->pending_scan = NULL;
797 scanner_pending_t *pending;
800 if (scanner->pending_stop)
804 lst = g_list_delete_link(lst, lst);
806 g_debug("scan category: %s", pending->category);
807 lms = setup_lms(pending->category, scanner);
809 while (pending->paths) {
811 scan_progress_t *sp = NULL;
813 if (scanner->pending_stop)
816 path = pending->paths->data;
817 pending->paths = g_list_delete_link(pending->paths,
820 g_debug("scan category %s, path %s", pending->category, path);
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;
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);
837 g_idle_add(report_scan_progress_and_free, sp);
844 scanner_pending_free(pending);
847 g_debug("finished scanner thread");
849 if (delete_older_than >= 0) {
850 g_debug("Delete from DB files with dtime older than %d days.",
856 GTimer *timer = g_timer_new();
858 g_debug("Starting SQL VACUUM...");
859 g_timer_start(timer);
862 g_debug("Finished VACUUM in %0.3f seconds.",
863 g_timer_elapsed(timer, NULL));
864 g_timer_destroy(timer);
867 scanner->cleanup_thread_idler = g_idle_add(scanner_thread_cleanup, scanner);
873 do_scan(scanner_t *scanner)
875 scanner->thread = g_thread_new("scanner", scanner_thread_work, scanner);
877 scanner_is_scanning_changed(scanner);
878 scanner_write_lock_changed(scanner);
882 dbus_scanner_scan(GDBusMethodInvocation *inv, scanner_t *scanner, GVariant *params)
884 if (scanner->thread) {
885 g_dbus_method_invocation_return_dbus_error(
886 inv, "org.lightmediascanner.AlreadyScanning",
887 "Scanner was already scanning.");
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.");
898 dbus_scanner_scan_params_set(scanner, params);
902 g_dbus_method_invocation_return_value(inv, NULL);
906 dbus_scanner_stop(GDBusMethodInvocation *inv, scanner_t *scanner)
908 if (!scanner->thread) {
909 g_dbus_method_invocation_return_dbus_error(
910 inv, "org.lightmediascanner.NotScanning",
911 "Scanner was already stopped.");
914 if (scanner->pending_stop) {
915 g_dbus_method_invocation_return_dbus_error(
916 inv, "org.lightmediascanner.AlreadyStopping",
917 "Scanner was already being stopped.");
921 scanner->pending_stop = g_object_ref(inv);
925 dbus_scanner_request_write_lock(GDBusMethodInvocation *inv, scanner_t *scanner, const char *sender)
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");
935 g_dbus_method_invocation_return_dbus_error(
936 inv, "org.lightmediascanner.IsScanning",
937 "Scanner is scanning and can't grant a write lock");
941 scanner_acquire_write_lock(scanner, sender);
942 g_dbus_method_invocation_return_value(inv, NULL);
946 dbus_scanner_release_write_lock(GDBusMethodInvocation *inv, scanner_t *scanner, const char *sender)
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.");
955 scanner_release_write_lock(scanner);
956 g_dbus_method_invocation_return_value(inv, NULL);
960 scanner_method_call(GDBusConnection *conn, const char *sender, const char *opath, const char *iface, const char *method, GVariant *params, GDBusMethodInvocation *inv, gpointer data)
962 scanner_t *scanner = data;
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);
975 scanner_update_id_changed(scanner_t *scanner)
977 if (scanner->changed_props.idler == 0)
978 scanner->changed_props.idler = g_idle_add(scanner_dbus_props_changed,
981 scanner->changed_props.update_id = TRUE;
985 scanner_categories_changed(scanner_t *scanner)
987 if (scanner->changed_props.idler == 0)
988 scanner->changed_props.idler = g_idle_add(scanner_dbus_props_changed,
991 scanner->changed_props.categories = TRUE;
995 scanner_get_prop(GDBusConnection *conn, const char *sender, const char *opath, const char *iface, const char *prop, GError **error, gpointer data)
997 scanner_t *scanner = data;
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;
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);
1015 ret = g_variant_new_uint64(scanner->update_id);
1016 } else if (strcmp(prop, "Categories") == 0)
1017 ret = categories_get_variant();
1024 struct scanner_should_monitor_data {
1025 const char *mountpoint;
1026 gboolean should_monitor;
1030 category_should_monitor(gpointer key, gpointer value, gpointer user_data)
1032 const scanner_category_t *sc = value;
1033 struct scanner_should_monitor_data *data = user_data;
1035 if (data->should_monitor)
1038 if (scanner_category_allows_path(sc->dirs, data->mountpoint)) {
1039 data->should_monitor = TRUE;
1045 should_monitor(const char *mountpoint)
1047 struct scanner_should_monitor_data data = {mountpoint, FALSE};
1048 g_hash_table_foreach(categories, category_should_monitor, &data);
1049 return data.should_monitor;
1053 scanner_mounts_parse(scanner_t *scanner)
1055 GList *paths = NULL;
1057 GError *error = NULL;
1059 status = g_io_channel_seek_position(scanner->mounts.channel, 0, G_SEEK_SET,
1062 g_warning("Couldn't rewind mountinfo channel: %s", error->message);
1070 int mount_id, parent_id, major, minor;
1071 char root[1024], mountpoint[1024];
1073 status = g_io_channel_read_line(scanner->mounts.channel, &str, NULL,
1076 case G_IO_STATUS_AGAIN:
1078 case G_IO_STATUS_NORMAL:
1081 case G_IO_STATUS_ERROR:
1082 g_warning("Couldn't read line of mountinfo: %s", error->message);
1084 case G_IO_STATUS_EOF:
1085 return g_list_sort(paths, (GCompareFunc)strcmp);
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);
1092 char *mp = g_strcompress(mountpoint);
1094 if (!should_monitor(mp)) {
1095 g_debug("Ignored mountpoint: %s. Not in any category.", mp);
1098 g_debug("Got mountpoint: %s", mp);
1099 paths = g_list_prepend(paths, mp);
1108 scanner_mount_pending_add_or_delete(scanner_t *scanner, gchar *mountpoint)
1112 for (itr = scanner->mounts.pending; itr != NULL; itr = itr->next) {
1113 if (strcmp(itr->data, mountpoint) == 0) {
1119 scanner->mounts.pending = g_list_prepend(scanner->mounts.pending,
1124 category_scan_pending_mountpoints(gpointer key, gpointer value, gpointer user_data)
1126 scanner_pending_t *pending = NULL;
1127 scanner_category_t *sc = value;
1128 scanner_t *scanner = user_data;
1131 for (n = scanner->mounts.pending; n != NULL; n = n->next) {
1132 const char *mountpoint = n->data;
1134 if (scanner_category_allows_path(sc->dirs, mountpoint)) {
1136 pending = scanner_pending_get_or_add(scanner, sc->category);
1138 scanner_pending_add(pending, NULL, mountpoint);
1144 scan_mountpoints(scanner_t *scanner)
1146 g_assert(scanner->thread == NULL);
1148 g_hash_table_foreach(categories, category_scan_pending_mountpoints,
1150 g_list_free_full(scanner->mounts.pending, g_free);
1151 scanner->mounts.pending = NULL;
1157 on_scan_mountpoints_timeout(gpointer data)
1159 scanner_t *scanner = data;
1161 if (!scanner->thread)
1162 scan_mountpoints(scanner);
1164 scanner->mounts.timer = 0;
1169 on_mounts_changed(GIOChannel *source, GIOCondition cond, gpointer data)
1171 scanner_t *scanner = data;
1172 GList *oldpaths = scanner->mounts.paths;
1173 GList *newpaths = scanner_mounts_parse(scanner);
1175 GList *current = NULL;
1177 for (o = oldpaths, n = newpaths; o != NULL && n != NULL;) {
1178 int r = strcmp(o->data, n->data);
1180 current = g_list_prepend(current, o->data);
1184 } else if (r < 0) { /* removed */
1185 scanner_mount_pending_add_or_delete(scanner, o->data);
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);
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);
1201 scanner->mounts.paths = g_list_reverse(current);
1203 g_list_free(oldpaths);
1204 g_list_free(newpaths);
1206 if (scanner->mounts.timer) {
1207 g_source_remove(scanner->mounts.timer);
1208 scanner->mounts.timer = 0;
1210 if (scanner->mounts.pending) {
1211 scanner->mounts.timer = g_timeout_add(SCAN_MOUNTPOINTS_TIMEOUT * 1000,
1212 on_scan_mountpoints_timeout,
1220 scanner_destroyed(gpointer data)
1222 scanner_t *scanner = data;
1224 g_free(scanner->write_lock);
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);
1235 if (scanner->write_lock_name_watcher)
1236 g_bus_unwatch_name(scanner->write_lock_name_watcher);
1238 if (scanner->thread) {
1239 g_warning("Shutdown while scanning, wait...");
1240 g_thread_join(scanner->thread);
1243 if (scanner->cleanup_thread_idler) {
1244 g_source_remove(scanner->cleanup_thread_idler);
1245 scanner_thread_cleanup(scanner);
1248 if (scanner->changed_props.idler) {
1249 g_source_remove(scanner->changed_props.idler);
1250 scanner_dbus_props_changed(scanner);
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);
1262 static const GDBusInterfaceVTable scanner_vtable = {
1263 scanner_method_call,
1269 on_name_acquired(GDBusConnection *conn, const gchar *name, gpointer data)
1271 GDBusInterfaceInfo *iface;
1272 GError *error = NULL;
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();
1282 iface = g_dbus_node_info_lookup_interface(introspection_data, BUS_IFACE);
1284 id = g_dbus_connection_register_object(conn,
1293 scanner->mounts.channel = g_io_channel_new_file("/proc/self/mountinfo",
1296 g_warning("No /proc/self/mountinfo file: %s. Disabled mount monitoring.",
1298 g_error_free(error);
1300 scanner->mounts.watch = g_io_add_watch(scanner->mounts.channel,
1302 on_mounts_changed, scanner);
1303 scanner->mounts.paths = scanner_mounts_parse(scanner);
1307 g_debug("Do startup scan");
1308 g_hash_table_foreach(categories, scan_params_all, scanner);
1312 scanner_update_id_changed(scanner);
1313 scanner_write_lock_changed(scanner);
1314 scanner_is_scanning_changed(scanner);
1315 scanner_categories_changed(scanner);
1317 g_debug("Acquired name org.lightmediascanner and registered object");
1321 str_array_find(const GArray *arr, const char *str)
1324 for (itr = (char **)arr->data; *itr; itr++)
1325 if (strcmp(*itr, str) == 0)
1331 static scanner_category_t *
1332 scanner_category_get_or_add(const char *category)
1334 scanner_category_t *sc = g_hash_table_lookup(categories, category);
1338 sc = scanner_category_new(category);
1339 g_hash_table_insert(categories, sc->category, sc);
1344 scanner_category_add_parser(scanner_category_t *sc, const char *parser)
1348 if (str_array_find(sc->parsers, parser))
1351 p = g_strdup(parser);
1352 g_array_append_val(sc->parsers, p);
1356 scanner_category_add_dir(scanner_category_t *sc, const char *dir)
1360 if (str_array_find(sc->dirs, dir))
1364 g_array_append_val(sc->dirs, p);
1368 populate_categories(void *data, const char *path)
1370 struct lms_parser_info *info;
1371 const char * const *itr;
1372 long do_parsers = (long)data;
1374 info = lms_parser_info(path);
1378 if (strcmp(info->name, "dummy") == 0)
1381 if (!info->categories)
1385 for (itr = info->categories; *itr != NULL; itr++) {
1386 scanner_category_t *sc;
1388 if (strcmp(*itr, "all") == 0)
1391 sc = scanner_category_get_or_add(*itr);
1394 scanner_category_add_parser(sc, path);
1398 lms_parser_info_free(info);
1404 populate_category_all_parsers(void *data, const char *path)
1406 struct lms_parser_info *info;
1407 scanner_category_t *sc = data;
1409 info = lms_parser_info(path);
1413 if (strcmp(info->name, "dummy") != 0)
1414 scanner_category_add_parser(sc, path);
1416 lms_parser_info_free(info);
1421 populate_category_parsers(void *data, const char *path, const struct lms_parser_info *info)
1423 scanner_category_t *sc = data;
1425 if (strcmp(info->name, "dummy") != 0)
1426 scanner_category_add_parser(sc, path);
1432 _populate_parser_internal(const char *category, const char *parser)
1434 scanner_category_t *sc = scanner_category_get_or_add(category);
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,
1442 scanner_category_add_parser(sc, parser);
1446 populate_parser_foreach(gpointer key, gpointer value, gpointer user_data)
1448 const char *category = key;
1449 const char *parser = user_data;
1450 _populate_parser_internal(category, parser);
1454 populate_parser(const char *category, const char *parser)
1457 g_hash_table_foreach(categories, populate_parser_foreach,
1460 _populate_parser_internal(category, parser);
1464 _populate_dir_internal(const char *category, const char *dir)
1466 scanner_category_t *sc = scanner_category_get_or_add(category);
1468 if (strcmp(dir, "defaults") != 0)
1469 scanner_category_add_dir(sc, dir);
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)},
1483 for (itr = defaults; itr->cat != NULL; itr++) {
1484 if (strcmp(itr->cat, category) == 0)
1485 scanner_category_add_dir(sc, itr->path);
1491 populate_dir_foreach(gpointer key, gpointer value, gpointer user_data)
1493 const char *category = key;
1494 const char *dir = user_data;
1495 _populate_dir_internal(category, dir);
1499 populate_dir(const char *category, const char *dir)
1502 g_hash_table_foreach(categories, populate_dir_foreach,
1505 _populate_dir_internal(category, dir);
1509 debug_categories(gpointer key, gpointer value, gpointer user_data)
1511 const scanner_category_t *sc = value;
1512 const char * const *itr;
1514 g_debug("category: %s", sc->category);
1516 if (sc->parsers->len) {
1517 for (itr = (const char * const *)sc->parsers->data; *itr != NULL; itr++)
1518 g_debug(" parser: %s", *itr);
1520 g_debug(" parser: <none>");
1522 if (sc->dirs->len) {
1523 for (itr = (const char * const *)sc->dirs->data; *itr != NULL; itr++)
1524 g_debug(" dir...: %s", *itr);
1526 g_debug(" dir...: <none>");
1530 on_sig_term(gpointer data)
1532 GMainLoop *loop = data;
1534 g_debug("got SIGTERM, exit.");
1535 g_main_loop_quit(loop);
1540 on_sig_int(gpointer data)
1542 GMainLoop *loop = data;
1544 g_debug("got SIGINT, exit.");
1545 g_main_loop_quit(loop);
1550 main(int argc, char *argv[])
1552 int ret = EXIT_SUCCESS;
1555 GError *error = NULL;
1556 GOptionContext *opt_ctx;
1557 char **parsers = 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\".",
1564 {"commit-interval", 'c', 0, G_OPTION_ARG_INT, &commit_interval,
1565 "Execute SQL COMMIT after NUMBER files are processed, "
1568 {"slave-timeout", 't', 0, G_OPTION_ARG_INT, &slave_timeout,
1569 "Number of seconds to wait for slave to reply, otherwise kills it. "
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. "
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.",
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.",
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}
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;
1630 g_option_context_free(opt_ctx);
1632 categories = g_hash_table_new_full(
1633 g_str_hash, g_str_equal, NULL,
1634 (GDestroyNotify)scanner_category_destroy);
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);
1653 lms_parsers_list(populate_categories, (void *)1L);
1657 lms_parsers_list(populate_categories, (void *)0L);
1659 for (itr = parsers; *itr != NULL; itr++) {
1660 char *sep = strchr(*itr, ':');
1669 if (path[0] != '\0')
1670 populate_parser(sep ? *itr : NULL, path);
1678 populate_dir(NULL, "defaults");
1682 for (itr = dirs; *itr != NULL; itr++) {
1683 char *sep = strchr(*itr, ':');
1692 if (path[0] != '\0')
1693 populate_dir(sep ? *itr : NULL, path);
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);
1706 char *tmp = g_strjoinv(", ", charsets);
1707 g_debug("charsets: %s", tmp);
1710 g_debug("charsets: <none>");
1712 g_hash_table_foreach(categories, debug_categories, NULL);
1714 introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
1715 g_assert(introspection_xml != NULL);
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);
1721 g_debug("starting main loop");
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);
1728 g_debug("main loop is finished");
1730 g_bus_unown_name(id);
1731 g_main_loop_unref(loop);
1733 g_dbus_node_info_unref(introspection_data);
1737 g_strfreev(charsets);
1738 g_strfreev(parsers);
1742 g_hash_table_destroy(categories);