1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * Copyright (C) 2011-2020 Shaun McCance <shaunm@gnome.org>
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 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
16  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Shaun McCance <shaunm@gnome.org>
19  */
20 
21 #include "config.h"
22 
23 #include <glib/gi18n.h>
24 #include <sqlite3.h>
25 
26 #include "yelp-sqlite-storage.h"
27 
28 static void        yelp_sqlite_storage_iface_init   (YelpStorageInterface   *iface);
29 static void        yelp_sqlite_storage_finalize     (GObject                *object);
30 static void        yelp_sqlite_storage_get_property (GObject                *object,
31                                                      guint                   prop_id,
32                                                      GValue                 *value,
33                                                      GParamSpec             *pspec);
34 static void        yelp_sqlite_storage_set_property (GObject                *object,
35                                                      guint                   prop_id,
36                                                      const GValue           *value,
37                                                      GParamSpec             *pspec);
38 
39 static void        yelp_sqlite_storage_update         (YelpStorage      *storage,
40                                                        const gchar      *doc_uri,
41                                                        const gchar      *full_uri,
42                                                        const gchar      *title,
43                                                        const gchar      *desc,
44                                                        const gchar      *icon,
45                                                        const gchar      *text);
46 static GVariant *  yelp_sqlite_storage_search         (YelpStorage      *storage,
47                                                        const gchar      *doc_uri,
48                                                        const gchar      *text);
49 static gchar *     yelp_sqlite_storage_get_root_title (YelpStorage      *storage,
50                                                        const gchar      *doc_uri);
51 static void        yelp_sqlite_storage_set_root_title (YelpStorage      *storage,
52                                                        const gchar      *doc_uri,
53                                                        const gchar      *title);
54 
55 typedef struct _YelpSqliteStoragePrivate YelpSqliteStoragePrivate;
56 struct _YelpSqliteStoragePrivate {
57     gchar   *filename;
58     sqlite3 *db;
59     GMutex mutex;
60 };
61 
62 enum {
63     PROP_0,
64     PROP_FILENAME
65 };
66 
G_DEFINE_TYPE_WITH_CODE(YelpSqliteStorage,yelp_sqlite_storage,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (YELP_TYPE_STORAGE,yelp_sqlite_storage_iface_init)G_ADD_PRIVATE (YelpSqliteStorage))67 G_DEFINE_TYPE_WITH_CODE (YelpSqliteStorage, yelp_sqlite_storage, G_TYPE_OBJECT,
68                          G_IMPLEMENT_INTERFACE (YELP_TYPE_STORAGE,
69                                                 yelp_sqlite_storage_iface_init)
70                          G_ADD_PRIVATE (YelpSqliteStorage) )
71 
72 static void
73 yelp_sqlite_storage_finalize (GObject *object)
74 {
75     YelpSqliteStoragePrivate *priv =
76         yelp_sqlite_storage_get_instance_private (YELP_SQLITE_STORAGE (object));
77 
78     if (priv->filename)
79         g_free (priv->filename);
80 
81     if (priv->db)
82         sqlite3_close (priv->db);
83 
84     g_mutex_clear (&priv->mutex);
85 
86     G_OBJECT_CLASS (yelp_sqlite_storage_parent_class)->finalize (object);
87 }
88 
89 static void
yelp_sqlite_storage_init(YelpSqliteStorage * storage)90 yelp_sqlite_storage_init (YelpSqliteStorage *storage)
91 {
92     YelpSqliteStoragePrivate *priv = yelp_sqlite_storage_get_instance_private (storage);
93     g_mutex_init (&priv->mutex);
94 }
95 
96 static void
yelp_sqlite_storage_constructed(GObject * object)97 yelp_sqlite_storage_constructed (GObject *object)
98 {
99     int status;
100     sqlite3_stmt *stmt = NULL;
101     YelpSqliteStoragePrivate *priv =
102         yelp_sqlite_storage_get_instance_private (YELP_SQLITE_STORAGE (object));
103 
104     if (priv->filename != NULL)
105         status = sqlite3_open (priv->filename, &(priv->db));
106     else
107         status = sqlite3_open (":memory:", &(priv->db));
108 
109     if (status != SQLITE_OK)
110         return;
111 
112     status = sqlite3_prepare_v2 (priv->db,
113                                  "create virtual table pages using fts4("
114                                  " doc_uri, lang, full_uri,"
115                                  " title, desc, icon, body"
116                                  ");",
117                                  -1, &stmt, NULL);
118     if (status != SQLITE_OK)
119         return;
120     sqlite3_step (stmt);
121     sqlite3_finalize (stmt);
122 
123     status = sqlite3_prepare_v2 (priv->db,
124                                  "create table titles (doc_uri text, lang text, title text);",
125                                  -1, &stmt, NULL);
126     if (status != SQLITE_OK)
127         return;
128     sqlite3_step (stmt);
129     sqlite3_finalize (stmt);
130 }
131 
132 static void
yelp_sqlite_storage_class_init(YelpSqliteStorageClass * klass)133 yelp_sqlite_storage_class_init (YelpSqliteStorageClass *klass)
134 {
135     GObjectClass *object_class = G_OBJECT_CLASS (klass);
136 
137     object_class->constructed = yelp_sqlite_storage_constructed;
138     object_class->finalize = yelp_sqlite_storage_finalize;
139     object_class->get_property = yelp_sqlite_storage_get_property;
140     object_class->set_property = yelp_sqlite_storage_set_property;
141 
142     g_object_class_install_property (object_class,
143                                      PROP_FILENAME,
144                                      g_param_spec_string ("filename",
145                                                           "Database filename",
146                                                           "The filename of the sqlite database",
147                                                           NULL,
148                                                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
149                                                           G_PARAM_STATIC_STRINGS));
150 }
151 
152 static void
yelp_sqlite_storage_iface_init(YelpStorageInterface * iface)153 yelp_sqlite_storage_iface_init (YelpStorageInterface *iface)
154 {
155     iface->update = yelp_sqlite_storage_update;
156     iface->search = yelp_sqlite_storage_search;
157     iface->get_root_title = yelp_sqlite_storage_get_root_title;
158     iface->set_root_title = yelp_sqlite_storage_set_root_title;
159 }
160 
161 YelpStorage *
yelp_sqlite_storage_new(const gchar * filename)162 yelp_sqlite_storage_new (const gchar *filename)
163 {
164     YelpStorage *storage;
165 
166     storage = g_object_new (YELP_TYPE_SQLITE_STORAGE,
167                             "filename", filename,
168                             NULL);
169 
170     return storage;
171 }
172 
173 static void
yelp_sqlite_storage_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)174 yelp_sqlite_storage_get_property (GObject    *object,
175                                   guint       prop_id,
176                                   GValue     *value,
177                                   GParamSpec *pspec)
178 {
179     YelpSqliteStoragePrivate *priv =
180         yelp_sqlite_storage_get_instance_private (YELP_SQLITE_STORAGE (object));
181 
182     switch (prop_id) {
183     case PROP_FILENAME:
184         g_value_set_string (value, priv->filename);
185         break;
186     default:
187         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
188         break;
189     }
190 }
191 
192 static void
yelp_sqlite_storage_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)193 yelp_sqlite_storage_set_property (GObject      *object,
194                                   guint         prop_id,
195                                   const GValue *value,
196                                   GParamSpec   *pspec)
197 {
198     YelpSqliteStoragePrivate *priv =
199         yelp_sqlite_storage_get_instance_private (YELP_SQLITE_STORAGE (object));
200 
201     switch (prop_id) {
202     case PROP_FILENAME:
203         priv->filename = g_value_dup_string (value);
204         break;
205     default:
206         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
207         break;
208     }
209 }
210 
211 /******************************************************************************/
212 
213 static void
yelp_sqlite_storage_update(YelpStorage * storage,const gchar * doc_uri,const gchar * full_uri,const gchar * title,const gchar * desc,const gchar * icon,const gchar * text)214 yelp_sqlite_storage_update (YelpStorage   *storage,
215                             const gchar   *doc_uri,
216                             const gchar   *full_uri,
217                             const gchar   *title,
218                             const gchar   *desc,
219                             const gchar   *icon,
220                             const gchar   *text)
221 {
222     sqlite3_stmt *stmt = NULL;
223     YelpSqliteStoragePrivate *priv =
224         yelp_sqlite_storage_get_instance_private (YELP_SQLITE_STORAGE (storage));
225 
226     g_mutex_lock (&priv->mutex);
227 
228     sqlite3_prepare_v2 (priv->db,
229                         "delete from pages where doc_uri = ? and lang = ? and full_uri = ?;",
230                         -1, &stmt, NULL);
231     sqlite3_bind_text (stmt, 1, doc_uri, -1, SQLITE_TRANSIENT);
232     sqlite3_bind_text (stmt, 2, g_get_language_names()[0], -1, SQLITE_STATIC);
233     sqlite3_bind_text (stmt, 3, full_uri, -1, SQLITE_TRANSIENT);
234     sqlite3_step (stmt);
235     sqlite3_finalize (stmt);
236 
237     sqlite3_prepare_v2 (priv->db,
238                         "insert into pages (doc_uri, lang, full_uri, title, desc, icon, body)"
239                         " values (?, ?, ?, ?, ?, ?, ?);",
240                         -1, &stmt, NULL);
241     sqlite3_bind_text (stmt, 1, doc_uri, -1, SQLITE_TRANSIENT);
242     sqlite3_bind_text (stmt, 2, g_get_language_names()[0], -1, SQLITE_STATIC);
243     sqlite3_bind_text (stmt, 3, full_uri, -1, SQLITE_TRANSIENT);
244     sqlite3_bind_text (stmt, 4, title, -1, SQLITE_TRANSIENT);
245     sqlite3_bind_text (stmt, 5, desc, -1, SQLITE_TRANSIENT);
246     sqlite3_bind_text (stmt, 6, icon, -1, SQLITE_TRANSIENT);
247     sqlite3_bind_text (stmt, 7, text, -1, SQLITE_TRANSIENT);
248     sqlite3_step (stmt);
249     sqlite3_finalize (stmt);
250 
251     g_mutex_unlock (&priv->mutex);
252 }
253 
254 static GVariant *
yelp_sqlite_storage_search(YelpStorage * storage,const gchar * doc_uri,const gchar * text)255 yelp_sqlite_storage_search (YelpStorage   *storage,
256                             const gchar   *doc_uri,
257                             const gchar   *text)
258 {
259     sqlite3_stmt *stmt = NULL;
260     GVariantBuilder builder;
261     GVariant *ret;
262     YelpSqliteStoragePrivate *priv =
263         yelp_sqlite_storage_get_instance_private (YELP_SQLITE_STORAGE (storage));
264 
265 
266     g_mutex_lock (&priv->mutex);
267 
268     sqlite3_prepare_v2 (priv->db,
269                         "select full_uri, title, desc, icon from pages where"
270                         " doc_uri = ? and lang = ? and body match ?;",
271                         -1, &stmt, NULL);
272     sqlite3_bind_text (stmt, 1, doc_uri, -1, SQLITE_TRANSIENT);
273     sqlite3_bind_text (stmt, 2, g_get_language_names()[0], -1, SQLITE_STATIC);
274     sqlite3_bind_text (stmt, 3, text, -1, SQLITE_TRANSIENT);
275 
276     g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ssss)"));
277     while (sqlite3_step (stmt) == SQLITE_ROW) {
278         g_variant_builder_add (&builder, "(ssss)",
279                                sqlite3_column_text (stmt, 0),
280                                sqlite3_column_text (stmt, 1),
281                                sqlite3_column_text (stmt, 2),
282                                sqlite3_column_text (stmt, 3));
283     }
284     sqlite3_finalize (stmt);
285     ret = g_variant_new ("a(ssss)", &builder);
286 
287     g_mutex_unlock (&priv->mutex);
288 
289     return ret;
290 }
291 
292 static gchar *
yelp_sqlite_storage_get_root_title(YelpStorage * storage,const gchar * doc_uri)293 yelp_sqlite_storage_get_root_title (YelpStorage *storage,
294                                     const gchar *doc_uri)
295 {
296     gchar *ret = NULL;
297     sqlite3_stmt *stmt = NULL;
298     YelpSqliteStoragePrivate *priv =
299         yelp_sqlite_storage_get_instance_private (YELP_SQLITE_STORAGE (storage));
300 
301     g_mutex_lock (&priv->mutex);
302 
303     sqlite3_prepare_v2 (priv->db,
304                         "select title from titles where doc_uri = ? and lang = ?;",
305                         -1, &stmt, NULL);
306     sqlite3_bind_text (stmt, 1, doc_uri, -1, SQLITE_TRANSIENT);
307     sqlite3_bind_text (stmt, 2, g_get_language_names()[0], -1, SQLITE_STATIC);
308     if (sqlite3_step (stmt) == SQLITE_ROW)
309         ret = g_strdup ((const gchar *) sqlite3_column_text (stmt, 0));
310     sqlite3_finalize (stmt);
311 
312     g_mutex_unlock (&priv->mutex);
313     return ret;
314 }
315 
316 static void
yelp_sqlite_storage_set_root_title(YelpStorage * storage,const gchar * doc_uri,const gchar * title)317 yelp_sqlite_storage_set_root_title (YelpStorage *storage,
318                                     const gchar *doc_uri,
319                                     const gchar *title)
320 {
321     sqlite3_stmt *stmt = NULL;
322     YelpSqliteStoragePrivate *priv =
323         yelp_sqlite_storage_get_instance_private (YELP_SQLITE_STORAGE (storage));
324 
325     g_mutex_lock (&priv->mutex);
326 
327     sqlite3_prepare_v2 (priv->db,
328                         "delete from titles where doc_uri = ? and lang = ?;",
329                         -1, &stmt, NULL);
330     sqlite3_bind_text (stmt, 1, doc_uri, -1, SQLITE_TRANSIENT);
331     sqlite3_bind_text (stmt, 2, g_get_language_names()[0], -1, SQLITE_STATIC);
332     sqlite3_step (stmt);
333     sqlite3_finalize (stmt);
334 
335     sqlite3_prepare_v2 (priv->db,
336                         "insert into titles (doc_uri, lang, title)"
337                         " values (?, ?, ?);",
338                         -1, &stmt, NULL);
339     sqlite3_bind_text (stmt, 1, doc_uri, -1, SQLITE_TRANSIENT);
340     sqlite3_bind_text (stmt, 2, g_get_language_names()[0], -1, SQLITE_STATIC);
341     sqlite3_bind_text (stmt, 3, title, -1, SQLITE_TRANSIENT);
342     sqlite3_step (stmt);
343     sqlite3_finalize (stmt);
344 
345     g_mutex_unlock (&priv->mutex);
346 }
347