From 730000103fd5bbc67f033fa462f15a72d2374564 Mon Sep 17 00:00:00 2001 From: peteruithoven Date: Fri, 23 May 2014 19:09:46 +0200 Subject: [PATCH] Update functionality --- Gruntfile.js | 1 + js/BoxPage.js | 25 +++++- js/UpdatePage.js | 200 ++++++++++++++++++++++++++++++++++++++++++++ js/UpdatingPage.js | 154 ++++++++++++++++++++++++++++++++++ js/api/UpdateAPI.js | 144 +++++++++++++++++++++++++++++++ less/styles.less | 10 +++ www/index.html | 44 +++++++++- 7 files changed, 572 insertions(+), 6 deletions(-) create mode 100644 js/UpdatePage.js create mode 100644 js/UpdatingPage.js create mode 100644 js/api/UpdateAPI.js 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 @@

@@ -183,10 +183,37 @@
- Back + Back

Update

+

+
+
+

Options

+ + + + +
+ +
+ +
+
+ + +
+ +
+ Back +

Updating...

+
+
+

+

@@ -203,6 +230,19 @@ +
+ +
+ Back +

Beta info

+
+
+

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.

+
+
+ +