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