evas/cserve2: Make Font_Entry inherit from Entry.
[profile/ivi/evas.git] / src / bin / evas_cserve2_cache.c
1 #ifdef HAVE_CONFIG_H
2 # include "config.h"
3 #endif
4
5 #include <string.h>
6
7 #include "evas_cserve2.h"
8
9 typedef struct _Request_Funcs Request_Funcs;
10 typedef struct _Request Request;
11
12 typedef struct _Entry Entry;
13 typedef struct _Reference Reference;
14 typedef struct _Waiter Waiter;
15 typedef struct _File_Data File_Data;
16 typedef struct _Image_Data Image_Data;
17 typedef struct _File_Watch File_Watch;
18
19 typedef struct _Font_Source Font_Source;
20 typedef struct _Font_Entry Font_Entry;
21 typedef struct _Font_Cache Font_Cache;
22 typedef struct _Glyph_Entry Glyph_Entry;
23
24 typedef void *(*Request_Msg_Create)(Entry *e, int *size);
25 typedef void (*Request_Response)(Entry *e, void *resp);
26 typedef void (*Request_Error)(Entry *e, Error_Type error);
27
28 struct _Request_Funcs {
29    Request_Msg_Create msg_create;
30    Request_Response response;
31    Request_Error error;
32 };
33
34 struct _Request {
35    Entry *entry;
36    Eina_List *waiters;
37    Eina_Bool processing;
38    Request_Funcs *funcs;
39 };
40
41 typedef enum {
42    CSERVE2_IMAGE_FILE,
43    CSERVE2_IMAGE_DATA,
44    CSERVE2_FONT_ENTRY
45 } Entry_Type;
46
47 struct _Entry {
48    unsigned int id;
49    Eina_List *references;
50    Request *request;
51    Entry_Type type;
52 };
53
54 struct _File_Data {
55    Entry base;
56    char *path;
57    char *key;
58    int w, h;
59    int frame_count;
60    int loop_count;
61    int loop_hint;
62    const char *loader_data;
63    File_Watch *watcher;
64    Eina_List *images;
65    Eina_Bool alpha : 1;
66    Eina_Bool invalid : 1;
67 };
68
69 // Default values for load options commented below
70 struct _Image_Data {
71    Entry base;
72    unsigned int file_id;
73    File_Data *file;
74    struct {
75       double dpi; // dpi < -1
76       int w, h; // w and h < -1
77       int scale_down; // scale_down < -1
78       int rx, ry, rw, rh; // rx, ry, rw, rh < -1
79       Eina_Bool orientation; // orientation == 0
80    } opts;
81    Shm_Handle *shm;
82    Eina_Bool alpha_sparse : 1;
83    Eina_Bool unused : 1;
84    Eina_Bool doload : 1;
85 };
86
87 struct _Font_Source {
88    const char *name;
89    const char *file;
90    int references;
91 };
92
93 struct _Font_Entry {
94    Entry *base;
95    unsigned int rend_flags;
96    unsigned int hint;
97    unsigned int size;
98    unsigned int dpi;
99    Font_Source *src;
100 };
101
102 struct _Font_Cache {
103    Font_Entry *fe;
104    struct {
105       const char *name;
106       void *data;
107       unsigned int size;
108       unsigned int usage;
109    } shm;
110    Eina_Inlist *glyphs;
111 };
112
113 struct _Glyph_Entry {
114    EINA_INLIST;
115    Font_Entry *fe;
116    Font_Cache *fi;
117    unsigned int index;
118    unsigned int offset;
119 };
120
121 struct _Reference {
122    Client *client;
123    Entry *entry;
124    unsigned int client_entry_id; // for reverse lookup
125    int count;
126 };
127
128 struct _Waiter {
129    Reference *ref;
130    unsigned int rid;
131    Message_Type type;
132 };
133
134 struct _File_Watch {
135    const char *path;
136    Eina_List *entries;
137 };
138
139 static Eina_List *open_requests = NULL;
140 static Eina_List *load_requests = NULL;
141 static Eina_List *spload_requests = NULL; // speculative preload requests
142
143 static unsigned int _file_id = 0; // id unique number
144 static unsigned int _image_id = 0; // id unique number
145 static Eina_Hash *file_ids = NULL; // maps path + key --> file_id
146 static Eina_Hash *file_entries = NULL; // maps file_id --> entry
147
148 static Eina_Hash *image_ids = NULL; // maps file id + load opts --> image id
149 static Eina_Hash *image_entries = NULL; // maps image_id --> entry
150
151 static Eina_Hash *font_sources = NULL; // font path --> font source
152 static Eina_Hash *font_entries = NULL; // maps font path + options --> entry
153
154 static Eina_Hash *file_watch = NULL;
155
156 static Eina_List *image_entries_lru = NULL;
157
158 static int max_unused_mem_usage = 5 * 1024; /* in kbytes */
159 static int unused_mem_usage = 0;
160
161 static void
162 _image_opened_send(Client *client, File_Data *entry, unsigned int rid)
163 {
164     int size;
165     Msg_Opened msg;
166
167     DBG("Sending OPENED reply for entry: %d and RID: %d.", entry->base.id, rid);
168     // clear the struct with possible paddings, since it is not aligned.
169     memset(&msg, 0, sizeof(msg));
170     msg.base.rid = rid;
171     msg.base.type = CSERVE2_OPENED;
172     msg.image.w = entry->w;
173     msg.image.h = entry->h;
174     msg.image.frame_count = entry->frame_count;
175     msg.image.loop_count = entry->loop_count;
176     msg.image.loop_hint = entry->loop_hint;
177     msg.image.alpha = entry->alpha;
178
179     size = sizeof(msg);
180     cserve2_client_send(client, &size, sizeof(size));
181     cserve2_client_send(client, &msg, sizeof(msg));
182     // _cserve2_cache_load_requests_process();
183 }
184
185 static void
186 _image_loaded_send(Client *client, Image_Data *entry, unsigned int rid)
187 {
188    int size;
189    const char *shmpath = cserve2_shm_name_get(entry->shm);
190    Msg_Loaded msg;
191    int path_len;
192    char *buf;
193
194    DBG("Sending LOADED reply for entry %d and RID: %d.", entry->base.id, rid);
195    path_len = strlen(shmpath) + 1;
196
197    memset(&msg, 0, sizeof(msg));
198    msg.base.rid = rid;
199    msg.base.type = CSERVE2_LOADED;
200
201    msg.shm.mmap_offset = cserve2_shm_map_offset_get(entry->shm);
202    msg.shm.use_offset = cserve2_shm_offset_get(entry->shm);
203    msg.shm.mmap_size = cserve2_shm_map_size_get(entry->shm);
204    msg.shm.image_size = cserve2_shm_size_get(entry->shm);
205    msg.alpha_sparse = entry->alpha_sparse;
206
207    buf = malloc(sizeof(msg) + path_len);
208
209    memcpy(buf, &msg, sizeof(msg));
210    memcpy(buf + sizeof(msg), shmpath, path_len);
211
212    size = sizeof(msg) + path_len;
213
214    cserve2_client_send(client, &size, sizeof(size));
215    cserve2_client_send(client, buf, size);
216
217    free(buf);
218 }
219
220 static void
221 _image_preloaded_send(Client *client, unsigned int rid)
222 {
223    int size;
224    Msg_Preloaded msg;
225
226    DBG("Sending PRELOADED reply for RID: %d.", rid);
227    memset(&msg, 0, sizeof(msg));
228    msg.base.rid = rid;
229    msg.base.type = CSERVE2_PRELOADED;
230
231    size = sizeof(msg);
232    cserve2_client_send(client, &size, sizeof(size));
233    cserve2_client_send(client, &msg, size);
234 }
235
236 static void *
237 _open_request_build(File_Data *f, int *bufsize)
238 {
239    char *buf;
240    int size, pathlen, keylen;
241    Slave_Msg_Image_Open msg;
242
243    pathlen = strlen(f->path) + 1;
244    keylen = strlen(f->key) + 1;
245
246    size = sizeof(msg) + pathlen + keylen;
247    buf = malloc(size);
248    if (!buf) return NULL;
249
250    memset(&msg, 0, sizeof(msg));
251    memcpy(buf, &msg, sizeof(msg));
252    memcpy(buf + sizeof(msg), f->path, pathlen);
253    memcpy(buf + sizeof(msg) + pathlen, f->key, keylen);
254
255    *bufsize = size;
256    return buf;
257 }
258
259 static void
260 _request_failed(Entry *e, Error_Type type)
261 {
262    Waiter *w;
263    Eina_List *l;
264    Reference *ref;
265
266    DBG("Request for entry %p failed with error %d", e, type);
267    EINA_LIST_FREE(e->request->waiters, w)
268      {
269         cserve2_client_error_send(w->ref->client, w->rid, type);
270
271         w->ref->count--;
272         free(w);
273      }
274
275    EINA_LIST_FOREACH(e->references, l, ref)
276      {
277         Eina_Hash *hash = NULL;
278         if (e->type == CSERVE2_IMAGE_FILE)
279           hash = ref->client->files.referencing;
280         else if (e->type == CSERVE2_IMAGE_DATA)
281           hash = ref->client->images.referencing;
282         else
283           continue;
284
285         eina_hash_del_by_key(hash, &(ref->client_entry_id));
286      }
287 }
288
289 static void
290 _open_request_response(File_Data *e, Slave_Msg_Image_Opened *resp)
291 {
292    Waiter *w;
293
294    e->w = resp->w;
295    e->h = resp->h;
296    e->frame_count = resp->frame_count;
297    e->loop_count = resp->loop_count;
298    e->loop_hint = resp->loop_hint;
299    e->alpha = resp->alpha;
300    if (resp->has_loader_data)
301      {
302         const char *ldata = (const char *)resp +
303                                            sizeof(Slave_Msg_Image_Opened);
304         e->loader_data = eina_stringshare_add(ldata);
305      }
306
307    DBG("Finished opening file %d. Notifying %d waiters.", e->base.id,
308        e->base.request->waiters ? eina_list_count(e->base.request->waiters) : 0);
309    EINA_LIST_FREE(e->base.request->waiters, w)
310      {
311         _image_opened_send(w->ref->client, e, w->rid);
312         free(w);
313      }
314 }
315
316 static Request_Funcs _open_funcs = {
317    .msg_create = (Request_Msg_Create)_open_request_build,
318    .response = (Request_Response)_open_request_response,
319    .error = (Request_Error)_request_failed
320 };
321
322 static void *
323 _load_request_build(Image_Data *i, int *bufsize)
324 {
325    char *buf, *ptr;
326    const char *shmpath;
327    int size;
328    int shmlen, filelen, keylen, loaderlen;
329    Slave_Msg_Image_Load msg;
330
331    // opening shm for this file
332    i->shm = cserve2_shm_request(i->file->w * i->file->h * 4);
333
334    shmpath = cserve2_shm_name_get(i->shm);
335
336    shmlen = strlen(shmpath) + 1;
337    filelen = strlen(i->file->path) + 1;
338    keylen = strlen(i->file->key) + 1;
339    if (i->file->loader_data)
340      loaderlen = strlen(i->file->loader_data) + 1;
341    else
342      loaderlen = 0;
343
344    size = sizeof(msg) + shmlen + filelen + keylen + loaderlen;
345    buf = malloc(size);
346    if (!buf) return NULL;
347
348    memset(&msg, 0, sizeof(msg));
349    msg.w = i->file->w;
350    msg.h = i->file->h;
351    msg.alpha = i->file->alpha;
352    msg.opts.w = i->opts.w;
353    msg.opts.h = i->opts.h;
354    msg.opts.rx = i->opts.rx;
355    msg.opts.ry = i->opts.ry;
356    msg.opts.rw = i->opts.rw;
357    msg.opts.rh = i->opts.rh;
358    msg.opts.scale_down_by = i->opts.scale_down;
359    msg.opts.dpi = i->opts.dpi;
360    msg.opts.orientation = i->opts.orientation;
361
362    msg.shm.mmap_offset = cserve2_shm_map_offset_get(i->shm);
363    msg.shm.image_offset = cserve2_shm_offset_get(i->shm);
364    msg.shm.mmap_size = cserve2_shm_map_size_get(i->shm);
365    msg.shm.image_size = cserve2_shm_size_get(i->shm);
366
367    msg.has_loader_data = !!loaderlen;
368
369    memcpy(buf, &msg, sizeof(msg));
370    ptr = buf + sizeof(msg);
371
372    memcpy(ptr, shmpath, shmlen);
373    ptr += shmlen;
374    memcpy(ptr, i->file->path, filelen);
375    ptr += filelen;
376    memcpy(ptr, i->file->key, keylen);
377    ptr += keylen;
378    memcpy(ptr, i->file->loader_data, loaderlen);
379
380    *bufsize = size;
381    return buf;
382 }
383
384 static void
385 _load_request_response(Image_Data *e, Slave_Msg_Image_Loaded *resp)
386 {
387    Waiter *w;
388
389    e->alpha_sparse = resp->alpha_sparse;
390    if (!e->doload)
391      DBG("Entry %d loaded by speculative preload.", e->base.id);
392
393    DBG("Finished loading image %d. Notifying %d waiters.", e->base.id,
394        e->base.request->waiters ? eina_list_count(e->base.request->waiters) : 0);
395    EINA_LIST_FREE(e->base.request->waiters, w)
396      {
397         if (w->type == CSERVE2_LOAD)
398           _image_loaded_send(w->ref->client, e, w->rid);
399         else if (w->type == CSERVE2_PRELOAD)
400           _image_preloaded_send(w->ref->client, w->rid);
401         // else w->type == CSERVE2_SETOPTS --> do nothing
402
403         free(w);
404      }
405 }
406
407 static Request_Funcs _load_funcs = {
408    .msg_create = (Request_Msg_Create)_load_request_build,
409    .response = (Request_Response)_load_request_response,
410    .error = (Request_Error)_request_failed
411 };
412
413 static unsigned int
414 _img_opts_id_get(Image_Data *im, char *buf, int size)
415 {
416    uintptr_t image_id;
417
418    snprintf(buf, size, "%u:%0.3f:%dx%d:%d:%d,%d+%dx%d:%d",
419             im->file_id, im->opts.dpi, im->opts.w, im->opts.h,
420             im->opts.scale_down, im->opts.rx, im->opts.ry,
421             im->opts.rw, im->opts.rh, im->opts.orientation);
422
423    image_id = (uintptr_t)eina_hash_find(image_ids, buf);
424
425    return image_id;
426 }
427
428 static int
429 _image_entry_size_get(Image_Data *e)
430 {
431    int size = sizeof(Image_Data);
432    /* XXX: get the overhead of the shm handler too */
433    if (e->shm)
434      size += cserve2_shm_size_get(e->shm);
435    return size / 1024;
436 }
437
438 static void
439 _file_id_free(File_Data *entry)
440 {
441    char buf[4096];
442
443    DBG("Removing entry file id: %d, file: \"%s:%s\"",
444        entry->base.id, entry->path, entry->key);
445    snprintf(buf, sizeof(buf), "%s:%s", entry->path, entry->key);
446    eina_hash_del_by_key(file_ids, buf);
447 }
448
449 static void
450 _image_id_free(Image_Data *entry)
451 {
452    char buf[4096];
453
454    DBG("Removing entry image id: %d", entry->base.id);
455
456    _img_opts_id_get(entry, buf, sizeof(buf));
457    eina_hash_del_by_key(image_ids, buf);
458 }
459
460 static void
461 _image_entry_free(Image_Data *entry)
462 {
463    File_Data *fentry = entry->file;
464
465    if (entry->base.request)
466      {
467         if (entry->base.request->processing)
468           entry->base.request->entry = NULL;
469         else if (!entry->base.request->waiters)
470           {
471              if (entry->doload)
472                load_requests = eina_list_remove(load_requests,
473                                                 entry->base.request);
474              else
475                spload_requests = eina_list_remove(spload_requests,
476                                                   entry->base.request);
477           }
478      }
479
480    if (entry->unused)
481      {
482         image_entries_lru = eina_list_remove(image_entries_lru, entry);
483         unused_mem_usage -= _image_entry_size_get(entry);
484      }
485
486    if (fentry)
487      fentry->images = eina_list_remove(fentry->images, entry);
488    if (entry->shm)
489      cserve2_shm_unref(entry->shm);
490    free(entry);
491 }
492
493 static void
494 _hash_image_entry_free(void *data)
495 {
496    Image_Data *entry = data;
497
498    _image_id_free(entry);
499    _image_entry_free(entry);
500 }
501
502 static void
503 _file_entry_free(File_Data *entry)
504 {
505    File_Watch *fw;
506
507    // Should we call free for each of the images too?
508    // If everything goes fine, it's not necessary.
509    if (entry->images)
510      {
511         ERR("Freeing file %d (\"%s:%s\") image data still referenced.",
512             entry->base.id, entry->path, entry->key);
513         eina_list_free(entry->images);
514      }
515
516    if (entry->base.request)
517      {
518         if (entry->base.request->processing)
519           entry->base.request->entry = NULL;
520         else if (!entry->base.request->waiters)
521           {
522              open_requests = eina_list_remove(open_requests,
523                                               entry->base.request);
524              free(entry->base.request);
525           }
526      }
527
528    if ((fw = entry->watcher))
529      {
530         fw->entries = eina_list_remove(fw->entries, entry);
531         if (!fw->entries)
532           eina_hash_del_by_key(file_watch, fw->path);
533      }
534
535    free(entry->key);
536    free(entry->path);
537    eina_stringshare_del(entry->loader_data);
538    free(entry);
539 }
540
541 static void
542 _hash_file_entry_free(void *data)
543 {
544    File_Data *entry = data;
545    // TODO: Add some checks to make sure that we are freeing an
546    // unused entry.
547
548    _file_id_free(entry);
549    _file_entry_free(entry);
550 }
551
552 static void
553 _file_watch_free(void *data)
554 {
555    File_Watch *fw = data;
556    cserve2_file_change_watch_del(fw->path);
557    eina_stringshare_del(fw->path);
558    eina_list_free(fw->entries);
559    free(fw);
560 }
561
562 static int
563 _font_entry_cmp(const Font_Entry *k1, int k1_length __UNUSED__, const Font_Entry *k2, int k2_length __UNUSED__)
564 {
565    if (k1->src->name == k2->src->name)
566      {
567         if (k1->size == k2->size)
568           {
569              if (k1->rend_flags == k2->rend_flags)
570                {
571                   if (k1->hint == k2->hint)
572                     return k1->dpi - k2->dpi;
573                   return k1->hint - k2->hint;
574                }
575              return k1->rend_flags - k2->rend_flags;
576           }
577         return k1->size - k2->size;
578      }
579    return strcmp(k1->src->name, k2->src->name);
580 }
581
582 static int
583 _font_entry_key_hash(const Font_Entry *key, int key_length __UNUSED__)
584 {
585    int hash;
586    hash = eina_hash_djb2(key->src->name, eina_stringshare_strlen(key->src->name) + 1);
587    hash ^= eina_hash_int32(&key->rend_flags, sizeof(int));
588    hash ^= eina_hash_int32(&key->size, sizeof(int));
589    hash ^= eina_hash_int32(&key->dpi, sizeof(int));
590
591    return hash;
592 }
593
594 static void
595 _font_entry_free(Font_Entry *fe)
596 {
597    free(fe);
598 }
599
600 static void
601 _font_source_free(Font_Source *fs)
602 {
603    if (fs->name) eina_stringshare_del(fs->name);
604    if (fs->file) eina_stringshare_del(fs->file);
605
606    free(fs);
607 }
608
609 void
610 cserve2_cache_init(void)
611 {
612    file_ids = eina_hash_string_superfast_new(NULL);
613    file_entries = eina_hash_int32_new(_hash_file_entry_free);
614    image_ids = eina_hash_string_superfast_new(NULL);
615    image_entries = eina_hash_string_superfast_new(_hash_image_entry_free);
616    file_watch = eina_hash_string_superfast_new(_file_watch_free);
617
618    font_sources = eina_hash_string_small_new(EINA_FREE_CB(_font_source_free));
619    font_entries = eina_hash_new(NULL,
620                                 EINA_KEY_CMP(_font_entry_cmp),
621                                 EINA_KEY_HASH(_font_entry_key_hash),
622                                 EINA_FREE_CB(_font_entry_free),
623                                 5);
624 }
625
626 void
627 cserve2_cache_shutdown(void)
628 {
629    eina_hash_free(image_entries);
630    eina_hash_free(image_ids);
631    eina_hash_free(file_entries);
632    eina_hash_free(file_ids);
633    eina_hash_free(file_watch);
634
635    eina_hash_free(font_entries);
636    eina_hash_free(font_sources);
637 }
638
639 static void
640 _request_answer_del(Eina_List **requests, Request *req, Client *client, Error_Type err)
641 {
642    Eina_List *l, *l_next;
643    Waiter *it;
644
645    DBG("Removing answer requests from entry: %d, client: %d",
646        req->entry->id, client->id);
647
648    EINA_LIST_FOREACH_SAFE(req->waiters, l, l_next, it)
649      {
650         if (it->ref->client->id == client->id)
651           {
652              cserve2_client_error_send(client, it->rid, err);
653              req->waiters = eina_list_remove_list(req->waiters, l);
654              free(it);
655           }
656      }
657
658    // FIXME: Should this be really here? I guess that it should be in the
659    // entry_free_cb function, or entry_reference_del, when there are no more
660    // references
661    if (!req->entry && !req->waiters)
662      {
663         *requests = eina_list_remove(*requests, req);
664         free(req);
665      }
666 }
667
668 static void
669 _request_answer_all_del(Eina_List **requests, Request *req, Error_Type err)
670 {
671    Waiter *it;
672
673    DBG("Removing all answer requests from entry: %d", req->entry->id);
674
675    EINA_LIST_FREE(req->waiters, it)
676      {
677         cserve2_client_error_send(it->ref->client, it->rid, err);
678         free(it);
679      }
680
681    *requests = eina_list_remove(*requests, req);
682    free(req);
683 }
684
685 static void
686 _request_answer_add(Request *req, Reference *ref, unsigned int rid, Message_Type type)
687 {
688    Waiter *w = malloc(sizeof(*w));
689
690    w->ref = ref;
691    w->rid = rid;
692    w->type = type;
693
694    DBG("Add answer request for entry id: %d, client: %d, rid: %d",
695        req->entry->id, ref->client->id, rid);
696    req->waiters = eina_list_append(req->waiters, w);
697 }
698
699 static void
700 _request_add(Eina_List **requests, Entry *entry, Reference *ref, unsigned int rid, Message_Type type)
701 {
702    Request *req;
703
704    // add the request if it doesn't exist yet
705    if (!entry->request)
706      {
707         req = malloc(sizeof(*req));
708         req->entry = entry;
709         req->waiters = NULL;
710         req->processing = EINA_FALSE;
711         entry->request = req;
712         if (type == CSERVE2_OPEN)
713           req->funcs = &_open_funcs;
714         else
715           req->funcs = &_load_funcs;
716         *requests = eina_list_append(*requests, req);
717         DBG("Add request for entry id: %d, client: %d, rid: %d",
718             req->entry->id, ref->client->id, rid);
719      }
720    else
721      req = entry->request;
722
723    if (type != CSERVE2_SETOPTS)
724      _request_answer_add(req, ref, rid, type);
725    else
726      DBG("Adding entry for speculative preload: id=%d", req->entry->id);
727 }
728
729 static Reference *
730 _entry_reference_add(Entry *entry, Client *client, unsigned int client_entry_id)
731 {
732    Reference *ref;
733
734    // increase reference for this file
735    ref = malloc(sizeof(*ref));
736    ref->client = client;
737    ref->entry = entry;
738    ref->client_entry_id = client_entry_id;
739    ref->count = 1;
740    entry->references = eina_list_append(entry->references, ref);
741
742    return ref;
743 }
744
745 static int
746 _cserve2_cache_open_requests_process(int nloaders)
747 {
748    Request *req;
749    char *slave_cmd_data;
750    int slave_cmd_size;
751
752    while ((nloaders > 0) && (open_requests))
753      {
754         // remove the first element from the list and process this element
755         req = eina_list_data_get(open_requests);
756         open_requests = eina_list_remove_list(open_requests, open_requests);
757
758         DBG("Processing OPEN request for file entry: %d", req->entry->id);
759
760         slave_cmd_data = req->funcs->msg_create(req->entry, &slave_cmd_size);
761
762         cserve2_slave_cmd_dispatch(req, IMAGE_OPEN, slave_cmd_data,
763                                    slave_cmd_size);
764
765         free(slave_cmd_data);
766
767         req->processing = EINA_TRUE;
768         nloaders--;
769      }
770
771    return nloaders;
772 }
773
774 static int
775 _cserve2_cache_load_requests_list_process(Eina_List **queue, int nloaders)
776 {
777    Eina_List *skipped = NULL;
778    Request *req;
779
780    while ((nloaders > 0) && (*queue))
781      {
782         Image_Data *ientry;
783         char *buf;
784         int size;
785
786         // remove the first element from the list and process this element
787         req = eina_list_data_get(*queue);
788         *queue = eina_list_remove_list(*queue, *queue);
789
790         ientry = (Image_Data *)req->entry;
791         if (!ientry->file)
792           {
793              ERR("File entry doesn't exist for entry id %d", req->entry->id);
794              _request_failed(req->entry, CSERVE2_INVALID_CACHE);
795              continue;
796           }
797
798         if (ientry->file->base.request)
799           {
800              /* OPEN still pending, skip this request */
801              skipped = eina_list_append(skipped, req);
802              continue;
803           }
804
805         DBG("Processing LOAD request for image entry: %d", req->entry->id);
806
807         buf = req->funcs->msg_create(req->entry, &size);
808
809         cserve2_slave_cmd_dispatch(req, IMAGE_LOAD, buf, size);
810
811         free(buf);
812
813         req->processing = EINA_TRUE;
814
815         nloaders--;
816      }
817
818    EINA_LIST_FREE(skipped, req)
819       *queue = eina_list_append(*queue, req);
820
821    return nloaders;
822 }
823
824 static void
825 _cserve2_cache_load_requests_process(int nloaders)
826 {
827    nloaders = _cserve2_cache_load_requests_list_process(&load_requests,
828                                                         nloaders);
829    _cserve2_cache_load_requests_list_process(&spload_requests, nloaders - 1);
830 }
831
832
833 void
834 cserve2_cache_requests_process(void)
835 {
836    int avail_loaders;
837
838    avail_loaders = cserve2_slave_available_get();
839    avail_loaders = _cserve2_cache_open_requests_process(avail_loaders);
840    _cserve2_cache_load_requests_process(avail_loaders);
841 }
842
843 static void
844 _entry_unused_push(Image_Data *e)
845 {
846    int size = _image_entry_size_get(e);
847
848    if ((size > max_unused_mem_usage) || !(e->doload))
849      {
850         eina_hash_del_by_key(image_entries, &e->base.id);
851         return;
852      }
853    while (size > (max_unused_mem_usage - unused_mem_usage))
854      {
855         Entry *ie = eina_list_data_get(eina_list_last(image_entries_lru));
856         eina_hash_del_by_key(image_entries, &ie->id);
857      }
858    image_entries_lru = eina_list_append(image_entries_lru, e);
859    e->unused = EINA_TRUE;
860    unused_mem_usage += size;
861 }
862
863 static void
864 _entry_reference_del(Entry *entry, Reference *ref)
865 {
866    entry->references = eina_list_remove(entry->references, ref);
867
868    if (entry->references)
869      goto free_ref;
870
871    if (entry->type == CSERVE2_IMAGE_FILE)
872      {
873         File_Data *fentry = (File_Data *)entry;
874
875         if (fentry->invalid)
876           _file_entry_free(fentry);
877         else
878           {
879              Image_Data *ie;
880              EINA_LIST_FREE(fentry->images, ie)
881                 ie->file = NULL;
882              eina_hash_del_by_key(file_entries, &entry->id);
883           }
884      }
885    else if (entry->type == CSERVE2_IMAGE_DATA)
886      {
887         Image_Data *ientry = (Image_Data *)entry;
888
889         if (!ientry->file)
890           eina_hash_del_by_key(image_entries, &entry->id);
891         else if (ientry->file->invalid)
892           _image_entry_free(ientry);
893         else
894           _entry_unused_push(ientry);
895      }
896    else
897      ERR("Wrong type of entry.");
898
899 free_ref:
900    free(ref);
901 }
902
903 static void
904 _entry_free_cb(void *data)
905 {
906    Reference *ref = data;
907    Entry *entry;
908
909    DBG("Removing client reference for entry id: %d, client: %d",
910        ref->entry->id, ref->client->id);
911
912    entry = ref->entry;
913
914    if (entry->request && !entry->request->processing)
915      {
916         if (entry->type == CSERVE2_IMAGE_FILE)
917           _request_answer_del(&open_requests, entry->request, ref->client,
918                               CSERVE2_REQUEST_CANCEL);
919         else if (entry->type == CSERVE2_IMAGE_DATA)
920           {
921              if (((Image_Data *)entry)->doload)
922                _request_answer_del(&load_requests, entry->request,
923                                    ref->client, CSERVE2_REQUEST_CANCEL);
924              else
925                _request_answer_del(&spload_requests, entry->request,
926                                    ref->client, CSERVE2_REQUEST_CANCEL);
927           }
928      }
929
930    _entry_reference_del(entry, ref);
931 }
932
933 void
934 cserve2_cache_client_new(Client *client)
935 {
936    client->files.referencing = eina_hash_int32_new(_entry_free_cb);
937    client->images.referencing = eina_hash_int32_new(_entry_free_cb);
938 }
939
940 void
941 cserve2_cache_client_del(Client *client)
942 {
943    // will call _entry_free_cb() for every entry
944    eina_hash_free(client->images.referencing);
945    // will call _entry_free_cb() for every entry
946    eina_hash_free(client->files.referencing);
947 }
948
949 static Image_Data *
950 _image_msg_new(Client *client, Msg_Setopts *msg)
951 {
952    Reference *ref;
953    Image_Data *im_entry;
954
955    ref = eina_hash_find(client->files.referencing, &msg->file_id);
956    if (!ref)
957      {
958         ERR("Couldn't find file id: %d, for image id: %d",
959             msg->file_id, msg->image_id);
960         cserve2_client_error_send(client, msg->base.rid,
961                                   CSERVE2_INVALID_CACHE);
962         return NULL;
963      }
964    if (((File_Data *)ref->entry)->invalid)
965      {
966         cserve2_client_error_send(client, msg->base.rid,
967                                   CSERVE2_FILE_CHANGED);
968         return NULL;
969      }
970
971    im_entry = calloc(1, sizeof(*im_entry));
972    im_entry->base.type = CSERVE2_IMAGE_DATA;
973    im_entry->file_id = ref->entry->id;
974    im_entry->file = (File_Data *)ref->entry;
975    im_entry->opts.dpi = msg->opts.dpi;
976    im_entry->opts.w = msg->opts.w;
977    im_entry->opts.h = msg->opts.h;
978    im_entry->opts.scale_down = msg->opts.scale_down;
979    im_entry->opts.rx = msg->opts.rx;
980    im_entry->opts.ry = msg->opts.ry;
981    im_entry->opts.rw = msg->opts.rw;
982    im_entry->opts.rh = msg->opts.rh;
983    im_entry->opts.orientation = msg->opts.orientation;
984
985    return im_entry;
986 }
987
988 static void
989 _file_changed_cb(const char *path __UNUSED__, Eina_Bool deleted __UNUSED__, void *data)
990 {
991    File_Watch *fw = data;
992    File_Data *e;
993    Eina_List *l;
994
995    EINA_LIST_FOREACH(fw->entries, l, e)
996      {
997         Eina_List *ll;
998         Image_Data *ie;
999
1000         e->invalid = EINA_TRUE;
1001         e->watcher = NULL;
1002
1003         EINA_LIST_FOREACH(e->images, ll, ie)
1004           {
1005              _image_id_free(ie);
1006              eina_hash_set(image_entries, &ie->base.id, NULL);
1007              if (ie->base.request && !ie->base.request->processing)
1008                {
1009                   if (ie->doload)
1010                     _request_answer_all_del(&load_requests, ie->base.request,
1011                                             CSERVE2_FILE_CHANGED);
1012                   else
1013                     _request_answer_all_del(&spload_requests, ie->base.request,
1014                                             CSERVE2_FILE_CHANGED);
1015                }
1016              ie->base.request = NULL;
1017              if (ie->unused)
1018                _image_entry_free(ie);
1019           }
1020
1021         _file_id_free(e);
1022         eina_hash_set(file_entries, &e->base.id, NULL);
1023         if (e->base.request && !e->base.request->processing)
1024           _request_answer_all_del(&open_requests, e->base.request,
1025                                   CSERVE2_FILE_CHANGED);
1026         e->base.request = NULL;
1027         if (!e->images && !e->base.references)
1028           _file_entry_free(e);
1029      }
1030
1031    eina_hash_del_by_key(file_watch, fw->path);
1032 }
1033
1034 int
1035 cserve2_cache_file_open(Client *client, unsigned int client_file_id, const char *path, const char *key, unsigned int rid)
1036 {
1037    unsigned int file_id;
1038    File_Data *entry;
1039    Reference *ref;
1040    File_Watch *fw;
1041    char buf[4906];
1042
1043    // look for this file on client references
1044    ref = eina_hash_find(client->files.referencing, &client_file_id);
1045    if (ref)
1046      {
1047         entry = (File_Data *)ref->entry;
1048
1049         if (entry->invalid)
1050           {
1051              cserve2_client_error_send(client, rid, CSERVE2_FILE_CHANGED);
1052              return -1;
1053           }
1054
1055         DBG("found client file id: %d", client_file_id);
1056         ref->count++;
1057
1058         // File already being loaded, just add the request to be replied
1059         if (entry->base.request)
1060           _request_answer_add(entry->base.request, ref, rid, CSERVE2_OPEN);
1061         else
1062           _image_opened_send(client, entry, rid);
1063         return 0;
1064      }
1065
1066    // search whether the file is already opened by another client
1067    snprintf(buf, sizeof(buf), "%s:%s", path, key);
1068    file_id = (unsigned int)eina_hash_find(file_ids, buf);
1069    if (file_id)
1070      {
1071         DBG("found file_id %u for client file id %d",
1072                 file_id, client_file_id);
1073         entry = eina_hash_find(file_entries, &file_id);
1074         if (!entry)
1075           {
1076              ERR("file \"%s\" is in file_ids hash but not in entries hash.",
1077                  buf);
1078              cserve2_client_error_send(client, rid, CSERVE2_INVALID_CACHE);
1079              return -1;
1080           }
1081         ref = _entry_reference_add((Entry *)entry, client, client_file_id);
1082         eina_hash_add(client->files.referencing, &client_file_id, ref);
1083         if (entry->base.request)
1084           _request_answer_add(entry->base.request, ref, rid, CSERVE2_OPEN);
1085         else // File already loaded, otherwise there would be a request
1086           _image_opened_send(client, entry, rid);
1087         return 0;
1088      }
1089
1090    file_id = _file_id++;
1091    while ((file_id == 0) || (eina_hash_find(file_entries, &file_id)))
1092      file_id = _file_id++;
1093
1094    DBG("Creating new entry with file_id: %u for file \"%s:%s\"",
1095        file_id, path, key);
1096    entry = calloc(1, sizeof(*entry));
1097    entry->base.type = CSERVE2_IMAGE_FILE;
1098    entry->path = strdup(path);
1099    entry->key = strdup(key);
1100    entry->base.id = file_id;
1101    eina_hash_add(file_entries, &file_id, entry);
1102    eina_hash_add(file_ids, buf, (void *)file_id);
1103    ref = _entry_reference_add((Entry *)entry, client, client_file_id);
1104    eina_hash_add(client->files.referencing, &client_file_id, ref);
1105
1106    fw = eina_hash_find(file_watch, entry->path);
1107    if (!fw)
1108      {
1109         fw = calloc(1, sizeof(File_Watch));
1110         fw->path = eina_stringshare_add(entry->path);
1111         cserve2_file_change_watch_add(fw->path, _file_changed_cb, fw);
1112         eina_hash_direct_add(file_watch, fw->path, fw);
1113      }
1114    fw->entries = eina_list_append(fw->entries, entry);
1115    entry->watcher = fw;
1116
1117    _request_add(&open_requests, (Entry *)entry, ref, rid, CSERVE2_OPEN);
1118
1119    // _open_image_default_set(entry);
1120
1121    return 0;
1122 }
1123
1124 void
1125 cserve2_cache_file_close(Client *client, unsigned int client_file_id)
1126 {
1127    Reference *ref = eina_hash_find(client->files.referencing,
1128                                         &client_file_id);
1129    if (!ref)
1130      {
1131         ERR("Couldn't find file %d in client hash.", client_file_id);
1132         return;
1133      }
1134
1135    ref->count--;
1136    if (ref->count <= 0)
1137      // will call _entry_free_cb() for this entry
1138      eina_hash_del_by_key(client->files.referencing, &client_file_id);
1139 }
1140
1141 int
1142 cserve2_cache_image_opts_set(Client *client, Msg_Setopts *msg)
1143 {
1144    Image_Data *entry;
1145    File_Data *fentry = NULL;
1146    Reference *ref, *oldref;
1147    unsigned int image_id;
1148    char buf[4096];
1149
1150    oldref = eina_hash_find(client->images.referencing, &msg->image_id);
1151
1152    // search whether the image is already loaded by another client
1153    entry = _image_msg_new(client, msg);
1154    if (!entry)
1155      return -1;
1156    image_id = _img_opts_id_get(entry, buf, sizeof(buf));
1157    if (image_id)
1158      {  // if so, just update the references
1159         free(entry);
1160         DBG("found image_id %d for client image id %d",
1161             image_id, msg->image_id);
1162         entry = eina_hash_find(image_entries, &image_id);
1163         if (!entry)
1164           {
1165              ERR("image id %d is in file_ids hash, but not in entries hash"
1166                  "with entry id %d.", msg->image_id, image_id);
1167              cserve2_client_error_send(client, msg->base.rid,
1168                                        CSERVE2_INVALID_CACHE);
1169              return -1;
1170           }
1171
1172         if (entry->unused)
1173           {
1174              DBG("Re-using old image entry (id: %d) from the LRU list.",
1175                  entry->base.id);
1176              entry->unused = EINA_FALSE;
1177              image_entries_lru = eina_list_remove(image_entries_lru, entry);
1178              unused_mem_usage -= _image_entry_size_get(entry);
1179           }
1180
1181         if (oldref && (oldref->entry->id == image_id))
1182           return 0;
1183
1184         ref = _entry_reference_add((Entry *)entry, client, msg->image_id);
1185
1186         if (oldref)
1187           eina_hash_del_by_key(client->images.referencing, &msg->image_id);
1188
1189         eina_hash_add(client->images.referencing, &msg->image_id, ref);
1190
1191         return 0;
1192      }
1193
1194    image_id = _image_id++;
1195    while ((image_id == 0) || (eina_hash_find(image_entries, &image_id)))
1196      image_id = _image_id++;
1197
1198    entry->base.id = image_id;
1199    eina_hash_add(image_entries, &image_id, entry);
1200    eina_hash_add(image_ids, buf, (void *)image_id);
1201    ref = _entry_reference_add((Entry *)entry, client, msg->image_id);
1202
1203    if (oldref)
1204      eina_hash_del_by_key(client->images.referencing, &msg->image_id);
1205    eina_hash_add(client->images.referencing, &msg->image_id, ref);
1206
1207    fentry = entry->file;
1208    fentry->images = eina_list_append(fentry->images, entry);
1209
1210    _request_add(&spload_requests, (Entry *)entry, ref, msg->base.rid,
1211                 CSERVE2_SETOPTS);
1212    return 0;
1213 }
1214
1215 void
1216 cserve2_cache_image_load(Client *client, unsigned int client_image_id, unsigned int rid)
1217 {
1218    Image_Data *entry;
1219    Reference *ref;
1220
1221    ref = eina_hash_find(client->images.referencing, &client_image_id);
1222    if (!ref)
1223      {
1224         ERR("Can't load: client %d has no image id %d",
1225             client->id, client_image_id);
1226         return;
1227      }
1228
1229    entry = (Image_Data *)ref->entry;
1230
1231    if (entry->file->invalid)
1232      {
1233         cserve2_client_error_send(client, rid, CSERVE2_FILE_CHANGED);
1234         return;
1235      }
1236
1237    DBG("Loading image id: %d", ref->entry->id);
1238
1239    // File already being loaded, just add the request to be replied
1240    if (entry->base.request)
1241      {
1242         _request_answer_add(entry->base.request, ref, rid, CSERVE2_LOAD);
1243         if ((!entry->base.request->processing) && (!entry->doload))
1244           {
1245              DBG("Removing entry %d from speculative preload and adding "
1246                  "to normal load queue.", entry->base.id);
1247              spload_requests = eina_list_remove(spload_requests,
1248                                                 entry->base.request);
1249              load_requests = eina_list_append(load_requests,
1250                                               entry->base.request);
1251           }
1252      }
1253    else if (entry->shm)
1254      _image_loaded_send(client, entry, rid);
1255    else
1256      _request_add(&load_requests, (Entry *)entry, ref, rid, CSERVE2_LOAD);
1257
1258    entry->doload = EINA_TRUE;
1259 }
1260
1261 void
1262 cserve2_cache_image_preload(Client *client, unsigned int client_image_id, unsigned int rid)
1263 {
1264    Image_Data *entry;
1265    Reference *ref;
1266
1267    ref = eina_hash_find(client->images.referencing, &client_image_id);
1268    if (!ref)
1269      {
1270         ERR("Can't load: client %d has no image id %d",
1271             client->id, client_image_id);
1272         return;
1273      }
1274
1275    entry = (Image_Data *)ref->entry;
1276
1277    if (entry->file->invalid)
1278      {
1279         cserve2_client_error_send(client, rid, CSERVE2_FILE_CHANGED);
1280         return;
1281      }
1282
1283    DBG("Loading image id: %d", ref->entry->id);
1284
1285    // File already being loaded, just add the request to be replied
1286    if (entry->base.request)
1287      {
1288         _request_answer_add(entry->base.request, ref, rid, CSERVE2_PRELOAD);
1289         if ((!entry->base.request->processing) && (!entry->doload))
1290           {
1291              DBG("Removing entry %d from speculative preload and adding "
1292                  "to normal (pre)load queue.", entry->base.id);
1293              spload_requests = eina_list_remove(spload_requests,
1294                                                 entry->base.request);
1295              load_requests = eina_list_append(load_requests,
1296                                               entry->base.request);
1297           }
1298      }
1299    else if (entry->shm)
1300      _image_preloaded_send(client, rid);
1301    else
1302      _request_add(&load_requests, (Entry *)entry, ref, rid, CSERVE2_PRELOAD);
1303
1304    entry->doload = EINA_TRUE;
1305 }
1306
1307 void
1308 cserve2_cache_image_unload(Client *client, unsigned int client_image_id)
1309 {
1310    Reference *ref = eina_hash_find(client->images.referencing,
1311                                    &client_image_id);
1312    if (!ref)
1313      {
1314         ERR("Couldn't find file %d in client hash.", client_image_id);
1315         return;
1316      }
1317
1318    ref->count--;
1319    if (ref->count <= 0)
1320      // will call _entry_free_cb() for this entry
1321      eina_hash_del_by_key(client->images.referencing, &client_image_id);
1322 }
1323
1324 void
1325 cserve2_cache_requests_response(Slave_Command type, void *msg, void *data)
1326 {
1327    Request *req = data;
1328
1329    if (!req->entry)
1330      {
1331         Waiter *w;
1332         DBG("Request finished but it has no entry anymore.");
1333         EINA_LIST_FREE(req->waiters, w)
1334           {
1335              cserve2_client_error_send(w->ref->client, w->rid,
1336                                        CSERVE2_REQUEST_CANCEL);
1337
1338              w->ref->count--;
1339              free(w);
1340           }
1341      }
1342    else if (type == ERROR)
1343      {
1344         Error_Type *error = msg;
1345         req->funcs->error(req->entry, *error);
1346      }
1347    else
1348      req->funcs->response(req->entry, msg);
1349
1350    if (req->entry)
1351      req->entry->request = NULL;
1352    free(req);
1353 }