1 // FIXME: htmlescape strings!
6 var FileRepo = new function() {
7 /* API for files repository */
9 const WL_URL = 'http://www.wolnelektury.pl';
12 this.init = function(success, error) {
13 self.initRoot(success);
16 this.initRoot = function(success) {
17 // fs size is irrelevant, PERSISTENT is futile (on Android, at least)
18 window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, function(fs) {
19 console.log('local fs found: ' + fs.root.fullPath);
23 console.log('local fs not found');
29 this.withLocalHtml = function(book_id, success, error) {
30 console.log('info:withLocalHtml: id:' + book_id);
31 View.spinner('Otwieranie treści utworu');
33 error && error('info:withLocalHtml: no local html: no usable filesystem');
35 var url = "file://" + self.root.fullPath + "/html/" + book_id;
36 console.log('info:withLocalHtml: local ajax: ' + url);
37 var xhr = new XMLHttpRequest();
38 xhr.open('GET', url, true);
39 xhr.onload = function() {
40 console.log('info:withLocalHtml: fetched by local ajax: ' + url);
41 success && success(xhr.responseText);
48 // downloads HTML file from server, saves it in cache and calls success with file contents
49 this.withHtmlFromServer = function(book_id, success, error) {
50 console.log('info:withHtmlFromServer: id:' + book_id);
52 Catalogue.withBook(book_id, function(book) {
53 var url = WL_URL + book.html_file;
54 console.log('info:withHtmlFromServer: fetching url: ' + url);
56 View.spinner("Pobieranie treści utworu z sieci");
59 window.plugins.downloader.downloadFile(url, self.root.fullPath + "/html/", ""+book_id, true,
61 console.log('info:withHtmlFromServer: loaded file from WL');
62 self.withLocalHtml(book_id, success, error);
64 console.log('error downloading file!')
65 error && error("error: "+data);
69 // there's no big fs, so we'll just get the text from AJAX
70 console.log('info:withHtmlFromServer: ajax: ' + url);
71 var xhr = new XMLHttpRequest();
73 xhr.onload = function() {
74 console.log('info:withHtmlFromServer: fetched by ajax: ' + url);
75 success && success(xhr.responseText);
82 // calls the callback with contents of the HTML file for a given book,
83 // loaded from the server and cached locally
84 this.withHtml = function(id, success, error) {
85 console.log('info:withHtml: id:' + id);
86 self.withLocalHtml(id, success, function() {
87 self.withHtmlFromServer(id, success, error);
93 var View = new function() {
95 self.minOffset = 1000;
105 self.init = function() {
106 console.log('View.init');
110 navigator.app.overrideBackbutton();
111 document.addEventListener("backbutton", View.goBack, true);
113 self._searchbox = document.getElementById("searchbox");
114 self._searchinput = document.getElementById("search");
115 self._content = document.getElementById("content");
117 self.enter(location.href);
121 this.sanitize = function(text) {
122 return text.replace(/&/g, "&").replace(/</g, "<");
125 this.showSearch = function() {
126 self._searchbox.style.display = "block";
129 this.hideSearch = function() {
130 self._searchbox.style.display = "none";
133 this.spinner = function(text) {
136 self._content.innerHTML = "<div class='spinner'><img src='img/spinner.gif' /><br/><span id='spinnertext'>" + text +"</span></div>";
140 this.content = function(text) {
141 console.log('content');
143 self._content.innerHTML = '';
144 self._content.innerHTML = text;
148 this.enter = function(url) {
149 console.log('View.enter: ' + url);
155 var query_start = url.indexOf('?');
156 if (query_start != -1) {
157 var slash_index = url.indexOf('/', query_start + 1);
158 if (slash_index != -1) {
159 view = url.substr(query_start + 1, slash_index - query_start - 1);
160 arg = url.substr(slash_index + 1);
163 view = url.substr(query_start + 1);
166 console.log('View.enter: ' + view + ' ' + arg);
167 self['enter' + view](arg);
170 this.enterIndex = function(arg) {
171 console.log('enterIndex');
173 var html = "<div class='book-list'>";
174 for (category in self.categories)
175 html += self.a('Category', category) + self.categories[category] + "</a>\n";
177 "<p id='logo'><img src='img/wl-logo.png' alt='Wolne Lektury' /><br/>\n" +
178 "szkolna biblioteka internetowa" +
179 //"<br/>v. " + VERSION +
184 this.enterBook = function(id) {
186 console.log('enterBook: ' + id);
189 Catalogue.withBook(id, function(book) {
190 Catalogue.withChildren(id, function(children) {
191 Catalogue.withAuthors(id, function(authors) {
192 var html = "<h1><span class='subheader'>";
194 for (a in authors) auths.push(authors[a].name);
195 html += auths.join(", ");
196 html += "</span>" + book.title + "</h1>\n";
197 if (book.html_file) {
198 html += "<p class='buttons'>" + self.a('BookText', id) + "Czytaj tekst</a></p>";
200 if (children.length) {
201 html += "<div class='book-list'>";
202 for (c in children) {
204 html += self.a('Book', child.id) + child.title + "</a>\n";
214 this.enterBookText = function(id) {
216 self.spinner("Otwieranie utworu");
217 console.log('enterBookText: ' + id);
220 FileRepo.withHtml(id, function(data) {
225 this.enterTag = function(id) {
227 console.log('enterTag: ' + id);
230 self.spinner("Otwieranie listy utworów");
232 Catalogue.withTag(id, function(tag) {
233 var html = "<h1><span class='subheader upper'>" + tag.category + ': </span>' + tag.name + "</h1>\n";
234 html += "<div class='book-list'>";
236 Catalogue.withBooks(tag._books, function(books) {
237 for (var i in books) {
239 html += self.a('Book', book.id) + book.title + "</a>\n";
249 this.enterCategory = function(category) {
250 console.log('enterCategory: ' + category);
251 self.spinner("Otwieranie katalogu");
254 Catalogue.withCategory(category, function(tags) {
255 var html = "<h1>" + self.categories[category] + "</h1>\n";
256 html += "<div class='book-list'>";
259 html += self.a('Tag', tag.id) + tag.name + "</a>\n";
268 this.enterSearch = function(query) {
269 console.log('enterTag: ' + query);
272 var html = "<h1><span class='subheader'>Szukana fraza:</span>" + View.sanitize(query) + "</h1>\n";
274 if (query.length < 2) {
275 html += "<p>Szukana fraza musi mieć co najmniej dwa znaki</p>";
280 Catalogue.withSearch(query, function(results) {
281 if (results.length == 1) {
282 self.enter(self.href(results[0].view, results[0].id));
285 if (results.length == 0) {
286 html += "<p>Brak wyników wyszukiwania</p>";
289 html += "<div class='book-list'>";
290 for (var i in results) {
291 var result = results[i];
292 html += self.a(result.view, result.id) + result.label + "</a>\n";
301 /* search form submit callback */
302 this.search = function() {
303 self.goTo('?Search/' + self._searchinput.value);
308 this.href = function(view, par) {
309 return "?"+view+"/"+par;
312 this.a = function(view, par) {
313 return "<a class='"+view+"' onclick='View.goTo(\"" +
314 self.href(view, par).replace(/["']/g, "\\$&") + "\");'>";
317 this.goTo = function(url) {
318 self.viewStack.push(self.current);
319 console.log('goTo: ' + url);
323 this.goBack = function() {
324 if (self.viewStack.length > 0) {
325 var url = self.viewStack.pop();
326 console.log('goBack: ' + url);
330 console.log('exiting');
331 navigator.app.exitApp();
338 // for preparing sql statements
340 // var s = new Sql("INSERT ... '{0}', '{1}' ...";
341 // s.prepare("abc", ...)
342 var Sql = function(scheme) {
346 self.sql_escape = function(term) {
347 return term.toString().replace("'", "''");
350 self.prepare = function() {
351 var args = arguments;
352 return self.text.replace(/{(\d+)}/g, function(match, number) {
353 return self.sql_escape(args[number]);
359 var Catalogue = new function() {
360 /* API for database */
365 this.init = function(success, error) {
366 console.log('Catalogue.init');
368 self.updateDB(function() {
369 self.db = window.openDatabase("wolnelektury", "1.0", "WL Catalogue", 1);
372 onFunctionCall: function(val) {
373 var re = new RegExp(val.getString(0));
374 if (val.getString(1).match(re))
380 self.db.createFunction("REGEXP", 2, regexp);*/
382 success && success();
384 error && error('Nie mogę otworzyć bazy danych: ' + err);
388 error && error('Błąd migracji: ' + err);
392 self.sqlSanitize = function(term) {
393 return term.toString().replace("'", "''");
397 /* check if DB needs updating and upload a fresh copy, if so */
398 this.updateDB = function(success, error) {
399 var db_ver = '0.1.4';
400 if (window.localStorage.getItem('db_ver') == db_ver) {
401 console.log('db ok, skipping')
402 success && success();
406 var done = function() {
407 window.localStorage.setItem('db_ver', db_ver);
408 console.log('db updated');
409 success && success();
413 // this is Android-specific for now
414 var version = device.version.split('.')[0];
417 self.upload_db_android1(done, error);
420 self.upload_db_android2(done, error);
424 error && error('Błąd migracji: ' + err);
429 this.upload_db_android1 = function(success, error) {
430 console.log('upload db for Android 1.x');
431 window.requestFileSystem(LocalFileSystem.APPLICATION, 0, function(fs) {
432 window.plugins.assetcopy.copy("initial/wolnelektury.db",
433 fs.root.fullPath + "/databases/wolnelektury.db", true,
435 console.log('db upload successful');
436 success && success();
438 error && error("database upload error: " + data);
444 this.upload_db_android2 = function(success, error) {
445 console.log('upload db for Android 2.x');
446 window.requestFileSystem(LocalFileSystem.APPLICATION, 0, function(fs) {
448 // upload databases description file
449 window.plugins.assetcopy.copy("initial/Databases.db",
450 fs.root.fullPath + "/app_database/Databases.db", true,
452 console.log('db descriptior upload successful');
454 // upload the database file
455 window.plugins.assetcopy.copy("initial/0000000000000001.db",
456 fs.root.fullPath + "/app_database/file__0/0000000000000001.db", true,
458 console.log('db upload successful');
459 success && success();
461 error && error("database upload error: " + data);
466 error && error("database descriptor upload error: " + data);
473 this.withBook = function(id, callback) {
474 console.log('withBook '+id)
475 self.db.transaction(function(tx) {
476 tx.executeSql("SELECT * FROM book WHERE id="+id, [],
477 function(tx, results) {
478 callback(results.rows.item(0));
483 this.withBooks = function(ids, callback) {
484 console.log('withBooks ' + ids)
485 self.db.transaction(function(tx) {
486 tx.executeSql("SELECT * FROM book WHERE id IN ("+ids+") ORDER BY title", [],
487 function(tx, results) {
489 var count = results.rows.length;
490 for (var i=0; i<count; ++i) {
491 items.push(results.rows.item(i));
498 this.withAuthors = function(id, callback) {
499 console.log('withAuthors ' + id);
501 self.db.transaction(function(tx) {
502 tx.executeSql("SELECT t.name " +
503 "FROM book_tag bt LEFT JOIN tag t ON t.id=bt.tag " +
504 "WHERE bt.book="+id+" AND t.category='autor' " +
505 "ORDER BY t.sort_key", [],
506 function(tx, results) {
508 var count = results.rows.length;
509 for (var i=0; i<count; ++i) {
510 tags.push(results.rows.item(i));
517 this.withChildren = function(id, callback) {
518 console.log('withChildren ' + id)
519 self.db.transaction(function(tx) {
520 tx.executeSql("SELECT * FROM book WHERE parent="+id+" ORDER BY parent_number", [],
521 function(tx, results) {
523 var count = results.rows.length;
524 for (var i=0; i<count; ++i) {
525 books.push(results.rows.item(i));
532 this.withTag = function(id, callback) {
533 console.log('withTag '+id)
534 self.db.transaction(function(tx) {
535 tx.executeSql("SELECT * FROM tag WHERE id="+id, [],
536 function(tx, results) {
537 callback(results.rows.item(0));
542 this.withCategory = function(category, callback) {
543 console.log('withCategory ' + category)
544 self.db.transaction(function(tx) {
545 tx.executeSql("SELECT * FROM tag WHERE category='"+category+"'", [],
546 function(tx, results) {
548 var count = results.rows.length;
549 for (var i=0; i<count; ++i)
550 items.push(results.rows.item(i));
557 /* this.withUnrelatedBooks = function(books, success) {
558 if (books.length == 0) return books;
562 book_ids[books[i].id] = true;
566 var addIfUnrelated = function(book) {
567 var addIfUnrelated_wrapped = function(b) {
569 if (b.parent in book_ids) {
573 else self.withBook(b.parent, addIfUnrelated_wrapped);
576 new_books.push(book);
581 addIfUnrelated_wrapped(book);
585 var filterBooks = function() {
586 console.log('filterBooks: ' + books.length);
588 addIfUnrelated(books.shift());
591 success && success(new_books);
599 this.withBooksTagged = function(tag, callback, withChildren) {
600 self.db.transaction(function(tx) {
601 tx.executeSql("SELECT book.* " +
602 "FROM book LEFT JOIN book_tag ON book_tag.book=book.id " +
603 "WHERE book_tag.tag="+tag+" ORDER BY book.title", [],
604 function(tx, results) {
606 var count = results.rows.length;
607 console.log('withBooksTagged('+tag+'): '+count);
608 for (var i=0; i<count; ++i) {
609 books.push(results.rows.item(i));
613 else self.withUnrelatedBooks(books, callback);
619 /* takes a query, returns a list of {view,id,label} objects to a callback */
620 this.withSearch = function(term, callback) {
621 console.log('searching...');
624 function booksFound(tx, results) {
625 var len = results.rows.length;
626 console.log('found books: ' + len);
627 for (var i=0; i<len; i++) {
628 var item = results.rows.item(i);
637 function tagsFound(tx, results) {
638 var len = results.rows.length;
639 console.log('found tags: ' + len);
640 for (var i=0; i<len; i++) {
641 var item = results.rows.item(i);
645 label: item.name + ' (' + item.category + ')'
648 // TODO error handling
654 // TODO pliterki, start of the word match
655 self.db.transaction(function(tx) {
656 sql_term = self.sqlSanitize(term); // this is still insane, % and _
657 tx.executeSql("SELECT id, title FROM book WHERE title LIKE '%"+sql_term+"%' ORDER BY title LIMIT 10", [],
658 //tx.executeSql("SELECT id, title FROM book WHERE title REGEXP '.*"+sql_term+".*' ORDER BY title", [],
659 function(tx, results) {
661 booksFound(tx, results);
662 // and proceed to tags
663 tx.executeSql("SELECT id, name, category FROM tag WHERE name LIKE '%"+sql_term+"%' ORDER BY name LIMIT 10",
667 console.log('ERROR:search: '+err.code);
674 // each book and tag in its own transaction
675 // TODO: error handling
676 self.parse = function(json, success, error) {
677 console.log('parsing');
679 var addBookSql = new Sql("INSERT INTO book (id, title, html_file, html_file_size) VALUES ('{0}', '{1}', '{2}', '{3}')");
680 var addBookTagSql = new Sql("INSERT INTO book_tag (book, tag) VALUES ('{0}', '{1}')");
681 var addTagSql = new Sql("INSERT INTO tag (id, category, name) VALUES ('{0}', '{1}', '{2}')");
684 var books = json.added.books;
685 while (books.length) {
686 var book = books.shift();
687 if (!book.html) book.html = '';
688 if (!book.html_size) book.html_size = '';
689 sqls.push(addBookSql.prepare(book.id, book.title, book.html, book.html_size));
690 for (var t in book.tags) {
691 sqls.push(addBookTagSql.prepare(book.id, book.tags[t]));
694 console.log('ASSERT json.added.books.length=0: ' + json.added.books.length);
696 var categories = {author: 'autor', epoch: 'epoch', genre: 'genre', kind: 'kind', theme: 'motyw'};
697 var tags = json.added.tags;
698 while (tags.length) {
699 var tag = tags.shift();
700 sqls.push(addTagSql.prepare(tag.id, categories[tag.category], tag.name));
703 self.chainSql(sqls, success, error);
710 console.log('onLoad');
711 document.addEventListener("deviceready", onDeviceReady, false);
714 function onDeviceReady() {
715 console.log('onDeviceReady');
716 var error = function(err) { alert(err); };
719 console.log('after catalogue.init');
722 console.log('after FileRepo.init');