From: Radek Czajka Date: Thu, 5 May 2011 11:36:26 +0000 (+0200) Subject: Initial commit X-Git-Url: https://git.mdrn.pl/wl-mobile.git/commitdiff_plain/2f315aa89bfdc0be1cb84ef27809be8886e55abe Initial commit --- 2f315aa89bfdc0be1cb84ef27809be8886e55abe diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..f523e53 --- /dev/null +++ b/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90786c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/bin/ +*.db + +.project +.pydevproject +.settings + + + diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..b92e718 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/www/css/book_text.css b/assets/www/css/book_text.css new file mode 100644 index 0000000..cc54ce3 --- /dev/null +++ b/assets/www/css/book_text.css @@ -0,0 +1,367 @@ +#book-text { + font-size: 12px; + line-height: 1.5em; + margin: 0; +} + +#book-text a { + color: blue; + text-decoration: none; +} + + +/* ================================== */ +/* = Header with logo and menu = */ +/* ================================== */ +#book-text #header { + margin: 3.4em 0 0 1.4em; +} + +#book-text img { + border: none; +} + + +#book-text #menu { + position: fixed; + left: 0em; + top: 0em; + width: 100%; + height: 1.5em; + background: #333; + color: #FFF; + opacity: 0.9; + z-index: 99; +} + +#book-text #menu ul { + list-style: none; + padding: 0; + margin: 0; +} + +#book-text #menu li a { + display: block; + float: left; + width: 7.5em; + height: 1.5em; + margin-left: 0.5em; + text-align: center; + color: #FFF; +} + +#book-text #menu li a:hover, #menu li a:active { + color: #000; + background: #FFF url(/static/img/arrow-down.png) no-repeat center right; +} + +#book-text #menu li a.selected { + color: #000; + background: #FFF url(/static/img/arrow-up.png) no-repeat center right; +} +#book-text #menu a.menu-link { + display: block; + float: left; + height: 1.5em; + margin-left: 0.5em; + text-align: center; + color: #FFF; +} +#book-text #menu span { + color: #888; + font-style: italic; + font-size: .75em; + margin-right: 0.5em; +} + + +#book-text #toc, #themes, #nota_red, #info { + position: fixed; + left: 0em; + top: 1.5em; + width: 37em; + padding: 1.5em; + background: #FFF; + border-bottom: 0.25em solid #DDD; + border-right: 0.25em solid #DDD; + display: none; + height: 16em; + overflow-x: hidden; + overflow-y: auto; + opacity: 0.9; + z-index: 99; +} + +#book-text #toc ol, #themes ol { + list-style: none; + padding: 0; + margin: 0; +} + +#book-text #toc ol li { + font-weight: bold; +} + +#book-text #toc ol ol { + padding: 0 0 1.5em 1.5em; + margin: 0; +} + +#book-text #toc ol ol li { + font-weight: normal; +} + +#book-text #toc h2 { + display: none; +} + +#book-text #toc .anchor { + float: none; + margin: 0; + color: blue; + font-size: 16px; + position: inherit; +} + +#book-text #info p { + text-align: justify; + margin: 1.5em 0 0; +} + +/* =================================================== */ +/* = Common elements: headings, paragraphs and lines = */ +/* =================================================== */ +#book-text h1 { + font-size: 2.5em; + margin: 1.5em 0; + text-align: center; + line-height: 1.5em; + font-weight: bold; +} + +#book-text h2 { + font-size: 1.7em; + margin: 1.5em 0 0; + font-weight: bold; + line-height: 1.5em; +} + +#book-text h3 { + font-size: 1.4em; + margin: 1.5em 0 0; + font-weight: normal; + line-height: 1.5em; +} + +#book-text h4 { + font-size: 1em; + margin: 1.5em 0 0; + line-height: 1.5em; +} + +#book-text p { + margin: 0; +} + +/* ======================== */ +/* = Footnotes and themes = */ +/* ======================== */ +#book-text .theme-begin { + display:none; +/* border-left: 0.1em solid #DDDDDD; + color: #777; + padding: 0 0.5em; + + font-style: normal; + font-weight: normal; + font-variant: normal; + letter-spacing: 0; + text-transform: none; + text-decoration: none; + width: 7.5em; + + font-size: 10px; + float: right; + margin-bottom: 0.5em; + clear: both; + line-height: 1.5em; + text-align: left; +*/} + +#book-text .annotation { + font-style: normal; + font-weight: normal; + font-size: 12px; + padding-left: 2px; + position: relative; + top: -4px; +} + +#book-text #footnotes { + margin-top: 3em; +} + +#book-text #footnotes .annotation { + display: block; + float: left; + width: 2.5em; + clear: both; +} + +#book-text #footnotes div { + margin: 1.5em 0 0 0; +} + +#book-text #footnotes p, #footnotes ul { + margin-left: 2.5em; + font-size: 0.875em; +} + +#book-text #footnotes .permalink { + font-size: .75em; +} + +#book-text blockquote { + font-size: 0.875em; +} + +/* ============= */ +/* = Numbering = */ +/* ============= */ +/*.verse, .paragraph { + position:relative; +} +#book-text .anchor { + position: absolute; + margin: -0.25em -0.5em; + left: -3em; + color: #777; + font-size: 12px; + width: 2em; + text-align: center; + padding: 0.25em 0.5em; + line-height: 1.5em; +} + +#book-text .anchor:hover, #book-text .anchor:active { + color: #FFF; + background-color: #CCC; +}*/ +#book-text .anchor{display:none;} + +/* =================== */ +/* = Custom elements = */ +/* =================== */ +#book-text span.author { + font-size: 0.5em; + display: block; + line-height: 1.5em; + margin-bottom: 0.25em; +} + +#book-text span.collection { + font-size: 0.375em; + display: block; + line-height: 1.5em; + margin-bottom: -0.25em; +} + +#book-text span.subtitle { + font-size: 0.5em; + display: block; + line-height: 1.5em; + margin-top: -0.25em; +} + +#book-text span.translator { + font-size: 0.375em; + display: block; + line-height: 1.5em; + margin-top: 0.25em; +} + +#book-text div.didaskalia { + font-style: italic; + margin: 0.5em 0 0 1.5em; +} + +#book-text div.kwestia { + margin: 0.5em 0 0; +} + +#book-text div.stanza { + margin: 1.5em 0 0; +} + +#book-text div.kwestia div.stanza { + margin: 0; +} + +#book-text p.paragraph { + text-align: justify; + margin: 1.5em 0 0; +} + +#book-text p.motto { + text-align: justify; + font-style: italic; + margin: 1.5em 0 0; +} + +#book-text p.motto_podpis { + font-size: 0.875em; + text-align: right; +} + +#book-text div.fragment { + border-bottom: 0.1em solid #999; + padding-bottom: 1.5em; +} + +#book-text div.note p, div.dedication p, div.note p.paragraph, div.dedication p.paragraph { + text-align: right; + font-style: italic; +} + +#book-text hr.spacer { + height: 3em; + visibility: hidden; +} + +#book-text hr.spacer-line { + margin: 1.5em 0; + border: none; + border-bottom: 0.1em solid #000; +} + +#book-text p.spacer-asterisk { + padding: 0; + margin: 1.5em 0; + text-align: center; +} + +#book-text div.person-list ol { + list-style: none; + padding: 0 0 0 1.5em; +} + +#book-text p.place-and-time { + font-style: italic; +} + +#book-text em.math, em.foreign-word, em.book-title, em.didaskalia { + font-style: italic; +} + +#book-text em.author-emphasis { + letter-spacing: 0.1em; +} + +#book-text em.person { + font-style: normal; + font-variant: small-caps; +} + +#book-text .verse:after { + content: "\feff"; +} diff --git a/assets/www/css/style.css b/assets/www/css/style.css new file mode 100644 index 0000000..b1f0022 --- /dev/null +++ b/assets/www/css/style.css @@ -0,0 +1,84 @@ +body { + padding: 0; + margin: 0; + font-size: 12px; + font-family: verdana, arial, helvetica, sans-serif; +} + +.spinner { + padding: 5em; +} + +#searchbox { + font-size: 1.5em; + background: #84bf2a; + padding: 5px; + display: none; +} +#search { + border: none; + width: 100%; + padding: 5px 0 5px 0; +} +#swrap { + margin-right:85px; +} +#searchbutton { + width:80px; + float:right; +} +#logo { + padding: 1em; + text-align:center; +} +#content { + padding: 10px; +} +.buttons a { + display: block; + background: #b6e372; + font-size: 20px; + padding: 1em; + border-radius: 8px; + -webkit-border-radius: 8px; + /*margin: 0.5em;*/ + text-align:center; + color: #295158; + text-decoration: none; +} + +.book-list a { + display: block; + background: #eee; + font-size: 16px; + padding: 15px; + margin: 5px; + border-radius: 8px; + -webkit-border-radius: 8px; + text-align:center; + color: #295158; + text-decoration: none; +} + +a.Book { + background: #ccc; +} + +h1 .subheader { + display:block; + font-size: 70%; +} +.upper { + text-transform: capitalize; +} + + +.footer { + color: #888; + border-top: 1px solid #888; + margin-top: 2em; + font-size: 0.9em; +} +.footer a { + color: #888; +} \ No newline at end of file diff --git a/assets/www/img/spinner.gif b/assets/www/img/spinner.gif new file mode 100644 index 0000000..c69e937 Binary files /dev/null and b/assets/www/img/spinner.gif differ diff --git a/assets/www/img/wl-logo.png b/assets/www/img/wl-logo.png new file mode 100644 index 0000000..b623d8b Binary files /dev/null and b/assets/www/img/wl-logo.png differ diff --git a/assets/www/index.html b/assets/www/index.html new file mode 100644 index 0000000..55be216 --- /dev/null +++ b/assets/www/index.html @@ -0,0 +1,38 @@ + + + + Wolne Lektury + + + + + + + + + + + + + + + + +
+ +
+
+Ładowanie + +
+ + + + + \ No newline at end of file diff --git a/assets/www/js/assetcopy.js b/assets/www/js/assetcopy.js new file mode 100644 index 0000000..e7e0ac5 --- /dev/null +++ b/assets/www/js/assetcopy.js @@ -0,0 +1,14 @@ +function AssetCopy() { + +} + +AssetCopy.prototype.copy = function(asset, target, overwrite, win, fail) { + if(overwrite==false) overwrite="false"; + else overwrite="true"; + PhoneGap.exec(win, fail, "AssetCopy", "copy", [asset, target, overwrite]); +}; + +PhoneGap.addConstructor(function() { + PhoneGap.addPlugin("assetcopy", new AssetCopy()); + PluginManager.addService("AssetCopy","pl.org.nowoczesnapolska.wlmobi.AssetCopy"); +}); \ No newline at end of file diff --git a/assets/www/js/catalogue.js b/assets/www/js/catalogue.js new file mode 100644 index 0000000..18f809f --- /dev/null +++ b/assets/www/js/catalogue.js @@ -0,0 +1,727 @@ +// FIXME: htmlescape strings! + +var VERSION = '0.1'; + + +var FileRepo = new function() { + /* API for files repository */ + var self = this; + const WL_URL = 'http://www.wolnelektury.pl'; + this.root = null; + + this.init = function(success, error) { + self.initRoot(success); + }; + + this.initRoot = function(success) { + // fs size is irrelevant, PERSISTENT is futile (on Android, at least) + window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, function(fs) { + console.log('local fs found: ' + fs.root.fullPath); + self.root = fs.root; + success && success(); + }, function() { + console.log('local fs not found'); + success && success(); + }); + }; + + + this.withLocalHtml = function(book_id, success, error) { + console.log('info:withLocalHtml: id:' + book_id); + View.spinner('Otwieranie treści utworu'); + if (!self.root) + error && error('info:withLocalHtml: no local html: no usable filesystem'); + + var url = "file://" + self.root.fullPath + "/html/" + book_id; + console.log('info:withLocalHtml: local ajax: ' + url); + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.onload = function() { + console.log('info:withLocalHtml: fetched by local ajax: ' + url); + success && success(xhr.responseText); + } + xhr.onerror = error; + xhr.send(); + }; + + + // downloads HTML file from server, saves it in cache and calls success with file contents + this.withHtmlFromServer = function(book_id, success, error) { + console.log('info:withHtmlFromServer: id:' + book_id); + // read file from WL + Catalogue.withBook(book_id, function(book) { + var url = WL_URL + book.html_file; + console.log('info:withHtmlFromServer: fetching url: ' + url); + + View.spinner("Pobieranie treści utworu z sieci"); + + if (self.root) { + window.plugins.downloader.downloadFile(url, self.root.fullPath + "/html/", ""+book_id, true, + function(data){ + console.log('info:withHtmlFromServer: loaded file from WL'); + self.withLocalHtml(book_id, success, error); + }, function(data) { + console.log('error downloading file!') + error && error("error: "+data); + }); + } + else { + // there's no big fs, so we'll just get the text from AJAX + console.log('info:withHtmlFromServer: ajax: ' + url); + var xhr = new XMLHttpRequest(); + xhr.open(url); + xhr.onload = function() { + console.log('info:withHtmlFromServer: fetched by ajax: ' + url); + success && success(xhr.responseText); + } + xhr.send(); + } + }); + }; + + // calls the callback with contents of the HTML file for a given book, + // loaded from the server and cached locally + this.withHtml = function(id, success, error) { + console.log('info:withHtml: id:' + id); + self.withLocalHtml(id, success, function() { + self.withHtmlFromServer(id, success, error); + }); + }; +}; + + +var View = new function() { + var self = this; + self.minOffset = 1000; + //self.element + self.categories = { + autor: 'Autorzy', + rodzaj: 'Rodzaje', + gatunek: 'Gatunki', + epoka: 'Epoki' + }; + + + self.init = function() { + console.log('View.init'); + + self.viewStack = []; + self.current; + navigator.app.overrideBackbutton(); + document.addEventListener("backbutton", View.goBack, true); + + self._searchbox = document.getElementById("searchbox"); + self._searchinput = document.getElementById("search"); + self._content = document.getElementById("content"); + + self.enter(location.href); + }; + + + this.sanitize = function(text) { + return text.replace(/&/g, "&").replace(/
"; + scroll(0, 0); + }; + + this.content = function(text) { + console.log('content'); + + self._content.innerHTML = ''; + self._content.innerHTML = text; + scroll(0, 0); + } + + this.enter = function(url) { + console.log('View.enter: ' + url); + + self.current = url; + var view = 'Index'; + var arg = null; + + var query_start = url.indexOf('?'); + if (query_start != -1) { + var slash_index = url.indexOf('/', query_start + 1); + if (slash_index != -1) { + view = url.substr(query_start + 1, slash_index - query_start - 1); + arg = url.substr(slash_index + 1); + } + else { + view = url.substr(query_start + 1); + } + } + console.log('View.enter: ' + view + ' ' + arg); + self['enter' + view](arg); + } + + this.enterIndex = function(arg) { + console.log('enterIndex'); + self.showSearch(); + var html = "
"; + for (category in self.categories) + html += self.a('Category', category) + self.categories[category] + "\n"; + html += "
" + + ""; + self.content(html); + }; + + this.enterBook = function(id) { + id = parseInt(id); + console.log('enterBook: ' + id); + self.showSearch(); + + Catalogue.withBook(id, function(book) { + Catalogue.withChildren(id, function(children) { + Catalogue.withAuthors(id, function(authors) { + var html = "

"; + var auths = []; + for (a in authors) auths.push(authors[a].name); + html += auths.join(", "); + html += "" + book.title + "

\n"; + if (book.html_file) { + html += "

" + self.a('BookText', id) + "Czytaj tekst

"; + } + if (children.length) { + html += "
"; + for (c in children) { + child = children[c]; + html += self.a('Book', child.id) + child.title + "\n"; + } + html += "
"; + } + self.content(html); + }); + }); + }); + } + + this.enterBookText = function(id) { + id = parseInt(id); + self.spinner("Otwieranie utworu"); + console.log('enterBookText: ' + id); + self.hideSearch(); + + FileRepo.withHtml(id, function(data) { + self.content(data); + }); + } + + this.enterTag = function(id) { + id = parseInt(id); + console.log('enterTag: ' + id); + self.showSearch(); + + self.spinner("Otwieranie listy utworów"); + + Catalogue.withTag(id, function(tag) { + var html = "

" + tag.category + ': ' + tag.name + "

\n"; + html += "
"; + if (tag._books) { + Catalogue.withBooks(tag._books, function(books) { + for (var i in books) { + var book = books[i]; + html += self.a('Book', book.id) + book.title + "\n"; + } + html += "
"; + self.content(html); + }); + } + }); + }; + + + this.enterCategory = function(category) { + console.log('enterCategory: ' + category); + self.spinner("Otwieranie katalogu"); + self.showSearch(); + + Catalogue.withCategory(category, function(tags) { + var html = "

" + self.categories[category] + "

\n"; + html += "
"; + for (i in tags) { + tag = tags[i]; + html += self.a('Tag', tag.id) + tag.name + "\n"; + } + html += "
"; + self.content(html); + }); + }; + + + + this.enterSearch = function(query) { + console.log('enterTag: ' + query); + self.showSearch(); + + var html = "

Szukana fraza:" + View.sanitize(query) + "

\n"; + + if (query.length < 2) { + html += "

Szukana fraza musi mieć co najmniej dwa znaki

"; + self.content(html); + return; + } + + Catalogue.withSearch(query, function(results) { + if (results.length == 1) { + self.enter(self.href(results[0].view, results[0].id)); + return; + } + if (results.length == 0) { + html += "

Brak wyników wyszukiwania

"; + } + else { + html += "
"; + for (var i in results) { + var result = results[i]; + html += self.a(result.view, result.id) + result.label + "\n"; + } + html += "
"; + } + self.content(html); + }); + }; + + + /* search form submit callback */ + this.search = function() { + self.goTo('?Search/' + self._searchinput.value); + return false; + } + + + this.href = function(view, par) { + return "?"+view+"/"+par; + }; + + this.a = function(view, par) { + return ""; + }; + + this.goTo = function(url) { + self.viewStack.push(self.current); + console.log('goTo: ' + url); + self.enter(url); + }; + + this.goBack = function() { + if (self.viewStack.length > 0) { + var url = self.viewStack.pop(); + console.log('goBack: ' + url); + self.enter(url); + } + else { + console.log('exiting'); + navigator.app.exitApp(); + } + }; +} + + +/* +// for preparing sql statements +// use like: +// var s = new Sql("INSERT ... '{0}', '{1}' ..."; +// s.prepare("abc", ...) +var Sql = function(scheme) { + var self = this; + self.text = scheme; + + self.sql_escape = function(term) { + return term.toString().replace("'", "''"); + }; + + self.prepare = function() { + var args = arguments; + return self.text.replace(/{(\d+)}/g, function(match, number) { + return self.sql_escape(args[number]); + }); + } +};*/ + + +var Catalogue = new function() { + /* API for database */ + + var self = this; + self.db = null; + + this.init = function(success, error) { + console.log('Catalogue.init'); + + self.updateDB(function() { + self.db = window.openDatabase("wolnelektury", "1.0", "WL Catalogue", 1); + if (self.db) { + /*var regexp = { + onFunctionCall: function(val) { + var re = new RegExp(val.getString(0)); + if (val.getString(1).match(re)) + return 1; + else + return 0; + } + }; + self.db.createFunction("REGEXP", 2, regexp);*/ + + success && success(); + } else { + error && error('Nie mogę otworzyć bazy danych: ' + err); + } + + }, function(err) { + error && error('Błąd migracji: ' + err); + }); + }; + + self.sqlSanitize = function(term) { + return term.toString().replace("'", "''"); + }; + + + /* check if DB needs updating and upload a fresh copy, if so */ + this.updateDB = function(success, error) { + var db_ver = '0.1.3'; + if (window.localStorage.getItem('db_ver') == db_ver) { + console.log('db ok, skipping') + success && success(); + return; + } + + var done = function() { + window.localStorage.setItem('db_ver', db_ver); + console.log('db updated'); + success && success(); + }; + + // db initialize + // this is Android-specific for now + var version = device.version.split('.')[0]; + switch(version) { + case '1': + self.upload_db_android1(done, error); + break; + case '2': + self.upload_db_android2(done, error); + break; + case '3': + default: + error && error('Błąd migracji: ' + err); + }; + }; + + + this.upload_db_android1 = function(success, error) { + console.log('upload db for Android 1.x'); + window.requestFileSystem(LocalFileSystem.APPLICATION, 0, function(fs) { + window.plugins.assetcopy.copy("initial/wolnelektury.db", + fs.root.fullPath + "/databases/wolnelektury.db", true, + function(data) { + console.log('db upload successful'); + success && success(); + }, function(data) { + error && error("database upload error: " + data); + }); + }, error); + }; + + + this.upload_db_android2 = function(success, error) { + console.log('upload db for Android 2.x'); + window.requestFileSystem(LocalFileSystem.APPLICATION, 0, function(fs) { + + // upload databases description file + window.plugins.assetcopy.copy("initial/Databases.db", + fs.root.fullPath + "/app_database/Databases.db", true, + function(data) { + console.log('db descriptior upload successful'); + + // upload the database file + window.plugins.assetcopy.copy("initial/0000000000000001.db", + fs.root.fullPath + "/app_database/file__0/0000000000000001.db", true, + function(data) { + console.log('db upload successful'); + success && success(); + }, function(data) { + error && error("database upload error: " + data); + }); + + + }, function(data) { + error && error("database descriptor upload error: " + data); + }); + + }, error); + }; + + + this.withBook = function(id, callback) { + console.log('withBook '+id) + self.db.transaction(function(tx) { + tx.executeSql("SELECT * FROM book WHERE id="+id, [], + function(tx, results) { + callback(results.rows.item(0)); + }); + }); + }; + + this.withBooks = function(ids, callback) { + console.log('withBooks ' + ids) + self.db.transaction(function(tx) { + tx.executeSql("SELECT * FROM book WHERE id IN ("+ids+") ORDER BY title", [], + function(tx, results) { + var items = []; + var count = results.rows.length; + for (var i=0; i + * + * @param name The plugin name + * @param obj The plugin object + */ +PhoneGap.addPlugin = function(name, obj) { + if (!window.plugins[name]) { + window.plugins[name] = obj; + } + else { + console.log("Error: Plugin "+name+" already exists."); + } +}; + +/** + * onDOMContentLoaded channel is fired when the DOM content + * of the page has been parsed. + */ +PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded'); + +/** + * onNativeReady channel is fired when the PhoneGap native code + * has been initialized. + */ +PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady'); + +/** + * onPhoneGapInit channel is fired when the web page is fully loaded and + * PhoneGap native code has been initialized. + */ +PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit'); + +/** + * onPhoneGapReady channel is fired when the JS PhoneGap objects have been created. + */ +PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady'); + +/** + * onPhoneGapInfoReady channel is fired when the PhoneGap device properties + * has been set. + */ +PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady'); + +/** + * onResume channel is fired when the PhoneGap native code + * resumes. + */ +PhoneGap.onResume = new PhoneGap.Channel('onResume'); + +/** + * onPause channel is fired when the PhoneGap native code + * pauses. + */ +PhoneGap.onPause = new PhoneGap.Channel('onPause'); + +/** + * onDestroy channel is fired when the PhoneGap native code + * is destroyed. It is used internally. + * Window.onunload should be used by the user. + */ +PhoneGap.onDestroy = new PhoneGap.Channel('onDestroy'); +PhoneGap.onDestroy.subscribeOnce(function() { + PhoneGap.shuttingDown = true; +}); +PhoneGap.shuttingDown = false; + +// _nativeReady is global variable that the native side can set +// to signify that the native code is ready. It is a global since +// it may be called before any PhoneGap JS is ready. +if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } + +/** + * onDeviceReady is fired only after all PhoneGap objects are created and + * the device properties are set. + */ +PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); + + +// Array of channels that must fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsArray = [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady]; + +// Hashtable of user defined channels that must also fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsMap = {}; + +/** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up PhoneGap's "deviceready" event until the feature has been initialized + * and PhoneGap.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ +PhoneGap.waitForInitialization = function(feature) { + if (feature) { + var channel = new PhoneGap.Channel(feature); + PhoneGap.deviceReadyChannelsMap[feature] = channel; + PhoneGap.deviceReadyChannelsArray.push(channel); + } +}; + +/** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ +PhoneGap.initializationComplete = function(feature) { + var channel = PhoneGap.deviceReadyChannelsMap[feature]; + if (channel) { + channel.fire(); + } +}; + +/** + * Create all PhoneGap objects once page has fully loaded and native side is ready. + */ +PhoneGap.Channel.join(function() { + + // Start listening for XHR callbacks + setTimeout(function() { + if (PhoneGap.UsePolling) { + PhoneGap.JSCallbackPolling(); + } + else { + var polling = prompt("usePolling", "gap_callbackServer:"); + if (polling == "true") { + PhoneGap.JSCallbackPolling(); + } + else { + PhoneGap.JSCallback(); + } + } + }, 1); + + // Run PhoneGap constructors + PhoneGap.onPhoneGapInit.fire(); + + // Fire event to notify that all objects are created + PhoneGap.onPhoneGapReady.fire(); + + // Fire onDeviceReady event once all constructors have run and PhoneGap info has been + // received from native side, and any user defined initialization channels. + PhoneGap.Channel.join(function() { + + // Turn off app loading dialog + navigator.notification.activityStop(); + + PhoneGap.onDeviceReady.fire(); + + // Fire the onresume event, since first one happens before JavaScript is loaded + PhoneGap.onResume.fire(); + }, PhoneGap.deviceReadyChannelsArray); + +}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); + +// Listen for DOMContentLoaded and notify our channel subscribers +document.addEventListener('DOMContentLoaded', function() { + PhoneGap.onDOMContentLoaded.fire(); +}, false); + +// Intercept calls to document.addEventListener and watch for deviceready +PhoneGap.m_document_addEventListener = document.addEventListener; + +document.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (e === 'deviceready') { + PhoneGap.onDeviceReady.subscribeOnce(handler); + } else if (e === 'resume') { + PhoneGap.onResume.subscribe(handler); + if (PhoneGap.onDeviceReady.fired) { + PhoneGap.onResume.fire(); + } + } else if (e === 'pause') { + PhoneGap.onPause.subscribe(handler); + } + else { + // If subscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]); + } + + PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +// Intercept calls to document.removeEventListener and watch for events that +// are generated by PhoneGap native code +PhoneGap.m_document_removeEventListener = document.removeEventListener; + +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]); + } + + PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture); +}; + +/** + * Method to fire event from native code + */ +PhoneGap.fireEvent = function(type) { + var e = document.createEvent('Events'); + e.initEvent(type); + document.dispatchEvent(e); +}; + +/** + * If JSON not included, use our own stringify. (Android 1.6) + * The restriction on ours is that it must be an array of simple types. + * + * @param args + * @return + */ +PhoneGap.stringify = function(args) { + if (typeof JSON === "undefined") { + var s = "["; + var i, type, start, name, nameType, a; + for (i = 0; i < args.length; i++) { + if (args[i] != null) { + if (i > 0) { + s = s + ","; + } + type = typeof args[i]; + if ((type === "number") || (type === "boolean")) { + s = s + args[i]; + } else if (args[i] instanceof Array) { + s = s + "[" + args[i] + "]"; + } else if (args[i] instanceof Object) { + start = true; + s = s + '{'; + for (name in args[i]) { + if (args[i][name] !== null) { + if (!start) { + s = s + ','; + } + s = s + '"' + name + '":'; + nameType = typeof args[i][name]; + if ((nameType === "number") || (nameType === "boolean")) { + s = s + args[i][name]; + } else if ((typeof args[i][name]) === 'function') { + // don't copy the functions + s = s + '""'; + } else if (args[i][name] instanceof Object) { + s = s + this.stringify(args[i][name]); + } else { + s = s + '"' + args[i][name] + '"'; + } + start = false; + } + } + s = s + '}'; + } else { + a = args[i].replace(/\\/g, '\\\\'); + a = encodeURIComponent(a).replace(/%([0-9A-F]{2})/g, "\\x$1"); + //a = a.replace(/\n/g, '\\n'); + //a = a.replace(/"/g, '\\"'); + s = s + '"' + a + '"'; + } + } + } + s = s + "]"; + return s; + } else { + return JSON.stringify(args); + } +}; + +/** + * Does a deep clone of the object. + * + * @param obj + * @return + */ +PhoneGap.clone = function(obj) { + var i, retVal; + if(!obj) { + return obj; + } + + if(obj instanceof Array){ + retVal = []; + for(i = 0; i < obj.length; ++i){ + retVal.push(PhoneGap.clone(obj[i])); + } + return retVal; + } + + if (obj instanceof Function) { + return obj; + } + + if(!(obj instanceof Object)){ + return obj; + } + + if (obj instanceof Date) { + return obj; + } + + retVal = {}; + for(i in obj){ + if(!(i in retVal) || retVal[i] !== obj[i]) { + retVal[i] = PhoneGap.clone(obj[i]); + } + } + return retVal; +}; + +PhoneGap.callbackId = 0; +PhoneGap.callbacks = {}; +PhoneGap.callbackStatus = { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }; + + +/** + * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. + * The native side can return: + * Synchronous: PluginResult object as a JSON string + * Asynchrounous: Empty string "" + * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, + * depending upon the result of the action. + * + * @param {Function} success The success callback + * @param {Function} fail The fail callback + * @param {String} service The name of the service to use + * @param {String} action Action to be run in PhoneGap + * @param {String[]} [args] Zero or more arguments to pass to the method + */ +PhoneGap.exec = function(success, fail, service, action, args) { + try { + var callbackId = service + PhoneGap.callbackId++; + if (success || fail) { + PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; + } + + var r = prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); + + // If a result was returned + if (r.length > 0) { + eval("var v="+r+";"); + + // If status is OK, then return value back to caller + if (v.status === PhoneGap.callbackStatus.OK) { + + // If there is a success callback, then call it now with + // returned value + if (success) { + try { + success(v.message); + } catch (e) { + console.log("Error in success callback: " + callbackId + " = " + e); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + return v.message; + } + + // If no result + else if (v.status === PhoneGap.callbackStatus.NO_RESULT) { + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + + // If error, then display error + else { + console.log("Error: Status="+v.status+" Message="+v.message); + + // If there is a fail callback, then call it now with returned value + if (fail) { + try { + fail(v.message); + } + catch (e1) { + console.log("Error in error callback: "+callbackId+" = "+e1); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + return null; + } + } + } catch (e2) { + console.log("Error: "+e2); + } +}; + +/** + * Called by native code when returning successful result from an action. + * + * @param callbackId + * @param args + */ +PhoneGap.callbackSuccess = function(callbackId, args) { + if (PhoneGap.callbacks[callbackId]) { + + // If result is to be sent to callback + if (args.status === PhoneGap.callbackStatus.OK) { + try { + if (PhoneGap.callbacks[callbackId].success) { + PhoneGap.callbacks[callbackId].success(args.message); + } + } + catch (e) { + console.log("Error in success callback: "+callbackId+" = "+e); + } + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } +}; + +/** + * Called by native code when returning error result from an action. + * + * @param callbackId + * @param args + */ +PhoneGap.callbackError = function(callbackId, args) { + if (PhoneGap.callbacks[callbackId]) { + try { + if (PhoneGap.callbacks[callbackId].fail) { + PhoneGap.callbacks[callbackId].fail(args.message); + } + } + catch (e) { + console.log("Error in error callback: "+callbackId+" = "+e); + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } +}; + + +/** + * Internal function used to dispatch the request to PhoneGap. It processes the + * command queue and executes the next command on the list. If one of the + * arguments is a JavaScript object, it will be passed on the QueryString of the + * url, which will be turned into a dictionary on the other end. + * @private + */ +// TODO: Is this used? +PhoneGap.run_command = function() { + if (!PhoneGap.available || !PhoneGap.queue.ready) { + return; + } + PhoneGap.queue.ready = false; + + var args = PhoneGap.queue.commands.shift(); + if (PhoneGap.queue.commands.length === 0) { + clearInterval(PhoneGap.queue.timer); + PhoneGap.queue.timer = null; + } + + var uri = []; + var dict = null; + var i; + for (i = 1; i < args.length; i++) { + var arg = args[i]; + if (arg === undefined || arg === null) { + arg = ''; + } + if (typeof(arg) === 'object') { + dict = arg; + } else { + uri.push(encodeURIComponent(arg)); + } + } + var url = "gap://" + args[0] + "/" + uri.join("/"); + if (dict !== null) { + var name; + var query_args = []; + for (name in dict) { + if (dict.hasOwnProperty(name) && (typeof (name) === 'string')) { + query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name])); + } + } + if (query_args.length > 0) { + url += "?" + query_args.join("&"); + } + } + document.location = url; + +}; + +PhoneGap.JSCallbackPort = null; +PhoneGap.JSCallbackToken = null; + +/** + * This is only for Android. + * + * Internal function that uses XHR to call into PhoneGap Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +PhoneGap.JSCallback = function() { + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If polling flag was changed, start using polling from now on + if (PhoneGap.UsePolling) { + PhoneGap.JSCallbackPolling(); + return; + } + + var xmlhttp = new XMLHttpRequest(); + + // Callback function when XMLHttpRequest is ready + xmlhttp.onreadystatechange=function(){ + if(xmlhttp.readyState === 4){ + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If callback has JavaScript statement to execute + if (xmlhttp.status === 200) { + + var msg = xmlhttp.responseText; + setTimeout(function() { + try { + var t = eval(msg); + } + catch (e) { + // If we're getting an error here, seeing the message will help in debugging + console.log("JSCallback: Message from Server: " + msg); + console.log("JSCallback Error: "+e); + } + }, 1); + setTimeout(PhoneGap.JSCallback, 1); + } + + // If callback ping (used to keep XHR request from timing out) + else if (xmlhttp.status === 404) { + setTimeout(PhoneGap.JSCallback, 10); + } + + // If security error + else if (xmlhttp.status === 403) { + console.log("JSCallback Error: Invalid token. Stopping callbacks."); + } + + // If server is stopping + else if (xmlhttp.status === 503) { + console.log("JSCallback Error: Service unavailable. Stopping callbacks."); + } + + // If request wasn't GET + else if (xmlhttp.status === 400) { + console.log("JSCallback Error: Bad request. Stopping callbacks."); + } + + // If error, restart callback server + else { + console.log("JSCallback Error: Request failed."); + prompt("restartServer", "gap_callbackServer:"); + PhoneGap.JSCallbackPort = null; + PhoneGap.JSCallbackToken = null; + setTimeout(PhoneGap.JSCallback, 100); + } + } + }; + + if (PhoneGap.JSCallbackPort === null) { + PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:"); + } + if (PhoneGap.JSCallbackToken === null) { + PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:"); + } + xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true); + xmlhttp.send(); +}; + +/** + * The polling period to use with JSCallbackPolling. + * This can be changed by the application. The default is 50ms. + */ +PhoneGap.JSCallbackPollingPeriod = 50; + +/** + * Flag that can be set by the user to force polling to be used or force XHR to be used. + */ +PhoneGap.UsePolling = false; // T=use polling, F=use XHR + +/** + * This is only for Android. + * + * Internal function that uses polling to call into PhoneGap Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +PhoneGap.JSCallbackPolling = function() { + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If polling flag was changed, stop using polling from now on + if (!PhoneGap.UsePolling) { + PhoneGap.JSCallback(); + return; + } + + var msg = prompt("", "gap_poll:"); + if (msg) { + setTimeout(function() { + try { + var t = eval(""+msg); + } + catch (e) { + console.log("JSCallbackPolling: Message from Server: " + msg); + console.log("JSCallbackPolling Error: "+e); + } + }, 1); + setTimeout(PhoneGap.JSCallbackPolling, 1); + } + else { + setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); + } +}; + +/** + * Create a UUID + * + * @return + */ +PhoneGap.createUUID = function() { + return PhoneGap.UUIDcreatePart(4) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(6); +}; + +PhoneGap.UUIDcreatePart = function(length) { + var uuidpart = ""; + var i, uuidchar; + for (i=0; i frequency + 10 sec + PhoneGap.exec( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Accelerometer", "getTimeout", []); + + // Start watch timer + var id = PhoneGap.createUUID(); + navigator.accelerometer.timers[id] = setInterval(function() { + PhoneGap.exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); + }, (frequency ? frequency : 1)); + + return id; +}; + +/** + * Clears the specified accelerometer watch. + * + * @param {String} id The id of the watch returned from #watchAcceleration. + */ +Accelerometer.prototype.clearWatch = function(id) { + + // Stop javascript timer & remove from timer list + if (id && navigator.accelerometer.timers[id] !== undefined) { + clearInterval(navigator.accelerometer.timers[id]); + delete navigator.accelerometer.timers[id]; + } +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.accelerometer === "undefined") { + navigator.accelerometer = new Accelerometer(); + } +}); +}; +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010-2011, IBM Corporation + */ + +if (!PhoneGap.hasResource("app")) { +PhoneGap.addResource("app"); + +/** + * Constructor + */ +App = function() {}; + +/** + * Clear the resource cache. + */ +App.prototype.clearCache = function() { + PhoneGap.exec(null, null, "App", "clearCache", []); +}; + +/** + * Load the url into the webview. + * + * @param url The URL to load + * @param props Properties that can be passed in to the activity: + * wait: int => wait msec before loading URL + * loadingDialog: "Title,Message" => display a native loading dialog + * hideLoadingDialogOnPage: boolean => hide loadingDialog when page loaded instead of when deviceready event occurs. + * loadInWebView: boolean => cause all links on web page to be loaded into existing web view, instead of being loaded into new browser. + * loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error + * errorUrl: URL => URL to load if there's an error loading specified URL with loadUrl(). Should be a local URL such as file:///android_asset/www/error.html"); + * keepRunning: boolean => enable app to keep running in background + * + * Example: + * App app = new App(); + * app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); + */ +App.prototype.loadUrl = function(url, props) { + PhoneGap.exec(null, null, "App", "loadUrl", [url, props]); +}; + +/** + * Cancel loadUrl that is waiting to be loaded. + */ +App.prototype.cancelLoadUrl = function() { + PhoneGap.exec(null, null, "App", "cancelLoadUrl", []); +}; + +/** + * Clear web history in this web view. + * Instead of BACK button loading the previous web page, it will exit the app. + */ +App.prototype.clearHistory = function() { + PhoneGap.exec(null, null, "App", "clearHistory", []); +}; + +/** + * Add a class that implements a service. + * + * @param serviceType + * @param className + */ +App.prototype.addService = function(serviceType, className) { + PhoneGap.exec(null, null, "App", "addService", [serviceType, className]); +}; + +/** + * Override the default behavior of the Android back button. + * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. + * + * Note: The user should not have to call this method. Instead, when the user + * registers for the "backbutton" event, this is automatically done. + * + * @param override T=override, F=cancel override + */ +App.prototype.overrideBackbutton = function(override) { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [override]); +}; + +/** + * Exit and terminate the application. + */ +App.prototype.exitApp = function() { + return PhoneGap.exec(null, null, "App", "exitApp", []); +}; + +PhoneGap.addConstructor(function() { + navigator.app = window.app = new App(); +}); +}; +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010, IBM Corporation + */ + +if (!PhoneGap.hasResource("camera")) { +PhoneGap.addResource("camera"); + +/** + * This class provides access to the device camera. + * + * @constructor + */ +Camera = function() { + this.successCallback = null; + this.errorCallback = null; + this.options = null; +}; + +/** + * Format of image that returned from getPicture. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.DestinationType = { + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) +}; +Camera.prototype.DestinationType = Camera.DestinationType; + +/** + * Source to getPicture from. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.PictureSourceType = { + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) +}; +Camera.prototype.PictureSourceType = Camera.PictureSourceType; + +/** + * Gets a picture from source defined by "options.sourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are sourceType=CAMERA and destinationType=DATA_URL. + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +Camera.prototype.getPicture = function(successCallback, errorCallback, options) { + + // successCallback required + if (typeof successCallback !== "function") { + console.log("Camera Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Camera Error: errorCallback is not a function"); + return; + } + + this.options = options; + var quality = 80; + if (options.quality) { + quality = this.options.quality; + } + var destinationType = Camera.DestinationType.DATA_URL; + if (this.options.destinationType) { + destinationType = this.options.destinationType; + } + var sourceType = Camera.PictureSourceType.CAMERA; + if (typeof this.options.sourceType === "number") { + sourceType = this.options.sourceType; + } + PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType]); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.camera === "undefined") { + navigator.camera = new Camera(); + } +}); +}; +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010-2011, IBM Corporation + */ + +/** + * The CaptureError interface encapsulates all errors in the Capture API. + */ +function CaptureError() { + this.code = null; +}; + +// Capture error codes +CaptureError.CAPTURE_INTERNAL_ERR = 0; +CaptureError.CAPTURE_APPLICATION_BUSY = 1; +CaptureError.CAPTURE_INVALID_ARGUMENT = 2; +CaptureError.CAPTURE_NO_MEDIA_FILES = 3; +CaptureError.CAPTURE_NOT_SUPPORTED = 20; + +/** + * The Capture interface exposes an interface to the camera and microphone of the hosting device. + */ +function Capture() { + this.supportedAudioFormats = []; + this.supportedImageFormats = []; + this.supportedVideoFormats = []; +}; + +/** + * Launch audio recorder application for recording audio clip(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureAudioOptions} options + */ +Capture.prototype.captureAudio = function(successCallback, errorCallback, options) { + PhoneGap.exec(successCallback, errorCallback, "Capture", "captureAudio", [options]); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype.captureImage = function(successCallback, errorCallback, options) { + PhoneGap.exec(successCallback, errorCallback, "Capture", "captureImage", [options]); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype._castMediaFile = function(pluginResult) { + var mediaFiles = []; + var i; + for (i=0; i frequency + 10 sec + PhoneGap.exec( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.exec(null, null, "Compass", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Compass", "getTimeout", []); + + // Start watch timer to get headings + var id = PhoneGap.createUUID(); + navigator.compass.timers[id] = setInterval( + function() { + PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); + }, (frequency ? frequency : 1)); + + return id; +}; + + +/** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchHeading. + */ +Compass.prototype.clearWatch = function(id) { + + // Stop javascript timer & remove from timer list + if (id && navigator.compass.timers[id]) { + clearInterval(navigator.compass.timers[id]); + delete navigator.compass.timers[id]; + } +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.compass === "undefined") { + navigator.compass = new Compass(); + } +}); +}; +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010, IBM Corporation + */ + +if (!PhoneGap.hasResource("contact")) { +PhoneGap.addResource("contact"); + +/** +* Contains information about a single contact. +* @param {DOMString} id unique identifier +* @param {DOMString} displayName +* @param {ContactName} name +* @param {DOMString} nickname +* @param {ContactField[]} phoneNumbers array of phone numbers +* @param {ContactField[]} emails array of email addresses +* @param {ContactAddress[]} addresses array of addresses +* @param {ContactField[]} ims instant messaging user ids +* @param {ContactOrganization[]} organizations +* @param {DOMString} revision date contact was last updated +* @param {DOMString} birthday contact's birthday +* @param {DOMString} gender contact's gender +* @param {DOMString} note user notes about contact +* @param {ContactField[]} photos +* @param {ContactField[]} categories +* @param {ContactField[]} urls contact's web sites +* @param {DOMString} timezone the contacts time zone +*/ +var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, + ims, organizations, revision, birthday, gender, note, photos, categories, urls, timezone) { + this.id = id || null; + this.rawId = null; + this.displayName = displayName || null; + this.name = name || null; // ContactName + this.nickname = nickname || null; + this.phoneNumbers = phoneNumbers || null; // ContactField[] + this.emails = emails || null; // ContactField[] + this.addresses = addresses || null; // ContactAddress[] + this.ims = ims || null; // ContactField[] + this.organizations = organizations || null; // ContactOrganization[] + this.revision = revision || null; + this.birthday = birthday || null; + this.gender = gender || null; + this.note = note || null; + this.photos = photos || null; // ContactField[] + this.categories = categories || null; // ContactField[] + this.urls = urls || null; // ContactField[] + this.timezone = timezone || null; +}; + +/** + * ContactError. + * An error code assigned by an implementation when an error has occurred + */ +var ContactError = function() { + this.code=null; +}; + +/** + * Error codes + */ +ContactError.UNKNOWN_ERROR = 0; +ContactError.INVALID_ARGUMENT_ERROR = 1; +ContactError.NOT_FOUND_ERROR = 2; +ContactError.TIMEOUT_ERROR = 3; +ContactError.PENDING_OPERATION_ERROR = 4; +ContactError.IO_ERROR = 5; +ContactError.NOT_SUPPORTED_ERROR = 6; +ContactError.PERMISSION_DENIED_ERROR = 20; + +/** +* Removes contact from device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.remove = function(successCB, errorCB) { + if (this.id === null) { + var errorObj = new ContactError(); + errorObj.code = ContactError.NOT_FOUND_ERROR; + errorCB(errorObj); + } + else { + PhoneGap.exec(successCB, errorCB, "Contacts", "remove", [this.id]); + } +}; + +/** +* Creates a deep copy of this Contact. +* With the contact ID set to null. +* @return copy of this Contact +*/ +Contact.prototype.clone = function() { + var clonedContact = PhoneGap.clone(this); + var i; + clonedContact.id = null; + clonedContact.rawId = null; + // Loop through and clear out any id's in phones, emails, etc. + if (clonedContact.phoneNumbers) { + for (i = 0; i < clonedContact.phoneNumbers.length; i++) { + clonedContact.phoneNumbers[i].id = null; + } + } + if (clonedContact.emails) { + for (i = 0; i < clonedContact.emails.length; i++) { + clonedContact.emails[i].id = null; + } + } + if (clonedContact.addresses) { + for (i = 0; i < clonedContact.addresses.length; i++) { + clonedContact.addresses[i].id = null; + } + } + if (clonedContact.ims) { + for (i = 0; i < clonedContact.ims.length; i++) { + clonedContact.ims[i].id = null; + } + } + if (clonedContact.organizations) { + for (i = 0; i < clonedContact.organizations.length; i++) { + clonedContact.organizations[i].id = null; + } + } + if (clonedContact.tags) { + for (i = 0; i < clonedContact.tags.length; i++) { + clonedContact.tags[i].id = null; + } + } + if (clonedContact.photos) { + for (i = 0; i < clonedContact.photos.length; i++) { + clonedContact.photos[i].id = null; + } + } + if (clonedContact.urls) { + for (i = 0; i < clonedContact.urls.length; i++) { + clonedContact.urls[i].id = null; + } + } + return clonedContact; +}; + +/** +* Persists contact to device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.save = function(successCB, errorCB) { + PhoneGap.exec(successCB, errorCB, "Contacts", "save", [this]); +}; + +/** +* Contact name. +* @param formatted +* @param familyName +* @param givenName +* @param middle +* @param prefix +* @param suffix +*/ +var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { + this.formatted = formatted || null; + this.familyName = familyName || null; + this.givenName = givenName || null; + this.middleName = middle || null; + this.honorificPrefix = prefix || null; + this.honorificSuffix = suffix || null; +}; + +/** +* Generic contact field. +* @param {DOMString} id unique identifier, should only be set by native code +* @param type +* @param value +* @param pref +*/ +var ContactField = function(type, value, pref) { + this.id = null; + this.type = type || null; + this.value = value || null; + this.pref = pref || null; +}; + +/** +* Contact address. +* @param {DOMString} id unique identifier, should only be set by native code +* @param formatted +* @param streetAddress +* @param locality +* @param region +* @param postalCode +* @param country +*/ +var ContactAddress = function(formatted, streetAddress, locality, region, postalCode, country) { + this.id = null; + this.formatted = formatted || null; + this.streetAddress = streetAddress || null; + this.locality = locality || null; + this.region = region || null; + this.postalCode = postalCode || null; + this.country = country || null; +}; + +/** +* Contact organization. +* @param {DOMString} id unique identifier, should only be set by native code +* @param name +* @param dept +* @param title +* @param startDate +* @param endDate +* @param location +* @param desc +*/ +var ContactOrganization = function(name, dept, title) { + this.id = null; + this.name = name || null; + this.department = dept || null; + this.title = title || null; +}; + +/** +* Represents a group of Contacts. +*/ +var Contacts = function() { + this.inProgress = false; + this.records = []; +}; +/** +* Returns an array of Contacts matching the search criteria. +* @param fields that should be searched +* @param successCB success callback +* @param errorCB error callback +* @param {ContactFindOptions} options that can be applied to contact searching +* @return array of Contacts matching search criteria +*/ +Contacts.prototype.find = function(fields, successCB, errorCB, options) { + PhoneGap.exec(successCB, errorCB, "Contacts", "search", [fields, options]); +}; + +/** +* This function creates a new contact, but it does not persist the contact +* to device storage. To persist the contact to device storage, invoke +* contact.save(). +* @param properties an object who's properties will be examined to create a new Contact +* @returns new Contact object +*/ +Contacts.prototype.create = function(properties) { + var i; + var contact = new Contact(); + for (i in properties) { + if (contact[i] !== 'undefined') { + contact[i] = properties[i]; + } + } + return contact; +}; + +/** +* This function returns and array of contacts. It is required as we need to convert raw +* JSON objects into concrete Contact objects. Currently this method is called after +* navigator.service.contacts.find but before the find methods success call back. +* +* @param jsonArray an array of JSON Objects that need to be converted to Contact objects. +* @returns an array of Contact objects +*/ +Contacts.prototype.cast = function(pluginResult) { + var contacts = []; + var i; + for (i=0; i][;base64], + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsDataURL = function(file) { + this.fileName = ""; + if (typeof file.fullPath === "undefined") { + this.fileName = file; + } else { + this.fileName = file.fullPath; + } + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart === "function") { + this.onloadstart({"type":"loadstart", "target":this}); + } + + var me = this; + + // Read file + navigator.fileMgr.readAsDataURL(this.fileName, + + // Success callback + function(r) { + var evt; + + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload({"type":"load", "target":me}); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend({"type":"loadend", "target":me}); + } + }, + + // Error callback + function(e) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // Save error + var fileError = new FileError(); + fileError.code = e; + me.error = fileError; + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror({"type":"error", "target":me}); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend({"type":"loadend", "target":me}); + } + } + ); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsBinaryString = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsArrayBuffer = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; + +//----------------------------------------------------------------------------- +// File Writer +//----------------------------------------------------------------------------- + +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + * + * @param file {File} File object containing file properties + * @param append if true write to the end of the file, otherwise overwrite the file + */ +FileWriter = function(file) { + this.fileName = ""; + this.length = 0; + if (file) { + this.fileName = file.fullPath || file; + this.length = file.size || 0; + } + // default is to write at the beginning of the file + this.position = 0; + + this.readyState = 0; // EMPTY + + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onwritestart = null; // When writing starts + this.onprogress = null; // While writing the file, and reporting partial file data + this.onwrite = null; // When the write has successfully completed. + this.onwriteend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. + this.onerror = null; // When the write has failed (see errors). +}; + +// States +FileWriter.INIT = 0; +FileWriter.WRITING = 1; +FileWriter.DONE = 2; + +/** + * Abort writing file. + */ +FileWriter.prototype.abort = function() { + // check for invalid state + if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { + throw FileError.INVALID_STATE_ERR; + } + + // set error + var error = new FileError(), evt; + error.code = error.ABORT_ERR; + this.error = error; + + // If error callback + if (typeof this.onerror === "function") { + this.onerror({"type":"error", "target":this}); + } + // If abort callback + if (typeof this.onabort === "function") { + this.oneabort({"type":"abort", "target":this}); + } + + this.readyState = FileWriter.DONE; + + // If write end callback + if (typeof this.onwriteend == "function") { + this.onwriteend({"type":"writeend", "target":this}); + } +}; + +/** + * Writes data to the file + * + * @param text to be written + */ +FileWriter.prototype.write = function(text) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart({"type":"writestart", "target":me}); + } + + // Write file + navigator.fileMgr.write(this.fileName, text, this.position, + + // Success callback + function(r) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // So if the user wants to keep appending to the file + me.length = Math.max(me.length, me.position + r); + // position always increases by bytes written because file would be extended + me.position += r; + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite({"type":"write", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, + + // Error callback + function(e) { + var evt; + + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // Save error + var fileError = new FileError(); + fileError.code = e; + me.error = fileError; + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror({"type":"error", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + } + ); + +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + if (!offset) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger then file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart({"type":"writestart", "target":this}); + } + + // Write file + navigator.fileMgr.truncate(this.fileName, size, + + // Success callback + function(r) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r); + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite({"type":"write", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, + + // Error callback + function(e) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // Save error + var fileError = new FileError(); + fileError.code = e; + me.error = fileError; + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror({"type":"error", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + } + ); +}; + +LocalFileSystem = function() { +}; + +// File error codes +LocalFileSystem.TEMPORARY = 0; +LocalFileSystem.PERSISTENT = 1; +LocalFileSystem.RESOURCE = 2; +LocalFileSystem.APPLICATION = 3; + +/** + * Requests a filesystem in which to store application data. + * + * @param {int} type of file system being requested + * @param {Function} successCallback is called with the new FileSystem + * @param {Function} errorCallback is called with a FileError + */ +LocalFileSystem.prototype.requestFileSystem = function(type, size, successCallback, errorCallback) { + if (type < 0 || type > 3) { + if (typeof errorCallback == "function") { + errorCallback({ + "code": FileError.SYNTAX_ERR + }); + } + } + else { + PhoneGap.exec(successCallback, errorCallback, "File", "requestFileSystem", [type, size]); + } +}; + +/** + * + * @param {DOMString} uri referring to a local file in a filesystem + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +LocalFileSystem.prototype.resolveLocalFileSystemURI = function(uri, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "resolveLocalFileSystemURI", [uri]); +}; + +/** +* This function returns and array of contacts. It is required as we need to convert raw +* JSON objects into concrete Contact objects. Currently this method is called after +* navigator.service.contacts.find but before the find methods success call back. +* +* @param a JSON Objects that need to be converted to DirectoryEntry or FileEntry objects. +* @returns an entry +*/ +LocalFileSystem.prototype._castFS = function(pluginResult) { + var entry = null; + entry = new DirectoryEntry(); + entry.isDirectory = pluginResult.message.root.isDirectory; + entry.isFile = pluginResult.message.root.isFile; + entry.name = pluginResult.message.root.name; + entry.fullPath = pluginResult.message.root.fullPath; + pluginResult.message.root = entry; + return pluginResult; +} + +LocalFileSystem.prototype._castEntry = function(pluginResult) { + var entry = null; + if (pluginResult.message.isDirectory) { + console.log("This is a dir"); + entry = new DirectoryEntry(); + } + else if (pluginResult.message.isFile) { + console.log("This is a file"); + entry = new FileEntry(); + } + entry.isDirectory = pluginResult.message.isDirectory; + entry.isFile = pluginResult.message.isFile; + entry.name = pluginResult.message.name; + entry.fullPath = pluginResult.message.fullPath; + pluginResult.message = entry; + return pluginResult; +} + +LocalFileSystem.prototype._castEntries = function(pluginResult) { + var entries = pluginResult.message; + var retVal = []; + for (i=0; i; +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..d76c57a Binary files /dev/null and b/res/drawable-hdpi/icon.png differ diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..0f2180d Binary files /dev/null and b/res/drawable-ldpi/icon.png differ diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..128dcce Binary files /dev/null and b/res/drawable-mdpi/icon.png differ diff --git a/res/layout/main.xml b/res/layout/main.xml new file mode 100644 index 0000000..3a5f117 --- /dev/null +++ b/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..f2d4fb8 --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Hello World, Catalogue! + Wolne Lektury + diff --git a/src/pl/org/nowoczesnapolska/wlmobi/AssetCopy.java b/src/pl/org/nowoczesnapolska/wlmobi/AssetCopy.java new file mode 100644 index 0000000..89f970a --- /dev/null +++ b/src/pl/org/nowoczesnapolska/wlmobi/AssetCopy.java @@ -0,0 +1,76 @@ +package pl.org.nowoczesnapolska.wlmobi; + +/* + @author Radek Czajka + */ + +import org.json.JSONArray; +import org.json.JSONException; + +import android.util.Log; +import android.content.res.AssetManager; + +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; + +public class AssetCopy extends Plugin{ + + @Override + public PluginResult execute(String action, JSONArray args, String callbackId) { + if (action.equals("copy")) { + try { + return this.copy(args.getString(0), args.getString(1), args.getString(2)); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.ERROR, "Param errrors"); + } + } + else { + return new PluginResult(PluginResult.Status.INVALID_ACTION); + } + } + + private PluginResult copy(String assetPath, String targetPath, String overwrite) { + int index = targetPath.lastIndexOf('/'); + String targetDir = targetPath.substring(0, index); + + try { + File dir = new File(targetDir); + if(!dir.exists()) { + Log.d("AssetCopy", "directory " + targetDir + " created"); + dir.mkdirs(); + } + + File fout = new File(targetPath); + + if(overwrite.equals("false") && fout.exists()) { + Log.d("AssetCopy", "File already exists"); + return new PluginResult(PluginResult.Status.OK, "exist"); + } + + FileOutputStream fos = new FileOutputStream(fout); + + AssetManager assetManager = this.ctx.getResources().getAssets(); + InputStream is = assetManager.open(assetPath); + + byte[] buffer = new byte[1024]; + int len1 = 0; + + while ( (len1 = is.read(buffer)) > 0 ) { + fos.write(buffer,0, len1); + } + + fos.close(); + + Log.d("AssetCopy", "Copied to " + targetPath); + } catch (IOException e) { + Log.d("AssetCopy", "Error: " + e); + return new PluginResult(PluginResult.Status.ERROR, "Error: " + e); + } + return new PluginResult(PluginResult.Status.OK, targetPath); + } +} \ No newline at end of file diff --git a/src/pl/org/nowoczesnapolska/wlmobi/Catalogue.java b/src/pl/org/nowoczesnapolska/wlmobi/Catalogue.java new file mode 100644 index 0000000..f8d69ec --- /dev/null +++ b/src/pl/org/nowoczesnapolska/wlmobi/Catalogue.java @@ -0,0 +1,13 @@ +package pl.org.nowoczesnapolska.wlmobi; + +import android.os.Bundle; +import com.phonegap.*; + +public class Catalogue extends DroidGap { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + super.loadUrl("file:///android_asset/www/index.html"); + } +} \ No newline at end of file diff --git a/src/pl/org/nowoczesnapolska/wlmobi/Downloader.java b/src/pl/org/nowoczesnapolska/wlmobi/Downloader.java new file mode 100644 index 0000000..be5a7e9 --- /dev/null +++ b/src/pl/org/nowoczesnapolska/wlmobi/Downloader.java @@ -0,0 +1,96 @@ +package pl.org.nowoczesnapolska.wlmobi; + +/* + @author Mauro Rocco http://www.toforge.com + + Radek Czajka: don't prepend /sdcard/ +*/ + +import org.json.JSONArray; +import org.json.JSONException; + +import android.util.Log; + +import com.phonegap.DroidGap; +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +public class Downloader extends Plugin{ + + @Override + public PluginResult execute(String action, JSONArray args, String callbackId) { + if (action.equals("downloadFile")) { + try { + return this.downloadUrl(args.getString(0),args.getString(1),args.getString(2),args.getString(3)); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.ERROR, "Param errrors"); + } + } + else { + return new PluginResult(PluginResult.Status.INVALID_ACTION); + } + + } + + private PluginResult downloadUrl(String fileUrl, String dirName, String fileName, String overwrite){ + try{ + Log.d("DownloaderPlugin", "DIRECTORY CALLED "+dirName+" created"); + File dir = new File(dirName); + if(!dir.exists()){ + Log.d("DownloaderPlugin", "directory "+dirName+" created"); + dir.mkdirs(); + } + + File file = new File(dirName+fileName); + + if(overwrite.equals("false") && file.exists()){ + Log.d("DownloaderPlugin", "File already exist"); + return new PluginResult(PluginResult.Status.OK, "exist"); + } + + URL url = new URL(fileUrl); + HttpURLConnection ucon = (HttpURLConnection) url.openConnection(); + ucon.setRequestMethod("GET"); + ucon.setDoOutput(true); + ucon.connect(); + + Log.d("DownloaderPlugin", "download begining"); + + Log.d("DownloaderPlugin", "download url:" + url); + + InputStream is = ucon.getInputStream(); + + byte[] buffer = new byte[1024]; + + int len1 = 0; + + FileOutputStream fos = new FileOutputStream(file); + + while ( (len1 = is.read(buffer)) > 0 ) { + fos.write(buffer, 0, len1); + //new String(buffer, "ISO8859_1").getBytes("UTF-8"), 0, len1); + } + + fos.close(); + + Log.d("DownloaderPlugin", "Download complete in" + fileName); + + } catch (IOException e) { + + Log.d("DownloaderPlugin", "Error: " + e); + return new PluginResult(PluginResult.Status.ERROR, "Error: " + e); + + } + + return new PluginResult(PluginResult.Status.OK, fileName); + + } + +} \ No newline at end of file