lightmediascannerctl: start daemon if not running.
[platform/upstream/lightmediascanner.git] / src / bin / lightmediascannerctl.c
1 #include <gio/gio.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <time.h>
6
7 static gboolean
8 start_service_by_name(void)
9 {
10     GError *error = NULL;
11     GVariant *var;
12     GDBusConnection *conn;
13
14     conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
15     if (error) {
16         fprintf(stderr, "Could not get session bus connection: %s\n",
17                 error->message);
18         g_error_free(error);
19         return FALSE;
20     }
21
22     var = g_dbus_connection_call_sync(conn,
23                                       "org.freedesktop.DBus",
24                                       "/org/freedesktop/DBus",
25                                       "org.freedesktop.DBus",
26                                       "StartServiceByName",
27                                       g_variant_new("(su)",
28                                                     "org.lightmediascanner", 0),
29                                       G_VARIANT_TYPE("(u)"),
30                                       G_DBUS_CALL_FLAGS_NONE,
31                                       10000,
32                                       NULL,
33                                       &error);
34     g_object_unref(conn);
35     if (var)
36         g_variant_unref(var);
37
38     if (error) {
39         fprintf(stderr, "Could not start org.lightmediascanner: %s\n",
40                 error->message);
41         g_error_free(error);
42         return FALSE;
43     }
44
45     return TRUE;
46 }
47
48 struct app {
49     int ret;
50     int argc;
51     char **argv;
52     void (*action)(struct app *app);
53     GDBusProxy *proxy;
54     GMainLoop *loop;
55     GTimer *timer;
56 };
57
58 static void
59 print_server(GDBusProxy *proxy)
60 {
61     char **props, **itr;
62     char *nameowner;
63
64     nameowner = g_dbus_proxy_get_name_owner(proxy);
65     if (!nameowner) {
66         puts("Server is not running.");
67         return;
68     }
69
70     printf("Server at %s\n", nameowner);
71     props = g_dbus_proxy_get_cached_property_names(proxy);
72     if (!props)
73         return;
74
75     for (itr = props; *itr != NULL; itr++) {
76         GVariant *value = g_dbus_proxy_get_cached_property(proxy, *itr);
77         char *str = g_variant_print(value, TRUE);
78         printf("\t%s = %s\n", *itr, str);
79         g_variant_unref(value);
80         g_free(str);
81     }
82     g_strfreev(props);
83     g_free(nameowner);
84 }
85
86
87 static void
88 do_status(struct app *app)
89 {
90     print_server(app->proxy);
91     g_main_loop_quit(app->loop);
92     app->ret = EXIT_SUCCESS;
93 }
94
95 static void
96 on_properties_changed(GDBusProxy *proxy, GVariant *changed, const char *const *invalidated, gpointer user_data)
97 {
98     struct app *app = user_data;
99
100     printf("%015.3f --- Properties Changed ---\n",
101            g_timer_elapsed(app->timer, NULL));
102
103     if (g_variant_n_children(changed) > 0) {
104         GVariantIter *itr;
105         const char *prop;
106         GVariant *value;
107
108         printf("Changed Properties:");
109         g_variant_get(changed, "a{sv}", &itr);
110         while (g_variant_iter_loop(itr, "{&sv}", &prop, &value)) {
111             char *str;
112             str = g_variant_print(value, TRUE);
113             printf(" %s=%s", prop, str);
114             g_free(str);
115         }
116         g_variant_iter_free(itr);
117         printf("\n");
118     }
119
120     if (invalidated[0] != NULL) {
121         const char * const *itr;
122         printf("Invalidated Properties:");
123         for (itr = invalidated; *itr != NULL; itr++)
124             printf(" %s", *itr);
125         printf("\n");
126     }
127
128     print_server(proxy);
129 }
130
131 static gboolean
132 do_delayed_print_server(gpointer data)
133 {
134     GDBusProxy *proxy = data;
135     char **props;
136     char *nameowner;
137
138     nameowner = g_dbus_proxy_get_name_owner(proxy);
139     if (!nameowner) {
140         print_server(proxy);
141         return FALSE;
142     }
143     g_free(nameowner);
144
145     props = g_dbus_proxy_get_cached_property_names(proxy);
146     if (!props) {
147         g_timeout_add(1000, do_delayed_print_server, proxy);
148         return FALSE;
149     }
150
151     g_strfreev(props);
152     print_server(data);
153     return FALSE;
154 }
155
156 static void
157 on_name_owner_notify(GObject *object, GParamSpec *pspec, gpointer user_data)
158 {
159     GDBusProxy *proxy = G_DBUS_PROXY(object);
160     struct app *app = user_data;
161
162     printf("%015.3f --- Name Owner Changed ---\n",
163            g_timer_elapsed(app->timer, NULL));
164     do_delayed_print_server(proxy);
165 }
166
167 static void
168 do_monitor(struct app *app)
169 {
170     app->timer = g_timer_new();
171     g_timer_start(app->timer);
172
173     print_server(app->proxy);
174     g_signal_connect(app->proxy, "g-properties-changed",
175                      G_CALLBACK(on_properties_changed),
176                      app);
177     g_signal_connect(app->proxy, "notify::g-name-owner",
178                      G_CALLBACK(on_name_owner_notify),
179                      app);
180 }
181
182 static void
183 on_properties_changed_check_lock(GDBusProxy *proxy, GVariant *changed, const char *const *invalidated, gpointer user_data)
184 {
185     struct app *app = user_data;
186     gboolean lost_lock = FALSE;
187
188     if (g_variant_n_children(changed) > 0) {
189         GVariantIter *itr;
190         const char *prop;
191         GVariant *value;
192
193         g_variant_get(changed, "a{sv}", &itr);
194         while (g_variant_iter_loop(itr, "{&sv}", &prop, &value)) {
195             if (strcmp(prop, "WriteLocked") == 0) {
196                 if (!g_variant_get_boolean(value))
197                     lost_lock = TRUE;
198                 break;
199             }
200         }
201         g_variant_iter_free(itr);
202     }
203
204     if (invalidated[0] != NULL) {
205         const char * const *itr;
206         for (itr = invalidated; *itr != NULL; itr++) {
207             if (strcmp(*itr, "WriteLocked") == 0) {
208                 lost_lock = TRUE;
209                 break;
210             }
211         }
212     }
213
214     if (lost_lock) {
215         fputs("Lost lock, exit.\n", stderr);
216         app->ret = EXIT_FAILURE;
217         g_main_loop_quit(app->loop);
218     }
219 }
220
221 static void
222 do_write_lock(struct app *app)
223 {
224     GVariant *ret;
225     GError *error = NULL;
226     char *nameowner;
227
228     nameowner = g_dbus_proxy_get_name_owner(app->proxy);
229     if (!nameowner) {
230         fputs("Server is not running, cannot get write lock!\n", stderr);
231         app->ret = EXIT_FAILURE;
232         g_main_loop_quit(app->loop);
233         return;
234     }
235
236     printf("Server at %s, try to get write lock\n", nameowner);
237     g_free(nameowner);
238
239     g_signal_connect(app->proxy, "g-properties-changed",
240                      G_CALLBACK(on_properties_changed_check_lock),
241                      app);
242
243     ret = g_dbus_proxy_call_sync(app->proxy, "RequestWriteLock",
244                                  g_variant_new("()"),
245                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL,
246                                  &error);
247
248     if (!ret) {
249         fprintf(stderr, "Could not get write lock: %s\n", error->message);
250         g_error_free(error);
251         app->ret = EXIT_FAILURE;
252         g_main_loop_quit(app->loop);
253         return;
254     }
255
256     g_variant_unref(ret);
257     puts("Got write lock, close program to release it.");
258 }
259
260 static void
261 on_properties_changed_check_scan(GDBusProxy *proxy, GVariant *changed, const char *const *invalidated, gpointer user_data)
262 {
263     struct app *app = user_data;
264
265     if (g_variant_n_children(changed) > 0) {
266         GVariantIter *itr;
267         const char *prop;
268         GVariant *value;
269
270         g_variant_get(changed, "a{sv}", &itr);
271         while (g_variant_iter_loop(itr, "{&sv}", &prop, &value)) {
272             if (strcmp(prop, "IsScanning") == 0) {
273                 if (g_variant_get_boolean(value) && !app->timer) {
274                     app->timer = g_timer_new();
275                     g_timer_start(app->timer);
276                 } else if (!g_variant_get_boolean(value) && app->timer) {
277                     g_timer_stop(app->timer);
278                     app->ret = EXIT_SUCCESS;
279                     g_main_loop_quit(app->loop);
280                 }
281                 g_variant_unref(value);
282                 break;
283             } else
284                 g_variant_unref(value);
285         }
286         g_variant_iter_free(itr);
287     }
288
289     if (invalidated[0] != NULL) {
290         const char * const *itr;
291         for (itr = invalidated; *itr != NULL; itr++) {
292             if (strcmp(*itr, "IsScanning") == 0) {
293                 fputs("Lost server, exit.\n", stderr);
294                 app->ret = EXIT_FAILURE;
295                 g_main_loop_quit(app->loop);
296                 break;
297             }
298         }
299     }
300 }
301
302 static void
303 on_signal(GDBusProxy *proxy, gchar *sender, gchar *signal, GVariant *params, gpointer user_data)
304 {
305     if (g_str_equal(signal, "ScanProgress")) {
306         const gchar *category = NULL, *path = NULL;
307         guint64 uptodate = 0, processed = 0, deleted = 0,
308             skipped = 0, errors = 0;
309
310         g_variant_get(params, "(&s&sttttt)",
311                       &category,
312                       &path,
313                       &uptodate,
314                       &processed,
315                       &deleted,
316                       &skipped,
317                       &errors);
318
319         printf("Scan Progress %s:%s uptodate=%"G_GUINT64_FORMAT", "
320                "processed=%"G_GUINT64_FORMAT", deleted=%"G_GUINT64_FORMAT", "
321                "skipped=%"G_GUINT64_FORMAT", errors=%"G_GUINT64_FORMAT"\n",
322                category, path, uptodate, processed, deleted, skipped, errors);
323     }
324 }
325
326 static void
327 populate_scan_params(gpointer key, gpointer value, gpointer user_data)
328 {
329     const char *category = key;
330     const GArray *paths = value;
331     GVariantBuilder *builder = user_data;
332     GVariantBuilder *sub;
333     char **itr;
334
335     sub = g_variant_builder_new(G_VARIANT_TYPE("as"));
336     for (itr = (char **)paths->data; *itr != NULL; itr++)
337         g_variant_builder_add(sub, "s", *itr);
338
339     g_variant_builder_add(builder, "{sv}", category, g_variant_builder_end(sub));
340     g_variant_builder_unref(sub);
341 }
342
343 static void
344 do_free_array(gpointer data)
345 {
346     g_array_free(data, TRUE);
347 }
348
349 static void
350 do_scan(struct app *app)
351 {
352     GVariantBuilder *builder;
353     GVariant *ret;
354     GError *error = NULL;
355     char *nameowner;
356     int i;
357     GHashTable *categories;
358
359     nameowner = g_dbus_proxy_get_name_owner(app->proxy);
360     if (!nameowner) {
361         fputs("Server is not running, cannot start scan!\n", stderr);
362         app->ret = EXIT_FAILURE;
363         g_main_loop_quit(app->loop);
364         return;
365     }
366
367     printf("Server at %s, try to start scan\n", nameowner);
368     g_free(nameowner);
369
370     g_signal_connect(app->proxy, "g-properties-changed",
371                      G_CALLBACK(on_properties_changed_check_scan),
372                      app);
373     g_signal_connect(app->proxy, "g-signal",
374                      G_CALLBACK(on_signal),
375                      app);
376
377     categories = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, do_free_array);
378     for (i = 0; i < app->argc; i++) {
379         GArray *arr;
380         char *arg = app->argv[i];
381         char *sep = strchr(arg, ':');
382         char *path;
383
384
385         if (!sep) {
386             fprintf(stderr, "Ignored scan parameter: invalid format '%s'\n",
387                     arg);
388             continue;
389         }
390
391         *sep = '\0';
392         path = sep + 1;
393
394         arr = g_hash_table_lookup(categories, arg);
395         if (!arr) {
396             arr = g_array_new(TRUE, FALSE, sizeof(char *));
397             g_hash_table_insert(categories, arg, arr);
398         }
399
400         if (path[0])
401             g_array_append_val(arr, path);
402     }
403
404     builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
405     g_hash_table_foreach(categories, populate_scan_params, builder);
406
407     ret = g_dbus_proxy_call_sync(app->proxy, "Scan",
408                                  g_variant_new("(a{sv})", builder),
409                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL,
410                                  &error);
411     g_variant_builder_unref(builder);
412     g_hash_table_destroy(categories);
413
414     if (!ret) {
415         fprintf(stderr, "Could not start scan: %s\n", error->message);
416         g_error_free(error);
417         app->ret = EXIT_FAILURE;
418         g_main_loop_quit(app->loop);
419         return;
420     }
421
422     g_variant_unref(ret);
423 }
424
425 static void
426 do_stop(struct app *app)
427 {
428     GVariant *ret;
429     GError *error = NULL;
430     char *nameowner;
431
432     nameowner = g_dbus_proxy_get_name_owner(app->proxy);
433     if (!nameowner) {
434         fputs("Server is not running, cannot stop scan!\n", stderr);
435         app->ret = EXIT_FAILURE;
436         g_main_loop_quit(app->loop);
437         return;
438     }
439
440     printf("Server at %s, try to stop scan\n", nameowner);
441     g_free(nameowner);
442
443     ret = g_dbus_proxy_call_sync(app->proxy, "Stop",
444                                  g_variant_new("()"),
445                                  G_DBUS_CALL_FLAGS_NONE, -1, NULL,
446                                  &error);
447
448     if (!ret) {
449         fprintf(stderr, "Could not stop scan: %s\n", error->message);
450         g_error_free(error);
451         app->ret = EXIT_FAILURE;
452         g_main_loop_quit(app->loop);
453         return;
454     }
455
456     app->ret = EXIT_SUCCESS;
457     g_main_loop_quit(app->loop);
458     g_variant_unref(ret);
459 }
460
461 static gboolean
462 do_action(gpointer data)
463 {
464     struct app *app = data;
465     app->action(app);
466     return FALSE;
467 }
468
469 static void
470 print_help(const char *prog)
471 {
472     printf("Usage:\n"
473            "\t%s <action>\n"
474            "\n"
475            "Action is one of:\n"
476            "\tstatus          print server properties and exit.\n"
477            "\tmonitor         monitor server and its properties.\n"
478            "\twrite-lock      try to get a write-lock and keep it while running.\n"
479            "\tscan [params]   start scan. May receive parameters as a series of \n"
480            "\t                CATEGORY:PATH to limit scan.\n"
481            "\tstop            stop ongoing scan.\n"
482            "\thelp            this message.\n"
483            "\n",
484            prog);
485 }
486
487 int
488 main(int argc, char *argv[])
489 {
490     GError *error = NULL;
491     struct app app;
492     int i;
493
494     if (argc < 2) {
495         fprintf(stderr, "Missing action, see --help.\n");
496         return EXIT_FAILURE;
497     }
498
499     for (i = 1; i < argc; i++) {
500         if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
501             print_help(argv[0]);
502             return EXIT_SUCCESS;
503         }
504     }
505
506     if (strcmp(argv[1], "status") == 0)
507         app.action = do_status;
508     else if (strcmp(argv[1], "monitor") == 0)
509         app.action = do_monitor;
510     else if (strcmp(argv[1], "write-lock") == 0)
511         app.action = do_write_lock;
512     else if (strcmp(argv[1], "scan") == 0)
513         app.action = do_scan;
514     else if (strcmp(argv[1], "stop") == 0)
515         app.action = do_stop;
516     else if (strcmp(argv[1], "help") == 0) {
517         print_help(argv[0]);
518         return EXIT_SUCCESS;
519     } else {
520         fprintf(stderr, "Unknown action '%s', see --help.\n", argv[1]);
521         return EXIT_FAILURE;
522     }
523
524     if (!start_service_by_name())
525         return EXIT_FAILURE;
526
527     app.timer = NULL;
528     app.loop = g_main_loop_new(NULL, FALSE);
529     app.proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION,
530                                               G_DBUS_PROXY_FLAGS_NONE,
531                                               NULL,
532                                               "org.lightmediascanner",
533                                               "/org/lightmediascanner/Scanner1",
534                                               "org.lightmediascanner.Scanner1",
535                                               NULL,
536                                               &error);
537     if (error) {
538         g_error("Could not create proxy: %s", error->message);
539         g_error_free(error);
540         return EXIT_FAILURE;
541     }
542
543     app.argc = argc - 2;
544     app.argv = argv + 2;
545     app.ret = EXIT_SUCCESS;
546
547     g_idle_add(do_action, &app);
548
549     g_main_loop_run(app.loop);
550     g_object_unref(app.proxy);
551     g_main_loop_unref(app.loop);
552
553     if (app.timer) {
554         printf("Elapsed time: %0.3f seconds\n",
555                g_timer_elapsed(app.timer, NULL));
556         g_timer_destroy(app.timer);
557     }
558
559     return app.ret;
560 }