spec: remove pkg_initdb in post script
[profile/ivi/rygel.git] / src / plugins / media-export / rygel-media-export-database.vala
1 /*
2  * Copyright (C) 2009,2011 Jens Georg <mail@jensge.org>.
3  *
4  * Author: Jens Georg <mail@jensge.org>
5  *
6  * This file is part of Rygel.
7  *
8  * Rygel is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Rygel is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  */
22
23 using Sqlite;
24
25 public errordomain Rygel.MediaExport.DatabaseError {
26     IO_ERROR,
27     SQLITE_ERROR
28 }
29
30 namespace Rygel.MediaExport {
31     extern static int utf8_collate_str (uint8[] a, uint8[] b);
32 }
33
34 /**
35  * This class is a thin wrapper around SQLite's database object.
36  *
37  * It adds statement preparation based on GValue and a cancellable exec
38  * function.
39  */
40 internal class Rygel.MediaExport.Database : SqliteWrapper {
41
42     /**
43      * Function to implement the custom SQL function 'contains'
44      */
45     private static void utf8_contains (Sqlite.Context context,
46                                        Sqlite.Value[] args)
47                                        requires (args.length == 2) {
48         if (args[0].to_text () == null ||
49             args[1].to_text () == null) {
50            context.result_int (0);
51
52            return;
53         }
54
55         var pattern = Regex.escape_string (args[1].to_text ());
56         if (Regex.match_simple (pattern,
57                                 args[0].to_text (),
58                                 RegexCompileFlags.CASELESS)) {
59             context.result_int (1);
60         } else {
61             context.result_int (0);
62         }
63     }
64
65     /**
66      * Function to implement the custom SQLite collation 'CASEFOLD'.
67      *
68      * Uses utf8 case-fold to compare the strings.
69      */
70     private static int utf8_collate (int alen, void* a, int blen, void* b) {
71         // unowned to prevent array copy
72         unowned uint8[] _a = (uint8[]) a;
73         _a.length = alen;
74
75         unowned uint8[] _b = (uint8[]) b;
76         _b.length = blen;
77
78         return utf8_collate_str (_a, _b);
79     }
80
81     /**
82      * Open a database in the user's cache directory as defined by XDG
83      *
84      * @param name of the database, used to build full path
85      * (<cache-dir>/rygel/<name>.db)
86      */
87     public Database (string name) throws DatabaseError {
88         string db_file;
89
90         if (name != ":memory:") {
91             var dirname = Path.build_filename (
92                                         Environment.get_user_cache_dir (),
93                                         "rygel");
94             DirUtils.create_with_parents (dirname, 0750);
95             db_file = Path.build_filename (dirname, "%s.db".printf (name));
96         } else {
97             db_file = name;
98         }
99
100         base (db_file);
101
102         debug ("Using database file %s", db_file);
103
104         this.exec ("PRAGMA synchronous = OFF");
105         this.exec ("PRAGMA temp_store = MEMORY");
106         this.exec ("PRAGMA count_changes = OFF");
107
108         this.db.create_function ("contains",
109                                  2,
110                                  Sqlite.UTF8,
111                                  null,
112                                  Database.utf8_contains,
113                                  null,
114                                  null);
115
116         this.db.create_collation ("CASEFOLD",
117                                   Sqlite.UTF8,
118                                   Database.utf8_collate);
119     }
120
121     /**
122      * SQL query function.
123      *
124      * Use for all queries that return a result set.
125      *
126      * @param sql The SQL query to run.
127      * @param args Values to bind in the SQL query or null.
128      * @throws DatabaseError if the underlying SQLite operation fails.
129      */
130     public DatabaseCursor exec_cursor (string        sql,
131                                        GLib.Value[]? arguments = null)
132                                        throws DatabaseError {
133         return new DatabaseCursor (this.db, sql, arguments);
134     }
135
136     /**
137      * Simple SQL query execution function.
138      *
139      * Use for all queries that don't return anything.
140      *
141      * @param sql The SQL query to run.
142      * @param args Values to bind in the SQL query or null.
143      * @throws DatabaseError if the underlying SQLite operation fails.
144      */
145     public void exec (string        sql,
146                       GLib.Value[]? arguments = null)
147                       throws DatabaseError {
148         if (arguments == null) {
149             this.throw_if_code_is_error (this.db.exec (sql));
150
151             return;
152         }
153
154         var cursor = this.exec_cursor (sql, arguments);
155         while (cursor.has_next ()) {
156             cursor.next ();
157         }
158     }
159
160     /**
161      * Execute a SQL query that returns a single number.
162      *
163      * @param sql The SQL query to run.
164      * @param args Values to bind in the SQL query or null.
165      * @return The contents of the first row's column as an int.
166      * @throws DatabaseError if the underlying SQLite operation fails.
167      */
168     public int query_value (string        sql,
169                              GLib.Value[]? args = null)
170                              throws DatabaseError {
171         var cursor = this.exec_cursor (sql, args);
172         var statement = cursor.next ();
173         return statement->column_int (0);
174     }
175
176     /**
177      * Analyze triggers of database
178      */
179     public void analyze () {
180         this.db.exec ("ANALYZE");
181     }
182
183     /**
184      * Special GValue to pass to exec or exec_cursor to bind a column to
185      * NULL
186      */
187     public static GLib.Value @null () {
188         GLib.Value v = GLib.Value (typeof (void *));
189         v.set_pointer (null);
190
191         return v;
192     }
193
194     /**
195      * Start a transaction
196      */
197     public void begin () throws DatabaseError {
198         this.exec ("BEGIN");
199     }
200
201     /**
202      * Commit a transaction
203      */
204     public void commit () throws DatabaseError {
205         this.exec ("COMMIT");
206     }
207
208     /**
209      * Rollback a transaction
210      */
211     public void rollback () {
212         try {
213             this.exec ("ROLLBACK");
214         } catch (DatabaseError error) {
215             critical (_("Failed to roll back transaction: %s"),
216                       error.message);
217         }
218     }
219 }