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.
8 var WL_INITIAL = WL + '/media/api/mobile/initial/initial.db';
9 var WL_UPDATE = WL + '/api/changes/SINCE.json?book_fields=author,html,parent,parent_number,sort_key,title' +
10 '&tag_fields=books,category,name,sort_key' +
11 '&tag_categories=author,epoch,genre,kind';
15 var categories = {'author': 'autor',
22 // FIXME: htmlescape strings!
25 // for preparing sql statements
27 // var s = new Sql("INSERT ... '{0}', '{1}' ...";
28 // s.prepare("abc", ...)
29 var Sql = function(scheme) {
33 self.sql_escape = function(term) {
34 return term.toString().replace("'", "''");
37 self.prepare = function() {
39 return self.text.replace(/{(\d+)}/g, function(match, number) {
40 return self.sql_escape(args[parseInt(number)]);
46 var Catalogue = new function() {
47 /* API for database */
52 this.init = function(success, error) {
53 console.log('Catalogue.init');
55 self.updateDB(function() {
56 self.db = window.openDatabase("wolnelektury", "1.0", "WL Catalogue", 1);
59 onFunctionCall: function(val) {
60 var re = new RegExp(val.getString(0));
61 if (val.getString(1).match(re))
67 self.db.createFunction("REGEXP", 2, regexp);*/
71 error && error('Nie mogę otworzyć bazy danych: ' + err);
75 error && error('Błąd migracji: ' + err);
79 self.sqlSanitize = function(term) {
80 return term.toString().replace("'", "''");
84 /* check if DB needs updating and upload a fresh copy, if so */
85 this.updateDB = function(success, error) {
86 var has_ver = window.localStorage.getItem('db_ver');
87 if (has_ver == DB_VER) {
88 console.log('db ok, skipping')
93 var done = function() {
95 window.localStorage.setItem('db_ver', DB_VER);
96 console.log('db updated');
101 // this is Android-specific for now
102 var android = device.version.split('.')[0];
104 self.upload_db_android(done, error);
106 error && error("Nieobsługiwana wersja systemu. Wymagany Android>=2.0.");
111 this.upload_db_android = function(success, error) {
112 // TODO: this should be downloaded from teh net, not stored in res
114 console.log('upload db for Android 2.x+');
115 // upload databases description file
117 var dbname = "wolnelektury";
118 var db = window.openDatabase(dbname, "1.0", "WL Catalogue", 500000);
120 console.log('db created successfully');
121 DBPut.fetch(WL_INITIAL, function(data) {
122 console.log('db fetch successful');
123 success && success();
125 error && error('Błąd podczas pobierania bazy danych: ' + data);
128 error && error('Błąd podczas inicjowania bazy danych: ' + data);
133 this.withState = function(callback) {
134 self.db.transaction(function(tx) {
135 tx.executeSql("SELECT * FROM state", [],
136 function(tx, results) {
137 if (results.rows.length) {
138 callback(results.rows.item(0));
141 callback({last_checked: 0});
148 this.withBook = function(id, callback, error) {
149 console.log('withBook '+id)
150 self.db.transaction(function(tx) {
151 tx.executeSql("SELECT * FROM book WHERE id="+id, [],
152 function(tx, results) {
153 if (results.rows.length) {
154 callback(results.rows.item(0));
163 this.withBooks = function(ids, callback) {
164 console.log('withBooks ' + ids)
165 self.db.transaction(function(tx) {
166 tx.executeSql("SELECT * FROM book WHERE id IN ("+ids+") ORDER BY sort_key", [],
167 function(tx, results) {
169 var count = results.rows.length;
170 for (var i=0; i<count; ++i) {
171 items.push(results.rows.item(i));
179 this.withChildren = function(id, callback) {
180 console.log('withChildren ' + id)
181 self.db.transaction(function(tx) {
182 tx.executeSql("SELECT * FROM book WHERE parent="+id+" ORDER BY parent_number, sort_key", [],
183 function(tx, results) {
185 var count = results.rows.length;
186 for (var i=0; i<count; ++i) {
187 books.push(results.rows.item(i));
194 this.withTag = function(id, callback, error) {
195 console.log('withTag '+id)
196 self.db.transaction(function(tx) {
197 tx.executeSql("SELECT * FROM tag WHERE id="+id, [],
198 function(tx, results) {
199 if (results.rows.length) {
200 callback(results.rows.item(0));
209 this.withCategory = function(category, callback) {
210 console.log('withCategory ' + category)
211 self.db.transaction(function(tx) {
212 tx.executeSql("SELECT * FROM tag WHERE category='"+category+"' ORDER BY sort_key", [],
213 function(tx, results) {
215 var count = results.rows.length;
216 for (var i=0; i<count; ++i)
217 items.push(results.rows.item(i));
224 /* takes a query, returns a list of {view,id,label} objects to a callback */
225 this.withSearch = function(term, callback) {
226 console.log('searching...');
227 term = term.replace(/^\s+|\s+$/g, '') ;
230 function booksFound(tx, results) {
231 var len = results.rows.length;
232 console.log('found books: ' + len);
233 for (var i=0; i<len; i++) {
234 var item = results.rows.item(i);
242 function tagsFound(tx, results) {
243 var len = results.rows.length;
244 console.log('found tags: ' + len);
245 for (var i=0; i<len; i++) {
246 var item = results.rows.item(i);
252 // TODO error handling
258 // TODO pliterki, start of the word match
259 self.db.transaction(function(tx) {
260 sql_term = self.sqlSanitize(term); // this is still insane, % and _
261 tx.executeSql("SELECT * FROM book WHERE title LIKE '%"+sql_term+"%' ORDER BY sort_key LIMIT 10", [],
262 //tx.executeSql("SELECT * FROM book WHERE title REGEXP '.*"+sql_term+".*' ORDER BY sort_key", [],
263 function(tx, results) {
265 booksFound(tx, results);
266 // and proceed to tags
267 tx.executeSql("SELECT * FROM tag WHERE name LIKE '%"+sql_term+"%' ORDER BY sort_key LIMIT 10",
271 console.log('ERROR:search: '+err.code);
277 self.chainSqls = function(sqls, success, error) {
278 self.db.transaction(function(tx) {
279 var do_next = function() {
281 var sql = sqls.shift();
283 tx.executeSql(sql, [], do_next, error);
286 success && success();
294 self.update = function(data, success, error) {
295 var addBookSql = new Sql("\
296 INSERT OR REPLACE INTO book \
297 (id, title, html_file, html_file_size, parent, parent_number, sort_key, pretty_size, authors) \
299 ('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}', '{7}', '{8}')");
300 var addTagSql = new Sql("INSERT OR REPLACE INTO tag (id, category, name, sort_key, books) VALUES ('{0}', '{1}', '{2}', '{3}', '{4}')");
305 for (i in data.deleted.books) {
306 var book_id = data.deleted.books[i];
307 sqls.push("DELETE FROM book WHERE id=" + book_id);
308 FileRepo.deleteIfExists(book_id);
311 for (i in data.deleted.tags) {
312 var tag_id = data.deleted.tags[i];
313 sqls.push("DELETE FROM tag WHERE id=" + tag_id);
318 for (i in data.updated.books) {
319 var book = data.updated.books[i];
320 if (!book.html) book.html = {};
321 if (!book.html.url) book.html.url = '';
322 if (!book.html.size) book.html.size = '';
323 if (!book.parent) book.parent = '';
324 if (!book.parent_number) book.parent_number = '';
325 var pretty_size = prettySize(book.html.size);
326 sqls.push(addBookSql.prepare(
327 book.id, book.title, book.html.url, book.html.size,
328 book.parent, book.parent_number, book.sort_key, pretty_size, book.author
330 FileRepo.deleteIfExists(book.id);
333 for (i in data.updated.tags) {
334 var tag = data.updated.tags[i];
335 var category = categories[tag.category];
336 var books = tag.books.join(',');
337 sqls.push(addTagSql.prepare(tag.id, category, tag.name, tag.sort_key, books));
341 sqls.push("UPDATE state SET last_checked=" + data.time_checked);
343 self.chainSqls(sqls, success, error);
347 this.sync = function(success, error) {
348 self.withState(function(state) {
349 var url = WL_UPDATE.replace("SINCE", state.last_checked);
350 console.log('sync: ' + url);
351 var xhr = new XMLHttpRequest();
352 xhr.open("GET", url);
353 xhr.onload = function() {
354 console.log('sync: fetched by ajax: ' + url);
355 self.update(JSON.parse(xhr.responseText), success, error);
357 xhr.onerror = function(e) {
358 error && error("Błąd aktualizacji bazy danych." + e);
364 this.updateLocal = function() {
365 FileRepo.withLocal(function(local) {
366 self.db.transaction(function(tx) {
367 tx.executeSql("UPDATE book SET _local=0", [], function(tx, results) {
370 for (var i = 0; i < ll; i ++) {
371 ids.push(local[i].name);
374 tx.executeSql("UPDATE book SET _local=1 where id in ("+ids+")");
378 self.db.transaction(function(tx) {
379 tx.executeSql("UPDATE book SET _local=0");