enable "make doc", improve doxygen support for client library.
[framework/uifw/ethumb.git] / src / lib / client / Ethumb_Client.c
1 /**
2  * @file
3  *
4  * This is the client-server thumbnail library, see @ref
5  * tutorial_ethumb_client.
6  *
7  * Copyright (C) 2009 by ProFUSION embedded systems
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or (at your
12  * option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,  but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License
17  * for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22  * USA.
23  *
24  * @author Rafael Antognolli <antognolli@profusion.mobi>
25  * @author Gustavo Sverzut Barbieri <barbieri@profusion.mobi>
26  */
27
28 /**
29  * @page tutorial_ethumb_client Client-Server Thumbnailing Tutorial
30  *
31  * @section tutorial_ethumb_client_intro Introduction
32  *
33  * Ethumb provides both in process and client-server generation
34  * methods. The advantage of the client-server method is that current
35  * process will not do the heavy operations that may block, stopping
36  * animations and other user interactions. Instead the client library
37  * will configure a local #Ethumb instance and mirrors/controls a
38  * remote process using DBus. The simple operations like most setters
39  * and getters as well as checking for thumbnail existence
40  * (ethumb_client_thumb_exists()) is done locally, while expensive
41  * (ethumb_client_generate()) are done on server and then reported
42  * back to application when it is finished (both success or failure).
43  *
44  * @section tutorial_ethumb_client_connect Connecting to Server
45  *
46  * TODO
47  *
48  * @section tutorial_ethumb_client_generate Requesting Thumbnail Generation
49  *
50  * TODO
51  *
52  * @section tutorial_ethumb_client_setup Setup Extra Thumbnail Parameters
53  *
54  * TODO
55  *
56  * @section tutorial_ethumb_client_server_died Handle Server Disconnection
57  *
58  * TODO
59  */
60
61 /**
62  * @cond LOCAL
63  */
64
65 #ifdef HAVE_CONFIG_H
66 #include "config.h"
67 #endif
68 #include <Eina.h>
69 #include <eina_safety_checks.h>
70 #include <Ethumb.h>
71 #include "Ethumb_Client.h"
72 #include <stdio.h>
73 #include <stdlib.h>
74 #include <limits.h>
75 #include <string.h>
76 #include <unistd.h>
77 #include <errno.h>
78 #include <sys/types.h>
79 #include <stdbool.h>
80
81 #ifndef PATH_MAX
82 #define PATH_MAX 4096
83 #endif
84
85 #include <E_DBus.h>
86
87 #define MAX_ID 2000000
88
89 static int _log_dom = -1;
90 #define DBG(...) EINA_LOG_DOM_DBG(_log_dom, __VA_ARGS__)
91 #define INF(...) EINA_LOG_DOM_INFO(_log_dom, __VA_ARGS__)
92 #define WRN(...) EINA_LOG_DOM_WARN(_log_dom, __VA_ARGS__)
93 #define ERR(...) EINA_LOG_DOM_ERR(_log_dom, __VA_ARGS__)
94
95 struct _Ethumb_Client
96 {
97    Ethumb *ethumb;
98    int id_count;
99
100    E_DBus_Connection *conn;
101    E_DBus_Signal_Handler *name_owner_changed_handler;
102    E_DBus_Signal_Handler *generated_signal;
103    DBusPendingCall *pending_get_name_owner;
104    DBusPendingCall *pending_start_service_by_name;
105    const char *unique_name;
106    DBusPendingCall *pending_new;
107    struct {
108       Ethumb_Client_Connect_Cb cb;
109       void *data;
110       Eina_Free_Cb free_data;
111    } connect;
112    Eina_List *pending_add;
113    Eina_List *pending_remove;
114    Eina_List *pending_gen;
115    DBusPendingCall *pending_clear;
116    DBusPendingCall *pending_setup;
117    struct {
118       Ethumb_Client_Die_Cb cb;
119       void *data;
120       Eina_Free_Cb free_data;
121    } die;
122    const char *object_path;
123
124    Eina_Bool ethumb_dirty : 1;
125    Eina_Bool connected : 1;
126    Eina_Bool server_started : 1;
127 };
128
129 struct _ethumb_pending_add
130 {
131    dbus_int32_t id;
132    const char *file;
133    const char *key;
134    const char *thumb;
135    const char *thumb_key;
136    Ethumb_Client_Generate_Cb generated_cb;
137    void *data;
138    Eina_Free_Cb free_data;
139    DBusPendingCall *pending_call;
140    Ethumb_Client *client;
141 };
142
143 struct _ethumb_pending_remove
144 {
145    dbus_int32_t id;
146    Ethumb_Client_Generate_Cancel_Cb cancel_cb;
147    void *data;
148    Eina_Free_Cb free_data;
149    DBusPendingCall *pending_call;
150    Ethumb_Client *client;
151 };
152
153 struct _ethumb_pending_gen
154 {
155    dbus_int32_t id;
156    const char *file;
157    const char *key;
158    const char *thumb;
159    const char *thumb_key;
160    Ethumb_Client_Generate_Cb generated_cb;
161    void *data;
162    Eina_Free_Cb free_data;
163 };
164
165 static const char _ethumb_dbus_bus_name[] = "org.enlightenment.Ethumb";
166 static const char _ethumb_dbus_interface[] = "org.enlightenment.Ethumb";
167 static const char _ethumb_dbus_objects_interface[] = "org.enlightenment.Ethumb.objects";
168 static const char _ethumb_dbus_path[] = "/org/enlightenment/Ethumb";
169 static const char fdo_interface[] = "org.freedesktop.DBus";
170 static const char fdo_bus_name[] = "org.freedesktop.DBus";
171 static const char fdo_path[] = "/org/freedesktop/DBus";
172
173 static int _initcount = 0;
174
175 static void _ethumb_client_generated_cb(void *data, DBusMessage *msg);
176 static void _ethumb_client_get_name_owner(void *data, DBusMessage *msg, DBusError *err);
177
178 static inline bool
179 __dbus_callback_check_and_init(const char *file, int line, const char *function, DBusMessage *msg, DBusMessageIter *itr, DBusError *err)
180 {
181    if (!msg)
182      {
183         ERR("%s:%d:%s() callback without message arguments!\n",
184                 file, line, function);
185
186         if (err)
187           ERR("%s:%d:%s() an error was reported by server: "
188                   "name=\"%s\", message=\"%s\"\n",
189                   file, line, function, err->name, err->message);
190
191         return 0;
192      }
193
194    if (!dbus_message_iter_init(msg, itr))
195      {
196         ERR("%s:%d:%s() could not init iterator.\n",
197                 file, line, function);
198         return 0;
199      }
200
201    return 1;
202 }
203
204 #define _dbus_callback_check_and_init(msg, itr, err)                    \
205   __dbus_callback_check_and_init(__FILE__, __LINE__, __FUNCTION__,      \
206                                  msg, itr, err)
207
208 static inline bool
209 __dbus_iter_type_check(int type, int expected, const char *expected_name)
210 {
211    if (type == expected)
212      return 1;
213
214    ERR("expected type %s (%c) but got %c instead!\n",
215            expected_name, expected, type);
216
217    return 0;
218 }
219 #define _dbus_iter_type_check(t, e) __dbus_iter_type_check(t, e, #e)
220
221 #define CHECK_NULL_RETURN(ptr, ...)                                     \
222   do                                                                    \
223     {                                                                   \
224        if ((ptr) == NULL)                                               \
225          {                                                              \
226             ERR("%s == NULL!\n", #ptr);         \
227             return __VA_ARGS__;                                         \
228          }                                                              \
229     }                                                                   \
230   while (0)
231
232 static void
233 _ethumb_client_name_owner_changed(void *data, DBusMessage *msg)
234 {
235    DBusError err;
236    const char *name, *from, *to;
237    Ethumb_Client *client = data;
238
239    dbus_error_init(&err);
240    if (!dbus_message_get_args(msg, &err,
241        DBUS_TYPE_STRING, &name,
242        DBUS_TYPE_STRING, &from,
243        DBUS_TYPE_STRING, &to,
244        DBUS_TYPE_INVALID))
245      {
246         ERR("could not get NameOwnerChanged arguments: %s: %s\n",
247             err.name, err.message);
248         dbus_error_free(&err);
249         return;
250      }
251
252    if (strcmp(name, _ethumb_dbus_bus_name) != 0)
253      return;
254
255    DBG("NameOwnerChanged from=[%s] to=[%s]\n", from, to);
256
257    if (from[0] != '\0' && to[0] == '\0')
258      {
259         DBG("exit ethumbd at %s\n", from);
260         if (strcmp(client->unique_name, from) != 0)
261           WRN("%s was not the known name %s, ignored.\n",
262                from, client->unique_name);
263         else
264           {
265              ERR("server exit!!!\n");
266              if (client->die.cb)
267                {
268                   client->die.cb(client->die.data, client);
269                   client->die.cb = NULL;
270                }
271              if (client->die.free_data)
272                {
273                   client->die.free_data(client->die.data);
274                   client->die.free_data = NULL;
275                   client->die.data = NULL;
276                }
277           }
278      }
279    else
280      DBG("unknown change from %s to %s\n", from, to);
281 }
282
283 static void
284 _ethumb_client_report_connect(Ethumb_Client *client, Eina_Bool success)
285 {
286    if (!client->connect.cb)
287      {
288         ERR("already called?!");
289         return;
290      }
291
292    client->connect.cb(client->connect.data, client, success);
293    if (client->connect.free_data)
294      {
295         client->connect.free_data(client->connect.data);
296         client->connect.free_data = NULL;
297      }
298    client->connect.cb = NULL;
299    client->connect.data = NULL;
300 }
301
302 static void
303 _ethumb_client_new_cb(void *data, DBusMessage *msg, DBusError *error)
304 {
305    DBusMessageIter iter;
306    const char *opath;
307    int t;
308    Ethumb_Client *client = data;
309
310    client->pending_new = NULL;
311
312    if (!_dbus_callback_check_and_init(msg, &iter, error))
313      goto end_error;
314    t = dbus_message_iter_get_arg_type(&iter);
315    if (!_dbus_iter_type_check(t, DBUS_TYPE_OBJECT_PATH))
316      goto end_error;
317
318    dbus_message_iter_get_basic(&iter, &opath);
319    if (opath[0] == '\0')
320      goto end_error;
321
322    client->object_path = eina_stringshare_add(opath);
323
324    client->generated_signal = e_dbus_signal_handler_add(
325       client->conn, _ethumb_dbus_bus_name, opath,
326       _ethumb_dbus_objects_interface, "generated",
327       _ethumb_client_generated_cb, client);
328
329    _ethumb_client_report_connect(client, 1);
330    return;
331
332 end_error:
333    _ethumb_client_report_connect(client, 0);
334 }
335
336 static void
337 _ethumb_client_call_new(Ethumb_Client *client)
338 {
339    DBusMessage *msg;
340
341    msg = dbus_message_new_method_call(_ethumb_dbus_bus_name, _ethumb_dbus_path,
342                                       _ethumb_dbus_interface, "new");
343    client->pending_new = e_dbus_message_send(client->conn, msg,
344                                              _ethumb_client_new_cb, -1,
345                                              client);
346    dbus_message_unref(msg);
347 }
348
349 static void
350 _ethumb_client_start_server_cb(void *data, DBusMessage *msg, DBusError *err)
351 {
352    Ethumb_Client *client = data;
353    DBusMessageIter iter;
354    dbus_uint32_t ret;
355    int t;
356
357    client->pending_start_service_by_name = NULL;
358
359    if (!_dbus_callback_check_and_init(msg, &iter, err))
360      goto error;
361
362    t = dbus_message_iter_get_arg_type(&iter);
363    if (!_dbus_iter_type_check(t, DBUS_TYPE_UINT32))
364      goto error;
365
366    dbus_message_iter_get_basic(&iter, &ret);
367    if ((ret != 1) && (ret != 2))
368      {
369         ERR("Error starting Ethumbd DBus service by its name: retcode %u\n",
370             ret);
371         goto error;
372      }
373
374    client->server_started = 1;
375    DBG("Ethumbd DBus service started successfully (%d), now request its name\n",
376        ret);
377
378    if (client->pending_get_name_owner)
379      {
380         DBG("already requesting name owner, cancel and try again\n");
381         dbus_pending_call_cancel(client->pending_get_name_owner);
382      }
383
384    client->pending_get_name_owner = e_dbus_get_name_owner
385      (client->conn, _ethumb_dbus_bus_name, _ethumb_client_get_name_owner,
386       client);
387    if (!client->pending_get_name_owner)
388      {
389         ERR("could not create a get_name_owner request.\n");
390         goto error;
391      }
392
393    return;
394
395  error:
396    ERR("failed to start Ethumbd DBus service by its name.\n");
397    _ethumb_client_report_connect(client, 0);
398 }
399
400 static void
401 _ethumb_client_start_server(Ethumb_Client *client)
402 {
403    if (client->pending_start_service_by_name)
404      {
405         DBG("already pending start service by name.\n");
406         return;
407      }
408
409    client->server_started = 0;
410    client->pending_start_service_by_name = e_dbus_start_service_by_name
411      (client->conn, _ethumb_dbus_bus_name, 0, _ethumb_client_start_server_cb,
412       client);
413    if (!client->pending_start_service_by_name)
414      {
415         ERR("could not start service by name!\n");
416         _ethumb_client_report_connect(client, 0);
417      }
418 }
419
420 static void
421 _ethumb_client_get_name_owner(void *data, DBusMessage *msg, DBusError *err)
422 {
423    DBusMessageIter iter;
424    const char *uid;
425    Ethumb_Client *client = data;
426    int t;
427
428    client->pending_get_name_owner = NULL;
429
430    if (dbus_error_is_set(err) && (!client->server_started))
431      {
432         DBG("could not find server (%s), try to start it...\n", err->message);
433         _ethumb_client_start_server(client);
434         return;
435      }
436
437    if (!_dbus_callback_check_and_init(msg, &iter, err))
438      goto error;
439
440    t = dbus_message_iter_get_arg_type(&iter);
441    if (!_dbus_iter_type_check(t, DBUS_TYPE_STRING))
442      goto error;
443
444    dbus_message_iter_get_basic(&iter, &uid);
445    if (!uid)
446      {
447         ERR("no name owner!\n");
448         goto error;
449      }
450
451    DBG("unique name = %s\n", uid);
452    client->unique_name = eina_stringshare_add(uid);
453
454    _ethumb_client_call_new(client);
455    client->connected = 1;
456    return;
457
458 error:
459    _ethumb_client_report_connect(client, 0);
460 }
461
462 /**
463  * @endcond
464  */
465
466 EAPI int
467 ethumb_client_init(void)
468 {
469    if (_initcount)
470      return ++_initcount;
471
472    if (!eina_init())
473      {
474         fprintf(stderr, "ERROR: Could not initialize log module.\n");
475         return 0;
476      }
477    _log_dom = eina_log_domain_register("ethumb_client", EINA_COLOR_YELLOW);
478    if (_log_dom < 0)
479      {
480         EINA_LOG_ERR("Could not register log domain: ethumb_client");
481         eina_shutdown();
482         return 0;
483      }
484
485    ethumb_init();
486    e_dbus_init();
487
488    return ++_initcount;
489 }
490
491 EAPI int
492 ethumb_client_shutdown(void)
493 {
494    _initcount--;
495    if (_initcount > 0)
496      return _initcount;
497
498    e_dbus_shutdown();
499    ethumb_shutdown();
500    eina_log_domain_unregister(_log_dom);
501    _log_dom = -1;
502    eina_shutdown();
503    return _initcount;
504 }
505
506 /**
507  * Connects to Ethumb server and return the client instance.
508  *
509  * This is the "constructor" of Ethumb_Client, where everything
510  * starts.
511  *
512  * If server was down, it is tried to start it using DBus activation,
513  * then the connection is retried.
514  *
515  * This call is asynchronous and will not block, instead it will be in
516  * "not connected" state until @a connect_cb is called with either
517  * success or failure. On failure, then no methods should be
518  * called. On success you're now able to setup and then ask generation
519  * of thumbnails.
520  *
521  * Usually you should listen for server death/disconenction with
522  * ethumb_client_on_server_die_callback_set().
523  *
524  * @param connect_cb function to call to report connection success or
525  *        failure. Do not call any other ethumb_client method until
526  *        this function returns. The first received parameter is the
527  *        given argument @a data. Must @b not be @c NULL. This
528  *        function will not be called if user explicitly calls
529  *        ethumb_client_disconnect().
530  * @param data context to give back to @a connect_cb. May be @c NULL.
531  * @param free_data function used to release @a data resources, if
532  *        any. May be @c NULL. If this function exists, it will be
533  *        called immediately after @a connect_cb is called or if user
534  *        explicitly calls ethumb_client_disconnect() before such
535  *        (that is, don't rely on @a data after @a connect_cb was
536  *        called!)
537  *
538  * @return client instance or NULL if failed. If @a connect_cb is
539  *         missing it returns @c NULL. If it fail for other
540  *         conditions, @c NULL is also returned and @a connect_cb is
541  *         called with @c success=EINA_FALSE. The client instance is
542  *         not ready to be used until @a connect_cb is called.
543  */
544 EAPI Ethumb_Client *
545 ethumb_client_connect(Ethumb_Client_Connect_Cb connect_cb, const void *data, Eina_Free_Cb free_data)
546 {
547    Ethumb_Client *eclient;
548
549    EINA_SAFETY_ON_NULL_RETURN_VAL(connect_cb, NULL);
550
551    eclient = calloc(1, sizeof(*eclient));
552    if (!eclient)
553      {
554         ERR("could not allocate Ethumb_Client structure.\n");
555         goto err;
556      }
557
558    eclient->connect.cb = connect_cb;
559    eclient->connect.data = (void *)data;
560    eclient->connect.free_data = free_data;
561
562    eclient->ethumb = ethumb_new();
563    if (!eclient->ethumb)
564      {
565         ERR("could not create ethumb handler.\n");
566         goto ethumb_new_err;
567      }
568
569    eclient->conn = e_dbus_bus_get(DBUS_BUS_SESSION);
570    if (!eclient->conn)
571      {
572         ERR("could not connect to session bus.\n");
573         goto connection_err;
574      }
575
576    eclient->name_owner_changed_handler = e_dbus_signal_handler_add(
577          eclient->conn, fdo_bus_name, fdo_path, fdo_interface,
578          "NameOwnerChanged", _ethumb_client_name_owner_changed, eclient);
579
580    eclient->pending_get_name_owner = e_dbus_get_name_owner(
581          eclient->conn, _ethumb_dbus_bus_name, _ethumb_client_get_name_owner,
582          eclient);
583    if (!eclient->pending_get_name_owner)
584      {
585         ERR("could not create a get_name_owner request.\n");
586         goto connection_err;
587      }
588
589    return eclient;
590
591 connection_err:
592    ethumb_free(eclient->ethumb);
593 ethumb_new_err:
594    free(eclient);
595 err:
596    connect_cb((void *)data, NULL, EINA_FALSE);
597    if (free_data)
598      free_data((void *)data);
599    return NULL;
600 }
601
602 /**
603  * Disconnect the client, releasing all client resources.
604  *
605  * This is the destructor of Ethumb_Client, after it's disconnected
606  * the client handle is now gone and should not be used.
607  */
608 EAPI void
609 ethumb_client_disconnect(Ethumb_Client *client)
610 {
611    void *data;
612
613    EINA_SAFETY_ON_NULL_RETURN(client);
614
615    if (!client->connected)
616      goto end_connection;
617
618    EINA_LIST_FREE(client->pending_add, data)
619      {
620         struct _ethumb_pending_add *pending = data;
621         eina_stringshare_del(pending->file);
622         eina_stringshare_del(pending->key);
623         eina_stringshare_del(pending->thumb);
624         eina_stringshare_del(pending->thumb_key);
625         dbus_pending_call_cancel(pending->pending_call);
626         dbus_pending_call_unref(pending->pending_call);
627         if (pending->free_data)
628           pending->free_data(pending->data);
629         free(pending);
630      }
631
632    EINA_LIST_FREE(client->pending_gen, data)
633      {
634         struct _ethumb_pending_gen *pending = data;
635         eina_stringshare_del(pending->file);
636         eina_stringshare_del(pending->key);
637         eina_stringshare_del(pending->thumb);
638         eina_stringshare_del(pending->thumb_key);
639         if (pending->free_data)
640           pending->free_data(pending->data);
641         free(pending);
642      }
643
644    EINA_LIST_FREE(client->pending_remove, data)
645      {
646         struct _ethumb_pending_remove *pending = data;
647         dbus_pending_call_cancel(pending->pending_call);
648         dbus_pending_call_unref(pending->pending_call);
649         if (pending->free_data)
650           pending->free_data(pending->data);
651         free(pending);
652      }
653
654    if (client->pending_clear)
655      {
656         dbus_pending_call_cancel(client->pending_clear);
657         dbus_pending_call_unref(client->pending_clear);
658      }
659
660 end_connection:
661    if (client->object_path)
662      eina_stringshare_del(client->object_path);
663
664    if (client->pending_new)
665      dbus_pending_call_cancel(client->pending_new);
666
667    if (client->unique_name)
668      eina_stringshare_del(client->unique_name);
669
670    if (client->pending_get_name_owner)
671      dbus_pending_call_cancel(client->pending_get_name_owner);
672
673    if (client->pending_start_service_by_name)
674      dbus_pending_call_cancel(client->pending_start_service_by_name);
675
676    ethumb_free(client->ethumb);
677
678    e_dbus_signal_handler_del(client->conn, client->name_owner_changed_handler);
679    if (client->connected)
680      e_dbus_signal_handler_del(client->conn, client->generated_signal);
681    e_dbus_connection_close(client->conn);
682
683    if (client->connect.free_data)
684      client->connect.free_data(client->connect.data);
685    if (client->die.free_data)
686      client->die.free_data(client->die.data);
687
688    free(client);
689 }
690
691 /**
692  * Sets the callback to report server died.
693  *
694  * When server dies there is nothing you can do, just release
695  * resources with ethumb_client_disconnect() and probably try to
696  * connect again.
697  *
698  * Usually you should set this callback and handle this case, it does
699  * happen!
700  *
701  * @param client the client instance to monitor. Must @b not be @c
702  *        NULL.
703  * @param server_die_cb function to call back when server dies. The
704  *        first parameter will be the argument @a data. May be @c
705  *        NULL.
706  * @param data context to give back to @a server_die_cb. May be @c
707  *        NULL.
708  * @param free_data used to release @a data resources after @a
709  *        server_die_cb is called or user calls
710  *        ethumb_client_disconnect().
711  */
712 EAPI void
713 ethumb_client_on_server_die_callback_set(Ethumb_Client *client, Ethumb_Client_Die_Cb server_die_cb, const void *data, Eina_Free_Cb free_data)
714 {
715    EINA_SAFETY_ON_NULL_RETURN(client);
716
717    if (client->die.free_data)
718      client->die.free_data(client->die.data);
719
720    client->die.cb = server_die_cb;
721    client->die.data = (void *)data;
722    client->die.free_data = free_data;
723 }
724
725 /**
726  * @cond LOCAL
727  */
728
729 static void
730 _ethumb_client_ethumb_setup_cb(void *data, DBusMessage *msg, DBusError *error)
731 {
732    DBusMessageIter iter;
733    int t;
734    dbus_bool_t result = 0;
735    Ethumb_Client *client = data;
736
737    client->pending_setup = NULL;
738
739    if (!_dbus_callback_check_and_init(msg, &iter, error))
740      return;
741
742    t = dbus_message_iter_get_arg_type(&iter);
743    if (!_dbus_iter_type_check(t, DBUS_TYPE_BOOLEAN))
744      return;
745
746    dbus_message_iter_get_basic(&iter, &result);
747 }
748
749 static const char *
750 _ethumb_client_dbus_get_bytearray(DBusMessageIter *iter)
751 {
752    int el_type;
753    int length;
754    DBusMessageIter riter;
755    const char *result;
756
757    el_type = dbus_message_iter_get_element_type(iter);
758    if (el_type != DBUS_TYPE_BYTE)
759      {
760         ERR("not an byte array element.\n");
761         return NULL;
762      }
763
764    dbus_message_iter_recurse(iter, &riter);
765    dbus_message_iter_get_fixed_array(&riter, &result, &length);
766
767    if (result[0] == '\0')
768      return NULL;
769    else
770      return eina_stringshare_add(result);
771 }
772
773 static void
774 _ethumb_client_dbus_append_bytearray(DBusMessageIter *iter, const char *string)
775 {
776    DBusMessageIter viter;
777
778    if (!string)
779      string = "";
780
781    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &viter);
782    dbus_message_iter_append_fixed_array(&viter, DBUS_TYPE_BYTE, &string,
783                                         strlen(string) + 1);
784    dbus_message_iter_close_container(iter, &viter);
785 }
786
787 /**
788  * @endcond
789  */
790
791 /**
792  * Send setup to server.
793  *
794  * This method is called automatically by ethumb_client_generate() if
795  * any property was changed. No need to call it manually.
796  *
797  * @param client client instance. Must @b not be @c NULL and client
798  *        must be connected (after connected_cb is called).
799  */
800 EAPI void
801 ethumb_client_ethumb_setup(Ethumb_Client *client)
802 {
803    DBusMessage *msg;
804    DBusMessageIter iter, aiter, diter, viter, vaiter;
805    Ethumb *e = client->ethumb;
806    const char *entry;
807    dbus_int32_t tw, th, format, aspect, quality, compress;
808    float cx, cy;
809    double t;
810    const char *theme_file, *group, *swallow;
811    const char *directory, *category;
812    double video_time, video_start, video_interval;
813    dbus_int32_t video_ntimes, video_fps, document_page;
814
815    EINA_SAFETY_ON_NULL_RETURN(client);
816    EINA_SAFETY_ON_FALSE_RETURN(client->connected);
817    client->ethumb_dirty = 0;
818
819    msg = dbus_message_new_method_call(_ethumb_dbus_bus_name,
820                                       client->object_path,
821                                       _ethumb_dbus_objects_interface,
822                                       "ethumb_setup");
823    dbus_message_iter_init_append(msg, &iter);
824    dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &aiter);
825
826 /**
827  * @cond LOCAL
828  */
829 #define _open_variant_iter(str_entry, str_type, end_iter)                  \
830    entry = str_entry;                                                      \
831    dbus_message_iter_open_container(&aiter, DBUS_TYPE_DICT_ENTRY, NULL, &diter); \
832    dbus_message_iter_append_basic(&diter, DBUS_TYPE_STRING, &entry);   \
833    dbus_message_iter_open_container(&diter, DBUS_TYPE_VARIANT, str_type,   \
834                                     &end_iter);
835
836 #define _close_variant_iter(end_iter)                                      \
837    dbus_message_iter_close_container(&diter, &end_iter);                   \
838    dbus_message_iter_close_container(&aiter, &diter);
839 /**
840  * @endcond
841  */
842
843    /* starting array elements */
844
845    _open_variant_iter("size", "(ii)", viter);
846    dbus_message_iter_open_container(&viter, DBUS_TYPE_STRUCT, NULL, &vaiter);
847    ethumb_thumb_size_get(e, &tw, &th);
848    dbus_message_iter_append_basic(&vaiter, DBUS_TYPE_INT32, &tw);
849    dbus_message_iter_append_basic(&vaiter, DBUS_TYPE_INT32, &th);
850    dbus_message_iter_close_container(&viter, &vaiter);
851    _close_variant_iter(viter);
852
853    _open_variant_iter("format", "i", viter);
854    format = ethumb_thumb_format_get(e);
855    dbus_message_iter_append_basic(&viter, DBUS_TYPE_INT32, &format);
856    _close_variant_iter(viter);
857
858    _open_variant_iter("aspect", "i", viter);
859    aspect = ethumb_thumb_aspect_get(e);
860    dbus_message_iter_append_basic(&viter, DBUS_TYPE_INT32, &aspect);
861    _close_variant_iter(viter);
862
863    _open_variant_iter("crop", "(dd)", viter);
864    dbus_message_iter_open_container(&viter, DBUS_TYPE_STRUCT, NULL, &vaiter);
865    ethumb_thumb_crop_align_get(e, &cx, &cy);
866    t = cx;
867    dbus_message_iter_append_basic(&vaiter, DBUS_TYPE_DOUBLE, &t);
868    t = cy;
869    dbus_message_iter_append_basic(&vaiter, DBUS_TYPE_DOUBLE, &t);
870    dbus_message_iter_close_container(&viter, &vaiter);
871    _close_variant_iter(viter);
872
873    _open_variant_iter("quality", "i", viter);
874    quality = ethumb_thumb_quality_get(e);
875    dbus_message_iter_append_basic(&viter, DBUS_TYPE_INT32, &quality);
876    _close_variant_iter(viter);
877
878    _open_variant_iter("compress", "i", viter);
879    compress = ethumb_thumb_compress_get(e);
880    dbus_message_iter_append_basic(&viter, DBUS_TYPE_INT32, &compress);
881    _close_variant_iter(viter);
882
883    _open_variant_iter("frame", "(ayayay)", viter);
884    dbus_message_iter_open_container(&viter, DBUS_TYPE_STRUCT, NULL, &vaiter);
885    ethumb_frame_get(e, &theme_file, &group, &swallow);
886    _ethumb_client_dbus_append_bytearray(&vaiter, theme_file);
887    _ethumb_client_dbus_append_bytearray(&vaiter, group);
888    _ethumb_client_dbus_append_bytearray(&vaiter, swallow);
889    dbus_message_iter_close_container(&viter, &vaiter);
890    _close_variant_iter(viter);
891
892    _open_variant_iter("directory", "ay", viter);
893    directory = ethumb_thumb_dir_path_get(e);
894    _ethumb_client_dbus_append_bytearray(&viter, directory);
895    _close_variant_iter(viter);
896
897    _open_variant_iter("category", "ay", viter);
898    category = ethumb_thumb_category_get(e);
899    _ethumb_client_dbus_append_bytearray(&viter, category);
900    _close_variant_iter(viter);
901
902    _open_variant_iter("video_time", "d", viter);
903    video_time = ethumb_video_time_get(e);
904    dbus_message_iter_append_basic(&viter, DBUS_TYPE_DOUBLE, &video_time);
905    _close_variant_iter(viter);
906
907    _open_variant_iter("video_start", "d", viter);
908    video_start = ethumb_video_start_get(e);
909    dbus_message_iter_append_basic(&viter, DBUS_TYPE_DOUBLE, &video_start);
910    _close_variant_iter(viter);
911
912    _open_variant_iter("video_interval", "d", viter);
913    video_interval = ethumb_video_interval_get(e);
914    dbus_message_iter_append_basic(&viter, DBUS_TYPE_DOUBLE, &video_interval);
915    _close_variant_iter(viter);
916
917    _open_variant_iter("video_ntimes", "i", viter);
918    video_ntimes = ethumb_video_ntimes_get(e);
919    dbus_message_iter_append_basic(&viter, DBUS_TYPE_INT32, &video_ntimes);
920    _close_variant_iter(viter);
921
922    _open_variant_iter("video_fps", "i", viter);
923    video_fps = ethumb_video_fps_get(e);
924    dbus_message_iter_append_basic(&viter, DBUS_TYPE_INT32, &video_fps);
925    _close_variant_iter(viter);
926
927    _open_variant_iter("document_page", "i", viter);
928    document_page = ethumb_document_page_get(e);
929    dbus_message_iter_append_basic(&viter, DBUS_TYPE_INT32, &document_page);
930    _close_variant_iter(viter);
931
932 #undef _open_variant_iter
933 #undef _close_variant_iter
934
935    dbus_message_iter_close_container(&iter, &aiter);
936
937    client->pending_setup = e_dbus_message_send(client->conn, msg,
938                                                _ethumb_client_ethumb_setup_cb,
939                                                -1, client);
940    dbus_message_unref(msg);
941 }
942
943 /**
944  * @cond LOCAL
945  */
946
947 static void
948 _ethumb_client_generated_cb(void *data, DBusMessage *msg)
949 {
950    DBusMessageIter iter;
951    dbus_int32_t id = -1;
952    const char *thumb;
953    const char *thumb_key;
954    Ethumb_Client *client = data;
955    int t;
956    dbus_bool_t success;
957    Eina_List *l;
958    int found;
959    struct _ethumb_pending_gen *pending;
960
961    dbus_message_iter_init(msg, &iter);
962
963    t = dbus_message_iter_get_arg_type(&iter);
964    if (!_dbus_iter_type_check(t, DBUS_TYPE_INT32))
965      goto end;
966    dbus_message_iter_get_basic(&iter, &id);
967    dbus_message_iter_next(&iter);
968
969    t = dbus_message_iter_get_arg_type(&iter);
970    if (!_dbus_iter_type_check(t, DBUS_TYPE_ARRAY))
971      goto end;
972    thumb = _ethumb_client_dbus_get_bytearray(&iter);
973    dbus_message_iter_next(&iter);
974
975    t = dbus_message_iter_get_arg_type(&iter);
976    if (!_dbus_iter_type_check(t, DBUS_TYPE_ARRAY))
977      goto end;
978    thumb_key = _ethumb_client_dbus_get_bytearray(&iter);
979    dbus_message_iter_next(&iter);
980
981    t = dbus_message_iter_get_arg_type(&iter);
982    if (!_dbus_iter_type_check(t, DBUS_TYPE_BOOLEAN))
983      goto end;
984    dbus_message_iter_get_basic(&iter, &success);
985
986    found = 0;
987    l = client->pending_gen;
988    while (l)
989      {
990         pending = l->data;
991         if (pending->id == id)
992           {
993              found = 1;
994              break;
995           }
996         l = l->next;
997      }
998
999    if (found)
1000      {
1001         client->pending_gen = eina_list_remove_list(client->pending_gen, l);
1002         pending->generated_cb(pending->data, client, id,
1003                               pending->file, pending->key,
1004                               pending->thumb, pending->thumb_key,
1005                               success);
1006         if (pending->free_data)
1007           pending->free_data(pending->data);
1008         eina_stringshare_del(pending->file);
1009         eina_stringshare_del(pending->key);
1010         eina_stringshare_del(pending->thumb);
1011         eina_stringshare_del(pending->thumb_key);
1012         free(pending);
1013      }
1014
1015 end:
1016    eina_stringshare_del(thumb);
1017    eina_stringshare_del(thumb_key);
1018 }
1019
1020 static void
1021 _ethumb_client_queue_add_cb(void *data, DBusMessage *msg, DBusError *error)
1022 {
1023    DBusMessageIter iter;
1024    int t;
1025    dbus_int32_t id = -1;
1026    struct _ethumb_pending_add *pending = data;
1027    struct _ethumb_pending_gen *generating;
1028    Ethumb_Client *client = pending->client;
1029
1030    client->pending_add = eina_list_remove(client->pending_add, pending);
1031
1032    if (!_dbus_callback_check_and_init(msg, &iter, error))
1033      goto end;
1034
1035    t = dbus_message_iter_get_arg_type(&iter);
1036    if (!_dbus_iter_type_check(t, DBUS_TYPE_INT32))
1037      goto end;
1038
1039    dbus_message_iter_get_basic(&iter, &id);
1040
1041    generating = calloc(1, sizeof(*generating));
1042    generating->id = id;
1043    generating->file = pending->file;
1044    generating->key = pending->key;
1045    generating->thumb = pending->thumb;
1046    generating->thumb_key = pending->thumb_key;
1047    generating->generated_cb = pending->generated_cb;
1048    generating->data = pending->data;
1049    generating->free_data = pending->free_data;
1050    client->pending_gen = eina_list_append(client->pending_gen, generating);
1051
1052 end:
1053    free(pending);
1054 }
1055
1056 static int
1057 _ethumb_client_queue_add(Ethumb_Client *client, const char *file, const char *key, const char *thumb, const char *thumb_key, Ethumb_Client_Generate_Cb generated_cb, const void *data, Eina_Free_Cb free_data)
1058 {
1059    DBusMessage *msg;
1060    DBusMessageIter iter;
1061    struct _ethumb_pending_add *pending;
1062
1063    pending = calloc(1, sizeof(*pending));
1064    pending->id = client->id_count;
1065    pending->file = eina_stringshare_add(file);
1066    pending->key = eina_stringshare_add(key);
1067    pending->thumb = eina_stringshare_add(thumb);
1068    pending->thumb_key = eina_stringshare_add(thumb_key);
1069    pending->generated_cb = generated_cb;
1070    pending->data = (void *)data;
1071    pending->free_data = free_data;
1072    pending->client = client;
1073
1074    client->id_count = (client->id_count + 1) % MAX_ID;
1075
1076    msg = dbus_message_new_method_call(_ethumb_dbus_bus_name,
1077                                       client->object_path,
1078                                       _ethumb_dbus_objects_interface,
1079                                       "queue_add");
1080
1081    dbus_message_iter_init_append(msg, &iter);
1082    dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &pending->id);
1083    _ethumb_client_dbus_append_bytearray(&iter, file);
1084    _ethumb_client_dbus_append_bytearray(&iter, key);
1085    _ethumb_client_dbus_append_bytearray(&iter, thumb);
1086    _ethumb_client_dbus_append_bytearray(&iter, thumb_key);
1087
1088    pending->pending_call = e_dbus_message_send(client->conn, msg,
1089                                                _ethumb_client_queue_add_cb,
1090                                                -1, pending);
1091    client->pending_add = eina_list_append(client->pending_add, pending);
1092    dbus_message_unref(msg);
1093
1094    return pending->id;
1095 }
1096
1097 static void
1098 _ethumb_client_queue_remove_cb(void *data, DBusMessage *msg, DBusError *error)
1099 {
1100    DBusMessageIter iter;
1101    int t;
1102    dbus_bool_t success = 0;
1103    struct _ethumb_pending_remove *pending = data;
1104    Ethumb_Client *client = pending->client;
1105
1106    client->pending_remove = eina_list_remove(client->pending_remove, pending);
1107
1108    if (!_dbus_callback_check_and_init(msg, &iter, error))
1109      goto end;
1110
1111    t = dbus_message_iter_get_arg_type(&iter);
1112    if (!_dbus_iter_type_check(t, DBUS_TYPE_BOOLEAN))
1113      goto end;
1114
1115    dbus_message_iter_get_basic(&iter, &success);
1116
1117 end:
1118    if (pending->cancel_cb)
1119      pending->cancel_cb(pending->data, success);
1120    if (pending->free_data)
1121      pending->free_data(pending->data);
1122    free(pending);
1123 }
1124 /**
1125  * @endcond
1126  */
1127
1128 /**
1129  * Ask server to cancel generation of thumbnail.
1130  *
1131  * @param client client instance. Must @b not be @c NULL and client
1132  *        must be connected (after connected_cb is called).
1133  * @param id valid id returned by ethumb_client_generate()
1134  * @param cancel_cb function to report cancellation results.
1135  * @param data context argument to give back to @a cancel_cb. May be
1136  *        @c NULL.
1137  * @param data context to give back to @a cancel_cb. May be @c
1138  *        NULL.
1139  * @param free_data used to release @a data resources after @a
1140  *        cancel_cb is called or user calls
1141  *        ethumb_client_disconnect().
1142  */
1143 EAPI void
1144 ethumb_client_generate_cancel(Ethumb_Client *client, int id, Ethumb_Client_Generate_Cancel_Cb cancel_cb, const void *data, Eina_Free_Cb free_data)
1145 {
1146    DBusMessage *msg;
1147    struct _ethumb_pending_remove *pending;
1148    Eina_List *l;
1149    int found;
1150    dbus_int32_t id32 = id;
1151    EINA_SAFETY_ON_NULL_RETURN(client);
1152    EINA_SAFETY_ON_FALSE_RETURN(id >= 0);
1153
1154    pending = calloc(1, sizeof(*pending));
1155    pending->id = id;
1156    pending->cancel_cb = cancel_cb;
1157    pending->data = (void *)data;
1158    pending->free_data = free_data;
1159    pending->client = client;
1160
1161    msg = dbus_message_new_method_call(_ethumb_dbus_bus_name,
1162                                       client->object_path,
1163                                       _ethumb_dbus_objects_interface,
1164                                       "queue_remove");
1165
1166    dbus_message_append_args(msg, DBUS_TYPE_INT32, &id32, DBUS_TYPE_INVALID);
1167    pending->pending_call = e_dbus_message_send(client->conn, msg,
1168                                                _ethumb_client_queue_remove_cb,
1169                                                -1, pending);
1170    client->pending_remove = eina_list_append(client->pending_remove, pending);
1171
1172    found = 0;
1173    l = client->pending_add;
1174    while (l)
1175      {
1176         struct _ethumb_pending_add *pending = l->data;
1177         if (pending->id != id32)
1178           {
1179              l = l->next;
1180              continue;
1181           }
1182         client->pending_add = eina_list_remove_list(client->pending_add, l);
1183         eina_stringshare_del(pending->file);
1184         eina_stringshare_del(pending->key);
1185         eina_stringshare_del(pending->thumb);
1186         eina_stringshare_del(pending->thumb_key);
1187         dbus_pending_call_cancel(pending->pending_call);
1188         dbus_pending_call_unref(pending->pending_call);
1189         if (pending->free_data)
1190           pending->free_data(pending->data);
1191         free(pending);
1192         found = 1;
1193         break;
1194      }
1195
1196    if (found)
1197      goto end;
1198
1199    l = client->pending_gen;
1200    while (l)
1201      {
1202         struct _ethumb_pending_gen *pending = l->data;
1203         if (pending->id != id32)
1204           {
1205              l = l->next;
1206              continue;
1207           }
1208         client->pending_gen = eina_list_remove_list(client->pending_gen, l);
1209         eina_stringshare_del(pending->file);
1210         eina_stringshare_del(pending->key);
1211         eina_stringshare_del(pending->thumb);
1212         eina_stringshare_del(pending->thumb_key);
1213         if (pending->free_data)
1214           pending->free_data(pending->data);
1215         free(pending);
1216         found = 1;
1217         break;
1218      }
1219
1220 end:
1221    dbus_message_unref(msg);
1222 }
1223
1224 /**
1225  * @cond LOCAL
1226  */
1227 static void
1228 _ethumb_client_queue_clear_cb(void *data, DBusMessage *msg __UNUSED__, DBusError *error __UNUSED__)
1229 {
1230    Ethumb_Client *client = data;
1231
1232    client->pending_clear = NULL;
1233 }
1234 /**
1235  * @endcond
1236  */
1237
1238 /**
1239  * Ask server to cancel generation of all thumbnails.
1240  *
1241  * @param client client instance. Must @b not be @c NULL and client
1242  *        must be connected (after connected_cb is called).
1243  *
1244  * @see ethumb_client_generate_cancel()
1245  */
1246 EAPI void
1247 ethumb_client_generate_cancel_all(Ethumb_Client *client)
1248 {
1249    DBusMessage *msg;
1250    void *data;
1251    EINA_SAFETY_ON_NULL_RETURN(client);
1252
1253    if (client->pending_clear)
1254      return;
1255
1256    EINA_LIST_FREE(client->pending_add, data)
1257      {
1258         struct _ethumb_pending_add *pending = data;
1259         eina_stringshare_del(pending->file);
1260         eina_stringshare_del(pending->key);
1261         eina_stringshare_del(pending->thumb);
1262         eina_stringshare_del(pending->thumb_key);
1263         dbus_pending_call_cancel(pending->pending_call);
1264         dbus_pending_call_unref(pending->pending_call);
1265         if (pending->free_data)
1266           pending->free_data(pending->data);
1267         free(pending);
1268      }
1269
1270    EINA_LIST_FREE(client->pending_gen, data)
1271      {
1272         struct _ethumb_pending_gen *pending = data;
1273         eina_stringshare_del(pending->file);
1274         eina_stringshare_del(pending->key);
1275         eina_stringshare_del(pending->thumb);
1276         eina_stringshare_del(pending->thumb_key);
1277         if (pending->free_data)
1278           pending->free_data(pending->data);
1279         free(pending);
1280      }
1281
1282    msg = dbus_message_new_method_call(_ethumb_dbus_bus_name,
1283                                       client->object_path,
1284                                       _ethumb_dbus_objects_interface,
1285                                       "queue_clear");
1286
1287    client->pending_clear = e_dbus_message_send(client->conn, msg,
1288                                                _ethumb_client_queue_clear_cb,
1289                                                -1, client);
1290
1291    dbus_message_unref(msg);
1292 }
1293
1294 /**
1295  * Configure future requests to use FreeDesktop.Org preset.
1296  *
1297  * This is a preset to provide freedesktop.org (fdo) standard
1298  * compliant thumbnails. That is, files are stored as JPEG under
1299  * ~/.thumbnails/SIZE, with size being either normal (128x128) or
1300  * large (256x256).
1301  *
1302  * @param s size identifier, either #ETHUMB_THUMB_NORMAL (0) or
1303  *        #ETHUMB_THUMB_LARGE (1).
1304  *
1305  * @see ethumb_client_size_set()
1306  * @see ethumb_client_aspect_set()
1307  * @see ethumb_client_crop_align_set()
1308  * @see ethumb_client_category_set()
1309  * @see ethumb_client_dir_path_set()
1310  */
1311 EAPI void
1312 ethumb_client_fdo_set(Ethumb_Client *client, Ethumb_Thumb_FDO_Size s)
1313 {
1314    EINA_SAFETY_ON_NULL_RETURN(client);
1315
1316    client->ethumb_dirty = 1;
1317    ethumb_thumb_fdo_set(client->ethumb, s);
1318 }
1319
1320 /**
1321  * Configure future request to use custom size.
1322  *
1323  * @param w width, default is 128.
1324  * @param h height, default is 128.
1325  */
1326 EAPI void
1327 ethumb_client_size_set(Ethumb_Client *client, int tw, int th)
1328 {
1329    EINA_SAFETY_ON_NULL_RETURN(client);
1330
1331    client->ethumb_dirty = 1;
1332    ethumb_thumb_size_set(client->ethumb, tw, th);
1333 }
1334
1335 /**
1336  * Retrieve future request to use custom size.
1337  *
1338  * @param w where to return width. May be #NULL.
1339  * @param h where to return height. May be #NULL.
1340  */
1341 EAPI void
1342 ethumb_client_size_get(const Ethumb_Client *client, int *tw, int *th)
1343 {
1344    if (tw) *tw = 0;
1345    if (th) *th = 0;
1346    EINA_SAFETY_ON_NULL_RETURN(client);
1347
1348    ethumb_thumb_size_get(client->ethumb, tw, th);
1349 }
1350
1351 /**
1352  * Configure format to use for future requests.
1353  *
1354  * @param f format identifier to use, either #ETHUMB_THUMB_FDO (0),
1355  *        #ETHUMB_THUMB_JPEG (1) or #ETHUMB_THUMB_EET (2). Default is FDO.
1356  */
1357 EAPI void
1358 ethumb_client_format_set(Ethumb_Client *client, Ethumb_Thumb_Format f)
1359 {
1360    EINA_SAFETY_ON_NULL_RETURN(client);
1361
1362    client->ethumb_dirty = 1;
1363    ethumb_thumb_format_set(client->ethumb, f);
1364 }
1365
1366 /**
1367  * Retrieve format to use for future requests.
1368  *
1369  * @return format identifier to use, either #ETHUMB_THUMB_FDO (0),
1370  *         #ETHUMB_THUMB_JPEG (1) or #ETHUMB_THUMB_EET (2).
1371  */
1372 EAPI Ethumb_Thumb_Format
1373 ethumb_client_format_get(const Ethumb_Client *client)
1374 {
1375    EINA_SAFETY_ON_NULL_RETURN_VAL(client, 0);
1376
1377    return ethumb_thumb_format_get(client->ethumb);
1378 }
1379
1380 /**
1381  * Configure aspect mode to use.
1382  *
1383  * If aspect is kept (#ETHUMB_THUMB_KEEP_ASPECT), then image will be
1384  * rescaled so the largest dimension is not bigger than it's specified
1385  * size (see ethumb_client_size_get()) and the other dimension is
1386  * resized in the same proportion. Example: size is 256x256, image is
1387  * 1000x500, resulting thumbnail is 256x128.
1388  *
1389  * If aspect is ignored (#ETHUMB_THUMB_IGNORE_ASPECT), then image will
1390  * be distorted to match required thumbnail size. Example: size is
1391  * 256x256, image is 1000x500, resulting thumbnail is 256x256.
1392  *
1393  * If crop is required (#ETHUMB_THUMB_CROP), then image will be
1394  * cropped so the smallest dimension is not bigger than its specified
1395  * size (see ethumb_client_size_get()) and the other dimension will
1396  * overflow, not being visible in the final image. How it will
1397  * overflow is speficied by ethumb_client_crop_align_set()
1398  * alignment. Example: size is 256x256, image is 1000x500, crop
1399  * alignment is 0.5, 0.5, resulting thumbnail is 256x256 with 250
1400  * pixels from left and 250 pixels from right being lost, that is just
1401  * the 500x500 central pixels of image will be considered for scaling.
1402  *
1403  * @param a aspect mode identifier, either #ETHUMB_THUMB_KEEP_ASPECT (0),
1404  *        #ETHUMB_THUMB_IGNORE_ASPECT (1) or #ETHUMB_THUMB_CROP (2).
1405  */
1406 EAPI void
1407 ethumb_client_aspect_set(Ethumb_Client *client, Ethumb_Thumb_Aspect a)
1408 {
1409    EINA_SAFETY_ON_NULL_RETURN(client);
1410
1411    client->ethumb_dirty = 1;
1412    ethumb_thumb_aspect_set(client->ethumb, a);
1413 }
1414
1415 /**
1416  * Get current aspect in use for requests.
1417  *
1418  * @return aspect in use for future requests.
1419  */
1420 EAPI Ethumb_Thumb_Aspect
1421 ethumb_client_aspect_get(const Ethumb_Client *client)
1422 {
1423    EINA_SAFETY_ON_NULL_RETURN_VAL(client, 0);
1424
1425    return ethumb_thumb_aspect_get(client->ethumb);
1426 }
1427
1428 /**
1429  * Configure crop alignment in use for future requests.
1430  *
1431  * @param x horizontal alignment. 0.0 means left side will be visible
1432  *        or right side is being lost. 1.0 means right side will be
1433  *        visible or left side is being lost. 0.5 means just center is
1434  *        visible, both sides will be lost.  Default is 0.5.
1435  * @param y vertical alignment. 0.0 is top visible, 1.0 is bottom
1436  *        visible, 0.5 is center visible. Default is 0.5
1437  */
1438 EAPI void
1439 ethumb_client_crop_align_set(Ethumb_Client *client, float x, float y)
1440 {
1441    EINA_SAFETY_ON_NULL_RETURN(client);
1442
1443    client->ethumb_dirty = 1;
1444    ethumb_thumb_crop_align_set(client->ethumb, x, y);
1445 }
1446
1447 /**
1448  * Get current crop alignment in use for requests.
1449  *
1450  * @param x where to return horizontal alignment. May be #NULL.
1451  * @param y where to return vertical alignment. May be #NULL.
1452  */
1453 EAPI void
1454 ethumb_client_crop_align_get(const Ethumb_Client *client, float *x, float *y)
1455 {
1456    if (x) *x = 0.0;
1457    if (y) *y = 0.0;
1458    EINA_SAFETY_ON_NULL_RETURN(client);
1459
1460    ethumb_thumb_crop_align_get(client->ethumb, x, y);
1461 }
1462
1463 /**
1464  * Configure quality to be used in thumbnails.
1465  *
1466  * @param quality value from 0 to 100, default is 80. The effect
1467  *        depends on the format being used, PNG will not use it.
1468  */
1469 EAPI void
1470 ethumb_client_quality_set(Ethumb_Client *client, int quality)
1471 {
1472    EINA_SAFETY_ON_NULL_RETURN(client);
1473
1474    ethumb_thumb_quality_set(client->ethumb, quality);
1475 }
1476
1477 /**
1478  * Get quality to be used in thumbnails.
1479  *
1480  * @return quality value from 0 to 100, default is 80. The effect
1481  *         depends on the format being used, PNG will not use it.
1482  */
1483 EAPI int
1484 ethumb_client_quality_get(const Ethumb_Client *client)
1485 {
1486    EINA_SAFETY_ON_NULL_RETURN_VAL(client, 0);
1487
1488    return ethumb_thumb_quality_get(client->ethumb);
1489 }
1490
1491 /**
1492  * Configure compression level used in requests.
1493  *
1494  * @param compress value from 0 to 9, default is 9. The effect
1495  *        depends on the format being used, JPEG will not use it.
1496  */
1497 EAPI void
1498 ethumb_client_compress_set(Ethumb_Client *client, int compress)
1499 {
1500    EINA_SAFETY_ON_NULL_RETURN(client);
1501
1502    ethumb_thumb_compress_set(client->ethumb, compress);
1503 }
1504
1505 /**
1506  * Get compression level used in requests.
1507  *
1508  * @return compress value from 0 to 9, default is 9. The effect
1509  *         depends on the format being used, JPEG will not use it.
1510  */
1511 EAPI int
1512 ethumb_client_compress_get(const Ethumb_Client *client)
1513 {
1514    EINA_SAFETY_ON_NULL_RETURN_VAL(client, 0);
1515
1516    return ethumb_thumb_compress_get(client->ethumb);
1517 }
1518
1519 /**
1520  * Set frame to apply to future thumbnails.
1521  *
1522  * This will create an edje object that will have image swallowed
1523  * in. This can be used to simulate Polaroid or wood frames in the
1524  * generated image. Remeber it is bad to modify the original contents
1525  * of thumbnails, but sometimes it's useful to have it composited and
1526  * avoid runtime overhead.
1527  *
1528  * @param file file path to edje.
1529  * @param group group inside edje to use.
1530  * @param swallow name of swallow part.
1531  */
1532 EAPI Eina_Bool
1533 ethumb_client_frame_set(Ethumb_Client *client, const char *file, const char *group, const char *swallow)
1534 {
1535    EINA_SAFETY_ON_NULL_RETURN_VAL(client, 0);
1536
1537    client->ethumb_dirty = 1;
1538    return ethumb_frame_set(client->ethumb, file, group, swallow);
1539 }
1540
1541 /**
1542  * Configure where to store thumbnails in future requests.
1543  *
1544  * Note that this is the base, a category is added to this path as a
1545  * sub directory.
1546  *
1547  * @param path base directory where to store thumbnails. Default is
1548  *        ~/.thumbnails
1549  */
1550 EAPI void
1551 ethumb_client_dir_path_set(Ethumb_Client *client, const char *path)
1552 {
1553    EINA_SAFETY_ON_NULL_RETURN(client);
1554
1555    client->ethumb_dirty = 1;
1556    ethumb_thumb_dir_path_set(client->ethumb, path);
1557 }
1558
1559 EAPI const char *
1560 ethumb_client_dir_path_get(const Ethumb_Client *client)
1561 {
1562    EINA_SAFETY_ON_NULL_RETURN_VAL(client, NULL);
1563
1564    return ethumb_thumb_dir_path_get(client->ethumb);
1565 }
1566
1567 /**
1568  * Category directory to store thumbnails.
1569  *
1570  * @param category category sub directory to store thumbnail. Default
1571  *        is either "normal" or "large" for FDO compliant thumbnails
1572  *        or WIDTHxHEIGHT-ASPECT[-FRAMED]-FORMAT. It can be a string
1573  *        or None to use auto generated names.
1574  */
1575 EAPI void
1576 ethumb_client_category_set(Ethumb_Client *client, const char *category)
1577 {
1578    EINA_SAFETY_ON_NULL_RETURN(client);
1579
1580    client->ethumb_dirty = 1;
1581    ethumb_thumb_category_set(client->ethumb, category);
1582 }
1583
1584 EAPI const char *
1585 ethumb_client_category_get(const Ethumb_Client *client)
1586 {
1587    EINA_SAFETY_ON_NULL_RETURN_VAL(client, NULL);
1588
1589    return ethumb_thumb_category_get(client->ethumb);
1590 }
1591
1592 EAPI void
1593 ethumb_client_video_time_set(Ethumb_Client *client, float time)
1594 {
1595    EINA_SAFETY_ON_NULL_RETURN(client);
1596
1597    client->ethumb_dirty = 1;
1598    ethumb_video_time_set(client->ethumb, time);
1599 }
1600
1601 EAPI void
1602 ethumb_client_video_start_set(Ethumb_Client *client, float start)
1603 {
1604    EINA_SAFETY_ON_NULL_RETURN(client);
1605
1606    client->ethumb_dirty = 1;
1607    ethumb_video_start_set(client->ethumb, start);
1608 }
1609
1610 EAPI void
1611 ethumb_client_video_interval_set(Ethumb_Client *client, float interval)
1612 {
1613    EINA_SAFETY_ON_NULL_RETURN(client);
1614
1615    client->ethumb_dirty = 1;
1616    ethumb_video_interval_set(client->ethumb, interval);
1617 }
1618
1619 EAPI void
1620 ethumb_client_video_ntimes_set(Ethumb_Client *client, int ntimes)
1621 {
1622    EINA_SAFETY_ON_NULL_RETURN(client);
1623
1624    client->ethumb_dirty = 1;
1625    ethumb_video_ntimes_set(client->ethumb, ntimes);
1626 }
1627
1628 EAPI void
1629 ethumb_client_video_fps_set(Ethumb_Client *client, int fps)
1630 {
1631    EINA_SAFETY_ON_NULL_RETURN(client);
1632
1633    client->ethumb_dirty = 1;
1634    ethumb_video_fps_set(client->ethumb, fps);
1635 }
1636
1637 EAPI void
1638 ethumb_client_document_page_set(Ethumb_Client *client, int page)
1639 {
1640    EINA_SAFETY_ON_NULL_RETURN(client);
1641
1642    client->ethumb_dirty = 1;
1643    ethumb_document_page_set(client->ethumb, page);
1644 }
1645
1646 /**
1647  * Set source file to be thumbnailed.
1648  *
1649  * Calling this function has the side effect of resetting values set
1650  * with ethumb_client_thumb_path_set() or auto-generated with
1651  * ethumb_client_thumb_exists().
1652  *
1653  * @param client the client instance to use. Must @b not be @c
1654  *        NULL. May be pending connected (can be called before @c
1655  *        connected_cb)
1656  * @param path the filesystem path to use. May be @c NULL.
1657  * @param key the extra argument/key inside @a path to read image
1658  *        from. This is only used for formats that allow multiple
1659  *        resources in one file, like EET or Edje (group name).
1660  *
1661  * @return #EINA_TRUE on success, #EINA_FALSE on failure.
1662  */
1663 EAPI Eina_Bool
1664 ethumb_client_file_set(Ethumb_Client *client, const char *path, const char *key)
1665 {
1666    EINA_SAFETY_ON_NULL_RETURN_VAL(client, 0);
1667
1668    return ethumb_file_set(client->ethumb, path, key);
1669 }
1670
1671 /**
1672  * Get values set with ethumb_client_file_get()
1673  */
1674 EAPI void
1675 ethumb_client_file_get(Ethumb_Client *client, const char **path, const char **key)
1676 {
1677    if (path) *path = NULL;
1678    if (key) *key = NULL;
1679    EINA_SAFETY_ON_NULL_RETURN(client);
1680
1681    ethumb_file_get(client->ethumb, path, key);
1682 }
1683
1684 /**
1685  * Reset previously set file to @c NULL.
1686  *
1687  * @param client the client instance to use. Must @b not be @c
1688  *        NULL. May be pending connected (can be called before @c
1689  *        connected_cb)
1690  */
1691 EAPI void
1692 ethumb_client_file_free(Ethumb_Client *client)
1693 {
1694    EINA_SAFETY_ON_NULL_RETURN(client);
1695
1696    ethumb_file_free(client->ethumb);
1697 }
1698
1699 /**
1700  * Set a defined path and key to store the thumbnail.
1701  *
1702  * If not explicitly given, the thumbnail path will be auto-generated
1703  * by ethumb_client_thumb_exists() or server using configured
1704  * parameters like size, aspect and category.
1705  *
1706  * Set these to @c NULL to forget previously given values. After
1707  * ethumb_client_file_set() these values will be reset to @c NULL.
1708  */
1709 EAPI void
1710 ethumb_client_thumb_path_set(Ethumb_Client *client, const char *path, const char *key)
1711 {
1712    EINA_SAFETY_ON_NULL_RETURN(client);
1713
1714    ethumb_thumb_path_set(client->ethumb, path, key);
1715 }
1716
1717 /**
1718  * Get the configured thumbnail path.
1719  *
1720  * This returns the value set with ethumb_client_thumb_path_set() or
1721  * auto-generated by ethumb_client_thumb_exists() if it was not set.
1722  *
1723  * @param path where to return configured path. May be @c NULL.  If
1724  *        there was no path configured with
1725  *        ethumb_client_thumb_path_set() and
1726  *        ethumb_client_thumb_exists() was not called, then it will
1727  *        probably return @c NULL. If not @c NULL, then it will be a
1728  *        pointer to a stringshared instance, but @b no references are
1729  *        added (do it with eina_stringshare_ref())!
1730  * @param key where to return configured key. May be @c NULL.  If
1731  *        there was no key configured with
1732  *        ethumb_client_thumb_key_set() and
1733  *        ethumb_client_thumb_exists() was not called, then it will
1734  *        probably return @c NULL. If not @c NULL, then it will be a
1735  *        pointer to a stringshared instance, but @b no references are
1736  *        added (do it with eina_stringshare_ref())!
1737  */
1738 EAPI void
1739 ethumb_client_thumb_path_get(Ethumb_Client *client, const char **path, const char **key)
1740 {
1741    if (path) *path = NULL;
1742    if (key) *key = NULL;
1743    EINA_SAFETY_ON_NULL_RETURN(client);
1744
1745    ethumb_thumb_path_get(client->ethumb, path, key);
1746 }
1747
1748 /**
1749  * Checks whenever file already exists (locally!)
1750  *
1751  * This will check locally (not calling server) if thumbnail already
1752  * exists or not, also calculating the thumbnail path. See
1753  * ethumb_client_thumb_path_get(). Path must be configured with
1754  * ethumb_client_file_set() before using it and the last set file will
1755  * be used!
1756  *
1757  * @param client client instance. Must @b not be @c NULL and client
1758  *        must be configured with ethumb_client_file_set().
1759  *
1760  * @return #EINA_TRUE if it exists, #EINA_FALSE otherwise.
1761  */
1762 EAPI Eina_Bool
1763 ethumb_client_thumb_exists(Ethumb_Client *client)
1764 {
1765    EINA_SAFETY_ON_NULL_RETURN_VAL(client, 0);
1766
1767    return ethumb_exists(client->ethumb);
1768 }
1769
1770 /**
1771  * Ask server to generate thumbnail.
1772  *
1773  * This process is asynchronous and will report back from main loop
1774  * using @a generated_cb. One can cancel this request by calling
1775  * ethumb_client_generate_cancel() or
1776  * ethumb_client_generate_cancel_all(), but not that request might be
1777  * processed by server already and no generated files will be removed
1778  * if that is the case.
1779  *
1780  * This will not check if file already exists, this should be done by
1781  * explicitly calling ethumb_client_thumb_exists(). That is, this
1782  * function will override any existing thumbnail.
1783  *
1784  * @param client client instance. Must @b not be @c NULL and client
1785  *        must be connected (after connected_cb is called).
1786  * @param generated_cb function to report generation results.
1787  * @param data context argument to give back to @a generated_cb. May
1788  *        be @c NULL.
1789  * @param data context to give back to @a generate_cb. May be @c
1790  *        NULL.
1791  * @param free_data used to release @a data resources after @a
1792  *        generated_cb is called or user calls
1793  *        ethumb_client_disconnect().
1794  *
1795  * @return identifier or -1 on error. If -1 is returned (error) then
1796  *         @a free_data is @b not called!
1797  *
1798  * @see ethumb_client_connect()
1799  * @see ethumb_client_file_set()
1800  * @see ethumb_client_thumb_exists()
1801  * @see ethumb_client_generate_cancel()
1802  * @see ethumb_client_generate_cancel_all()
1803  */
1804 EAPI int
1805 ethumb_client_generate(Ethumb_Client *client, Ethumb_Client_Generate_Cb generated_cb, const void *data, Eina_Free_Cb free_data)
1806 {
1807    const char *file, *key, *thumb, *thumb_key;
1808    int id;
1809    EINA_SAFETY_ON_NULL_RETURN_VAL(client, -1);
1810    EINA_SAFETY_ON_FALSE_RETURN_VAL(client->connected, -1);
1811
1812    ethumb_file_get(client->ethumb, &file, &key);
1813    if (!file)
1814      {
1815         ERR("no file set.\n");
1816         return -1;
1817      }
1818
1819    ethumb_thumb_path_get(client->ethumb, &thumb, &thumb_key);
1820
1821    if (client->ethumb_dirty)
1822      ethumb_client_ethumb_setup(client);
1823    id = _ethumb_client_queue_add(client, file, key, thumb, thumb_key,
1824                                  generated_cb, data, free_data);
1825
1826    return id;
1827 }