c97b3984e5333d2740497cce554ae85ae406fccc
[framework/uifw/eet.git] / src / examples / eet-data-file_descriptor.c
1 #include <Eina.h>
2 #include <Eet.h>
3 #include <stdio.h>
4 #include <limits.h>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <unistd.h>
8
9 // complex real-world structures based on elmdentica database
10 typedef struct {
11    const char *screen_name;
12    const char *name;
13    const char *message;
14    unsigned int id;
15    unsigned int status_id;
16    unsigned int date;
17    unsigned int timeline;
18 } My_Message;
19
20 typedef struct {
21    const char *dm_to;
22    const char *message;
23 } My_Post;
24
25 typedef struct {
26    unsigned int id;
27    const char *name;
28    Eina_List *messages;
29    Eina_List *posts;
30 } My_Account;
31
32 typedef struct {
33    unsigned int version; // it is recommended to use versioned configuration!
34    Eina_List *accounts;
35 } My_Cache;
36
37 // string that represents the entry in eet file, you might like to have
38 // different profiles or so in the same file, this is possible with
39 // different strings
40 static const char MY_CACHE_FILE_ENTRY[] = "cache";
41
42 // keep the descriptor static global, so it can be
43 // shared by different functions (load/save) of this and only this
44 // file.
45 static Eet_Data_Descriptor *_my_cache_descriptor;
46 static Eet_Data_Descriptor *_my_account_descriptor;
47 static Eet_Data_Descriptor *_my_message_descriptor;
48 static Eet_Data_Descriptor *_my_post_descriptor;
49
50 // keep file handle alive, so mmap()ed strings are all alive as well
51 static Eet_File *_my_cache_file = NULL;
52 static Eet_Dictionary *_my_cache_dict = NULL;
53
54 static void
55 _my_cache_descriptor_init(void)
56 {
57    Eet_Data_Descriptor_Class eddc;
58
59    // The FILE variant is good for caches and things that are just
60    // appended, but needs to take care when changing strings and files must
61    // be kept open so mmap()ed strings will be kept alive.
62    EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, My_Cache);
63    _my_cache_descriptor = eet_data_descriptor_file_new(&eddc);
64
65    EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, My_Account);
66    _my_account_descriptor = eet_data_descriptor_file_new(&eddc);
67
68    EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, My_Message);
69    _my_message_descriptor = eet_data_descriptor_file_new(&eddc);
70
71    EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, My_Post);
72    _my_post_descriptor = eet_data_descriptor_file_new(&eddc);
73
74    // Describe the members to be saved:
75    // Use a temporary macro so we don't type a lot, also avoid errors:
76
77 #define ADD_BASIC(member, eet_type)                                     \
78    EET_DATA_DESCRIPTOR_ADD_BASIC                                        \
79      (_my_message_descriptor, My_Message, #member, member, eet_type)
80    ADD_BASIC(screen_name, EET_T_STRING);
81    ADD_BASIC(name, EET_T_STRING);
82    ADD_BASIC(message, EET_T_STRING);
83    ADD_BASIC(id, EET_T_UINT);
84    ADD_BASIC(status_id, EET_T_UINT);
85    ADD_BASIC(date, EET_T_UINT);
86    ADD_BASIC(timeline, EET_T_UINT);
87 #undef ADD_BASIC
88
89 #define ADD_BASIC(member, eet_type)                                     \
90    EET_DATA_DESCRIPTOR_ADD_BASIC                                        \
91      (_my_post_descriptor, My_Post, #member, member, eet_type)
92    ADD_BASIC(dm_to, EET_T_STRING);
93    ADD_BASIC(message, EET_T_STRING);
94 #undef ADD_BASIC
95
96 #define ADD_BASIC(member, eet_type)                                     \
97    EET_DATA_DESCRIPTOR_ADD_BASIC                                        \
98      (_my_account_descriptor, My_Account, #member, member, eet_type)
99    ADD_BASIC(name, EET_T_STRING);
100    ADD_BASIC(id, EET_T_UINT);
101 #undef ADD_BASIC
102
103    EET_DATA_DESCRIPTOR_ADD_LIST
104      (_my_account_descriptor, My_Account, "messages", messages,
105       _my_message_descriptor);
106    EET_DATA_DESCRIPTOR_ADD_LIST
107      (_my_account_descriptor, My_Account, "posts", posts,
108       _my_post_descriptor);
109
110 #define ADD_BASIC(member, eet_type)                                     \
111    EET_DATA_DESCRIPTOR_ADD_BASIC                                        \
112      (_my_cache_descriptor, My_Cache, #member, member, eet_type)
113    ADD_BASIC(version, EET_T_UINT);
114 #undef ADD_BASIC
115
116    EET_DATA_DESCRIPTOR_ADD_LIST
117      (_my_cache_descriptor, My_Cache, "accounts", accounts,
118       _my_account_descriptor);
119 }
120
121 static void
122 _my_cache_descriptor_shutdown(void)
123 {
124    eet_data_descriptor_free(_my_cache_descriptor);
125    eet_data_descriptor_free(_my_account_descriptor);
126    eet_data_descriptor_free(_my_message_descriptor);
127    eet_data_descriptor_free(_my_post_descriptor);
128 }
129
130 // need to check if the pointer came from mmaped area in eet_dictionary
131 // or it was allocated with eina_stringshare_add()
132 static void
133 _eet_string_free(const char *str)
134 {
135    if (!str)
136      return;
137    if ((_my_cache_dict) && (eet_dictionary_string_check(_my_cache_dict, str)))
138      return;
139    eina_stringshare_del(str);
140 }
141
142 static My_Message *
143 _my_message_new(const char *message)
144 {
145    My_Message *msg = calloc(1, sizeof(My_Message));
146    if (!msg)
147      {
148         fprintf(stderr, "ERROR: could not calloc My_Message\n");
149         return NULL;
150      }
151    msg->message = eina_stringshare_add(message);
152    return msg;
153 }
154
155 static void
156 _my_message_free(My_Message *msg)
157 {
158    _eet_string_free(msg->screen_name);
159    _eet_string_free(msg->name);
160    _eet_string_free(msg->message);
161    free(msg);
162 }
163
164 static My_Post *
165 _my_post_new(const char *message)
166 {
167    My_Post *post = calloc(1, sizeof(My_Post));
168    if (!post)
169      {
170         fprintf(stderr, "ERROR: could not calloc My_Post\n");
171         return NULL;
172      }
173    post->message = eina_stringshare_add(message);
174    return post;
175 }
176
177 static void
178 _my_post_free(My_Post *post)
179 {
180    _eet_string_free(post->dm_to);
181    _eet_string_free(post->message);
182    free(post);
183 }
184
185 static My_Account *
186 _my_account_new(const char *name)
187 {
188    My_Account *acc = calloc(1, sizeof(My_Account));
189    if (!acc)
190      {
191         fprintf(stderr, "ERROR: could not calloc My_Account\n");
192         return NULL;
193      }
194    acc->name = eina_stringshare_add(name);
195    return acc;
196 }
197
198 static void
199 _my_account_free(My_Account *acc)
200 {
201    My_Message *m;
202    My_Post *p;
203
204    _eet_string_free(acc->name);
205
206    EINA_LIST_FREE(acc->messages, m)
207      _my_message_free(m);
208
209    EINA_LIST_FREE(acc->posts, p)
210      _my_post_free(p);
211
212    free(acc);
213 }
214
215 static My_Cache *
216 _my_cache_new(void)
217 {
218    My_Cache *my_cache = calloc(1, sizeof(My_Cache));
219    if (!my_cache)
220      {
221         fprintf(stderr, "ERROR: could not calloc My_Cache\n");
222         return NULL;
223      }
224
225    my_cache->version = 1;
226    return my_cache;
227 }
228
229 static void
230 _my_cache_free(My_Cache *my_cache)
231 {
232    My_Account *acc;
233    EINA_LIST_FREE(my_cache->accounts, acc)
234      _my_account_free(acc);
235    free(my_cache);
236 }
237
238 static My_Account *
239 _my_cache_account_find(My_Cache *my_cache, const char *name)
240 {
241    My_Account *acc;
242    Eina_List *l;
243    EINA_LIST_FOREACH(my_cache->accounts, l, acc)
244      if (strcmp(acc->name, name) == 0)
245        return acc;
246    return NULL;
247 }
248
249 static My_Cache *
250 _my_cache_load(const char *filename)
251 {
252    My_Cache *my_cache;
253    Eet_File *ef = eet_open(filename, EET_FILE_MODE_READ);
254    if (!ef)
255      {
256         fprintf(stderr, "ERROR: could not open '%s' for read\n", filename);
257         return NULL;
258      }
259
260    my_cache = eet_data_read(ef, _my_cache_descriptor, MY_CACHE_FILE_ENTRY);
261    if (!my_cache)
262      {
263         eet_close(ef);
264         return NULL;
265      }
266
267    if (my_cache->version < 1)
268      {
269         fprintf(stderr,
270                 "WARNING: version %#x was too old, upgrading it to %#x\n",
271                 my_cache->version, 1);
272
273         my_cache->version = 1;
274      }
275
276    if (_my_cache_file)
277      eet_close(_my_cache_file);
278    _my_cache_file = ef;
279    _my_cache_dict = eet_dictionary_get(ef);
280
281    return my_cache;
282 }
283
284 static Eina_Bool
285 _my_cache_save(const My_Cache *my_cache, const char *filename)
286 {
287    char tmp[PATH_MAX];
288    Eet_File *ef;
289    Eina_Bool ret;
290    unsigned int i, len;
291    struct stat st;
292
293    len = eina_strlcpy(tmp, filename, sizeof(tmp));
294    if (len + 12 >= (int)sizeof(tmp))
295      {
296         fprintf(stderr, "ERROR: file name is too big: %s\n", filename);
297         return EINA_FALSE;
298      }
299
300    i = 0;
301    do
302      {
303         snprintf(tmp + len, 12, ".%u", i);
304         i++;
305      }
306    while (stat(tmp, &st) == 0);
307
308    ef = eet_open(tmp, EET_FILE_MODE_WRITE);
309    if (!ef)
310      {
311         fprintf(stderr, "ERROR: could not open '%s' for write\n", tmp);
312         return EINA_FALSE;
313      }
314
315    ret = eet_data_write
316      (ef, _my_cache_descriptor, MY_CACHE_FILE_ENTRY, my_cache, EINA_TRUE);
317
318    // VERY IMPORTANT NOTE:
319    // after eet_close(), all strings mmaped from file will be GONE, invalid!
320    // you'll need to free the old cache and open the new one.
321    // For cache this is okay, as you should be saving not so often or just
322    // at end.
323    //
324    // This is a trade off, you save memory by using mmap()ed strings, but
325    // you have to care about this.
326    eet_close(ef);
327
328    if (ret)
329      {
330         unlink(filename);
331         rename(tmp, filename);
332      }
333
334    return ret;
335 }
336
337 int main(int argc, char *argv[])
338 {
339    My_Cache *my_cache;
340    const Eina_List *l_acc;
341    My_Account *acc;
342    int ret = 0;
343
344    if (argc < 3)
345      {
346         fprintf(stderr,
347                 "Usage:\n\t%s <input> <output> [action] [action-params]\n\n"
348                 "Where actions and their parameters:\n"
349                 "\tacc <name>\n"
350                 "\tpost <account-name> <message>\n"
351                 "\tmessage <account-name> <message>\n"
352                 "\n",
353                 argv[0]);
354         return -1;
355      }
356
357    eina_init();
358    eet_init();
359    _my_cache_descriptor_init();
360
361    my_cache = _my_cache_load(argv[1]);
362    if (!my_cache)
363      {
364         printf("creating new cache.\n");
365         my_cache = _my_cache_new();
366         if (!my_cache)
367           {
368              ret = -2;
369              goto end;
370           }
371      }
372
373    if (argc > 3)
374      {
375         if (strcmp(argv[3], "acc") == 0)
376           {
377              if (argc == 5)
378                {
379                   My_Account *acc = _my_cache_account_find(my_cache, argv[4]);
380                   if (!acc)
381                     {
382                        acc = _my_account_new(argv[4]);
383                        my_cache->accounts = eina_list_append
384                          (my_cache->accounts, acc);
385                     }
386                   else
387                     fprintf(stderr, "ERROR: account '%s' already exists.\n",
388                             argv[4]);
389                }
390              else
391                fprintf(stderr, "ERROR: wrong number of parameters (%d).\n",
392                        argc);
393           }
394         else if (strcmp(argv[3], "post") == 0)
395           {
396              if (argc == 6)
397                {
398                   My_Account *acc = _my_cache_account_find(my_cache, argv[4]);
399                   if (acc)
400                     {
401                        My_Post *post = _my_post_new(argv[5]);
402                        acc->posts = eina_list_append(acc->posts, post);
403                     }
404                   else
405                     fprintf(stderr, "ERROR: unknown account: '%s'\n", argv[4]);
406                }
407              else
408                fprintf(stderr, "ERROR: wrong number of parameters (%d).\n",
409                        argc);
410           }
411         else if (strcmp(argv[3], "message") == 0)
412           {
413              if (argc == 6)
414                {
415                   My_Account *acc = _my_cache_account_find(my_cache, argv[4]);
416                   if (acc)
417                     {
418                        My_Message *msg = _my_message_new(argv[5]);
419                        acc->messages = eina_list_append(acc->messages, msg);
420                     }
421                   else
422                     fprintf(stderr, "ERROR: unknown account: '%s'\n", argv[4]);
423                }
424              else
425                fprintf(stderr, "ERROR: wrong number of parameters (%d).\n",
426                        argc);
427           }
428         else
429           fprintf(stderr, "ERROR: unknown action '%s'\n", argv[2]);
430      }
431
432    printf("My_Cache:\n"
433           "\tversion.: %#x\n"
434           "\taccounts: %u\n",
435           my_cache->version,
436           eina_list_count(my_cache->accounts));
437    EINA_LIST_FOREACH(my_cache->accounts, l_acc, acc)
438      {
439         const My_Post *post;
440
441         printf("\t  > %-#8x '%.20s' stats: m=%u, p=%u\n",
442                acc->id, acc->name ? acc->name : "",
443                eina_list_count(acc->messages),
444                eina_list_count(acc->posts));
445
446         if (eina_list_count(acc->messages))
447           {
448              const Eina_List *l;
449              const My_Message *msg;
450              printf("\t  |messages:\n");
451
452              EINA_LIST_FOREACH(acc->messages, l, msg)
453                {
454                   printf("\t  |   %-8x '%s' [%s]: '%.20s'\n",
455                          msg->id,
456                          msg->name ? msg->name : "",
457                          msg->screen_name ? msg->screen_name : "",
458                          msg->message ? msg->message : "");
459                }
460           }
461
462         if (eina_list_count(acc->posts))
463           {
464              const Eina_List *l;
465              const My_Post *post;
466              printf("\t  |posts:\n");
467
468              EINA_LIST_FOREACH(acc->posts, l, post)
469                {
470                   if (post->dm_to)
471                     printf("\t  |  @%s: '%.20s'\n", post->dm_to, post->message);
472                   else
473                     printf("\t  |  '%.20s'\n", post->message);
474                }
475           }
476         printf("\n");
477      }
478
479    if (!_my_cache_save(my_cache, argv[2]))
480      ret = -3;
481
482    _my_cache_free(my_cache);
483
484  end:
485    _my_cache_descriptor_shutdown();
486    eet_shutdown();
487    eina_shutdown();
488
489    return ret;
490 }