offline version
[wl-mobile.git] / assets / www / js / catalogue.js
1 /*
2  * This file is part of WolneLektury-Mobile, licensed under GNU Affero GPLv3 or later.
3  * Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4  */
5
6 var DB_VER = 'o0.2';
7
8
9 var categories = {'author': 'autor',
10               'epoch': 'epoka', 
11               'genre': 'gatunek', 
12               'kind': 'rodzaj', 
13               'theme': 'motyw'
14               }
15
16 // FIXME: htmlescape strings!
17
18
19 // for preparing sql statements
20 // use like: 
21 //   var s = new Sql("INSERT ... '{0}', '{1}' ...";
22 //   s.prepare("abc", ...)
23 var Sql = function(scheme) {
24         var self = this;
25         self.text = scheme;
26         
27         self.sql_escape = function(term) {
28                 return term.toString().replace("'", "''");
29         };
30         
31         self.prepare = function() {
32                 var args = arguments;
33                 return self.text.replace(/{(\d+)}/g, function(match, number) {
34                         return self.sql_escape(args[parseInt(number)]);
35                 });
36         }
37 };
38
39
40 var Catalogue = new function() {
41         /* API for database */
42
43         var self = this;
44         self.db = null;
45
46         this.init = function(success, error) {
47                 console.log('Catalogue.init');
48                 
49                 self.updateDB(function() {
50                         self.db = window.openDatabase("wolnelektury", "1.0", "WL Catalogue", 1);
51                         if (self.db) {
52                                 success && success();
53                         } else {
54                                 error && error('Nie mogę otworzyć bazy danych: ' + err);
55                         }
56                         
57                 }, function(err) {
58                         error && error('Błąd migracji: ' + err);
59                 });
60         };
61
62         self.sqlSanitize = function(term) {
63                 return term.toString().replace("'", "''");
64         };
65
66
67         /* check if DB needs updating and upload a fresh copy, if so */
68         this.updateDB = function(success, error) {
69                 var has_ver = window.localStorage.getItem('db_ver');
70                 if (has_ver == DB_VER) {
71                         console.log('db ok, skipping')
72                         success && success();
73                         return;
74                 }
75
76                 var done = function() {
77                         window.localStorage.setItem('db_ver', DB_VER);
78                         console.log('db updated');
79                         success && success();
80                 };
81
82                 // db initialize
83                 // this is Android-specific for now
84                 self.upload_db_android(done, error);
85         };
86
87
88         this.upload_db_android = function(success, error) {
89                 console.log('upload db for Android 2.x+');
90
91                 var dbname = "wolnelektury";
92                 window.AssetCopy.copy("initial/Databases.db",
93                         "/data/data/pl.org.nowoczesnapolska.wloffline/app_database/Databases.db", true,
94                         function(data) {
95                                 console.log('db descriptor upload successful');
96                                 window.AssetCopy.copy("initial/0000000000000001.db",
97                                         "/data/data/pl.org.nowoczesnapolska.wloffline/app_database/file__0/0000000000000001.db", true,
98                                         function(data) {
99                                                 console.log('db upload successful');
100                                                 success && success();
101                                         }, function(data) {
102                                                 error && error("database upload error: " + data);
103                                         });
104                         }, function(data) {
105                                 error && error("database descriptor upload error: " + data);
106                         });
107         };
108         
109         this.withState = function(callback) {
110                 self.db.transaction(function(tx) {
111                         tx.executeSql("SELECT * FROM state", [], 
112                                 function(tx, results) {
113                                         if (results.rows.length) {
114                                                 callback(results.rows.item(0));
115                                         }
116                                         else {
117                                                 callback({last_checked: 0});
118                                         }
119                                 });
120                 });
121         };
122
123
124         this.withBook = function(id, callback, error) {
125                 console.log('withBook '+id)
126                 self.db.transaction(function(tx) {
127                         tx.executeSql("SELECT * FROM book WHERE id="+id, [], 
128                                 function(tx, results) {
129                                         if (results.rows.length) {
130                                                 callback(results.rows.item(0));
131                                         }
132                                         else {
133                                                 error && error();
134                                         }
135                                 });
136                 });
137         };
138
139         this.withBooks = function(ids, callback) {
140                 console.log('withBooks ' + ids)
141                 self.db.transaction(function(tx) {
142                         tx.executeSql("SELECT * FROM book WHERE id IN ("+ids+") ORDER BY sort_key", [], 
143                                 function(tx, results) {
144                                         var items = [];
145                                         var count = results.rows.length;
146                                         for (var i=0; i<count; ++i) {
147                                                 items.push(results.rows.item(i));
148                                         }
149                                         callback(items);
150                                 });
151                 });
152         };
153
154
155         this.withChildren = function(id, callback) {
156                 console.log('withChildren ' + id)
157                 self.db.transaction(function(tx) {
158                         tx.executeSql("SELECT * FROM book WHERE parent="+id+" ORDER BY parent_number, sort_key", [], 
159                                 function(tx, results) {
160                                         var books = [];
161                                         var count = results.rows.length;
162                                         for (var i=0; i<count; ++i) {
163                                                 books.push(results.rows.item(i));
164                                         }
165                                         callback(books);
166                         });
167                 });
168         };
169
170         this.withTag = function(id, callback, error) {
171                 console.log('withTag '+id)
172                 self.db.transaction(function(tx) {
173                         tx.executeSql("SELECT * FROM tag WHERE id="+id, [], 
174                                 function(tx, results) {
175                                         if (results.rows.length) {
176                                                 callback(results.rows.item(0));
177                                         }
178                                         else {
179                                                 error && error();
180                                         }
181                                 });
182                 });
183         };
184
185         this.withCategory = function(category, callback) {
186                 console.log('withCategory ' + category)
187                 self.db.transaction(function(tx) {
188                         tx.executeSql("SELECT * FROM tag WHERE category='"+category+"' ORDER BY sort_key", [], 
189                                 function(tx, results) {
190                                         var items = [];
191                                         var count = results.rows.length;
192                                         for (var i=0; i<count; ++i)
193                                                 items.push(results.rows.item(i));
194                                         callback(items);
195                                 });
196                 });
197         };
198
199
200         /* takes a query, returns a list of {view,id,label} objects to a callback */
201         this.withSearch = function(term, callback) {
202                 console.log('searching...');
203                 term =  term.replace(/^\s+|\s+$/g, '') ;
204                 var found = [];
205
206                 function booksFound(tx, results) {
207                         var len = results.rows.length;
208                         console.log('found books: ' + len);
209                         for (var i=0; i<len; i++) {
210                                 var item = results.rows.item(i);
211                                 found.push({
212                                         view: "Book",
213                                         item: item
214                                 });
215                         }
216                 };
217
218                 function tagsFound(tx, results) {
219                         var len = results.rows.length;
220                         console.log('found tags: ' + len);
221                         for (var i=0; i<len; i++) {
222                                 var item = results.rows.item(i);
223                                 found.push({
224                                         view: "Tag",
225                                         item: item
226                                 });
227                         }
228                         // TODO error handling
229                         callback(found);
230                 };
231
232
233                 // FIXME escaping
234                 // TODO pliterki, start of the word match
235                 self.db.transaction(function(tx) {
236                         sql_term = self.sqlSanitize(term); // this is still insane, % and _
237                         tx.executeSql("SELECT * FROM book WHERE title LIKE '%"+sql_term+"%' ORDER BY sort_key LIMIT 10", [],
238                         //tx.executeSql("SELECT * FROM book WHERE title REGEXP '.*"+sql_term+".*' ORDER BY sort_key", [],
239                                 function(tx, results) {
240                                         // save the books
241                                         booksFound(tx, results);
242                                         // and proceed to tags
243                                         tx.executeSql("SELECT * FROM tag WHERE name LIKE '%"+sql_term+"%' ORDER BY sort_key LIMIT 10",
244                                                         [], tagsFound);
245                                 },
246                                 function(err) {
247                                         console.log('ERROR:search: '+err.code);
248                                         callback([]);
249                                 });
250                 });
251         };
252
253         self.chainSqls = function(sqls, success, error) {
254                 self.db.transaction(function(tx) {
255                         var do_next = function() {
256                                 if (sqls.length) {
257                                         var sql = sqls.shift();
258                                         console.log(sql);
259                                         tx.executeSql(sql, [], do_next, error);
260                                 }
261                                 else {
262                                         success && success();
263                                 }
264                         }
265                         do_next();
266                 });
267         };
268
269
270 }