#ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #ifdef _WIN32 # include #endif #ifdef BUILD_PTHREAD #include #endif #include "Evas.h" #include "evas_cs.h" #define D(...) EINA_LOG_DOM_DBG(_evas_cserve_bin_log_dom, __VA_ARGS__) #ifdef ERR #undef ERR #endif #define ERR(...) EINA_LOG_DOM_ERR(_evas_cserve_bin_log_dom, __VA_ARGS__) #ifdef DBG #undef DBG #endif #define DBG(...) EINA_LOG_DOM_DBG(_evas_cserve_bin_log_dom, __VA_ARGS__) #ifdef WRN #undef WRN #endif #define WRN(...) EINA_LOG_DOM_WARN(_evas_cserve_bin_log_dom, __VA_ARGS__) #ifdef CSERVE_BIN_DEFAULT_COLOR #undef CSERVE_BIN_DEFAULT_COLOR #endif #define CSERVE_BIN_DEFAULT_COLOR EINA_COLOR_BLUE // fixme:'s // // preload - make it work (both) // // pants! typedef struct _Img Img; typedef struct _Lopt Lopt; typedef struct _Load_Inf Load_Inf; struct _Lopt { int scale_down_by; // if > 1 then use this double dpi; // if > 0.0 use this int w, h; // if > 0 use this struct { int x, y, w, h; } region; }; struct _Img { Image_Entry ie; int ref; int dref; int usage; Mem *mem; const char *key; time_t cached; struct { const char *file; const char *key; time_t modtime; time_t last_stat; } file; struct { int load1saved, load2saved; double load1, load2; } stats; Lopt load_opts; struct { int w, h; void *data; Eina_Bool alpha : 1; } image; int incache; LK(lock); Eina_Bool dead : 1; Eina_Bool active : 1; Eina_Bool useless : 1; Eina_Bool killme : 1; }; struct _Load_Inf { Img *img; Client *c; }; // config static int stat_res_interval = 2; static time_t t_now = 0; static int server_id = 0; LK(strshr_freeme_lock); static int strshr_freeme_count = 0; static int strshr_freeme_alloc = 0; static const char **strshr_freeme = NULL; static int cache_cleanme = 0; static Evas_Cache_Image *cache = NULL; static Eina_Hash *active_images = NULL; LK(cache_lock); static Eina_List *cache_images = NULL; static int cache_usage = 0; static int cache_max_usage = 1 * 1024; static int cache_max_adjust = 0; static int cache_item_timeout = -1; static int cache_item_timeout_check = -1; static Mem *stat_mem = NULL; static int _evas_cserve_bin_log_dom = -1; static Eina_List *stat_mems = NULL; static void cache_clean(void); #ifndef _WIN32 static double get_time(void) { struct timeval timev; gettimeofday(&timev, NULL); return (double)timev.tv_sec + (((double)timev.tv_usec) / 1000000); } #else static double get_time(void) { return (double)GetTickCount()/1000.0; } #endif static int mem_total = 0; static int mem_free = 0; static int mem_buffers = 0; static int mem_cached = 0; static void meminfo_check(void) { FILE *f; char buf[1024]; int v; f = fopen("/proc/meminfo", "r"); if (!f) return; if (!fgets(buf, sizeof(buf), f)) goto done; v = 0; if (sscanf(buf, "%*s %i %*s", &v) != 1) goto done; mem_total = v; if (!fgets(buf, sizeof(buf), f)) goto done; v = 0; if (sscanf(buf, "%*s %i %*s", &v) != 1) goto done; mem_free = v; if (!fgets(buf, sizeof(buf), f)) goto done; v = 0; if (sscanf(buf, "%*s %i %*s", &v) != 1) goto done; mem_buffers = v; if (!fgets(buf, sizeof(buf), f)) goto done; v = 0; if (sscanf(buf, "%*s %i %*s", &v) != 1) goto done; mem_cached = v; done: fclose(f); } static int stats_dirty = 0; static int saved_loads = 0; static double saved_load_time = 0; static double saved_load_lifetime = 0; static int saved_loaddatas = 0; static double saved_loaddata_time = 0; static double saved_loaddata_lifetime = 0; static int saved_memory = 0; static int saved_memory_peak = 0; static int alloced_memory = 0; static int alloced_memory_peak = 0; static int real_memory = 0; static int real_memory_peak = 0; static Eina_Bool stats_hash_image_cb(const Eina_Hash *hash __UNUSED__, const void *key __UNUSED__, void *data, void *fdata __UNUSED__) { Img *img = data; saved_load_time += img->stats.load1 * img->stats.load1saved; saved_loaddata_time += img->stats.load2 * img->stats.load2saved; if (img->ref > 1) saved_memory += img->image.w * img->image.h * sizeof(DATA32) * (img->ref - 1); if (img->mem) { alloced_memory += img->image.w * img->image.h * sizeof(DATA32); real_memory += (((img->image.w * img->image.h * sizeof(DATA32)) + 4095) >> 12) << 12; } return 1; } static void stats_calc(void) { Img *img; Eina_List *l; if (!stats_dirty) return; stats_dirty = 0; saved_loads = 0; saved_load_time = 0; saved_loaddatas = 0; saved_loaddata_time = 0; saved_memory = 0; alloced_memory = 0; real_memory = 0; if (active_images) eina_hash_foreach(active_images, stats_hash_image_cb, NULL); LKL(cache_lock); EINA_LIST_FOREACH(cache_images, l, img) { saved_loads += img->stats.load1saved; saved_load_time += img->stats.load1 * img->stats.load1saved; saved_loaddatas += img->stats.load2saved; saved_loaddata_time += img->stats.load2 * img->stats.load2saved; if (img->mem) { alloced_memory += img->image.w * img->image.h * sizeof(DATA32); real_memory += (((img->image.w * img->image.h * sizeof(DATA32)) + 4095) >> 12) << 12; } } LKU(cache_lock); if (saved_memory > saved_memory_peak) saved_memory_peak = saved_memory; if (real_memory > real_memory_peak) real_memory_peak = real_memory; if (alloced_memory > alloced_memory_peak) alloced_memory_peak = alloced_memory; } static void stats_update(void) { stats_dirty = 1; } static void stats_lifetime_update(Img *img) { saved_load_lifetime += img->stats.load1 * img->stats.load1saved; saved_loaddata_lifetime += img->stats.load2 * img->stats.load2saved; } static void stat_clean(Mem *m) { int *ints; int size, pid, *ids, count, i; ints = (int *)m->data; size = ints[0]; if (!evas_cserve_mem_resize(m, size)) return; ints = (int *)m->data; pid = ints[1]; count = (size - (2 * sizeof(int))) / sizeof(int); ids = ints + 2; for (i = 0; i < count; i++) evas_cserve_mem_del(pid, ids[i]); } static int stat_init(Mem *m) { int *ints; ints = (int *)m->data; if (!evas_cserve_mem_resize(m, 2 * sizeof(int))) return 0; ints[0] = 2 * sizeof(int); ints[1] = getpid(); msync(m->data, 2 * sizeof(int), MS_SYNC | MS_INVALIDATE); return 1; } static int stat_update(Mem *m) { Eina_List *l; Mem *m2; int *ints, *ids, i; ints = (int *)m->data; ints[0] = (2 * sizeof(int)) + (eina_list_count(stat_mems) * sizeof(int)); if (!evas_cserve_mem_resize(m, ints[0])) return 0; ints = (int *)m->data; ids = ints + 2; i = 0; EINA_LIST_FOREACH(stat_mems, l, m2) { ids[i] = m2->id; i++; } msync(m->data, ints[0], MS_SYNC | MS_INVALIDATE); return 1; } static Image_Entry * _img_alloc(void) { Img *img; img = calloc(1, sizeof(Img)); LKI(img->lock); return (Image_Entry *)img; } static void _img_dealloc(Image_Entry *ie) { Img *img = (Img *)ie; LKD(img->lock); free(img); } static int _img_surface_alloc(Image_Entry *ie, unsigned int w, unsigned int h) { Img *img = (Img *)ie; img->mem = evas_cserve_mem_new(w * h * sizeof(DATA32), NULL); if (!img->mem) return -1; img->image.data = img->mem->data + img->mem->offset; stat_mems = eina_list_append(stat_mems, img->mem); stat_update(stat_mem); return 0; } static void _img_surface_delete(Image_Entry *ie) { Img *img = (Img *)ie; if (!img->mem) return; stat_mems = eina_list_remove(stat_mems, img->mem); stat_update(stat_mem); evas_cserve_mem_free(img->mem); img->mem = NULL; img->image.data = NULL; } static DATA32 * _img_surface_pixels(Image_Entry *ie) { Img *img = (Img *)ie; return img->image.data; } static int _img_load(Image_Entry *ie) { return evas_common_load_rgba_image_module_from_file(ie); } static void _img_unload(Image_Entry *ie __UNUSED__) { } static void _img_dirty_region(Image_Entry *ie __UNUSED__, unsigned int x __UNUSED__, unsigned int y __UNUSED__, unsigned int w __UNUSED__, unsigned int h __UNUSED__) { } static int _img_dirty(Image_Entry *dst __UNUSED__, const Image_Entry *src __UNUSED__) { return 0; } static int _img_size_set(Image_Entry *dst __UNUSED__, const Image_Entry *src __UNUSED__, unsigned int w __UNUSED__, unsigned int h __UNUSED__) { return 0; } static int _img_copied_data(Image_Entry *ie __UNUSED__, unsigned int w __UNUSED__, unsigned int h __UNUSED__, DATA32 *image_data __UNUSED__, int alpha __UNUSED__, int cspace __UNUSED__) { return 0; } static int _img_data(Image_Entry *ie __UNUSED__, unsigned int w __UNUSED__, unsigned int h __UNUSED__, DATA32 *image_data __UNUSED__, int alpha __UNUSED__, int cspace __UNUSED__) { return 0; } static int _img_color_space(Image_Entry *ie __UNUSED__, int cspace __UNUSED__) { return 0; } static int _img_load_data(Image_Entry *ie) { return evas_common_load_rgba_image_data_from_file(ie); } static int _img_mem_size_get(Image_Entry *ie __UNUSED__) { return 1; } static void img_init(void) { const Evas_Cache_Image_Func cache_funcs = { _img_alloc,//Image_Entry *(*alloc)(void); _img_dealloc,//void (*dealloc)(Image_Entry *im); /* The cache provide some helpers for surface manipulation. */ _img_surface_alloc,//int (*surface_alloc)(Image_Entry *im, unsigned int w, unsigned int h); _img_surface_delete,//void (*surface_delete)(Image_Entry *im); _img_surface_pixels,//DATA32 *(*surface_pixels)(Image_Entry *im); /* The cache is doing the allocation and deallocation, you must just do the rest. */ _img_load,//int (*constructor)(Image_Entry *im); _img_unload,//void (*destructor)(Image_Entry *im); _img_dirty_region,//void (*dirty_region)(Image_Entry *im, unisnged int x, unisnged int y, unisnged int w, unsigned int h); /* Only called when references > 0. Need to provide a fresh copie of im. */ /* The destination surface does have a surface, but no allocated pixel data. */ _img_dirty,//int (*dirty)(Image_Entry *dst, const Image_Entry *src); /* Only called when references == 1. We will call drop on im'. */ /* The destination surface does not have any surface. */ _img_size_set,//int (*size_set)(Image_Entry *dst, const Image_Entry *src, unisnged int w, unisnged int h); /* The destination surface does not have any surface. */ _img_copied_data,//int (*copied_data)(Image_Entry *dst, unisnged int w, unisnged int h, DATA32 *image_data, int alpha, int cspace); /* The destination surface does not have any surface. */ _img_data,//int (*data)(Image_Entry *dst, unsigned int w, unisgned int h, DATA32 *image_data, int alpha, int cspace); _img_color_space,//int (*color_space)(Image_Entry *dst, int cspace); /* This function need to update im->w and im->h. */ _img_load_data,//int (*load)(Image_Entry *im); _img_mem_size_get,//int (*mem_size_get)(Image_Entry *im); NULL,//void (*debug)(const char *context, Image_Entry *im); }; active_images = eina_hash_string_superfast_new(NULL); cache = evas_cache_image_init(&cache_funcs); LKI(cache_lock); LKI(strshr_freeme_lock); } static void img_shutdown(void) { evas_cache_image_shutdown(cache); cache = NULL; // FIXME: shutdown properly LKD(strshr_freeme_lock); LKI(cache_lock); } static Img * img_new(const char *file, const char *key, RGBA_Image_Loadopts *load_opts, const char *bufkey) { Img *img; struct stat st; int ret; Image_Entry *ie; int err = 0; double t; DBG("... stat %s", file); ret = stat(file, &st); if (ret < 0) return NULL; DBG("... load header"); t = get_time(); ie = evas_cache_image_request(cache, file, key, load_opts, &err); t = get_time() - t; DBG("... header done"); if (!ie) return NULL; DBG("... ie->cache = %p", ie->cache); img = (Img *)ie; img->stats.load1 = t; img->key = eina_stringshare_add(bufkey); img->file.modtime = st.st_mtime; img->file.last_stat = t_now; img->file.file = eina_stringshare_add(file); if (key) img->file.key = eina_stringshare_add(key); img->load_opts.scale_down_by = load_opts->scale_down_by; img->load_opts.dpi = load_opts->dpi; img->load_opts.w = load_opts->w; img->load_opts.h = load_opts->h; img->image.w = ie->w; img->image.h = ie->h; img->image.alpha = ie->flags.alpha; img->ref = 1; img->active = 1; img->usage = sizeof(Img) + strlen(img->key) + 1 + strlen(img->file.file) + 1; if (img->file.key) img->usage += strlen(img->file.key) + 1; eina_hash_direct_add(active_images, img->key, img); return img; } static void img_loaddata(Img *img) { double t; if (img->mem) return; t = get_time(); LKL(cache_lock); evas_cache_image_load_data((Image_Entry *)img); t = get_time() - t; img->stats.load2 = t; if (img->image.data) msync(img->image.data, img->image.w * img->image.h * sizeof(DATA32), MS_SYNC | MS_INVALIDATE); if (!img->active) cache_usage -= img->usage; img->usage += (4096 * (((img->image.w * img->image.h * sizeof(DATA32)) + 4095) / 4096)) + sizeof(Mem); if (!img->active) cache_usage += img->usage; LKU(cache_lock); cache_clean(); } static void img_free(Img *img) { if (img->incache > 0) { ERR("EEEEEEEEEEEEEEEEK!"); ERR("EEEEEEEEEEEEEEEEK! %p '%s' still in cache", img, img->file.file); ERR("EEEEEEEEEEEEEEEEK!"); return; } stats_lifetime_update(img); stats_update(); LKL(strshr_freeme_lock); strshr_freeme_count += 3; if (strshr_freeme_count > strshr_freeme_alloc) { const char **tmp; strshr_freeme_alloc += 32; tmp = realloc(strshr_freeme, strshr_freeme_alloc * sizeof(const char **)); if (tmp) strshr_freeme = tmp; else { ERR("realloc of strshr_freeme failed for %i items", strshr_freeme_alloc); strshr_freeme_alloc -= 32; strshr_freeme_count -= 3; return; } } strshr_freeme[strshr_freeme_count - 3] = img->key; strshr_freeme[strshr_freeme_count - 2] = img->file.file; strshr_freeme[strshr_freeme_count - 1] = img->file.key; LKU(strshr_freeme_lock); evas_cache_image_drop((Image_Entry *)img); } static void cache_clean(void) { DBG("... cache clean!!! do"); LKL(cache_lock); while ((cache_usage > ((cache_max_usage + cache_max_adjust) * 1024)) && (cache_images)) { Img *img; Eina_List *l; DBG("... clean loop %i > %i", cache_usage, (cache_max_usage + cache_max_adjust) * 1024); l = eina_list_last(cache_images); // THREAD: called from thread. happens to be safe as it uses no unlocked shared resources if (!l) break; img = l->data; if (!img) break; LKL(img->lock); DBG("... REMOVE %p '%s'", img, img->file.file); #ifdef BUILD_PTHREAD img->killme = 1; img->useless = 1; img->dead = 1; cache_cleanme++; LKU(img->lock); #else cache_images = eina_list_remove_list(cache_images, l); // FIXME: called from thread img->incache--; cache_usage -= img->usage; DBG("... IMG FREE %p", img); img_free(img); #endif } LKU(cache_lock); } static void cache_timeout(time_t t) { Eina_List *l, *l_next; Img *img; if (cache_item_timeout < 0) return; LKL(cache_lock); EINA_LIST_FOREACH_SAFE(cache_images, l, l_next, img) { LKL(img->lock); if ((t - img->cached) > cache_item_timeout) { cache_images = eina_list_remove_list(cache_images, l); img->incache--; cache_usage -= img->usage; img_free(img); } else LKU(img->lock); } LKU(cache_lock); } static void mem_cache_adjust(void) { int pval = cache_max_adjust; int max = 0; int mem_used; if (mem_total <= 0) return; mem_used = mem_total - mem_free - mem_cached - mem_buffers; #if 0 // this lets the image cache to grow to fill all real free ram, if // there is any (ie ram unused by disk cache) if (mem_free < mem_total) { cache_max_adjust = mem_free; return; } #endif max = ((mem_free + mem_cached + mem_buffers) / 8) - cache_max_usage; if (max < 0) max = 0; if (max > cache_max_usage) max = cache_max_usage; cache_max_adjust = max - cache_max_usage; if (cache_max_adjust < -cache_max_usage) cache_max_adjust = -cache_max_usage; DBG("... cache_max_adjust = %i", cache_max_adjust); if (pval != cache_max_adjust) { DBG("... cache clean"); // FIXME lock problem cache_clean(); } } static void img_cache(Img *img) { eina_hash_del(active_images, img->key, img); if (img->dead) { DBG("... img %p '%s' dead", img , img->file.file); img_free(img); return; } if ((cache_usage + img->usage) > ((cache_max_usage + cache_max_adjust) * 1024)) { DBG("... img %p '%s' too big for cache", img , img->file.file); img_free(img); return; } DBG("... img %p '%s' cached += %i", img , img->file.file, img->usage); if (img->incache > 0) { ERR("EEEEEEEEEEEEEEEEK!"); ERR("EEEEEEEEEEEEEEEEK! %p '%s' already in cache", img, img->file.file); ERR("EEEEEEEEEEEEEEEEK!"); return; } LKL(cache_lock); cache_images = eina_list_prepend(cache_images, img); LKU(cache_lock); img->incache++; cache_usage += img->usage; img->active = 0; img->cached = t_now; // FIXME: lock problem if (cache_usage > ((cache_max_usage + cache_max_adjust) * 1024)) cache_clean(); } static void img_dead(Img *img) { if (img->active) return; LKL(cache_lock); cache_images = eina_list_remove(cache_images, img); LKU(cache_lock); img->incache--; cache_usage -= img->usage; img_free(img); } static Eina_Bool img_ok(Img *img) { struct stat st; int ret; if (img->dead) return 0; if ((t_now > img->file.last_stat) && ((t_now - img->file.last_stat) < stat_res_interval)) return 1; img->file.last_stat = t_now; ret = stat(img->file.file, &st); img->file.last_stat = t_now; if (ret < 0) { img->dead = 1; img_dead(img); return 0; } if (st.st_mtime != img->file.modtime) { img->dead = 1; img_dead(img); return 0; } return 1; } static Img * img_load(const char *file, const char *key, RGBA_Image_Loadopts *load_opts) { Img *img; char buf[8192]; Eina_List *l, *l_next; if (!file) return NULL; DBG("... img_load '%s'", file); if (key) DBG("... ... key '%s'", key); if (key) snprintf(buf, sizeof(buf), "%s///::/%s/\001/%i/%1.8f/%ix%i", file, key, load_opts->scale_down_by, load_opts->dpi, load_opts->w, load_opts->h); else snprintf(buf, sizeof(buf), "%s///\001/%i/%1.8f/%ix%i", file, load_opts->scale_down_by, load_opts->dpi, load_opts->w, load_opts->h); DBG("... find '%s'", buf); img = eina_hash_find(active_images, buf); if ((img) && (img_ok(img))) { DBG("... found!"); img->stats.load1saved++; img->ref++; DBG("... stats update"); stats_update(); DBG("... return %p", img); return img; } // FIXME: keep hash of cached images too LKL(cache_lock); EINA_LIST_FOREACH_SAFE(cache_images, l, l_next, img) { if (!strcmp(img->key, buf)) { LKL(img->lock); if (img_ok(img)) { DBG("... found cached"); cache_images = eina_list_remove_list(cache_images, l); img->incache--; cache_usage -= img->usage; img->active = 1; img->stats.load1saved++; img->ref++; eina_hash_direct_add(active_images, img->key, img); DBG("... sats update"); stats_update(); DBG("... return %p", img); LKU(img->lock); LKU(cache_lock); return img; } LKU(img->lock); } } LKU(cache_lock); DBG("... ned new img"); return img_new(file, key, load_opts, buf); } static void img_unload(Img *img) { if (img->ref == 0) { ERR("EEEEEEEEEEEEEEEEK!"); ERR("EEEEEEEEEEEEEEEEK! %p '%s' already @ ref 0", img, img->file.file); ERR("EEEEEEEEEEEEEEEEK!"); return; } img->ref--; DBG("... img ref-- = %i", img->ref); if (img->ref == 0) { DBG("... img cache %p '%s'", img, img->file.file); img_cache(img); } } static void img_unloaddata(Img *img) { DBG("img_unloaddata() %p '%s'", img, img->file.file); if ((img->dref <= 0) && (img->useless) && (img->mem)) { Image_Entry *ie = (Image_Entry *)img; DBG("... really do forced unload"); if (!img->active) cache_usage -= img->usage; img->usage -= (4096 * (((img->image.w * img->image.h * sizeof(DATA32)) + 4095) / 4096)) + sizeof(Mem); if (!img->active) cache_usage += img->usage; evas_cserve_mem_free(img->mem); stat_mems = eina_list_remove(stat_mems, img->mem); img->mem = NULL; img->image.data = NULL; img->dref = 0; DBG("... done"); ie->flags.loaded = 0; ie->allocated.w = 0; ie->allocated.h = 0; } } static void img_useless(Img *img) { DBG("img_useless() %p", img); img->useless = 1; if (img->dref <= 0) img_unloaddata(img); } static void img_forcedunload(Img *img) { DBG("img_forcedunload() %p", img); img->dead = 1; img_unload(img); } static void img_preload(Img *img) { DBG("img_preload() %p", img); } static void client_del(void *data, Client *c) { Eina_List *images; Img *img; images = data; DBG("... CLIENT DEL %i", c->pid); EINA_LIST_FREE(images, img) { DBG("... unloaddata img %p", img); img_unloaddata(img); DBG("... unload img %p", img); img_unload(img); } } static Eina_Bool getinfo_hash_image_cb(const Eina_Hash *hash __UNUSED__, const void *key __UNUSED__, void *data, void *fdata __UNUSED__) { Img *img = data; Eina_List **list = fdata; *list = eina_list_append(*list, img); return 1; } #ifdef BUILD_PTHREAD static void * load_data_thread(void *data) { Load_Inf *li = data; Img *img = li->img; Client *c = li->c; Op_Loaddata_Reply msg; free(li); LKL(img->lock); if (img->mem) { memset(&msg, 0, sizeof(msg)); msg.mem.id = img->mem->id; msg.mem.offset = img->mem->offset; msg.mem.size = img->mem->size; DBG("... reply"); evas_cserve_client_send(c, OP_LOADDATA, sizeof(msg), (unsigned char *)(&msg)); LKU(c->lock); return NULL; } img_loaddata(img); memset(&msg, 0, sizeof(msg)); if (img->mem) { msg.mem.id = img->mem->id; msg.mem.offset = img->mem->offset; msg.mem.size = img->mem->size; } else msg.mem.id = msg.mem.offset = msg.mem.size = 0; LKU(img->lock); DBG("... reply"); evas_cserve_client_send(c, OP_LOADDATA, sizeof(msg), (unsigned char *)(&msg)); LKU(c->lock); return NULL; } #endif static int message(void *fdata __UNUSED__, Server *s __UNUSED__, Client *c, int opcode, int size, unsigned char *data) { // copy data into local aligned buffer... in case. unsigned char *tdata = alloca(size + 16); memcpy(tdata, data, size); t_now = time(NULL); DBG("message @ %i...", (int)t_now); switch (opcode) { case OP_INIT: { Op_Init *rep; Op_Init msg; memset(&msg, 0, sizeof(msg)); msg.pid = getpid(); msg.server_id = server_id; msg.handle = c; rep = (Op_Init *)tdata; c->pid = rep->pid; if (rep->server_id == 1) // 2nd channel conn { c->client_main = rep->handle; } c->func = client_del; c->data = NULL; DBG("OP_INIT %i", c->pid); DBG("... reply"); evas_cserve_client_send(c, OP_INIT, sizeof(msg), (unsigned char *)(&msg)); } break; case OP_LOAD: { Op_Load *rep; Op_Load_Reply msg; Img *img; RGBA_Image_Loadopts lopt; char *file = NULL, *key = NULL; memset(&lopt, 0, sizeof lopt); DBG("OP_LOAD %i", c->pid); rep = (Op_Load *)tdata; file = (char*) (data + sizeof(Op_Load)); key = file + strlen(file) + 1; if (key[0] == 0) key = NULL; lopt.scale_down_by = rep->lopt.scale_down_by; lopt.dpi = rep->lopt.dpi; lopt.w = rep->lopt.w; lopt.h = rep->lopt.h; lopt.region.x = rep->lopt.region.x; lopt.region.y = rep->lopt.region.y; lopt.region.w = rep->lopt.region.w; lopt.region.h = rep->lopt.region.h; DBG("... img_load '%s'", file); if (key) DBG("'%s'", (char *)key); else DBG(" '%s'", ""); DBG(" lopt { %i %1.1f %i %i { %i %i %i %i}}", lopt.scale_down_by, lopt.dpi, lopt.w, lopt.h, lopt.region.x, lopt.region.y, lopt.region.w, lopt.region.h); img = img_load(file, key, &lopt); DBG("... img_load = %p", img); if (img) { DBG("... add image to client list"); if (c->client_main) c->client_main->data = eina_list_append(c->client_main->data, img); else c->data = eina_list_append(c->data, img); } memset(&msg, 0, sizeof(msg)); msg.handle = img; if ((img) && (img->mem)) { msg.mem.id = img->mem->id; msg.mem.offset = img->mem->offset; msg.mem.size = img->mem->size; img->stats.load2saved++; stats_update(); } else msg.mem.id = msg.mem.offset = msg.mem.size = 0; if (img) { msg.image.w = img->image.w; msg.image.h = img->image.h; msg.image.alpha = img->image.alpha; } DBG("... reply"); evas_cserve_client_send(c, OP_LOAD, sizeof(msg), (unsigned char *)(&msg)); } break; case OP_UNLOAD: { Op_Unload *rep; Img *img; DBG("OP_UNLOAD %i", c->pid); rep = (Op_Unload *)tdata; img = rep->handle; if ((img) && (rep->server_id == server_id)) { Eina_Bool doflush = 0; DBG("... remove %p from list", img); if (c->client_main) c->client_main->data = eina_list_remove(c->client_main->data, img); else c->data = eina_list_remove(c->data, img); DBG("... unload %p", img); LKL(img->lock); img->ref++; img_unload(img); img->ref--; if (img->ref == 0) doflush = 1; LKU(img->lock); if (doflush) img_cache(img); cache_clean(); } } break; case OP_LOADDATA: { Op_Loaddata *rep; Op_Loaddata_Reply msg; Img *img; DBG("OP_LOADDATA %i", c->pid); rep = (Op_Loaddata *)tdata; img = rep->handle; if ((img) && (rep->server_id == server_id)) { if (img->mem) { DBG("... load saved - cached %p", img); img->stats.load2saved++; stats_update(); memset(&msg, 0, sizeof(msg)); if (img->mem) { msg.mem.id = img->mem->id; msg.mem.offset = img->mem->offset; msg.mem.size = img->mem->size; } else msg.mem.id = msg.mem.offset = msg.mem.size = 0; DBG("... reply"); evas_cserve_client_send(c, OP_LOADDATA, sizeof(msg), (unsigned char *)(&msg)); } else { #ifdef BUILD_PTHREAD pthread_t tid; pthread_attr_t attr; Load_Inf *li; DBG("... load data %p", img); pthread_attr_init(&attr); li = calloc(1, sizeof(Load_Inf)); if (li) { li->img= img; li->c = c; LKL(c->lock); if (pthread_create(&tid, &attr, load_data_thread, li)) { perror("pthread_create()"); } else pthread_detach(tid); } pthread_attr_destroy(&attr); #else img_loaddata(img); memset(&msg, 0, sizeof(msg)); if (img->mem) { msg.mem.id = img->mem->id; msg.mem.offset = img->mem->offset; msg.mem.size = img->mem->size; } else msg.mem.id = msg.mem.offset = msg.mem.size = 0; DBG("... reply"); evas_cserve_client_send(c, OP_LOADDATA, sizeof(msg), (unsigned char *)(&msg)); #endif } } else { msg.mem.id = msg.mem.offset = msg.mem.size = 0; evas_cserve_client_send(c, OP_LOADDATA, sizeof(msg), (unsigned char *)(&msg)); } } break; case OP_UNLOADDATA: { Op_Unloaddata *rep; Img *img; DBG("OP_UNLOADDATA %i", c->pid); rep = (Op_Unloaddata *)tdata; img = rep->handle; if ((img) && (rep->server_id == server_id)) { DBG("... dref--"); LKL(img->lock); img->dref--; if (img->dref < 0) img->dref = 0; DBG("... unload data %p '%s'", img, img->file.file); img_unloaddata(img); LKU(img->lock); } } break; case OP_USELESSDATA: { Op_Unloaddata *rep; Img *img; DBG("OP_USELESSDATA %i", c->pid); rep = (Op_Unloaddata *)tdata; img = rep->handle; if ((img) && (rep->server_id == server_id)) { DBG("... dref--"); LKL(img->lock); img->dref--; if (img->dref < 0) img->dref = 0; DBG("... useless %p", img); img_useless(img); LKU(img->lock); } } break; case OP_PRELOAD: { Op_Preload *rep; Img *img; DBG("OP_PRELOAD %i", c->pid); rep = (Op_Preload *)tdata; img = rep->handle; if ((img) && (rep->server_id == server_id)) { LKL(img->lock); if (c->client_main) c->client_main->data = eina_list_remove(c->client_main->data, img); else c->data = eina_list_remove(c->data, img); // FIXME: preload doesn't work async img_preload(img); LKU(img->lock); } } case OP_FORCEDUNLOAD: { Op_Forcedunload *rep; Img *img; DBG("OP_FORCEDUNLOAD %i", c->pid); rep = (Op_Forcedunload *)tdata; img = rep->handle; if ((img) && (rep->server_id == server_id)) { Eina_Bool doflush = 0; LKL(img->lock); DBG("remove %p from list", img); if (c->client_main) c->client_main->data = eina_list_remove(c->client_main->data, img); else c->data = eina_list_remove(c->data, img); DBG("... forced unload now"); img->ref++; img_forcedunload(img); img->ref--; if (img->ref == 0) doflush = 1; LKU(img->lock); if (doflush) img_cache(img); cache_clean(); } } break; case OP_GETCONFIG: { Op_Getconfig_Reply msg; DBG("OP_GETCONFIG %i", c->pid); msg.cache_max_usage = cache_max_usage; msg.cache_item_timeout = cache_item_timeout; msg.cache_item_timeout_check = cache_item_timeout_check; DBG("... reply"); evas_cserve_client_send(c, OP_GETCONFIG, sizeof(msg), (unsigned char *)(&msg)); } break; case OP_SETCONFIG: { Op_Setconfig *rep; DBG("OP_SETCONFIG %i", c->pid); rep = (Op_Setconfig *)tdata; cache_max_usage = rep->cache_max_usage; cache_item_timeout = rep->cache_item_timeout; cache_item_timeout_check = rep->cache_item_timeout_check; DBG("... cache timeout"); cache_timeout(t_now); DBG("... cache clean"); cache_clean(); } break; case OP_GETSTATS: { Op_Getstats_Reply msg; DBG("OP_GETSTATS %i", c->pid); stats_calc(); msg.saved_memory = saved_memory; msg.wasted_memory = (real_memory - alloced_memory); msg.saved_memory_peak = saved_memory_peak; msg.wasted_memory_peak = (real_memory_peak - alloced_memory_peak); msg.saved_time_image_header_load = saved_load_lifetime + saved_load_time; msg.saved_time_image_data_load = saved_loaddata_lifetime + saved_loaddata_time; DBG("... reply"); evas_cserve_client_send(c, OP_GETSTATS, sizeof(msg), (unsigned char *)(&msg)); } break; case OP_GETINFO: { Op_Getinfo_Reply *msg; int len; Eina_List *imgs = NULL, *l; Img *img; DBG("OP_GETINFO %i", c->pid); len = sizeof(Op_Getinfo_Reply); DBG("... foreach"); if (active_images) eina_hash_foreach(active_images, getinfo_hash_image_cb, &imgs); DBG("... walk foreach list output"); LKL(cache_lock); EINA_LIST_FOREACH(cache_images, l, img) { imgs = eina_list_append(imgs, img); } LKU(cache_lock); DBG("... walk image cache"); EINA_LIST_FOREACH(imgs, l, img) { len += sizeof(Op_Getinfo_Item); if (img->file.file) len += strlen(img->file.file); len++; if (img->file.key) len += strlen(img->file.key); len++; } DBG("... malloc msg"); msg = calloc(1, len); if (msg) { unsigned char *p; DBG("... init msg"); p = (unsigned char *)msg; msg->active.mem_total = 0; msg->active.count = 0; msg->cached.mem_total = 0; msg->cached.count = 0; p += sizeof(Op_Getinfo_Reply); DBG("... walk all imgs"); EINA_LIST_FOREACH(imgs, l, img) { Op_Getinfo_Item it; LKL(img->lock); DBG("... img %p", img); memset(&it, 0, sizeof(Op_Getinfo_Item)); it.file_key_size = 0; if (img->file.file) { strcpy((char *)p + sizeof(Op_Getinfo_Item) + it.file_key_size, img->file.file); it.file_key_size += strlen(img->file.file); } p[sizeof(Op_Getinfo_Item) + it.file_key_size] = 0; it.file_key_size += 1; if (img->file.key) { strcpy((char *)p + sizeof(Op_Getinfo_Item) + it.file_key_size, img->file.key); it.file_key_size += strlen(img->file.key); } p[sizeof(Op_Getinfo_Item) + it.file_key_size] = 0; it.file_key_size += 1; it.w = img->image.w; it.h = img->image.h; it.file_mod_time = img->file.modtime; it.file_checked_time = img->file.last_stat; if (!img->active) it.cached_time = img->cached; else it.cached_time = 0; it.refcount = img->ref; it.data_refcount = img->dref; it.memory_footprint = img->usage; it.head_load_time = img->stats.load1; it.data_load_time = img->stats.load2; it.alpha = img->image.alpha; if (img->image.data) it.data_loaded = 1; else it.data_loaded = 0; it.active = img->active; if (it.active) { msg->active.count++; msg->active.mem_total += img->usage; } else { msg->cached.count++; msg->cached.mem_total += img->usage; } it.dead = img->dead; it.useless = img->useless; DBG("... memcpy %p %p %zu ", p, &it, sizeof(Op_Getinfo_Item)); memcpy(p, &it, sizeof(Op_Getinfo_Item)); DBG("... memcpy done %p", img); p += sizeof(Op_Getinfo_Item) + it.file_key_size; LKU(img->lock); } DBG("... walk all imgs done"); msg->active.mem_total = (msg->active.mem_total + 1023) / 1024; msg->cached.mem_total = (msg->cached.mem_total + 1023) / 1024; DBG("... reply"); evas_cserve_client_send(c, OP_GETINFO, len, (unsigned char *)msg); free(msg); } else { DBG("... reply empty"); evas_cserve_client_send(c, OP_GETINFO, 0, NULL); } DBG("... free imgs list"); if (imgs) eina_list_free(imgs); } break; default: DBG("OP_... UNKNOWN??? %i opcode: %i", c->pid, opcode); break; } return 0; } static void parse_args(int argc, char **argv) { int i; for (i = 1; i < argc; i++) { if ((!strcmp(argv[i], "-h")) || (!strcmp(argv[i], "-help")) || (!strcmp(argv[i], "--help"))) { printf("Options:\n" "\t-h This help\n" "\t-csize Size of speculative cache (Kb)\n" "\t-ctime Maximum life of a cached image (seconds)\n" "\t-ctimecheck Time between checking the cache for timeouts (seconds)\n" "\t-debug Enable debug logging\n" "\n"); exit(0); } else if ((!strcmp(argv[i], "-csize")) && (i < (argc - 1))) { i++; cache_max_usage = atoi(argv[i]); } else if ((!strcmp(argv[i], "-ctime")) && (i < (argc - 1))) { i++; cache_item_timeout = atoi(argv[i]); } else if ((!strcmp(argv[i], "-ctimecheck")) && (i < (argc - 1))) { i++; cache_item_timeout_check = atoi(argv[i]); } else if (!strcmp(argv[i], "-debug")) { eina_log_level_set(EINA_LOG_LEVEL_DBG); } } } static int exit_flag = 0; static void exit_handler(int x __UNUSED__, siginfo_t *info __UNUSED__, void *data __UNUSED__) { exit_flag = 1; } static void pipe_handler(int x __UNUSED__, siginfo_t *info __UNUSED__, void *data __UNUSED__) { } static void signal_init(void) { struct sigaction action, old_action; action.sa_handler = NULL; action.sa_sigaction = exit_handler; action.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&action.sa_mask); sigaction(SIGINT, &action, &old_action); action.sa_handler = NULL; action.sa_sigaction = exit_handler; action.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&action.sa_mask); sigaction(SIGTERM, &action, &old_action); action.sa_handler = NULL; action.sa_sigaction = exit_handler; action.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&action.sa_mask); sigaction(SIGQUIT, &action, &old_action); action.sa_handler = NULL; action.sa_sigaction = pipe_handler; action.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&action.sa_mask); sigaction(SIGPIPE, &action, &old_action); // SIGUSR1 // SIGUSR2 // SIGHUP // SIGCHLD // SIGSEGV // SIGILL // SIGBUS // SIGFPE // SIGABRT } static void signal_shutdown(void) { } int main(int argc, char **argv) { Server *s; time_t last_check, t, t_next; pid_t pid; t = time(NULL); pid = getpid(); t ^= (pid << 24) | (pid << 16) | (pid << 8) | (pid); srand(t); server_id = rand(); parse_args(argc, argv); unsetenv("EVAS_CSERVE"); eina_init(); _evas_cserve_bin_log_dom = eina_log_domain_register ("evas_cserve_bin", CSERVE_BIN_DEFAULT_COLOR); if (_evas_cserve_bin_log_dom < 0) { EINA_LOG_ERR("impossible to create a log domain."); eina_shutdown(); exit(1); } DBG("evas init..."); evas_init(); DBG("img init..."); img_init(); DBG("signal init..."); signal_init(); DBG("cserve add..."); s = evas_cserve_server_add(); if (!s) { ERR("ERROR: server socket init fail. abort."); goto error; } DBG("mem open (status)..."); stat_mem = evas_cserve_mem_open(0, 0, "status", sizeof(int), 0); if (stat_mem) { WRN("WARNING: previous evas_cserve left garbage. cleaning up."); stat_clean(stat_mem); evas_cserve_mem_close(stat_mem); stat_mem = NULL; } DBG("mem new (status)..."); stat_mem = evas_cserve_mem_new(sizeof(int), "status"); if (!stat_mem) { ERR("ERROR: cannot create status shmseg. abort."); goto error; } DBG("init status..."); if (!stat_init(stat_mem)) { ERR("cannot init status shmseg. abort."); evas_cserve_mem_free(stat_mem); stat_mem = NULL; goto error; } DBG("cset server message handler..."); evas_cserve_server_message_handler_set(s, message, NULL); last_check = time(NULL); t_next = 0; if (cache_item_timeout_check > 0) t_next = cache_item_timeout_check; DBG("LOOP!!! ..."); for (;;) { /* fixme: timeout 0 only her - future use timeouts for timed * housekeping */ if (exit_flag) break; DBG("wait for messages..."); evas_cserve_server_wait(s, t_next * 1000000); if (exit_flag) break; t = time(NULL); t_next = t - last_check; if ((cache_item_timeout_check > 0) && ((t_next) >= cache_item_timeout_check)) { DBG("check timeout of items..."); t_next = cache_item_timeout_check; last_check = t; DBG("cache timeout..."); cache_timeout(t); DBG("meminfo check..."); meminfo_check(); DBG("mem cache adjust..."); mem_cache_adjust(); } if ((t_next <= 0) && (cache_item_timeout_check > 0)) t_next = 1; DBG("sleep for %isec...", (int)t_next); LKL(strshr_freeme_lock); if (strshr_freeme_count > 0) { int i; for (i = 0; i < strshr_freeme_count; i++) eina_stringshare_del(strshr_freeme[i]); strshr_freeme_count = 0; } LKU(strshr_freeme_lock); LKL(cache_lock); if (cache_cleanme) { Eina_List *l; Img *img; Eina_List *kills = NULL; EINA_LIST_FOREACH(cache_images, l, img) { LKL(img->lock); if (img->killme) kills = eina_list_append(kills, img); LKU(img->lock); } while (kills) { img = kills->data; kills = eina_list_remove_list(kills, kills); LKL(img->lock); cache_images = eina_list_remove(cache_images, img); img->incache--; cache_usage -= img->usage; DBG("... IMG FREE %p", img); img_free(img); } cache_cleanme = 0; } LKU(cache_lock); } DBG("end loop..."); error: DBG("cleanup..."); if (stat_mem) { DBG("clean mem stat..."); stat_clean(stat_mem); } DBG("signal shutdown..."); signal_shutdown(); DBG("img shutdown..."); img_shutdown(); if (stat_mem) { DBG("free stat mem..."); evas_cserve_mem_free(stat_mem); stat_mem = NULL; } if (s) { DBG("del server..."); evas_cserve_server_del(s); } DBG("evas shutdown..."); evas_shutdown(); eina_log_domain_unregister(_evas_cserve_bin_log_dom); DBG("eina shutdown..."); eina_shutdown(); DBG("exit.."); return 0; }