]> git.street.me.uk Git - andy/viking.git/blob - src/mapcache.c
Add ability to remove the map cache for a single map type.
[andy/viking.git] / src / mapcache.c
1 /*
2  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3  *
4  * Copyright (C) 2003-2005, Evan Battaglia <gtoevan@gmx.net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <string.h>
28 #include "globals.h"
29 #include "mapcache.h"
30 #include "preferences.h"
31 #include "vik_compat.h"
32
33 #define MC_KEY_SIZE 64
34
35 typedef struct _List {
36   struct _List *next;
37   gchar *key;
38 } List;
39
40 /* a circular linked list, a pointer to the tail, and the tail points to the head */
41 /* this is so we can free the last */
42 static List *queue_tail = NULL;
43 static int queue_count = 0;
44
45 static guint32 cache_size = 0;
46 static guint32 max_cache_size = VIK_CONFIG_MAPCACHE_SIZE * 1024 * 1024;
47
48 static GHashTable *cache = NULL;
49
50 static GMutex *mc_mutex = NULL;
51
52 #define HASHKEY_FORMAT_STRING "%d-%d-%d-%d-%d-%d-%d-%.3f-%.3f"
53 #define HASHKEY_FORMAT_STRING_NOSHRINK_NOR_ALPHA "%d-%d-%d-%d-%d-%d-"
54 #define HASHKEY_FORMAT_STRING_TYPE "%d-"
55
56 static VikLayerParamScale params_scales[] = {
57   /* min, max, step, digits (decimal places) */
58  { 1, 1024, 1, 0 },
59 };
60
61 static VikLayerParam prefs[] = {
62   { VIK_LAYER_NUM_TYPES, VIKING_PREFERENCES_NAMESPACE "mapcache_size", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, N_("Map cache memory size (MB):"), VIK_LAYER_WIDGET_HSCALE, params_scales, NULL, NULL, NULL, NULL, NULL },
63 };
64
65 void a_mapcache_init ()
66 {
67   VikLayerParamData tmp;
68   tmp.u = VIK_CONFIG_MAPCACHE_SIZE;
69   a_preferences_register(prefs, tmp, VIKING_PREFERENCES_GROUP_KEY);
70
71   mc_mutex = vik_mutex_new ();
72   cache = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, g_object_unref );
73 }
74
75 static void cache_add(gchar *key, GdkPixbuf *pixbuf)
76 {
77 #if !GLIB_CHECK_VERSION(2,26,0)
78   // Only later versions of GLib actually return a value for this function
79   // Annoyingly the documentation doesn't say anything about this interface change :(
80   if ( g_hash_table_insert ( cache, key, pixbuf ) )
81 #else
82   g_hash_table_insert ( cache, key, pixbuf );
83 #endif
84   {
85     cache_size += gdk_pixbuf_get_rowstride(pixbuf) * gdk_pixbuf_get_height(pixbuf);
86     cache_size += 100;
87   }
88 }
89
90 static void cache_remove(const gchar *key)
91 {
92   GdkPixbuf *buf = g_hash_table_lookup ( cache, key );
93   if (buf) {
94     cache_size -= gdk_pixbuf_get_rowstride(buf) * gdk_pixbuf_get_height(buf);
95     cache_size -= 100;
96     g_hash_table_remove ( cache, key );
97   }
98 }
99
100 /* returns key from head, adds on newtailkey to tail. */
101 static gchar *list_shift_add_entry ( gchar *newtailkey )
102 {
103   gchar *oldheadkey = queue_tail->next->key;
104   queue_tail->next->key = newtailkey;
105   queue_tail = queue_tail->next;
106   return oldheadkey;
107 }
108
109 static gchar *list_shift ()
110 {
111   gchar *oldheadkey = queue_tail->next->key;
112   List *oldhead = queue_tail->next;
113   queue_tail->next = queue_tail->next->next;
114   g_free ( oldhead );
115   queue_count--;
116   return oldheadkey;
117 }
118
119 /* adds key to tail */
120 static void list_add_entry ( gchar *key )
121 {
122   List *newlist = g_malloc ( sizeof ( List ) );
123   newlist->key = key;
124   if ( queue_tail ) {
125     newlist->next = queue_tail->next;
126     queue_tail->next = newlist;
127     queue_tail = newlist;
128   } else {
129     newlist->next = newlist;
130     queue_tail = newlist;
131   }
132   queue_count++;
133 }
134
135 void a_mapcache_add ( GdkPixbuf *pixbuf, gint x, gint y, gint z, guint16 type, gint zoom, guint8 alpha, gdouble xshrinkfactor, gdouble yshrinkfactor, const gchar* name )
136 {
137   guint nn = name ? g_str_hash ( name ) : 0;
138   gchar *key = g_strdup_printf ( HASHKEY_FORMAT_STRING, type, x, y, z, zoom, nn, alpha, xshrinkfactor, yshrinkfactor );
139
140   g_mutex_lock(mc_mutex);
141   cache_add(key, pixbuf);
142
143   // TODO: that should be done on preference change only...
144   max_cache_size = a_preferences_get(VIKING_PREFERENCES_NAMESPACE "mapcache_size")->u * 1024 * 1024;
145
146   if ( cache_size > max_cache_size ) {
147     if ( queue_tail ) {
148       gchar *oldkey = list_shift_add_entry ( key );
149       cache_remove(oldkey);
150
151       while ( cache_size > max_cache_size &&
152              (queue_tail->next != queue_tail) ) { /* make sure there's more than one thing to delete */
153         oldkey = list_shift ();
154         cache_remove(oldkey);
155       }
156     }
157     /* chop off 'start' etc */
158   } else {
159     list_add_entry ( key );
160     /* business as usual */
161   }
162   g_mutex_unlock(mc_mutex);
163
164   static int tmp = 0;
165   if ( (++tmp == 100 )) { g_debug("DEBUG: cache count=%d size=%u list count=%d\n", g_hash_table_size(cache), cache_size, queue_count ); tmp=0; }
166 }
167
168 GdkPixbuf *a_mapcache_get ( gint x, gint y, gint z, guint16 type, gint zoom, guint8 alpha, gdouble xshrinkfactor, gdouble yshrinkfactor, const gchar* name )
169 {
170   static char key[MC_KEY_SIZE];
171   guint nn = name ? g_str_hash ( name ) : 0;
172   g_snprintf ( key, sizeof(key), HASHKEY_FORMAT_STRING, type, x, y, z, zoom, nn, alpha, xshrinkfactor, yshrinkfactor );
173   return g_hash_table_lookup ( cache, key );
174 }
175
176 /**
177  * Common function to remove cache items for keys starting with the specified string
178  */
179 static void flush_matching ( gchar *str )
180 {
181   if ( queue_tail == NULL )
182     return;
183
184   List *loop = queue_tail;
185   List *tmp;
186   gint len = strlen(str);
187
188   g_mutex_lock(mc_mutex);
189   do {
190     tmp = loop->next;
191     if ( tmp ) {
192     if ( strncmp(tmp->key, str, len) == 0 )
193     {
194       cache_remove(tmp->key);
195       if ( tmp == loop ) /* we deleted the last thing in the queue! */
196         loop = queue_tail = NULL;
197       else {
198         loop->next = tmp->next;
199         if ( tmp == queue_tail )
200           queue_tail = tmp->next;
201       }
202       g_free ( tmp );
203       tmp = NULL;
204       queue_count--;
205     }
206     else
207       loop = tmp;
208     } else
209       loop = NULL;
210   } while ( loop && (loop != queue_tail || tmp == NULL) );
211   /* loop thru list, looking for the one, compare first whatever chars */
212
213   cache_remove(str);
214   g_mutex_unlock(mc_mutex);
215 }
216
217 /**
218  * Appears this is only used when redownloading tiles (i.e. to invalidate old images)
219  */
220 void a_mapcache_remove_all_shrinkfactors ( gint x, gint y, gint z, guint16 type, gint zoom )
221 {
222   char key[MC_KEY_SIZE];
223   g_snprintf ( key, sizeof(key), HASHKEY_FORMAT_STRING_NOSHRINK_NOR_ALPHA, type, x, y, z, zoom, 0 );
224   flush_matching ( key );
225 }
226
227 void a_mapcache_flush ()
228 {
229   List *loop = queue_tail;
230   List *tmp;
231
232   if ( queue_tail == NULL )
233     return;
234
235   g_mutex_lock(mc_mutex);
236   do {
237     tmp = loop->next;
238     cache_remove(tmp->key);
239     if ( tmp == queue_tail ) /* we deleted the last thing in the queue */
240       loop = queue_tail = NULL;
241     else
242       loop->next = tmp->next;
243     g_free ( tmp );
244     tmp = NULL;
245   } while ( loop );
246
247   g_mutex_unlock(mc_mutex);
248 }
249
250 /**
251  * a_mapcache_flush_type:
252  *  @type: Specified map type
253  *
254  * Just remove cache items for the specified map type
255  *  i.e. all related xyz+zoom+alpha+etc...
256  */
257 void a_mapcache_flush_type ( guint16 type )
258 {
259   char key[MC_KEY_SIZE];
260   g_snprintf ( key, sizeof(key), HASHKEY_FORMAT_STRING_TYPE, type );
261   flush_matching ( key );
262 }
263
264 void a_mapcache_uninit ()
265 {
266   g_hash_table_destroy ( cache );
267   /* free list */
268   cache = NULL;
269   vik_mutex_free (mc_mutex);
270 }
271
272 // Size of mapcache in memory
273 gint a_mapcache_get_size ()
274 {
275   return cache_size;
276 }
277
278 // Count of items in the mapcache
279 gint a_mapcache_get_count ()
280 {
281   return g_hash_table_size ( cache );
282 }