Fix cache initialization
[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 /**
21  * SECTION:champlain-file-cache
22  * @short_description: Stores and loads cached tiles from the file system
23  *
24  * #ChamplainFileCache is a map source that stores and retrieves tiles from the
25  * file system. It can be temporary (deleted when the object is destroyed) or
26  * permanent. Tiles most frequently loaded gain in "popularity". This popularity
27  * is taken into account when purging the cache.
28  */
29
30 #define DEBUG_FLAG CHAMPLAIN_DEBUG_CACHE
31 #include "champlain-debug.h"
32
33 #include "champlain-file-cache.h"
34
35 #include <sqlite3.h>
36 #include <errno.h>
37 #include <glib.h>
38 #include <gio/gio.h>
39 #include <string.h>
40 #include <stdlib.h>
41
42 G_DEFINE_TYPE (ChamplainFileCache, champlain_file_cache, CHAMPLAIN_TYPE_TILE_CACHE);
43
44 #define GET_PRIVATE(obj)    (G_TYPE_INSTANCE_GET_PRIVATE((obj), CHAMPLAIN_TYPE_FILE_CACHE, ChamplainFileCachePrivate))
45
46 enum
47 {
48   PROP_0,
49   PROP_SIZE_LIMIT,
50   PROP_CACHE_DIR
51 };
52
53 typedef struct _ChamplainFileCachePrivate ChamplainFileCachePrivate;
54
55 struct _ChamplainFileCachePrivate
56 {
57   guint size_limit;
58   gchar *cache_dir;
59
60   gchar *real_cache_dir;
61   sqlite3 *db;
62   sqlite3_stmt *stmt_select;
63   sqlite3_stmt *stmt_update;
64 };
65
66 static void finalize_sql (ChamplainFileCache *file_cache);
67 static void delete_temp_cache (ChamplainFileCache *file_cache);
68 static void init_cache  (ChamplainFileCache *file_cache);
69 static gchar *get_filename (ChamplainFileCache *file_cache, ChamplainTile *tile);
70 static gboolean tile_is_expired (ChamplainFileCache *file_cache, ChamplainTile *tile);
71 static void delete_tile (ChamplainFileCache *file_cache, const gchar *filename);
72 static void delete_dir_recursive (GFile *parent);
73 static gboolean create_cache_dir (const gchar *dir_name);
74
75 static void fill_tile (ChamplainMapSource *map_source,
76                        ChamplainTile *tile);
77
78 static void store_tile (ChamplainTileCache *tile_cache,
79                         ChamplainTile *tile,
80                         const gchar *contents,
81                         gsize size);
82 static void refresh_tile_time (ChamplainTileCache *tile_cache, ChamplainTile *tile);
83 static void on_tile_filled (ChamplainTileCache *tile_cache, ChamplainTile *tile);
84 static void clean (ChamplainTileCache *tile_cache);
85
86 static void
87 champlain_file_cache_get_property (GObject *object,
88                                    guint property_id,
89                                    GValue *value,
90                                    GParamSpec *pspec)
91 {
92   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE (object);
93
94   switch (property_id)
95     {
96     case PROP_SIZE_LIMIT:
97       g_value_set_uint (value, champlain_file_cache_get_size_limit (file_cache));
98       break;
99     case PROP_CACHE_DIR:
100       g_value_set_string (value, champlain_file_cache_get_cache_dir (file_cache));
101       break;
102     default:
103       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
104     }
105 }
106
107 static void
108 champlain_file_cache_set_property (GObject *object,
109                                    guint property_id,
110                                    const GValue *value,
111                                    GParamSpec *pspec)
112 {
113   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE (object);
114   ChamplainFileCachePrivate *priv = GET_PRIVATE (object);
115
116   switch (property_id)
117     {
118     case PROP_SIZE_LIMIT:
119       champlain_file_cache_set_size_limit (file_cache, g_value_get_uint (value));
120       break;
121     case PROP_CACHE_DIR:
122       if (priv->cache_dir)
123         g_free (priv->cache_dir);
124       priv->cache_dir = g_strdup (g_value_get_string (value));
125       break;
126     default:
127       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
128     }
129 }
130
131 static void
132 champlain_file_cache_dispose (GObject *object)
133 {
134   G_OBJECT_CLASS (champlain_file_cache_parent_class)->dispose (object);
135 }
136
137 static void
138 finalize_sql (ChamplainFileCache *file_cache)
139 {
140   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
141   gint error;
142
143   if (priv->stmt_select)
144     {
145       sqlite3_finalize (priv->stmt_select);
146       priv->stmt_select = NULL;
147     }
148
149   if (priv->stmt_update)
150     {
151       sqlite3_finalize (priv->stmt_update);
152       priv->stmt_update = NULL;
153     }
154
155   if (priv->db)
156     {
157       error = sqlite3_close (priv->db);
158       if (error != SQLITE_OK)
159         DEBUG ("Sqlite returned error %d when closing cache.db", error);
160       priv->db = NULL;
161     }
162 }
163
164 static void
165 delete_temp_cache (ChamplainFileCache *file_cache)
166 {
167   ChamplainTileCache *tile_cache = CHAMPLAIN_TILE_CACHE(file_cache);
168   ChamplainFileCachePrivate *priv = GET_PRIVATE(file_cache);
169
170   if (!champlain_tile_cache_get_persistent (tile_cache) && priv->real_cache_dir)
171     {
172       GFile *file = NULL;
173
174       /* delete the directory contents */
175       file = g_file_new_for_path (priv->real_cache_dir);
176       delete_dir_recursive (file);
177
178       /* delete the directory itself */
179       if (!g_file_delete (file, NULL, NULL))
180         g_warning ("Failed to remove temporary cache main directory");
181
182       g_object_unref (file);
183     }
184 }
185
186 static void
187 champlain_file_cache_finalize (GObject *object)
188 {
189   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(object);
190   ChamplainTileCache *tile_cache = CHAMPLAIN_TILE_CACHE(file_cache);
191   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
192
193   finalize_sql (file_cache);
194
195   if (!champlain_tile_cache_get_persistent (tile_cache))
196     delete_temp_cache (file_cache);
197
198   g_free (priv->real_cache_dir);
199   g_free (priv->cache_dir);
200
201   G_OBJECT_CLASS (champlain_file_cache_parent_class)->finalize (object);
202 }
203
204 static gboolean
205 create_cache_dir (const gchar *dir_name)
206 {
207   /* If needed, create the cache's dirs */
208   if (dir_name)
209     {
210       if (g_mkdir_with_parents (dir_name, 0700) == -1 && errno != EEXIST)
211         {
212           g_warning ("Unable to create the image cache path '%s': %s",
213                      dir_name, g_strerror (errno));
214           return FALSE;
215         }
216     }
217   return TRUE;
218 }
219
220 static void
221 init_cache  (ChamplainFileCache *file_cache)
222 {
223   ChamplainTileCache *tile_cache = CHAMPLAIN_TILE_CACHE(file_cache);
224   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
225   gchar *filename = NULL;
226   gchar *error_msg = NULL;
227   gint error;
228
229   g_print ("init! '%d'\n", champlain_tile_cache_get_persistent (tile_cache));
230
231   g_return_if_fail (create_cache_dir (priv->cache_dir));
232
233   if (champlain_tile_cache_get_persistent (tile_cache))
234     {
235       if (priv->cache_dir)
236         priv->real_cache_dir = g_strdup (priv->cache_dir);
237       else
238         {
239           priv->real_cache_dir = g_build_path (G_DIR_SEPARATOR_S,
240                                                g_get_user_cache_dir (),
241                                                "champlain", NULL);
242           g_return_if_fail (create_cache_dir (priv->real_cache_dir));
243         }
244     }
245   else
246     {
247       /* Create temporary directory for non-persistent caches */
248       gchar *tmplate = NULL;
249
250       if (priv->cache_dir)
251         tmplate = g_build_filename (priv->cache_dir, "champlain-XXXXXX", NULL);
252       else
253         tmplate = g_build_filename (g_get_tmp_dir (), "champlain-XXXXXX", NULL);
254
255       priv->real_cache_dir = mkdtemp (tmplate);
256
257       if (!priv->real_cache_dir)
258         {
259           g_warning ("Filed to create filename for temporary cache");
260           g_free (tmplate);
261         }
262     }
263
264   g_return_if_fail (priv->real_cache_dir);
265
266   filename = g_build_filename (priv->real_cache_dir,
267                                "cache.db", NULL);
268   error = sqlite3_open_v2 (filename, &priv->db,
269                            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
270   g_free (filename);
271
272   if (error == SQLITE_ERROR)
273     {
274       DEBUG ("Sqlite returned error %d when opening cache.db", error);
275       return;
276     }
277
278   sqlite3_exec (priv->db,
279                 "PRAGMA synchronous=OFF;"
280                 "PRAGMA count_changes=OFF;",
281                 NULL, NULL, &error_msg);
282   if (error_msg != NULL)
283     {
284       DEBUG ("Set PRAGMA: %s", error_msg);
285       sqlite3_free (error_msg);
286       return;
287     }
288
289   sqlite3_exec (priv->db,
290                 "CREATE TABLE IF NOT EXISTS tiles ("
291                 "filename TEXT PRIMARY KEY, "
292                 "etag TEXT, "
293                 "popularity INT DEFAULT 1, "
294                 "size INT DEFAULT 0)",
295                 NULL, NULL, &error_msg);
296   if (error_msg != NULL)
297     {
298       DEBUG ("Creating table 'tiles' failed: %s", error_msg);
299       sqlite3_free (error_msg);
300       return;
301     }
302
303   error = sqlite3_prepare_v2 (priv->db,
304                               "SELECT etag FROM tiles WHERE filename = ?", -1,
305                               &priv->stmt_select, NULL);
306   if (error != SQLITE_OK)
307     {
308       priv->stmt_select = NULL;
309       DEBUG ("Failed to prepare the select Etag statement, error:%d: %s",
310              error, sqlite3_errmsg (priv->db));
311       return;
312     }
313
314   error = sqlite3_prepare_v2 (priv->db,
315                               "UPDATE tiles SET popularity = popularity + 1 WHERE filename = ?", -1,
316                               &priv->stmt_update, NULL);
317   if (error != SQLITE_OK)
318     {
319       priv->stmt_update = NULL;
320       DEBUG ("Failed to prepare the update popularity statement, error: %s",
321              sqlite3_errmsg (priv->db));
322       return;
323     }
324
325   g_object_notify (G_OBJECT (file_cache), "cache-dir");
326 }
327
328 static void
329 champlain_file_cache_constructed (GObject *object)
330 {
331   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(object);
332
333   init_cache (file_cache);
334
335   G_OBJECT_CLASS (champlain_file_cache_parent_class)->constructed (object);
336 }
337
338 static void
339 champlain_file_cache_class_init (ChamplainFileCacheClass *klass)
340 {
341   ChamplainMapSourceClass *map_source_class = CHAMPLAIN_MAP_SOURCE_CLASS (klass);
342   ChamplainTileCacheClass *tile_cache_class = CHAMPLAIN_TILE_CACHE_CLASS (klass);
343   GObjectClass *object_class = G_OBJECT_CLASS (klass);
344   GParamSpec *pspec;
345   gchar *cache_dir;
346
347   g_type_class_add_private (klass, sizeof (ChamplainFileCachePrivate));
348
349   object_class->finalize = champlain_file_cache_finalize;
350   object_class->dispose = champlain_file_cache_dispose;
351   object_class->get_property = champlain_file_cache_get_property;
352   object_class->set_property = champlain_file_cache_set_property;
353   object_class->constructed = champlain_file_cache_constructed;
354
355   /**
356   * ChamplainFileCache:size-limit:
357   *
358   * The cache size limit in bytes.
359   *
360   * Note: this new value will not be applied until you call #champlain_cache_purge
361   *
362   * Since: 0.4
363   */
364   pspec = g_param_spec_uint ("size-limit",
365                              "Size Limit",
366                              "The cache's size limit (Mb)",
367                              1,
368                              G_MAXINT,
369                              100000000,
370                              G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
371   g_object_class_install_property (object_class, PROP_SIZE_LIMIT, pspec);
372
373 #ifdef USE_MAEMO
374   cache_dir = g_strdup ("/home/user/MyDocs/.Maps/");
375 #else
376   cache_dir = g_build_path (G_DIR_SEPARATOR_S, g_get_user_cache_dir (), "champlain", NULL);
377 #endif
378
379   /**
380   * ChamplainFileCache:cache-dir:
381   *
382   * The directory where the tile database is stored.
383   *
384   * Since: 0.6
385   */
386   pspec = g_param_spec_string ("cache-dir",
387                                "Cache Directory",
388                                "The directory of the cache",
389                                cache_dir,
390                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
391   g_object_class_install_property (object_class, PROP_CACHE_DIR, pspec);
392
393   tile_cache_class->store_tile = store_tile;
394   tile_cache_class->refresh_tile_time = refresh_tile_time;
395   tile_cache_class->on_tile_filled = on_tile_filled;
396   tile_cache_class->clean = clean;
397
398   map_source_class->fill_tile = fill_tile;
399 }
400
401 static void
402 champlain_file_cache_init (ChamplainFileCache *file_cache)
403 {
404   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
405
406   /*
407      priv->cache_dir initialized by the default value of the
408      G_PARAM_CONSTRUCT_ONLY property
409   */
410   priv->size_limit = 100000000;
411   priv->real_cache_dir = NULL;
412   priv->db = NULL;
413   priv->stmt_select = NULL;
414   priv->stmt_update = NULL;
415 }
416
417 /**
418  * champlain_file_cache_new:
419  *
420  * Default constructor of #ChamplainFileCache.
421  *
422  * Returns: a constructed permanent cache of maximal size 100000000 B inside
423  * ~/.cache/champlain.
424  *
425  * Since: 0.6
426  */
427 ChamplainFileCache* champlain_file_cache_new (void)
428 {
429   return CHAMPLAIN_FILE_CACHE (g_object_new (CHAMPLAIN_TYPE_FILE_CACHE, NULL));
430 }
431
432 /**
433  * champlain_file_cache_new_full:
434  * @size_limit: maximal size of the cache in bytes
435  * @cache_dir: the directory where the cache is created. For temporary caches
436  * one more directory with random name is created inside this directory.
437  * When cache_dir == NULL, a cache in ~/.cache/champlain is used for permanent
438  * caches and /tmp for temporary caches.
439  * @persistent: if TRUE, the cache is persistent; otherwise the cache is
440  * temporary and will be deleted when the cache object is destructed.
441  *
442  * Constructor of #ChamplainFileCache.
443  *
444  * Returns: a constructed #ChamplainFileCache
445  *
446  * Since: 0.6
447  */
448 ChamplainFileCache* champlain_file_cache_new_full (guint size_limit,
449     const gchar *cache_dir, gboolean persistent)
450 {
451   ChamplainFileCache * cache;
452   cache = g_object_new (CHAMPLAIN_TYPE_FILE_CACHE, "size-limit", size_limit,
453                         "cache-dir", cache_dir, "persistent-cache", persistent, NULL);
454   return cache;
455 }
456
457 /**
458  * champlain_file_cache_get_size_limit:
459  * @file_cache: a #ChamplainCache
460  *
461  * Gets the cache size limit in bytes.
462  *
463  * Returns: size limit
464  *
465  * Since: 0.4
466  */
467 guint
468 champlain_file_cache_get_size_limit (ChamplainFileCache *file_cache)
469 {
470   g_return_val_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache), 0);
471
472   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
473   return priv->size_limit;
474 }
475
476 /**
477  * champlain_file_cache_get_cache_dir:
478  * @file_cache: a #ChamplainCache
479  *
480  * Gets the directory where the cache database is stored.
481  *
482  * Returns: the directory
483  *
484  * Since: 0.6
485  */
486 const gchar *
487 champlain_file_cache_get_cache_dir (ChamplainFileCache *file_cache)
488 {
489   g_return_val_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache), NULL);
490
491   ChamplainFileCachePrivate *priv = GET_PRIVATE(file_cache);
492   return priv->cache_dir;
493 }
494
495 /**
496  * champlain_file_cache_set_size_limit:
497  * @file_cache: a #ChamplainCache
498  * @size_limit: the cache limit in bytes
499  *
500  * Sets the cache size limit in bytes.
501  *
502  * Since: 0.4
503  */
504 void
505 champlain_file_cache_set_size_limit (ChamplainFileCache *file_cache,
506                                      guint size_limit)
507 {
508   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache));
509
510   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
511
512   priv->size_limit = size_limit;
513   g_object_notify (G_OBJECT (file_cache), "size-limit");
514 }
515
516 static gchar *
517 get_filename (ChamplainFileCache *file_cache, ChamplainTile *tile)
518 {
519   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
520
521   g_return_val_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache), NULL);
522   g_return_val_if_fail (CHAMPLAIN_IS_TILE (tile), NULL);
523   g_return_val_if_fail (priv->real_cache_dir, NULL);
524
525   ChamplainMapSource* map_source = CHAMPLAIN_MAP_SOURCE(file_cache);
526
527   gchar *filename = g_strdup_printf ("%s" G_DIR_SEPARATOR_S
528                                      "%s" G_DIR_SEPARATOR_S
529                                      "%d" G_DIR_SEPARATOR_S
530                                      "%d" G_DIR_SEPARATOR_S "%d.png",
531                                      priv->real_cache_dir,
532                                      champlain_map_source_get_id (map_source),
533                                      champlain_tile_get_zoom_level (tile),
534                                      champlain_tile_get_x (tile),
535                                      champlain_tile_get_y (tile));
536   return filename;
537 }
538
539 static gboolean
540 tile_is_expired (ChamplainFileCache *file_cache, ChamplainTile *tile)
541 {
542   g_return_val_if_fail (CHAMPLAIN_FILE_CACHE (file_cache), FALSE);
543   g_return_val_if_fail (CHAMPLAIN_TILE (tile), FALSE);
544
545   GTimeVal now = {0, };
546   const GTimeVal *modified_time = champlain_tile_get_modified_time (tile);
547   gboolean validate_cache = TRUE;
548
549   if (modified_time)
550     {
551       g_get_current_time (&now);
552       g_time_val_add (&now, (-24ul * 60ul * 60ul * 1000ul * 1000ul * 7ul)); // Cache expires in 7 days
553       validate_cache = modified_time->tv_sec < now.tv_sec;
554     }
555
556   DEBUG ("%p is %s expired", tile, (validate_cache ? "": "not"));
557
558   return validate_cache;
559 }
560
561 typedef struct
562 {
563   ChamplainMapSource *map_source;
564   ChamplainTile *tile;
565   gchar *filename;
566   gulong handler;
567 } TileLoadedCallbackData;
568
569 static void
570 tile_loaded_cb (ClutterTexture *texture,
571                 const GError *error,
572                 TileLoadedCallbackData *user_data)
573 {
574   ChamplainMapSource *map_source = user_data->map_source;
575   gchar *filename = user_data->filename;
576   ChamplainTile *tile = user_data->tile;
577   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(map_source);
578   ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);
579   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
580   GFileInfo *info = NULL;
581   GFile *file = NULL;
582   ClutterActor *actor = CLUTTER_ACTOR(texture);
583   GTimeVal modified_time = {0,};
584
585   g_signal_handler_disconnect (texture, user_data->handler);
586   g_free (user_data);
587
588   if (error)
589     {
590       DEBUG ("Failed to load tile %s, error: %s", filename, error->message);
591       goto load_next;
592     }
593
594   champlain_tile_set_content (tile, actor, FALSE);
595   champlain_tile_set_size (tile, champlain_map_source_get_tile_size (map_source));
596
597   /* Retrieve modification time */
598   file = g_file_new_for_path (filename);
599   info = g_file_query_info (file,
600                             G_FILE_ATTRIBUTE_TIME_MODIFIED,
601                             G_FILE_QUERY_INFO_NONE, NULL, NULL);
602   if (info)
603     {
604       g_file_info_get_modification_time (info, &modified_time);
605       champlain_tile_set_modified_time (tile, &modified_time);
606
607       g_object_unref (info);
608     }
609   g_object_unref (file);
610
611   /* Notify other caches that the tile has been filled */
612   if (CHAMPLAIN_IS_TILE_CACHE(next_source))
613     on_tile_filled (CHAMPLAIN_TILE_CACHE(next_source), tile);
614
615   if (tile_is_expired (file_cache, tile))
616     {
617       int sql_rc = SQLITE_OK;
618
619       /* Retrieve etag */
620       sql_rc = sqlite3_bind_text (priv->stmt_select, 1, filename, -1, SQLITE_STATIC);
621       if (sql_rc == SQLITE_ERROR)
622         {
623           DEBUG ("Failed to prepare the SQL query for finding the Etag of '%s', error: %s",
624                  filename, sqlite3_errmsg (priv->db));
625           goto load_next;
626         }
627
628       sql_rc = sqlite3_step (priv->stmt_select);
629       if (sql_rc == SQLITE_ROW)
630         {
631           const gchar *etag = (const gchar *) sqlite3_column_text (priv->stmt_select, 0);
632           champlain_tile_set_etag (CHAMPLAIN_TILE (tile), etag);
633         }
634       else if (sql_rc == SQLITE_DONE)
635         {
636           DEBUG ("'%s' does't have an etag",
637                  filename);
638           goto load_next;
639         }
640       else if (sql_rc == SQLITE_ERROR)
641         {
642           DEBUG ("Failed to finding the Etag of '%s', %d error: %s",
643                  filename, sql_rc, sqlite3_errmsg (priv->db));
644           goto load_next;
645         }
646
647       /* Validate the tile */
648       /* goto load_next; */
649     }
650   else
651     {
652       /* Tile loaded and no validation needed - done */
653       champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
654       goto cleanup;
655     }
656
657 load_next:
658   if (CHAMPLAIN_IS_MAP_SOURCE(next_source))
659     champlain_map_source_fill_tile (next_source, tile);
660
661   /* if we have some content, use the tile even if it wasn't validated */
662   if (champlain_tile_get_content (tile) &&
663       champlain_tile_get_state (tile) != CHAMPLAIN_STATE_DONE)
664     champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
665
666 cleanup:
667   sqlite3_reset (priv->stmt_select);
668   g_free (filename);
669   g_object_unref (tile);
670   g_object_unref (map_source);
671 }
672
673 static void
674 fill_tile (ChamplainMapSource *map_source,
675            ChamplainTile *tile)
676 {
677   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (map_source));
678   g_return_if_fail (CHAMPLAIN_IS_TILE (tile));
679
680   if (!champlain_tile_get_content (tile))
681     {
682       ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(map_source);
683       TileLoadedCallbackData *callback_data;
684       ClutterTexture *texture;
685
686       callback_data = g_new (TileLoadedCallbackData, 1);
687       callback_data->tile = tile;
688       callback_data->map_source = map_source;
689       callback_data->filename = get_filename (file_cache, tile);
690
691       DEBUG ("fill of %s", callback_data->filename);
692
693       /* Ref the tile as it may be freeing during the loading
694        * Unref when the loading is done.
695        */
696       g_object_ref (tile);
697       g_object_ref (map_source);
698
699       /* Load the cached version */
700       texture = CLUTTER_TEXTURE(clutter_texture_new ());
701       callback_data->handler = g_signal_connect (texture, "load-finished",
702                                G_CALLBACK (tile_loaded_cb),
703                                callback_data);
704       clutter_texture_set_load_async (texture, TRUE);
705       clutter_texture_set_from_file (texture, callback_data->filename, NULL);
706     }
707   else
708     {
709       ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);
710
711       /* Previous cache in the chain is validating its contents */
712       if (CHAMPLAIN_IS_MAP_SOURCE(next_source))
713         champlain_map_source_fill_tile (next_source, tile);
714     }
715 }
716
717 static void
718 refresh_tile_time (ChamplainTileCache *tile_cache, ChamplainTile *tile)
719 {
720   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (tile_cache));
721
722   ChamplainMapSource *map_source = CHAMPLAIN_MAP_SOURCE(tile_cache);
723   ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);
724   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(tile_cache);
725   gchar *filename = NULL;
726   GFile *file;
727   GFileInfo *info;
728
729   filename = get_filename (file_cache, tile);
730   file = g_file_new_for_path (filename);
731   g_free (filename);
732
733   info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
734                             G_FILE_QUERY_INFO_NONE, NULL, NULL);
735
736   if (info)
737     {
738       GTimeVal now = {0, };
739
740       g_get_current_time (&now);
741
742       g_file_info_set_modification_time (info, &now);
743       g_file_set_attributes_from_info (file, info, G_FILE_QUERY_INFO_NONE, NULL, NULL);
744     }
745
746   g_object_unref (file);
747   g_object_unref (info);
748
749   if (CHAMPLAIN_IS_TILE_CACHE(next_source))
750     {
751       refresh_tile_time (CHAMPLAIN_TILE_CACHE(next_source), tile);
752     }
753 }
754
755 static void
756 store_tile (ChamplainTileCache *tile_cache,
757             ChamplainTile *tile,
758             const gchar *contents,
759             gsize size)
760 {
761   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (tile_cache));
762
763   ChamplainMapSource *map_source = CHAMPLAIN_MAP_SOURCE(tile_cache);
764   ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);
765   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(tile_cache);
766   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
767   gchar *query = NULL;
768   gchar *error = NULL;
769   gchar *path = NULL;
770   gchar *filename = NULL;
771   GError *gerror = NULL;
772   GFile *file;
773   GFileOutputStream *ostream;
774   gsize bytes_written;
775
776   DEBUG ("Update of %p", tile);
777
778   filename = get_filename (file_cache, tile);
779   file = g_file_new_for_path (filename);
780
781   /* If the file exists, delete it */
782   g_file_delete (file, NULL, NULL);
783
784   /* If needed, create the cache's dirs */
785   path = g_path_get_dirname (filename);
786   if (g_mkdir_with_parents (path, 0700) == -1)
787     {
788       if (errno != EEXIST)
789         {
790           g_warning ("Unable to create the image cache path '%s': %s",
791                      path, g_strerror (errno));
792           goto store_next;
793         }
794     }
795
796   ostream = g_file_create (file, G_FILE_CREATE_PRIVATE, NULL, &gerror);
797   if (!ostream)
798     {
799       DEBUG ("GFileOutputStream creation failed: %s", gerror->message);
800       g_error_free (gerror);
801       goto store_next;
802     }
803
804   /* Write the cache */
805   if (!g_output_stream_write_all (G_OUTPUT_STREAM(ostream), contents, size, &bytes_written, NULL, &gerror))
806     {
807       DEBUG ("Writing file contents failed: %s", gerror->message);
808       g_error_free (gerror);
809       g_object_unref (ostream);
810       goto store_next;
811     }
812
813   g_object_unref (ostream);
814
815   query = sqlite3_mprintf ("REPLACE INTO tiles (filename, etag, size) VALUES (%Q, %Q, %d)",
816                            filename,
817                            champlain_tile_get_etag (tile),
818                            size);
819   sqlite3_exec (priv->db, query, NULL, NULL, &error);
820   if (error != NULL)
821     {
822       DEBUG ("Saving Etag and size failed: %s", error);
823       sqlite3_free (error);
824     }
825   sqlite3_free (query);
826
827 store_next:
828   if (CHAMPLAIN_IS_TILE_CACHE(next_source))
829     {
830       store_tile (CHAMPLAIN_TILE_CACHE(next_source), tile, contents, size);
831     }
832
833   g_free (filename);
834   g_free (path);
835   g_object_unref (file);
836 }
837
838 static void
839 on_tile_filled (ChamplainTileCache *tile_cache, ChamplainTile *tile)
840 {
841   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (tile_cache));
842   g_return_if_fail (CHAMPLAIN_IS_TILE (tile));
843
844   ChamplainMapSource *map_source = CHAMPLAIN_MAP_SOURCE(tile_cache);
845   ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);
846   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(tile_cache);
847   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
848   int sql_rc = SQLITE_OK;
849   gchar *filename = NULL;
850
851   filename = get_filename (file_cache, tile);
852
853   DEBUG ("popularity of %s", filename);
854
855   sql_rc = sqlite3_bind_text (priv->stmt_update, 1, filename, -1, SQLITE_STATIC);
856   if (sql_rc != SQLITE_OK)
857     {
858       DEBUG ("Failed to set values to the popularity query of '%s', error: %s",
859              filename, sqlite3_errmsg (priv->db));
860       goto call_next;
861     }
862
863   sql_rc = sqlite3_step (priv->stmt_update);
864   if (sql_rc != SQLITE_DONE)
865     {
866       /* may not be present in this cache */
867       goto call_next;
868     }
869
870 call_next:
871   if (CHAMPLAIN_IS_TILE_CACHE(next_source))
872     {
873       on_tile_filled (CHAMPLAIN_TILE_CACHE(next_source), tile);
874     }
875 }
876
877 static void
878 clean (ChamplainTileCache *tile_cache)
879 {
880   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (tile_cache));
881
882   ChamplainFileCache *file_cache = CHAMPLAIN_FILE_CACHE(tile_cache);
883   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
884
885   g_return_if_fail (!champlain_tile_cache_get_persistent (tile_cache));
886
887   finalize_sql (file_cache);
888   delete_temp_cache (file_cache);
889
890   g_free (priv->real_cache_dir);
891   priv->real_cache_dir = NULL;
892
893   init_cache (file_cache);
894 }
895
896 static void
897 delete_tile (ChamplainFileCache *file_cache, const gchar *filename)
898 {
899   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache));
900   gchar *query, *error = NULL;
901   GError *gerror = NULL;
902   GFile *file;
903
904   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
905
906   query = sqlite3_mprintf ("DELETE FROM tiles WHERE filename = %Q", filename);
907   sqlite3_exec (priv->db, query, NULL, NULL, &error);
908   if (error != NULL)
909     {
910       DEBUG ("Deleting tile from db failed: %s", error);
911       sqlite3_free (error);
912     }
913   sqlite3_free (query);
914
915   file = g_file_new_for_path (filename);
916   if (!g_file_delete (file, NULL, &gerror))
917     {
918       DEBUG ("Deleting tile from disk failed: %s", gerror->message);
919       g_error_free (gerror);
920     }
921   g_object_unref (file);
922 }
923
924 static gboolean
925 purge_on_idle (gpointer data)
926 {
927   champlain_file_cache_purge (CHAMPLAIN_FILE_CACHE (data));
928   return FALSE;
929 }
930
931 /**
932  * champlain_file_cache_purge_on_idle:
933  * @file_cache: a #ChamplainCache
934  *
935  * Purge the cache from the less popular tiles until cache's size limit is reached.
936  * This is a non blocking call as the purge will happen when the application is idle
937  *
938  * Since: 0.4
939  */
940 void
941 champlain_file_cache_purge_on_idle (ChamplainFileCache *file_cache)
942 {
943   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache));
944   g_idle_add (purge_on_idle, file_cache);
945 }
946
947 /**
948  * champlain_file_cache_purge:
949  * @file_cache: a #ChamplainCache
950  *
951  * Purge the cache from the less popular tiles until cache's size limit is reached.
952  *
953  * Since: 0.4
954  */
955 void
956 champlain_file_cache_purge (ChamplainFileCache *file_cache)
957 {
958   g_return_if_fail (CHAMPLAIN_IS_FILE_CACHE (file_cache));
959
960   ChamplainFileCachePrivate *priv = GET_PRIVATE (file_cache);
961   gchar *query;
962   sqlite3_stmt *stmt;
963   int rc = 0;
964   guint current_size = 0;
965   guint highest_popularity = 0;
966   gchar *error;
967
968   query = "SELECT SUM (size) FROM tiles";
969   rc = sqlite3_prepare (priv->db, query, strlen (query), &stmt, NULL);
970   if (rc != SQLITE_OK)
971     {
972       DEBUG ("Can't compute cache size %s", sqlite3_errmsg (priv->db));
973     }
974
975   rc = sqlite3_step (stmt);
976   if (rc != SQLITE_ROW)
977     {
978       DEBUG ("Failed to count the total cache consumption %s",
979              sqlite3_errmsg (priv->db));
980       sqlite3_finalize (stmt);
981       return;
982     }
983
984   current_size = sqlite3_column_int (stmt, 0);
985   if (current_size < priv->size_limit)
986     {
987       DEBUG ("Cache doesn't need to be purged at %d bytes", current_size);
988       sqlite3_finalize (stmt);
989       return;
990     }
991
992   sqlite3_finalize (stmt);
993
994   /* Ok, delete the less popular tiles until size_limit reached */
995   query = "SELECT filename, size, popularity FROM tiles ORDER BY popularity";
996   rc = sqlite3_prepare (priv->db, query, strlen (query), &stmt, NULL);
997   if (rc != SQLITE_OK)
998     {
999       DEBUG ("Can't fetch tiles to delete: %s", sqlite3_errmsg (priv->db));
1000     }
1001
1002   rc = sqlite3_step (stmt);
1003   while (rc == SQLITE_ROW && current_size > priv->size_limit)
1004     {
1005       const char *filename = sqlite3_column_text (stmt, 0);
1006       guint size;
1007
1008       filename = sqlite3_column_text (stmt, 0);
1009       size = sqlite3_column_int (stmt, 1);
1010       highest_popularity = sqlite3_column_int (stmt, 2);
1011       DEBUG ("Deleting %s of size %d", filename, size);
1012
1013       delete_tile (file_cache, filename);
1014
1015       current_size -= size;
1016
1017       rc = sqlite3_step (stmt);
1018     }
1019   DEBUG ("Cache size is now %d", current_size);
1020
1021   sqlite3_finalize (stmt);
1022
1023   query = sqlite3_mprintf ("UPDATE tiles SET popularity = popularity - %d",
1024                            highest_popularity);
1025   sqlite3_exec (priv->db, query, NULL, NULL, &error);
1026   if (error != NULL)
1027     {
1028       DEBUG ("Updating popularity failed: %s", error);
1029       sqlite3_free (error);
1030     }
1031   sqlite3_free (query);
1032 }
1033
1034 static void
1035 delete_dir_recursive (GFile *parent)
1036 {
1037   g_return_if_fail (parent);
1038
1039   GError *error = NULL;
1040   GFileEnumerator *enumerator;
1041   GFileInfo *info;
1042   GFile *child;
1043
1044   enumerator = g_file_enumerate_children (parent, "*",
1045                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
1046
1047   if (!enumerator)
1048     {
1049       DEBUG ("Failed to create file enumerator in delete_dir_recursive: %s", error->message);
1050       g_error_free (error);
1051       return;
1052     }
1053
1054   info = g_file_enumerator_next_file (enumerator, NULL, NULL);
1055   while (info && !error)
1056     {
1057       child = g_file_get_child (parent, g_file_info_get_name (info));
1058
1059       if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
1060         delete_dir_recursive (child);
1061
1062       error = NULL;
1063       if (!g_file_delete (child, NULL, &error))
1064         {
1065           DEBUG ("Deleting tile from disk failed: %s", error->message);
1066           g_error_free (error);
1067         }
1068
1069       g_object_unref (child);
1070       g_object_unref (info);
1071       info = g_file_enumerator_next_file (enumerator, NULL, NULL);
1072     }
1073
1074   g_file_enumerator_close (enumerator, NULL, NULL);
1075 }