IMDb SQLite: use the movie filter to build the SQL query
[cinaest] / src / imdb / imdb-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 IMDbSqlite : Object {
22         Database db;
23         List<string> genres;
24
25         public delegate void ReceiveMovieFunction (string title, int year, int rating);
26
27         public IMDbSqlite (string filename) {
28                 int rc;
29
30                 genres = new List<string> ();
31
32                 rc = Database.open (filename, out db);
33                 if (rc != Sqlite.OK) {
34                         stderr.printf ("Can't open database: %d, %s\n", rc, db.errmsg ());
35                         return;
36                 }
37
38                 rc = db.exec ("PRAGMA journal_mode = OFF;", callback, null);
39                 if (rc != Sqlite.OK) {
40                         stderr.printf ("Can't turn off journal mode: %d, %s\n", rc, db.errmsg ());
41                         return;
42                 }
43
44                 rc = db.exec ("PRAGMA locking_mode = EXCLUSIVE;", callback, null);
45                 if (rc != Sqlite.OK) {
46                         stderr.printf ("Can't get exclusive lock: %d, %s\n", rc, db.errmsg ());
47                         return;
48                 }
49
50                 rc = db.exec ("PRAGMA synchronous = OFF;", callback, null);
51                 if (rc != Sqlite.OK)
52                         stderr.printf ("Can't turn off synchronous access: %d, %s\n", rc, db.errmsg ());
53
54         }
55
56         public static int callback (int n_columns, string[] values,
57                                     string[] column_names) {
58                 for (int i = 0; i < n_columns; i++) {
59                         stdout.printf ("%s = %s\n", column_names[i], values[i]);
60                 }
61                 stdout.printf ("\n");
62
63                 return 0;
64         }
65
66         public int add_movie (string _title, int year) {
67                 string md5 = Checksum.compute_for_string (ChecksumType.MD5, _title);
68                 uint64 half_md5 = (md5.ndup (16)).to_uint64 (null, 16);
69
70                 string title = strip_year (_title, year);
71
72                 string sql = "INSERT INTO Movies(ID, Title, Year) VALUES (%lld, \"%s\", %d);".printf ((int64) half_md5, title, year);
73                 int rc;
74
75                 rc = db.exec (sql, callback, null);
76                 if (rc != Sqlite.OK) {
77                         stderr.printf ("Failed to insert movie \"%s\" (%d): %d, %s\n", title, year, rc, db.errmsg ());
78                         return 1;
79                 }
80
81                 return 0;
82         }
83
84         private string strip_year (string title, int year) {
85                 string year_suffix = " (%d)".printf (year);
86                 if (title.has_suffix (year_suffix)) {
87                         return title.substring (0, title.length - year_suffix.length);
88                 } else {
89                         return title.dup ();
90                 }
91         }
92
93         public int movie_set_rating (string title, int rating) {
94                 string md5 = Checksum.compute_for_string (ChecksumType.MD5, title);
95                 uint64 half_md5 = (md5.ndup (16)).to_uint64 (null, 16);
96
97                 var sql = "UPDATE Movies SET Rating=%d WHERE ID=%lld;".printf (rating, (int64) half_md5);
98                 int rc;
99
100                 rc = db.exec (sql, callback, null);
101                 if (rc != Sqlite.OK) {
102                         stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
103                         return 1;
104                 }
105
106                 return 0;
107         }
108
109         public int movie_add_genre (string title, string genre) {
110                 string md5 = Checksum.compute_for_string (ChecksumType.MD5, title);
111                 uint64 half_md5 = (md5.ndup (16)).to_uint64 (null, 16);
112                 string sql;
113                 int bit;
114                 int rc;
115
116                 bit = genre_bit (genre);
117                 if (bit == 0) {
118                         genres.append (genre);
119                         bit = genre_bit (genre);
120
121                         sql = "INSERT INTO Genres(Bit, Genre) VALUES (%d, \"%s\");".printf (bit, genre);
122
123                         rc = db.exec (sql, callback, null);
124                         if (rc != Sqlite.OK) {
125                                 stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
126                                 return 1;
127                         }
128                 }
129
130                 sql = "UPDATE Movies SET Genres=Genres|%d WHERE ID=%lld;".printf (bit, (int64) half_md5);
131                 rc = db.exec (sql, callback, null);
132                 if (rc != Sqlite.OK) {
133                         stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
134                         return 1;
135                 }
136
137                 return 0;
138         }
139
140         int genre_bit (string genre) {
141                 for (int i = 0; i < genres.length (); i++) {
142                         if (genres.nth_data (i) == genre)
143                                 return 1 << i;
144                 }
145                 return 0;
146         }
147
148         public int clear () {
149                 int rc;
150
151                 rc = db.exec ("DROP TABLE IF EXISTS Movies; CREATE TABLE Movies (ID INTEGER PRIMARY KEY, Title TEXT NOT NULL, Year INTEGER, Rating INTEGER, Genres INTEGER NOT NULL DEFAULT 0); DROP TABLE IF EXISTS Genres; CREATE TABLE Genres (Bit INTEGER PRIMARY KEY, Genre TEXT NOT NULL);", callback, null);
152                 if (rc != Sqlite.OK) {
153                         stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
154                         return 1;
155                 }
156
157                 return 0;
158         }
159
160         public int query (MovieFilter filter, ReceiveMovieFunction receive_movie) {
161                 var sql = "SELECT Title, Year, Rating FROM Movies";
162                 var sep = " WHERE ";
163                 Statement stmt;
164                 int rc;
165
166                 if (filter.title != null && filter.title != "") {
167                         if ("*" in filter.title)
168                                 sql += sep + "Title GLOB \"%s\"".printf (filter.title);
169                         else
170                                 sql += sep + "Title LIKE \"%s%%\"".printf (filter.title);
171                         sep = " AND ";
172                 }
173                 if (filter.year_min > 0) {
174                         sql += sep + "Year >= %d".printf (filter.year_min);
175                         sep = " AND ";
176                 }
177                 if (filter.year_max > 0) {
178                         sql += sep + "Year <= %d".printf (filter.year_max);
179                         sep = " AND ";
180                 }
181                 if (filter.rating_min > 0) {
182                         sql += sep + "Rating >= = %d".printf (filter.rating_min);
183                         sep = " AND ";
184                 }
185                 if (filter.genres > 0) {
186                         sql += sep + "Genres&%d = %d".printf (filter.genres, filter.genres);
187                 }
188                 sql += " LIMIT %d;".printf (100);
189
190                 stdout.printf("SQL: \"%s\"\n", sql);
191
192                 rc = db.prepare_v2 (sql, -1, out stmt);
193                 if (rc != Sqlite.OK) {
194                         stderr.printf ("SQL error: %d, %s\n", rc, db.errmsg ());
195                         return 1;
196                 }
197
198                 do {
199                         rc = stmt.step ();
200                         if (rc == Sqlite.ROW) {
201                                 string title = stmt.column_text (0);
202                                 int year = stmt.column_int (1);
203                                 int rating = stmt.column_int (2);
204                                 receive_movie (title, year, rating);
205                         }
206                 } while (rc == Sqlite.ROW);
207
208                 return 0;
209         }
210 }