1e8d068945c3217c092345f8f1eb7e5f2f27e4f6
[cinaest] / src / plugins / catalog-sqlite.vala
1 /* This file is part of Cinaest.
2  *
3  * Copyright (C) 2009 Philipp Zabel
4  *
5  * Cinaest is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * Cinaest 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
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with Cinaest. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 using Sqlite;
20
21 class CatalogSqlite : Object {
22         Database db;
23
24         public delegate void ReceiveMovieFunction (string title, int year, int rating, int genres);
25
26         public CatalogSqlite (string filename) {
27                 int rc;
28
29                 rc = Database.open (filename, out db);
30                 if (rc != Sqlite.OK) {
31                         stderr.printf ("Can't open database: %d, %s\n", rc, db.errmsg ());
32                         return;
33                 }
34
35                 rc = db.exec ("PRAGMA locking_mode = EXCLUSIVE;", callback, null);
36                 if (rc != Sqlite.OK) {
37                         stderr.printf ("Can't get exclusive lock: %d, %s\n", rc, db.errmsg ());
38                         return;
39                 }
40
41                 rc = db.exec ("PRAGMA synchronous = OFF;", callback, null);
42                 if (rc != Sqlite.OK)
43                         stderr.printf ("Can't turn off synchronous access: %d, %s\n", rc, db.errmsg ());
44
45                 prepare ();
46         }
47
48         public static int callback (int n_columns, string[] values,
49                                     string[] column_names) {
50                 for (int i = 0; i < n_columns; i++) {
51                         stdout.printf ("%s = %s\n", column_names[i], values[i]);
52                 }
53                 stdout.printf ("\n");
54
55                 return 0;
56         }
57
58         public int add_movie (string table, Movie movie) {
59                 string sql = "INSERT INTO %s(Title, Year, Rating, Genres) VALUES (\"%s\", %d, %d, %d);".printf (table, movie.title, movie.year, movie.rating, movie.genres.field);
60                 int rc;
61
62                 rc = db.exec (sql, callback, null);
63                 if (rc != Sqlite.OK) {
64                         stderr.printf ("Failed to insert movie \"%s\" (%d): %d, %s\n", movie.title, movie.year, rc, db.errmsg ());
65                         return 1;
66                 }
67
68                 return 0;
69         }
70
71         public int delete_movie (string table, Movie movie) {
72                 string sql = "DELETE FROM %s WHERE Title=\"%s\" AND Year=%d".printf (table, movie.title, movie.year);
73                 int rc;
74
75                 rc = db.exec (sql, callback, null);
76                 if (rc != Sqlite.OK) {
77                         stderr.printf ("Failed to delete movie \"%s\" (%d): %d, %s\n", movie.title, movie.year, rc, db.errmsg ());
78                         return 1;
79                 }
80
81                 return 0;
82         }
83
84         public int count (string table) {
85                 string sql = "SELECT count(*) FROM %s".printf (table);
86                 Statement stmt;
87                 int rc;
88                 int count = 0;
89
90                 rc = db.prepare_v2 (sql, -1, out stmt);
91                 if (rc != Sqlite.OK) {
92                         stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
93                         db.progress_handler (0, null);
94                         return 0;
95                 }
96
97                 do {
98                         rc = stmt.step ();
99                         if (rc == Sqlite.ROW) {
100                                 count = stmt.column_int (0);
101                         }
102                 } while (rc == Sqlite.ROW);
103
104                 return count;
105         }
106
107         public bool contains (string table, Movie movie) {
108                 string sql = "SELECT count(*) FROM %s WHERE Title=\"%s\" AND Year=%d".printf (table, movie.title, movie.year);
109                 Statement stmt;
110                 int rc;
111                 int count = 0;
112
113                 rc = db.prepare_v2 (sql, -1, out stmt);
114                 if (rc != Sqlite.OK) {
115                         stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
116                         db.progress_handler (0, null);
117                         return false;
118                 }
119
120                 do {
121                         rc = stmt.step ();
122                         if (rc == Sqlite.ROW) {
123                                 count = stmt.column_int (0);
124                         }
125                 } while (rc == Sqlite.ROW);
126
127                 return (count > 0);
128         }
129
130         private int prepare () {
131                 int rc;
132
133                 rc = db.exec ("CREATE TABLE IF NOT EXISTS Collection (Title TEXT NOT NULL, Year INTEGER, Rating INTEGER, Genres INTEGER NOT NULL DEFAULT 0); CREATE TABLE IF NOT EXISTS Loaned (Title TEXT NOT NULL, Year INTEGER, Rating INTEGER, Genres INTEGER NOT NULL DEFAULT 0); CREATE TABLE IF NOT EXISTS Watchlist (Title TEXT NOT NULL, Year INTEGER, Rating INTEGER, Genres INTEGER NOT NULL DEFAULT 0);", callback, null);
134                 if (rc != Sqlite.OK) {
135                         stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
136                         return 1;
137                 }
138
139                 return 0;
140         }
141
142         public int clear () {
143                 int rc;
144
145                 rc = db.exec ("DROP TABLE IF EXISTS Collection; CREATE TABLE Collection (Title TEXT NOT NULL, Year INTEGER, Rating INTEGER, Genres INTEGER NOT NULL DEFAULT 0); DROP TABLE IF EXISTS Loaned; CREATE TABLE Loaned (Title TEXT NOT NULL, Year INTEGER, Rating INTEGER, Genres INTEGER NOT NULL DEFAULT 0); DROP TABLE IF EXISTS Watchlist; CREATE TABLE Watchlist (Title TEXT NOT NULL, Year INTEGER, Rating INTEGER, Genres INTEGER NOT NULL DEFAULT 0);", callback, null);
146                 if (rc != Sqlite.OK) {
147                         stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
148                         return 1;
149                 }
150
151                 return 0;
152         }
153
154         private Cancellable? _cancellable;
155         public async int query (string table, MovieFilter filter, MovieSource.ReceiveMovieFunction callback, int limit, Cancellable? cancellable) {
156                 var sql = "SELECT Title, Year, Rating, Genres FROM %s".printf (table);
157                 var sep = " WHERE ";
158                 Statement stmt;
159                 int rc;
160
161                 // FIXME - how many opcodes until main loop iteration for best responsivity?
162                 _cancellable = cancellable;
163                 db.progress_handler (1000, progress_handler);
164
165                 if (filter.title != null && filter.title != "") {
166                         if ("*" in filter.title)
167                                 sql += sep + "Title GLOB \"%s (*)\"".printf (filter.title);
168                         else
169                                 sql += sep + "Title LIKE \"%s%%\"".printf (filter.title);
170                         sep = " AND ";
171                 }
172                 if (filter.year_min > 0) {
173                         sql += sep + "Year >= %d".printf (filter.year_min);
174                         sep = " AND ";
175                 }
176                 if (filter.year_max > 0) {
177                         sql += sep + "Year <= %d".printf (filter.year_max);
178                         sep = " AND ";
179                 }
180                 if (filter.rating_min > 0) {
181                         sql += sep + "Rating >= %d".printf (filter.rating_min);
182                         sep = " AND ";
183                 }
184                 if (filter.genres.field != 0) {
185                         sql += sep + "Genres&%d = %d".printf (filter.genres.field, filter.genres.field);
186                 }
187                 sql += " LIMIT %d;".printf (limit);
188
189                 stdout.printf("SQL: \"%s\"\n", sql);
190
191                 rc = db.prepare_v2 (sql, -1, out stmt);
192                 if (rc != Sqlite.OK) {
193                         stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
194                         db.progress_handler (0, null);
195                         return 1;
196                 }
197
198                 do {
199                         Idle.add (query.callback);
200                         yield;
201                         rc = stmt.step ();
202                         if (rc == Sqlite.ROW) {
203                                 var movie = new Movie ();
204                                 movie.year = stmt.column_int (1);
205                                 movie.title = stmt.column_text (0);
206                                 movie.rating = stmt.column_int (2);
207                                 movie.genres.field = stmt.column_int (3);
208                                 // TODO - depending on settings, this could be something else, like director info or runtime
209                                 movie.secondary = movie.genres.to_string ();
210                                 callback (movie);
211                         }
212                 } while (rc == Sqlite.ROW);
213
214                 db.progress_handler (0, null);
215                 return 0;
216         }
217
218         private int progress_handler () {
219                 ((MainContext) null).iteration (false);
220                 return (int) _cancellable.is_cancelled ();
221         }
222 }