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