diff --git a/Gruntfile.js b/Gruntfile.js
index 178e0d6..f9c8786 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -99,6 +99,7 @@ module.exports = function(grunt) {
NetworkAPI: true,
InfoAPI: true,
ConfigAPI: true,
+ UpdateAPI:true,
addToHomescreen: true
},
browser: true,
diff --git a/js/BoxPage.js b/js/BoxPage.js
index 021c591..69be2ae 100644
--- a/js/BoxPage.js
+++ b/js/BoxPage.js
@@ -12,12 +12,13 @@
var _title;
var _intro;
var _drawItem;
- //var _updateItem;
+ var _updateItem;
var _joinNetworkItem;
var _defaultItems;
var _networkStatus;
var _networkAPI = new NetworkAPI();
+ var _updateAPI = new UpdateAPI();
var _boxData = {};
var _retryRetrieveStatusDelay;
var _retryRetrieveStatusDelayTime = 3000;
@@ -34,7 +35,7 @@
_defaultItems = _list.children();
_drawItem = _list.find("#drawItem");
- //_updateItem = _list.find("#updateItem");
+ _updateItem = _list.find("#updateItem");
_joinNetworkItem = _list.find("#joinNetworkItem");
// make sure draw link is opened in same WebApp (added to homescreen)
@@ -59,6 +60,8 @@
_networkAPI.init(boxURL);
retrieveNetworkStatus();
+ _updateAPI.init(boxURL);
+ retrieveUpdateStatus();
});
$.mobile.document.on( "pagebeforehide", PAGE_ID, function( event, data ) {
clearTimeout(_retryRetrieveStatusDelay);
@@ -74,7 +77,6 @@
_retryRetrieveStatusDelay = setTimeout(_self.retrieveStatus, _retryRetrieveStatusDelayTime); // retry after delay
});
}
-
function setNetworkStatus(status) {
console.log(PAGE_ID+":setNetworkStatus: ",status);
var introText = "";
@@ -85,7 +87,10 @@
// display the right buttons
_defaultItems.toggleClass("ui-screen-hidden",false);
_joinNetworkItem.toggleClass("ui-screen-hidden",true);
- // ToDo: retrieve update information
+
+ var updateLink = _updateItem.find("a").attr("href");
+ updateLink = d3d.util.replaceURLParameters(updateLink,_boxData);
+ _updateItem.find("a").attr("href",updateLink);
} else { // offline
//console.log("offline");
@@ -111,4 +116,16 @@
_list.listview('refresh'); // jQuery mobile enhance content
_networkStatus = status;
}
+
+ function retrieveUpdateStatus() {
+ console.log(PAGE_ID+":retrieveUpdateStatus");
+ var updateCounter = _list.find("#updateItem .ui-li-count");
+ updateCounter.hide();
+ _updateAPI.status(function(data) { // completed
+ console.log("UpdateAPI:refresh:completed");
+ var canUpdate = data.can_update;
+ updateCounter.text(canUpdate? 1 : 0);
+ updateCounter.show();
+ });
+ }
})(window);
\ No newline at end of file
diff --git a/js/UpdatePage.js b/js/UpdatePage.js
new file mode 100644
index 0000000..e802b52
--- /dev/null
+++ b/js/UpdatePage.js
@@ -0,0 +1,200 @@
+/*
+ * This file is part of the Doodle3D project (http://doodle3d.com).
+ *
+ * Copyright (c) 2013, Doodle3D
+ * This software is licensed under the terms of the GNU GPL v2 or later.
+ * See file LICENSE.txt or visit http://www.gnu.org/licenses/gpl.html for full license details.
+ */
+
+(function (w) {
+ var _page;
+ var _form;
+ var _statusField;
+ var _infoField;
+ var _retainConfigurationCheckbox;
+ var _includeBetasCheckbox;
+ var _submitButton;
+
+ var _updateAPI = new UpdateAPI();
+ var _configAPI = new ConfigAPI();
+ var _pageData = {};
+ var _updateStatus = {};
+
+ var PAGE_ID = "#update";
+
+ var _self = this;
+
+ $.mobile.document.on( "pageinit", PAGE_ID, function( event, data ) {
+ //console.log(PAGE_ID+":pageinit");
+ _page = $(this);
+ _statusField = _page.find("#status");
+ _infoField = _page.find("#info");
+ _form = _page.find("form");
+ _retainConfigurationCheckbox = _form.find("#retainConfiguration");
+ _includeBetasCheckbox = _form.find("#includeBetas");
+ _submitButton = _form.find("input[type=submit]");
+
+ _retainConfigurationCheckbox.change(retainConfigurationChanged);
+ _includeBetasCheckbox.change(includeBetasChanged);
+ _form.submit(update);
+
+ // make sure links in (checkbox) labels are clickable
+ _form.find("label a").click(function(event) {
+ event.stopPropagation();
+ });
+ });
+ $.mobile.document.on( "pagebeforeshow", PAGE_ID, function( event, data ) {
+ //console.log(PAGE_ID+":pagebeforeshow");
+ _pageData = d3d.util.getPageParams(PAGE_ID);
+ if(_pageData === undefined) {
+ $.mobile.changePage("#boxes");
+ return;
+ }
+ var boxURL = "http://"+_pageData.localip;
+
+ _statusField.text("");
+ _submitButton.button('disable');
+ _submitButton.val("Update");
+ _submitButton.button("refresh");
+ _infoField.html("");
+
+ _updateAPI.init(boxURL);
+ retrieveUpdateStatus();
+ _configAPI.init(boxURL);
+ });
+ $.mobile.document.on( "pagebeforehide", PAGE_ID, function( event, data ) {
+ //console.log(PAGE_ID+":pagebeforehide");
+ });
+
+ function retrieveUpdateStatus() {
+ console.log(PAGE_ID+":retrieveUpdateStatus");
+
+ _submitButton.button('disable');
+
+ _updateAPI.status(function(data) { // completed
+ console.log(PAGE_ID+":retrieveUpdateStatus:completed");
+ updatePage(data);
+ _updateStatus = data;
+ });
+ }
+ function updatePage(data) {
+ // Status
+ var status = "";
+ switch(data.state_code){
+ case UpdateAPI.STATUS.NONE:
+ if(data.can_update) {
+ status = "Update available";
+ } else {
+ status = "You're up to date.";
+ }
+ break;
+ case UpdateAPI.STATUS.DOWNLOADING:
+ status = "Downloading update...";
+ break;
+ case UpdateAPI.STATUS.DOWNLOAD_FAILED:
+ status = "Downloading update failed.";
+ break;
+ case UpdateAPI.STATUS.IMAGE_READY:
+ status = "Update downloaded.";
+ break;
+ case UpdateAPI.STATUS.INSTALLING:
+ status = "Installing update... ";
+ break;
+ case UpdateAPI.STATUS.INSTALLED:
+ status = "Update complete!";
+ break;
+ case UpdateAPI.STATUS.INSTALL_FAILED:
+ status = "Installing update failed.";
+ break;
+ }
+ _statusField.text(status);
+
+ // Button
+ updateButton(data);
+
+ // Info
+ var html = 'Current version: ' + data.current_version;
+ if (data.current_release_date) {
+ html += ' (released: ' + formatDate(data.current_release_date) + ')';
+ }
+ var localReleasenotes = "http://"+_pageData.localip+"/ReleaseNotes.html";
+ html += ' (release notes).';
+ if(data.can_update) {
+ html += '
Latest version: ' + data.newest_version;
+ if (data.newest_release_date) {
+ html += ' (released: ' + formatDate(data.newest_release_date) + ')';
+ }
+ html += ' (release notes).';
+ }
+ _infoField.html(html);
+ }
+ function updateButton(data) {
+ console.log(PAGE_ID+":updateButton");
+ var retain = _retainConfigurationCheckbox.prop('checked');
+
+ var buttonText = "Update";
+ _submitButton.button('disable');
+ switch(data.state_code) {
+ case UpdateAPI.STATUS.NONE:
+ case UpdateAPI.STATUS.IMAGE_READY:
+ case UpdateAPI.STATUS.DOWNLOAD_FAILED:
+ case UpdateAPI.STATUS.INSTALL_FAILED:
+ if(data.can_update || !retain) {
+ _submitButton.button('enable');
+ if (data.newest_version_is_beta) {
+ if(retain) {
+ buttonText = "Update to beta";
+ } else {
+ buttonText = "Clean update to beta";
+ }
+ } else if (data.current_version_is_beta && !data.newest_version_is_newer) {
+ if(retain) {
+ buttonText = "Revert to latest stable release";
+ } else {
+ buttonText = "Clean revert to latest stable release";
+ }
+ } else if (!retain){
+ if(data.newest_version_is_newer) {
+ buttonText = "Clean update";
+ } else {
+ buttonText = "Reinstall";
+ }
+ }
+ }
+ break;
+ }
+ _submitButton.val(buttonText);
+ _submitButton.button("refresh");
+ }
+ function formatDate(ts) {
+ if (!ts || ts.length !== 8 || !/^[0-9]+$/.test(ts)) { return null; }
+ var fields = [ ts.substr(0, 4), ts.substr(4, 2), ts.substr(6, 2) ];
+ if (!fields || fields.length !== 3 || fields[1] > 12) { return null; }
+
+ var abbrMonths = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Sep', 'Aug', 'Oct', 'Nov', 'Dec' ];
+ return abbrMonths[fields[1] - 1] + " " + fields[2] + ", " + fields[0];
+ }
+ function retainConfigurationChanged () {
+ console.log(PAGE_ID+":retainConfigurationChanged");
+ updateButton(_updateStatus);
+ }
+ function includeBetasChanged () {
+ console.log(PAGE_ID+":includeBetasChanged");
+
+ var settings = {};
+ settings[_includeBetasCheckbox.attr('name')] = _includeBetasCheckbox.prop('checked');
+ _configAPI.save(settings,function() {
+ console.log(" saved");
+ retrieveUpdateStatus();
+ });
+ }
+
+ function update() {
+ console.log(PAGE_ID+":update");
+ var submitLink = _form.data("target");
+ submitLink = d3d.util.replaceURLParameters(submitLink,_pageData);
+ $.mobile.changePage(submitLink);
+ return false;
+ }
+
+})(window);
\ No newline at end of file
diff --git a/js/UpdatingPage.js b/js/UpdatingPage.js
new file mode 100644
index 0000000..e32bd5a
--- /dev/null
+++ b/js/UpdatingPage.js
@@ -0,0 +1,154 @@
+/*
+ * This file is part of the Doodle3D project (http://doodle3d.com).
+ *
+ * Copyright (c) 2013, Doodle3D
+ * This software is licensed under the terms of the GNU GPL v2 or later.
+ * See file LICENSE.txt or visit http://www.gnu.org/licenses/gpl.html for full license details.
+ */
+
+(function (w) {
+ var _page;
+ var _statusField;
+ var _descriptionField;
+
+ var _updateAPI = new UpdateAPI();
+ var _configAPI = new ConfigAPI();
+ var _pageData = {};
+ var _formData = {};
+ var _updateStatus = {};
+ var _installing = false;
+ var UPDATED_REDIRECT_DELAY = 5000;
+ var _updatedRedirectDelay;
+
+ var PAGE_ID = "#updating";
+
+ var _self = this;
+
+ $.mobile.document.on( "pageinit", PAGE_ID, function( event, data ) {
+ //console.log(PAGE_ID+":pageinit");
+ _page = $(this);
+ _statusField = _page.find("#status");
+ _descriptionField = _page.find("#description");
+ });
+ $.mobile.document.on( "pagebeforeshow", PAGE_ID, function( event, data ) {
+ console.log(PAGE_ID+":pagebeforeshow");
+ _pageData = d3d.util.getPageParams(PAGE_ID);
+ var form = data.prevPage.find("form");
+ // check if there are url params and
+ // a form from a prev page
+ if(_pageData === undefined ||
+ form.length === 0) {
+ $.mobile.changePage("#boxes");
+ return;
+ }
+ _formData = d3d.util.getFormData(form);
+ var boxURL = "http://"+_pageData.localip;
+ _updateAPI.init(boxURL);
+ _updateAPI.refreshing = onRefreshing;
+ _updateAPI.updated = onStatusUpdated;
+
+ downloadUpdate();
+ _updateAPI.startAutoRefresh();
+ });
+ $.mobile.document.on( "pagebeforehide", PAGE_ID, function( event, data ) {
+ //console.log(PAGE_ID+":pagebeforehide");
+ _updateAPI.stopAutoRefresh();
+ clearTimeout(_updatedRedirectDelay);
+ });
+
+
+ function downloadUpdate() {
+ console.log(PAGE_ID+":downloadUpdate");
+ _updateAPI.download();
+ // override state
+ _updateStatus.state_code = UpdateAPI.STATUS.DOWNLOADING;
+ updatePage(_updateStatus);
+ }
+ function installUpdate() {
+ console.log(PAGE_ID+":installUpdate");
+ var no_retain = !(_formData.retain);
+ console.log(" no_retain: ",no_retain);
+ _updateAPI.install(no_retain);
+ _installing = true;
+ }
+
+
+ function onRefreshing() {
+ //console.log("ConnectingToNetworkPage:onRefreshing");
+ d3d.util.showLoader(true);
+ }
+ function onStatusUpdated(data) {
+ console.log(PAGE_ID+": onStatusUpdated ");
+ console.log(" state_code: ",data.state_code," text: ",data.state_text);
+ switch(data.state_code) {
+ case UpdateAPI.STATUS.IMAGE_READY:
+ console.log(" _installing: ",_installing);
+ if(!_installing) {
+ installUpdate();
+ data.state_code = UpdateAPI.STATUS.INSTALLING;
+ }
+ break;
+ case UpdateAPI.STATUS.NONE:
+ console.log(" _installing: ",_installing);
+ if(_installing) {
+ data.state_code = UpdateAPI.STATUS.INSTALLED;
+ _updateAPI.stopAutoRefresh();
+ clearTimeout(_updatedRedirectDelay);
+ _updatedRedirectDelay = setTimeout(function () {
+ // redirect to box page
+ console.log(" redirect to box");
+ // replace this page with boxes page in history
+ window.history.replaceState(null, "", "#boxes");
+ var link = "#box";
+ link = d3d.util.replaceURLParameters(link,_pageData);
+ $.mobile.changePage(link);
+ },UPDATED_REDIRECT_DELAY);
+
+ }
+ break;
+ }
+ updatePage(data);
+ _updateStatus = data;
+ }
+ function updatePage(data) {
+ console.log(PAGE_ID+": updatePage state: ",data.state_code);
+ var status = "";
+ switch(data.state_code){
+ case UpdateAPI.STATUS.DOWNLOADING:
+ status = "Downloading update...";
+ break;
+ case UpdateAPI.STATUS.DOWNLOAD_FAILED:
+ status = "Downloading update failed";
+ break;
+ case UpdateAPI.STATUS.IMAGE_READY:
+ status = "Update downloaded";
+ break;
+ case UpdateAPI.STATUS.INSTALLING:
+ status = "Installing update... ";
+ break;
+ case UpdateAPI.STATUS.INSTALLED:
+ status = "Update complete!";
+ break;
+ case UpdateAPI.STATUS.INSTALL_FAILED:
+ status = "Installing update failed";
+ break;
+ }
+ console.log(" status: ",status);
+ _statusField.text(status);
+
+ // description
+ var description = "";
+ switch(data.state_code){
+ case UpdateAPI.STATUS.INSTALLING:
+ description = "Do not remove power from the WiFi-Box.";
+ break;
+ case UpdateAPI.STATUS.DOWNLOAD_FAILED:
+ case UpdateAPI.STATUS.INSTALL_FAILED:
+ description = data.state_text;
+ break;
+ }
+ console.log(" description: ",description);
+ _descriptionField.text(description);
+ }
+
+})(window);
\ No newline at end of file
diff --git a/js/api/UpdateAPI.js b/js/api/UpdateAPI.js
new file mode 100644
index 0000000..8d712d6
--- /dev/null
+++ b/js/api/UpdateAPI.js
@@ -0,0 +1,144 @@
+/*
+ * This file is part of the Doodle3D project (http://doodle3d.com).
+ *
+ * Copyright (c) 2013, Doodle3D
+ * This software is licensed under the terms of the GNU GPL v2 or later.
+ * See file LICENSE.txt or visit http://www.gnu.org/licenses/gpl.html for full license details.
+ */
+function UpdateAPI() {
+
+ // states from api, see Doodle3D firmware src/script/d3d-updater.lua
+ UpdateAPI.STATUS = {
+ NONE: 1, // default state
+ DOWNLOADING: 2,
+ DOWNLOAD_FAILED:3,
+ IMAGE_READY: 4, // download successful and checked
+ INSTALLING: 5,
+ INSTALLED: 6,
+ INSTALL_FAILED: 7
+ };
+ var _apiPath = "/d3dapi";
+ var _apiCGIPath = "/cgi-bin"+_apiPath;
+ var _wifiboxURL;
+ var _wifiboxCGIBinURL;
+ var _timeoutTime = 3000;
+ this.state; // update state from api
+ this.stateText = ""; // update state text from api
+ var _autoRefreshing = false;
+ var _refreshDelay;
+ this.refreshDelayTime = 2000;
+ //callbacks
+ this.refreshing; // I'm refreshing
+ this.updated; // New network status info
+
+ var _self = this;
+
+ this.init = function(wifiboxURL) {
+ _wifiboxURL = wifiboxURL+_apiPath;
+ _wifiboxCGIBinURL = wifiboxURL+_apiCGIPath;
+ }
+
+ this.status = function(completeHandler,failedHandler) {
+ //console.log("UpdateAPI:status");
+ $.ajax({
+ url: _wifiboxURL + "/update/status",
+ type: "GET",
+ dataType: 'json',
+ timeout: _timeoutTime,
+ success: function(response){
+ //console.log("UpdateAPI:status response: ",response);
+ if(response.status == "error" || response.status == "fail") {
+ if(failedHandler) failedHandler(response);
+ } else {
+ var data = response.data;
+ data.current_version_is_beta = versionIsBeta(data.current_version);
+ data.newest_version_is_beta = versionIsBeta(data.newest_version);
+ if(data.newest_release_date && data.current_release_date) {
+ data.newest_version_is_newer = (data.newest_release_date - data.current_release_date > 0);
+ } else {
+ data.newest_version_is_newer = true;
+ }
+ completeHandler(response.data);
+ }
+ }
+ }).fail(function() {
+ if(failedHandler) failedHandler();
+ });
+ }
+ this.download = function(completeHandler,failedHandler) {
+ //console.log("UpdateAPI:download");
+ $.ajax({
+ url: _wifiboxURL + "/update/download",
+ type: "POST",
+ dataType: 'json',
+ success: function(response){
+ //console.log("UpdatePanel:downloadUpdate response: ",response);
+ if(response.status == "error" || response.status == "fail") {
+ if(failedHandler) failedHandler(response);
+ } else {
+ var data = response.data;
+ completeHandler(response.data);
+ }
+ }
+ }).fail(function() {
+ //console.log("UpdatePanel:downloadUpdate: failed");
+ if(failedHandler) failedHandler();
+ });
+ }
+ this.install = function(noRetain, completeHandler,failedHandler) {
+ //console.log("UpdateAPI:install");
+ var postData = {no_retain:noRetain};
+ $.ajax({
+ url: _wifiboxURL + "/update/install",
+ type: "POST",
+ data: postData,
+ dataType: 'json',
+ success: function(response){
+ //console.log("UpdatePanel:installUpdate response: ",response);
+ }
+ }).fail(function() {
+ //console.log("UpdatePanel:installUpdate: no respons (there shouldn't be)");
+ });
+ }
+
+
+ this.startAutoRefresh = function(delay,refreshingHandler,updatedHandler) {
+ if(delay !== undefined) { _self.refreshDelayTime = delay; }
+ if(refreshingHandler !== undefined) { _self.refreshing = refreshingHandler; }
+ if(updatedHandler !== undefined) { _self.updated = updatedHandler; }
+ _autoRefreshing = true;
+ _self.refresh();
+ }
+ this.stopAutoRefresh = function() {
+ _autoRefreshing = false;
+ clearTimeout(_refreshDelay);
+ }
+ this.refresh = function() {
+ //console.log("UpdateAPI:refresh");
+ if(_self.refreshing) { _self.refreshing(); }
+ _self.status(function(data) { // completed
+ //console.log("UpdateAPI:refresh:completed");
+
+ if(_self.updated !== undefined &&
+ _self.state !== data.state_code) {
+ _self.state = data.state_code;
+ _self.updated(data);
+ }
+ if(_autoRefreshing) {
+ clearTimeout(_refreshDelay);
+ _refreshDelay = setTimeout(_self.refresh, _self.refreshDelayTime);
+ }
+ },function() { // failed
+ if(_autoRefreshing) {
+ // retry
+ clearTimeout(_refreshDelay);
+ _refreshDelay = setTimeout(_self.refresh, _self.refreshDelayTime);
+ }
+ });
+ }
+
+ function versionIsBeta(version) {
+ return version ? /.*-.*/g.test(version) : null;
+ }
+
+}
\ No newline at end of file
diff --git a/less/styles.less b/less/styles.less
index df2d353..537aa8c 100644
--- a/less/styles.less
+++ b/less/styles.less
@@ -74,6 +74,16 @@ body.ui-mobile-viewport {
}
}
+ form .ui-input-btn {
+ background-color: #60ACF0;
+ color: #fff;
+ text-shadow: 0px 1px 0px rgb(92, 152, 198);
+ &:hover {
+ background-color: #3F87D9;
+ color: #fff;
+ text-shadow: 0px 1px 0px rgb(92, 152, 198);
+ }
+ }
}
/* Active button */
.ui-page-theme-a .ui-btn.ui-btn-active,
diff --git a/www/index.html b/www/index.html
index aca152d..218595f 100644
--- a/www/index.html
+++ b/www/index.html
@@ -53,7 +53,7 @@
You can subscribe for Doodle3D beta software, this means you'll get the latest versions that are still under development. Only use this when you're prepared to deal with some bugs.
+This allows you to test new features and give us earlier feedback. The more people that test these versions the more stable the regular version will be.
+