1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3  *  Copyright © 2011 Igalia S.L.
4  *
5  *  This file is part of Epiphany.
6  *
7  *  Epiphany is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  Epiphany is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 #include "ephy-sqlite-connection.h"
23 
24 #include "ephy-lib-type-builtins.h"
25 
26 #include <errno.h>
27 #include <glib/gstdio.h>
28 #include <sqlite3.h>
29 
30 struct _EphySQLiteConnection {
31   GObject parent_instance;
32 
33   char *database_path;
34   sqlite3 *database;
35   EphySQLiteConnectionMode mode;
36 };
37 
38 G_DEFINE_TYPE (EphySQLiteConnection, ephy_sqlite_connection, G_TYPE_OBJECT);
39 
40 enum {
41   PROP_0,
42   PROP_MODE,
43   PROP_DATABASE_PATH,
44   LAST_PROP
45 };
46 
47 static GParamSpec *obj_properties[LAST_PROP];
48 
49 static void
ephy_sqlite_connection_finalize(GObject * self)50 ephy_sqlite_connection_finalize (GObject *self)
51 {
52   g_free (EPHY_SQLITE_CONNECTION (self)->database_path);
53   ephy_sqlite_connection_close (EPHY_SQLITE_CONNECTION (self));
54   G_OBJECT_CLASS (ephy_sqlite_connection_parent_class)->finalize (self);
55 }
56 
57 static void
ephy_sqlite_connection_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)58 ephy_sqlite_connection_set_property (GObject      *object,
59                                      guint         property_id,
60                                      const GValue *value,
61                                      GParamSpec   *pspec)
62 {
63   EphySQLiteConnection *self = EPHY_SQLITE_CONNECTION (object);
64 
65   switch (property_id) {
66     case PROP_MODE:
67       self->mode = g_value_get_enum (value);
68       break;
69     case PROP_DATABASE_PATH:
70       self->database_path = g_value_dup_string (value);
71       break;
72     default:
73       G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
74       break;
75   }
76 }
77 
78 static void
ephy_sqlite_connection_class_init(EphySQLiteConnectionClass * klass)79 ephy_sqlite_connection_class_init (EphySQLiteConnectionClass *klass)
80 {
81   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
82 
83   gobject_class->finalize = ephy_sqlite_connection_finalize;
84   gobject_class->set_property = ephy_sqlite_connection_set_property;
85 
86   obj_properties[PROP_MODE] =
87     g_param_spec_enum ("mode",
88                        "SQLite connection mode",
89                        "Whether the SQLite connection is read-only or writable",
90                        EPHY_TYPE_SQ_LITE_CONNECTION_MODE,
91                        EPHY_SQLITE_CONNECTION_MODE_READWRITE,
92                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
93 
94   obj_properties[PROP_DATABASE_PATH] =
95     g_param_spec_string ("database-path",
96                          "Database path",
97                          "The path of the SQLite database file",
98                          NULL,
99                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
100 
101   g_object_class_install_properties (gobject_class, LAST_PROP, obj_properties);
102 }
103 
104 static void
ephy_sqlite_connection_init(EphySQLiteConnection * self)105 ephy_sqlite_connection_init (EphySQLiteConnection *self)
106 {
107   self->database = NULL;
108 }
109 
110 GQuark
ephy_sqlite_error_quark(void)111 ephy_sqlite_error_quark (void)
112 {
113   return g_quark_from_static_string ("ephy-sqlite");
114 }
115 
116 static void
set_error_from_string(const char * string,GError ** error)117 set_error_from_string (const char  *string,
118                        GError     **error)
119 {
120   if (error)
121     *error = g_error_new_literal (ephy_sqlite_error_quark (), 0, string);
122 }
123 
124 EphySQLiteConnection *
ephy_sqlite_connection_new(EphySQLiteConnectionMode mode,const char * database_path)125 ephy_sqlite_connection_new (EphySQLiteConnectionMode  mode,
126                             const char               *database_path)
127 {
128   return EPHY_SQLITE_CONNECTION (g_object_new (EPHY_TYPE_SQLITE_CONNECTION,
129                                                "mode", mode,
130                                                "database-path", database_path,
131                                                NULL));
132 }
133 
134 gboolean
ephy_sqlite_connection_open(EphySQLiteConnection * self,GError ** error)135 ephy_sqlite_connection_open (EphySQLiteConnection  *self,
136                              GError               **error)
137 {
138   if (self->database) {
139     set_error_from_string ("Connection already open.", error);
140     return FALSE;
141   }
142 
143   if (sqlite3_open_v2 (self->database_path,
144                        &self->database,
145                        self->mode == EPHY_SQLITE_CONNECTION_MODE_MEMORY ? SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MEMORY
146                                                                         : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
147                        NULL) != SQLITE_OK) {
148     ephy_sqlite_connection_get_error (self, error);
149     self->database = NULL;
150     return FALSE;
151   }
152 
153   /* Create a local copy of current database for in memory usage */
154   if (self->mode == EPHY_SQLITE_CONNECTION_MODE_MEMORY) {
155     sqlite3 *init_db;
156     int rc;
157 
158     rc = sqlite3_open_v2 (self->database_path, &init_db, SQLITE_OPEN_READONLY, 0);
159     if (rc == SQLITE_OK) {
160       sqlite3_backup *backup;
161 
162       backup = sqlite3_backup_init (self->database, "main", init_db, "main");
163       rc = sqlite3_backup_step (backup, -1);
164 
165       if (rc != SQLITE_DONE)
166         g_warning ("Failed to copy history to in-memory database: %s", sqlite3_errstr (rc));
167 
168       sqlite3_backup_finish (backup);
169     }
170 
171     sqlite3_close (init_db);
172   } else {
173     ephy_sqlite_connection_execute (self, "PRAGMA main.journal_mode=WAL", error);
174     ephy_sqlite_connection_execute (self, "PRAGMA main.synchronous=NORMAL", error);
175     ephy_sqlite_connection_execute (self, "PRAGMA main.cache_size=10000", error);
176   }
177 
178   return TRUE;
179 }
180 
181 void
ephy_sqlite_connection_close(EphySQLiteConnection * self)182 ephy_sqlite_connection_close (EphySQLiteConnection *self)
183 {
184   if (self->database) {
185     sqlite3_close (self->database);
186     self->database = NULL;
187   }
188 }
189 
190 void
ephy_sqlite_connection_delete_database(EphySQLiteConnection * self)191 ephy_sqlite_connection_delete_database (EphySQLiteConnection *self)
192 {
193   g_autofree char *journal = NULL;
194   g_autofree char *shm = NULL;
195 
196   g_assert (EPHY_IS_SQLITE_CONNECTION (self));
197 
198   if (g_file_test (self->database_path, G_FILE_TEST_EXISTS) && g_unlink (self->database_path) == -1)
199     g_warning ("Failed to delete database at %s: %s", self->database_path, g_strerror (errno));
200 
201   journal = g_strdup_printf ("%s-wal", self->database_path);
202   if (g_file_test (journal, G_FILE_TEST_EXISTS) && g_unlink (journal) == -1)
203     g_warning ("Failed to delete database journal at %s: %s", journal, g_strerror (errno));
204 
205   shm = g_strdup_printf ("%s-shm", self->database_path);
206   if (g_file_test (shm, G_FILE_TEST_EXISTS) && g_unlink (shm) == -1)
207     g_warning ("Failed to delete database shm at %s: %s", shm, g_strerror (errno));
208 }
209 
210 void
ephy_sqlite_connection_get_error(EphySQLiteConnection * self,GError ** error)211 ephy_sqlite_connection_get_error (EphySQLiteConnection  *self,
212                                   GError               **error)
213 {
214   if (error)
215     *error = g_error_new_literal (ephy_sqlite_error_quark (), sqlite3_errcode (self->database), sqlite3_errmsg (self->database));
216 }
217 
218 gboolean
ephy_sqlite_connection_execute(EphySQLiteConnection * self,const char * sql,GError ** error)219 ephy_sqlite_connection_execute (EphySQLiteConnection  *self,
220                                 const char            *sql,
221                                 GError               **error)
222 {
223   if (self->database == NULL) {
224     set_error_from_string ("Connection not open.", error);
225     return FALSE;
226   }
227 
228   if (sqlite3_exec (self->database, sql, NULL, NULL, NULL) != SQLITE_OK) {
229     ephy_sqlite_connection_get_error (self, error);
230     return FALSE;
231   }
232   return TRUE;
233 }
234 
235 EphySQLiteStatement *
ephy_sqlite_connection_create_statement(EphySQLiteConnection * self,const char * sql,GError ** error)236 ephy_sqlite_connection_create_statement (EphySQLiteConnection  *self,
237                                          const char            *sql,
238                                          GError               **error)
239 {
240   sqlite3_stmt *prepared_statement;
241 
242   if (self->database == NULL) {
243     set_error_from_string ("Connection not open.", error);
244     return NULL;
245   }
246 
247   if (sqlite3_prepare_v2 (self->database, sql, -1, &prepared_statement, NULL) != SQLITE_OK) {
248     ephy_sqlite_connection_get_error (self, error);
249     return NULL;
250   }
251 
252   return EPHY_SQLITE_STATEMENT (g_object_new (EPHY_TYPE_SQLITE_STATEMENT,
253                                               "prepared-statement", prepared_statement,
254                                               "connection", self,
255                                               NULL));
256 }
257 
258 gint64
ephy_sqlite_connection_get_last_insert_id(EphySQLiteConnection * self)259 ephy_sqlite_connection_get_last_insert_id (EphySQLiteConnection *self)
260 {
261   return sqlite3_last_insert_rowid (self->database);
262 }
263 
264 void
ephy_sqlite_connection_enable_foreign_keys(EphySQLiteConnection * self)265 ephy_sqlite_connection_enable_foreign_keys (EphySQLiteConnection *self)
266 {
267   GError *error = NULL;
268 
269   g_assert (EPHY_IS_SQLITE_CONNECTION (self));
270 
271   ephy_sqlite_connection_execute (self, "PRAGMA foreign_keys=ON", &error);
272   if (error) {
273     g_warning ("Failed to enable foreign keys pragma: %s", error->message);
274     g_error_free (error);
275   }
276 }
277 
278 gboolean
ephy_sqlite_connection_begin_transaction(EphySQLiteConnection * self,GError ** error)279 ephy_sqlite_connection_begin_transaction (EphySQLiteConnection  *self,
280                                           GError               **error)
281 {
282   return ephy_sqlite_connection_execute (self, "BEGIN TRANSACTION", error);
283 }
284 
285 gboolean
ephy_sqlite_connection_commit_transaction(EphySQLiteConnection * self,GError ** error)286 ephy_sqlite_connection_commit_transaction (EphySQLiteConnection  *self,
287                                            GError               **error)
288 {
289   return ephy_sqlite_connection_execute (self, "COMMIT", error);
290 }
291 
292 gboolean
ephy_sqlite_connection_table_exists(EphySQLiteConnection * self,const char * table_name)293 ephy_sqlite_connection_table_exists (EphySQLiteConnection *self,
294                                      const char           *table_name)
295 {
296   GError *error = NULL;
297   gboolean table_exists = FALSE;
298 
299   EphySQLiteStatement *statement = ephy_sqlite_connection_create_statement (self,
300                                                                             "SELECT COUNT(type) FROM sqlite_master WHERE type='table' and name=?", &error);
301   if (error) {
302     g_warning ("Could not detect table existence: %s", error->message);
303     g_error_free (error);
304     return FALSE;
305   }
306 
307   ephy_sqlite_statement_bind_string (statement, 0, table_name, &error);
308   if (error) {
309     g_object_unref (statement);
310     g_warning ("Could not detect table existence: %s", error->message);
311     g_error_free (error);
312     return FALSE;
313   }
314 
315   ephy_sqlite_statement_step (statement, &error);
316   if (error) {
317     g_object_unref (statement);
318     g_warning ("Could not detect table existence: %s", error->message);
319     g_error_free (error);
320     return FALSE;
321   }
322 
323   table_exists = ephy_sqlite_statement_get_column_as_int (statement, 0);
324   g_object_unref (statement);
325   return table_exists;
326 }
327