Simplify use of cache for ordinary applications
[libchamplain:potyl-perl.git] / champlain / champlain-file-cache.c
1 /*
2  * Copyright (C) 2009 Pierre-Luc Beaudoin <pierre-luc@pierlux.com>
3  * Copyright (C) 2010 Jiri Techet <techet@gmail.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19
20 #define DEBUG_FLAG CHAMPLAIN_DEBUG_CACHE
21 #include "champlain-debug.h"
22
23 #include "champlain-file-cache.h"
24
25 #include <sqlite3.h>
26 #include <errno.h>
27 #include <glib.h>
28 #include <gio/gio.h>
29 #include <string.h>
30 #include <stdlib.h>
31
32 G_DEFINE_TYPE (ChamplainFileCache, champlain_file_cache, CHAMPLAIN_TYPE_TILE_CACHE);
33
34 #define GET_PRIVATE(obj)    (G_TYPE_INSTANCE_GET_PRIVATE((obj), CHAMPLAIN_TYPE_FILE_CACHE, ChamplainFileCachePrivate))
35
36 enum
37 {
38   PROP_0,
39   PROP_SIZE_LIMIT,
40   PROP_CACHE_DIR
41 };
42
43 typedef struct _ChamplainFileCachePrivate ChamplainFileCachePrivate;
44
45 struct _ChamplainFileCachePrivate
46 {
47   guint size_limit;
48   gchar *cache_dir;
49
50   gchar *real_cache_dir;
51   sqlite3 *db;
52   sqlite3_stmt *stmt_select;
53   sqlite3_stmt *stmt_update;
54 };
55
56 static void finalize_sql(ChamplainFileCache *file_cache);
57 static void delete_temp_cache (ChamplainFileCache *file_cache);
58 static void init_cache  (ChamplainFileCache *file_cache);
59 static gchar *get_filename (ChamplainFileCache *file_cache, ChamplainTile *tile);
60 static gboolean tile_is_expired (ChamplainFileCache *file_cache, ChamplainTile *tile);
61 static void delete_tile (ChamplainFileCache *file_cache, const gchar *filename);
62 static void delete_dir_recursive (GFile *parent);
63
64 static void fill_tile (ChamplainMapSource *map_source,
65                        ChamplainTile *tile);
66
67 static void store_tile (ChamplainTileCache *tile_cache,
68                         ChamplainTile *tile,
69                         const gchar *contents,
70                         gsize size);
71 static void refresh_tile_time (ChamplainTileCache *tile_cache, ChamplainTile *tile);
72 static void on_tile_filled (ChamplainTileCache *tile_cache, ChamplainTile *tile);
73 static void clean (ChamplainTileCache *tile_cache);
74
75 static void
76 champlain_file_cache_get_property (GObject *object,
77                                    guint property_id,
78                                    GValue *value,
79                                    GParamSpec *pspec)
80 {
81   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE (object);
82
83   switch (property_id)
84     {
85     case PROP_SIZE_LIMIT:
86       g_value_set_uint (value, champlain_file_cache_get_size_limit (file_cache));
87       break;
88     case PROP_CACHE_DIR:
89       g_value_set_string (value, champlain_file_cache_get_cache_dir (file_cache));
90       break;
91     default:
92       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
93     }
94 }
95
96 static void
97 champlain_file_cache_set_property (GObject *object,
98                                    guint property_id,
99                                    const GValue *value,
100                                    GParamSpec *pspec)
101 {
102   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE (object);
103   ChamplainFileCachePrivate *priv = GET_PRIVATE (object);
104
105   switch (property_id)
106     {
107     case PROP_SIZE_LIMIT:
108       champlain_file_cache_set_size_limit (file_cache, g_value_get_uint (value));
109       break;
110     case PROP_CACHE_DIR:
111       priv->cache_dir = g_strdup (g_value_get_string (value));
112       break;
113     default:
114       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
115     }
116 }
117
118 static void
119 champlain_file_cache_dispose (GObject *object)
120 {
121   G_OBJECT_CLASS (champlain_file_cache_parent_class)->dispose (object);
122 }
123
124 static void
125 finalize_sql(ChamplainFileCache *file_cache)
126 {
127   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
128   gint error;
129
130   if (priv->stmt_select)
131     {
132       sqlite3_finalize (priv->stmt_select);
133       priv->stmt_select = NULL;
134     }
135
136   if (priv->stmt_update)
137     {
138       sqlite3_finalize (priv->stmt_update);
139       priv->stmt_update = NULL;
140     }
141
142   if (priv->db)
143     {
144       error = sqlite3_close (priv->db);
145       if (error != SQLITE_OK)
146         DEBUG ("Sqlite returned error %d when closing cache.db", error);
147       priv->db = NULL;
148     }
149 }
150
151 static void
152 delete_temp_cache (ChamplainFileCache *file_cache)
153 {
154   ChamplainTileCache *tile_cache = CHAMPLAIN_TILE_CACHE(file_cache);
155   ChamplainFileCachePrivate *priv = GET_PRIVATE(file_cache);
156
157   if (!champlain_tile_cache_get_persistent(tile_cache) && priv->real_cache_dir)
158     {
159       GFile *file = NULL;
160
161       /* delete the directory contents */
162       file = g_file_new_for_path (priv->real_cache_dir);
163       delete_dir_recursive (file);
164
165       /* delete the directory itself */
166       if (!g_file_delete (file, NULL, NULL))
167         g_warning ("Failed to remove temporary cache main directory");
168
169       g_object_unref (file);
170     }
171 }
172
173 static void
174 champlain_file_cache_finalize (GObject *object)
175 {
176   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(object);
177   ChamplainTileCache *tile_cache = CHAMPLAIN_TILE_CACHE(file_cache);
178   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
179
180   finalize_sql (file_cache);
181
182   if (!champlain_tile_cache_get_persistent(tile_cache))
183     delete_temp_cache(file_cache);
184
185   g_free(priv->real_cache_dir);
186   g_free(priv->cache_dir);
187
188   G_OBJECT_CLASS (champlain_file_cache_parent_class)->finalize (object);
189 }
190
191 static void
192 init_cache  (ChamplainFileCache *file_cache)
193 {
194   ChamplainTileCache *tile_cache = CHAMPLAIN_TILE_CACHE(file_cache);
195   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
196   gchar *filename = NULL;
197   gchar *error_msg = NULL;
198   gint error;
199
200   /* If needed, create the cache's dirs */
201   if (priv->cache_dir)
202     {
203       if (g_mkdir_with_parents (priv->cache_dir, 0700) == -1 && errno != EEXIST)
204         {
205           g_warning ("Unable to create the image cache path '%s': %s",
206                      priv->cache_dir, g_strerror (errno));
207           return;
208         }
209     }
210
211   if (champlain_tile_cache_get_persistent(tile_cache))
212     priv->real_cache_dir = g_strdup(priv->cache_dir);
213   else
214     {
215       /* Create temporary directory for non-persistent caches */
216       gchar *tmplate = NULL;
217
218       if (priv->cache_dir)
219         tmplate = g_build_filename (priv->cache_dir, "champlain-XXXXXX", NULL);
220       else
221         tmplate = g_build_filename (g_get_tmp_dir (), "champlain-XXXXXX", NULL);
222
223       priv->real_cache_dir = mkdtemp(tmplate);
224
225       if (!priv->real_cache_dir)
226         {
227           g_warning ("Filed to create filename for temporary cache");
228           g_free(tmplate);
229         }
230     }
231
232   g_return_if_fail (priv->real_cache_dir);
233
234   filename = g_build_filename (priv->real_cache_dir,
235                                "cache.db", NULL);
236   error = sqlite3_open_v2 (filename, &priv->db,
237                            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
238   g_free (filename);
239
240   if (error == SQLITE_ERROR)
241     {
242       DEBUG ("Sqlite returned error %d when opening cache.db", error);
243       return;
244     }
245
246   sqlite3_exec (priv->db,
247                 "PRAGMA synchronous=OFF;"
248                 "PRAGMA count_changes=OFF;",
249                 NULL, NULL, &error_msg);
250   if (error_msg != NULL)
251     {
252       DEBUG ("Set PRAGMA: %s", error_msg);
253       sqlite3_free (error_msg);
254       return;
255     }
256
257   sqlite3_exec (priv->db,
258                 "CREATE TABLE IF NOT EXISTS tiles ("
259                 "filename TEXT PRIMARY KEY, "
260                 "etag TEXT, "
261                 "popularity INT DEFAULT 1, "
262                 "size INT DEFAULT 0)",
263                 NULL, NULL, &error_msg);
264   if (error_msg != NULL)
265     {
266       DEBUG ("Creating table 'tiles' failed: %s", error_msg);
267       sqlite3_free (error_msg);
268       return;
269     }
270
271   error = sqlite3_prepare_v2 (priv->db,
272                               "SELECT etag FROM tiles WHERE filename = ?", -1,
273                               &priv->stmt_select, NULL);
274   if (error != SQLITE_OK)
275     {
276       priv->stmt_select = NULL;
277       DEBUG ("Failed to prepare the select Etag statement, error:%d: %s",
278              error, sqlite3_errmsg (priv->db));
279       return;
280     }
281
282   error = sqlite3_prepare_v2 (priv->db,
283                               "UPDATE tiles SET popularity = popularity + 1 WHERE filename = ?", -1,
284                               &priv->stmt_update, NULL);
285   if (error != SQLITE_OK)
286     {
287       priv->stmt_update = NULL;
288       DEBUG ("Failed to prepare the update popularity statement, error: %s",
289              sqlite3_errmsg (priv->db));
290       return;
291     }
292
293   g_object_notify (G_OBJECT (file_cache), "cache-dir");
294 }
295
296 static void
297 champlain_file_cache_constructed  (GObject *object)
298 {
299   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(object);
300
301   init_cache(file_cache);
302
303   G_OBJECT_CLASS (champlain_file_cache_parent_class)->constructed (object);
304 }
305
306 static void
307 champlain_file_cache_class_init (ChamplainFileCacheClass *klass)
308 {
309   ChamplainMapSourceClass *map_source_class = CHAMPLAIN_MAP_SOURCE_CLASS (klass);
310   ChamplainTileCacheClass *tile_cache_class = CHAMPLAIN_TILE_CACHE_CLASS (klass);
311   GObjectClass *object_class = G_OBJECT_CLASS (klass);
312   GParamSpec *pspec;
313
314   g_type_class_add_private (klass, sizeof (ChamplainFileCachePrivate));
315
316   object_class->finalize = champlain_file_cache_finalize;
317   object_class->dispose = champlain_file_cache_dispose;
318   object_class->get_property = champlain_file_cache_get_property;
319   object_class->set_property = champlain_file_cache_set_property;
320   object_class->constructed = champlain_file_cache_constructed;
321
322   pspec = g_param_spec_uint ("size-limit",
323                              "Size Limit",
324                              "The cache's size limit (Mb)",
325                              1,
326                              G_MAXINT,
327                              100000000,
328                              G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
329   g_object_class_install_property (object_class, PROP_SIZE_LIMIT, pspec);
330
331   pspec = g_param_spec_string ("cache-dir",
332                                "Cache Directory",
333                                "The directory of the cache",
334                                "",
335                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
336   g_object_class_install_property (object_class, PROP_CACHE_DIR, pspec);
337
338   tile_cache_class->store_tile = store_tile;
339   tile_cache_class->refresh_tile_time = refresh_tile_time;
340   tile_cache_class->on_tile_filled = on_tile_filled;
341   tile_cache_class->clean = clean;
342
343   map_source_class->fill_tile = fill_tile;
344 }
345
346 static void
347 champlain_file_cache_init (ChamplainFileCache *file_cache)
348 {
349   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
350
351   priv->size_limit = 0;
352   priv->cache_dir = NULL;
353
354   priv->real_cache_dir = NULL;
355   priv->db = NULL;
356   priv->stmt_select = NULL;
357   priv->stmt_update = NULL;
358 }
359
360 ChamplainFileCache* champlain_file_cache_new (void)
361 {
362   gchar *cache_path;
363   ChamplainFileCache *cache;
364
365   cache_path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_cache_dir (), "champlain", NULL);
366   cache = g_object_new (CHAMPLAIN_TYPE_FILE_CACHE, "size-limit", 100000000,
367                         "cache-dir", cache_path, "persistent-cache", TRUE, NULL);
368   g_free(cache_path);
369
370   return cache;
371 }
372
373 ChamplainFileCache* champlain_file_cache_new_full (guint size_limit,
374     const gchar *cache_dir, gboolean persistent)
375 {
376   ChamplainFileCache * cache;
377   cache = g_object_new (CHAMPLAIN_TYPE_FILE_CACHE, "size-limit", size_limit,
378                         "cache-dir", cache_dir, "persistent-cache", persistent, NULL);
379   return cache;
380 }
381
382 guint
383 champlain_file_cache_get_size_limit (ChamplainFileCache *file_cache)
384 {
385   g_return_val_if_fail(CHAMPLAIN_IS_FILE_CACHE (file_cache), 0);
386
387   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
388   return priv->size_limit;
389 }
390
391 const gchar *
392 champlain_file_cache_get_cache_dir (ChamplainFileCache *file_cache)
393 {
394   g_return_val_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache), NULL);
395
396   ChamplainFileCachePrivate *priv = GET_PRIVATE(file_cache);
397   return priv->cache_dir;
398 }
399
400 void
401 champlain_file_cache_set_size_limit (ChamplainFileCache *file_cache,
402                                      guint size_limit)
403 {
404   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache));
405
406   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
407
408   priv->size_limit = size_limit;
409   g_object_notify (G_OBJECT (file_cache), "size-limit");
410 }
411
412 static gchar *
413 get_filename (ChamplainFileCache *file_cache, ChamplainTile *tile)
414 {
415   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
416
417   g_return_val_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache), NULL);
418   g_return_val_if_fail(CHAMPLAIN_IS_TILE (tile), NULL);
419   g_return_val_if_fail(priv->real_cache_dir, NULL);
420
421   ChamplainMapSource* map_source = CHAMPLAIN_MAP_SOURCE(file_cache);
422
423   gchar *filename = g_strdup_printf ("%s" G_DIR_SEPARATOR_S
424                                      "%s" G_DIR_SEPARATOR_S
425                                      "%d" G_DIR_SEPARATOR_S
426                                      "%d" G_DIR_SEPARATOR_S "%d.png",
427                                      priv->real_cache_dir,
428                                      champlain_map_source_get_id (map_source),
429                                      champlain_tile_get_zoom_level (tile),
430                                      champlain_tile_get_x (tile),
431                                      champlain_tile_get_y (tile));
432   return filename;
433 }
434
435 static gboolean
436 tile_is_expired (ChamplainFileCache *file_cache, ChamplainTile *tile)
437 {
438   g_return_val_if_fail(CHAMPLAIN_FILE_CACHE (file_cache), FALSE);
439   g_return_val_if_fail(CHAMPLAIN_TILE (tile), FALSE);
440
441   GTimeVal now = {0, };
442   const GTimeVal *modified_time = champlain_tile_get_modified_time (tile);
443   gboolean validate_cache = TRUE;
444
445   if (modified_time)
446     {
447       g_get_current_time (&now);
448       g_time_val_add (&now, (-24ul * 60ul * 60ul * 1000ul * 1000ul * 7ul)); // Cache expires in 7 days
449       validate_cache = modified_time->tv_sec < now.tv_sec;
450     }
451
452   DEBUG ("%p is %s expired", tile, (validate_cache ? "": "not"));
453
454   return validate_cache;
455 }
456
457 static void
458 fill_tile (ChamplainMapSource *map_source,
459            ChamplainTile *tile)
460 {
461   g_return_if_fail(CHAMPLAIN_IS_FILE_CACHE (map_source));
462   g_return_if_fail(CHAMPLAIN_IS_TILE (tile));
463
464   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(map_source);
465   ChamplainMapSource *next_source = champlain_map_source_get_next_source(map_source);
466   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
467   GFileInfo *info = NULL;
468   GFile *file = NULL;
469   GError *gerror = NULL;
470   ClutterActor *actor = NULL;
471   gchar *filename = NULL;
472   GTimeVal modified_time = {0,};
473
474   if (champlain_tile_get_content (tile))
475     /* Previous cache in the chain is validating its contents */
476     goto load_next;
477
478   filename = get_filename (file_cache, tile);
479   DEBUG ("fill of %s", filename);
480
481   /* Load the cached version */
482   actor = clutter_texture_new_from_file (filename, &gerror);
483   if (actor)
484     {
485       champlain_tile_set_content (tile, actor, FALSE);
486       champlain_tile_set_size (tile, champlain_map_source_get_tile_size(map_source));
487     }
488   else
489     {
490       DEBUG ("Failed to load tile %s, error: %s",
491              filename, gerror->message);
492       g_error_free (gerror);
493       goto load_next;
494     }
495
496   /* Retrieve modification time */
497   file = g_file_new_for_path (filename);
498   info = g_file_query_info (file,
499                             G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_ETAG_VALUE,
500                             G_FILE_QUERY_INFO_NONE, NULL, NULL);
501   if (info)
502     {
503       g_file_info_get_modification_time (info, &modified_time);
504       champlain_tile_set_modified_time (tile, &modified_time);
505     }
506   g_object_unref (file);
507   g_object_unref (info);
508
509   /* Notify other caches that the tile has been filled */
510   if (CHAMPLAIN_IS_TILE_CACHE(next_source))
511     {
512       on_tile_filled(CHAMPLAIN_TILE_CACHE(next_source), tile);
513     }
514
515   if (tile_is_expired (file_cache, tile))
516     {
517       int sql_rc = SQLITE_OK;
518
519       /* Retrieve etag */
520       sql_rc = sqlite3_bind_text (priv->stmt_select, 1, filename, -1, SQLITE_STATIC);
521       if (sql_rc == SQLITE_ERROR)
522         {
523           DEBUG ("Failed to prepare the SQL query for finding the Etag of '%s', error: %s",
524                  filename, sqlite3_errmsg (priv->db));
525           goto load_next;
526         }
527
528       sql_rc = sqlite3_step (priv->stmt_select);
529       if (sql_rc == SQLITE_ROW)
530         {
531           const gchar *etag = (const gchar *) sqlite3_column_text (priv->stmt_select, 0);
532           champlain_tile_set_etag (CHAMPLAIN_TILE (tile), etag);
533         }
534       else if (sql_rc == SQLITE_DONE)
535         {
536           DEBUG ("'%s' does't have an etag",
537                  filename);
538           goto load_next;
539         }
540       else if (sql_rc == SQLITE_ERROR)
541         {
542           DEBUG ("Failed to finding the Etag of '%s', %d error: %s",
543                  filename, sql_rc, sqlite3_errmsg (priv->db));
544           goto load_next;
545         }
546
547       /* Validate the tile */
548       /* goto load_next; */
549     }
550   else
551     {
552       /* Tile loaded and no validation needed - done */
553       champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
554       goto cleanup;
555     }
556
557 load_next:
558   if (CHAMPLAIN_IS_MAP_SOURCE(next_source))
559     champlain_map_source_fill_tile(next_source, tile);
560
561   /* if we have some content, use the tile even if it wasn't validated */
562   if (champlain_tile_get_content(tile) &&
563       champlain_tile_get_state(tile) != CHAMPLAIN_STATE_DONE)
564     champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
565
566 cleanup:
567   sqlite3_reset (priv->stmt_select);
568   g_free(filename);
569 }
570
571 static void
572 refresh_tile_time (ChamplainTileCache *tile_cache, ChamplainTile *tile)
573 {
574   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (tile_cache));
575
576   ChamplainMapSource *map_source = CHAMPLAIN_MAP_SOURCE(tile_cache);
577   ChamplainMapSource *next_source = champlain_map_source_get_next_source(map_source);
578   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(tile_cache);
579   gchar *filename = NULL;
580   GFile *file;
581   GFileInfo *info;
582
583   filename = get_filename (file_cache, tile);
584   file = g_file_new_for_path (filename);
585   g_free (filename);
586
587   info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
588                             G_FILE_QUERY_INFO_NONE, NULL, NULL);
589
590   if (info)
591     {
592       GTimeVal now = {0, };
593
594       g_get_current_time (&now);
595
596       g_file_info_set_modification_time (info, &now);
597       g_file_set_attributes_from_info (file, info, G_FILE_QUERY_INFO_NONE, NULL, NULL);
598     }
599
600   g_object_unref (file);
601   g_object_unref (info);
602
603   if (CHAMPLAIN_IS_TILE_CACHE(next_source))
604     {
605       refresh_tile_time(CHAMPLAIN_TILE_CACHE(next_source), tile);
606     }
607 }
608
609 static void
610 store_tile (ChamplainTileCache *tile_cache,
611             ChamplainTile *tile,
612             const gchar *contents,
613             gsize size)
614 {
615   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (tile_cache));
616
617   ChamplainMapSource *map_source = CHAMPLAIN_MAP_SOURCE(tile_cache);
618   ChamplainMapSource *next_source = champlain_map_source_get_next_source(map_source);
619   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(tile_cache);
620   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
621   gchar *query = NULL;
622   gchar *error = NULL;
623   gchar *path = NULL;
624   gchar *filename = NULL;
625   GError *gerror = NULL;
626   GFile *file;
627   GFileOutputStream *ostream;
628   gsize bytes_written;
629
630   DEBUG ("Update of %p", tile);
631
632   filename = get_filename (file_cache, tile);
633   file = g_file_new_for_path(filename);
634
635   /* If the file exists, delete it */
636   g_file_delete(file, NULL, NULL);
637
638   /* If needed, create the cache's dirs */
639   path = g_path_get_dirname (filename);
640   if (g_mkdir_with_parents (path, 0700) == -1)
641     {
642       if (errno != EEXIST)
643         {
644           g_warning ("Unable to create the image cache path '%s': %s",
645                      path, g_strerror (errno));
646           goto store_next;
647         }
648     }
649
650   ostream = g_file_create(file, G_FILE_CREATE_PRIVATE, NULL, &gerror);
651   if (!ostream)
652     {
653       DEBUG ("GFileOutputStream creation failed: %s", gerror->message);
654       g_error_free (gerror);
655       goto store_next;
656     }
657
658   /* Write the cache */
659   if (!g_output_stream_write_all(G_OUTPUT_STREAM(ostream), contents, size, &bytes_written, NULL, &gerror))
660     {
661       DEBUG ("Writing file contents failed: %s", gerror->message);
662       g_error_free (gerror);
663       g_object_unref(ostream);
664       goto store_next;
665     }
666
667   g_object_unref(ostream);
668
669   query = sqlite3_mprintf ("REPLACE INTO tiles (filename, etag, size) VALUES (%Q, %Q, %d)",
670                            filename,
671                            champlain_tile_get_etag (tile),
672                            size);
673   sqlite3_exec (priv->db, query, NULL, NULL, &error);
674   if (error != NULL)
675     {
676       DEBUG ("Saving Etag and size failed: %s", error);
677       sqlite3_free (error);
678     }
679   sqlite3_free (query);
680
681 store_next:
682   if (CHAMPLAIN_IS_TILE_CACHE(next_source))
683     {
684       store_tile(CHAMPLAIN_TILE_CACHE(next_source), tile, contents, size);
685     }
686
687   g_free(filename);
688   g_free(path);
689   g_object_unref(file);
690 }
691
692 static void
693 on_tile_filled (ChamplainTileCache *tile_cache, ChamplainTile *tile)
694 {
695   g_return_if_fail(CHAMPLAIN_IS_FILE_CACHE (tile_cache));
696   g_return_if_fail(CHAMPLAIN_IS_TILE (tile));
697
698   ChamplainMapSource *map_source = CHAMPLAIN_MAP_SOURCE(tile_cache);
699   ChamplainMapSource *next_source = champlain_map_source_get_next_source(map_source);
700   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(tile_cache);
701   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
702   int sql_rc = SQLITE_OK;
703   gchar *filename = NULL;
704
705   filename = get_filename (file_cache, tile);
706
707   DEBUG ("popularity of %s", filename);
708
709   sql_rc = sqlite3_bind_text (priv->stmt_update, 1, filename, -1, SQLITE_STATIC);
710   if (sql_rc != SQLITE_OK)
711     {
712       DEBUG ("Failed to set values to the popularity query of '%s', error: %s",
713              filename, sqlite3_errmsg (priv->db));
714       goto call_next;
715     }
716
717   sql_rc = sqlite3_step (priv->stmt_update);
718   if (sql_rc != SQLITE_DONE)
719     {
720       /* may not be present in this cache */
721       goto call_next;
722     }
723
724 call_next:
725   if (CHAMPLAIN_IS_TILE_CACHE(next_source))
726     {
727       on_tile_filled(CHAMPLAIN_TILE_CACHE(next_source), tile);
728     }
729 }
730
731 static void
732 clean (ChamplainTileCache *tile_cache)
733 {
734   g_return_if_fail(CHAMPLAIN_IS_FILE_CACHE (tile_cache));
735
736   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(tile_cache);
737   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
738
739   g_return_if_fail(!champlain_tile_cache_get_persistent(tile_cache));
740
741   finalize_sql (file_cache);
742   delete_temp_cache(file_cache);
743
744   g_free(priv->real_cache_dir);
745   priv->real_cache_dir = NULL;
746
747   init_cache(file_cache);
748 }
749
750 static void
751 delete_tile (ChamplainFileCache *file_cache, const gchar *filename)
752 {
753   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache));
754   gchar *query, *error = NULL;
755   GError *gerror = NULL;
756   GFile *file;
757
758   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
759
760   query = sqlite3_mprintf ("DELETE FROM tiles WHERE filename = %Q", filename);
761   sqlite3_exec (priv->db, query, NULL, NULL, &error);
762   if (error != NULL)
763     {
764       DEBUG ("Deleting tile from db failed: %s", error);
765       sqlite3_free (error);
766     }
767   sqlite3_free (query);
768
769   file = g_file_new_for_path (filename);
770   if (!g_file_delete (file, NULL, &gerror))
771     {
772       DEBUG ("Deleting tile from disk failed: %s", gerror->message);
773       g_error_free (gerror);
774     }
775   g_object_unref (file);
776 }
777
778 static gboolean
779 purge_on_idle (gpointer data)
780 {
781   champlain_file_cache_purge (CHAMPLAIN_FILE_CACHE (data));
782   return FALSE;
783 }
784
785 void
786 champlain_file_cache_purge_on_idle (ChamplainFileCache *file_cache)
787 {
788   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache));
789   g_idle_add (purge_on_idle, file_cache);
790 }
791
792 void
793 champlain_file_cache_purge (ChamplainFileCache *file_cache)
794 {
795   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache));
796
797   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
798   gchar *query;
799   sqlite3_stmt *stmt;
800   int rc = 0;
801   guint current_size = 0;
802   guint highest_popularity = 0;
803   gchar *error;
804
805   query = "SELECT SUM (size) FROM tiles";
806   rc = sqlite3_prepare (priv->db, query, strlen (query), &stmt, NULL);
807   if (rc != SQLITE_OK)
808     {
809       DEBUG ("Can't compute cache size %s", sqlite3_errmsg (priv->db));
810     }
811
812   rc = sqlite3_step (stmt);
813   if (rc != SQLITE_ROW)
814     {
815       DEBUG ("Failed to count the total cache consumption %s",
816              sqlite3_errmsg (priv->db));
817       sqlite3_finalize (stmt);
818       return;
819     }
820
821   current_size = sqlite3_column_int (stmt, 0);
822   if (current_size < priv->size_limit)
823     {
824       DEBUG ("Cache doesn't need to be purged at %d bytes", current_size);
825       sqlite3_finalize (stmt);
826       return;
827     }
828
829   sqlite3_finalize (stmt);
830
831   /* Ok, delete the less popular tiles until size_limit reached */
832   query = "SELECT filename, size, popularity FROM tiles ORDER BY popularity";
833   rc = sqlite3_prepare (priv->db, query, strlen (query), &stmt, NULL);
834   if (rc != SQLITE_OK)
835     {
836       DEBUG ("Can't fetch tiles to delete: %s", sqlite3_errmsg(priv->db));
837     }
838
839   rc = sqlite3_step (stmt);
840   while (rc == SQLITE_ROW && current_size > priv->size_limit)
841     {
842       const char *filename = sqlite3_column_text (stmt, 0);
843       guint size;
844
845       filename = sqlite3_column_text (stmt, 0);
846       size = sqlite3_column_int (stmt, 1);
847       highest_popularity = sqlite3_column_int (stmt, 2);
848       DEBUG ("Deleting %s of size %d", filename, size);
849
850       delete_tile (file_cache, filename);
851
852       current_size -= size;
853
854       rc = sqlite3_step (stmt);
855     }
856   DEBUG ("Cache size is now %d", current_size);
857
858   sqlite3_finalize (stmt);
859
860   query = sqlite3_mprintf ("UPDATE tiles SET popularity = popularity - %d",
861                            highest_popularity);
862   sqlite3_exec (priv->db, query, NULL, NULL, &error);
863   if (error != NULL)
864     {
865       DEBUG ("Updating popularity failed: %s", error);
866       sqlite3_free (error);
867     }
868   sqlite3_free (query);
869 }
870
871 static void
872 delete_dir_recursive (GFile *parent)
873 {
874   g_return_if_fail (parent);
875
876   GError *error = NULL;
877   GFileEnumerator *enumerator;
878   GFileInfo *info;
879   GFile *child;
880
881   enumerator = g_file_enumerate_children (parent, "*",
882                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
883
884   if (!enumerator)
885     {
886       DEBUG ("Failed to create file enumerator in delete_dir_recursive: %s", error->message);
887       g_error_free (error);
888       return;
889     }
890
891   info = g_file_enumerator_next_file (enumerator, NULL, NULL);
892   while (info && !error)
893     {
894       child = g_file_get_child (parent, g_file_info_get_name (info));
895
896       if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
897         delete_dir_recursive (child);
898
899       error = NULL;
900       if (!g_file_delete (child, NULL, &error))
901         {
902           DEBUG ("Deleting tile from disk failed: %s", error->message);
903           g_error_free (error);
904         }
905
906       g_object_unref (child);
907       g_object_unref (info);
908       info = g_file_enumerator_next_file (enumerator, NULL, NULL);
909     }
910
911   g_file_enumerator_close (enumerator, NULL, NULL);
912 }