This commit is contained in:
Jan Schneider 2017-03-25 01:45:39 +01:00
parent 9c3156f0f6
commit 63580ae33f
4 changed files with 392 additions and 52 deletions

View File

@ -17,14 +17,19 @@
#
namespace eval rmupdate {
variable addon_dir "/usr/local/addons/raspmatic-update"
variable tmp_dir "/usr/local/addons/raspmatic-update/tmp"
variable mnt_cur "/usr/local/addons/raspmatic-update/mnt_cur"
variable mnt_new "/usr/local/addons/raspmatic-update/mnt_new"
variable release_url "https://github.com/jens-maus/RaspberryMatic/releases"
variable addon_dir "/usr/local/addons/rmupdate"
variable img_dir "/usr/local/addons/rmupdate/var/img"
variable mnt_cur "/usr/local/addons/rmupdate/var/mnt_cur"
variable mnt_new "/usr/local/addons/rmupdate/var/mnt_new"
variable sys_dev "/dev/mmcblk0"
variable loop_dev "/dev/loop7"
}
proc ::rmupdate::compare_versions {a b} {
return [package vcompare $a $b]
}
proc ::rmupdate::write_log {str} {
puts stderr $str
#set fd [open "/tmp/rmupdate.log" "a"]
@ -33,7 +38,7 @@ proc ::rmupdate::write_log {str} {
}
proc ::rmupdate::version {} {
variable version_file
variable addon_dir
set fp [open "${addon_dir}/VERSION" r]
set data [read $fp]
close $fp
@ -153,35 +158,18 @@ proc ::rmupdate::update_filesystems {image} {
}
}
proc ::rmupdate::get_latest_firmware_download_url {} {
set data [exec wget "https://github.com/jens-maus/RaspberryMatic/releases/latest" --no-check-certificate -q -O-]
foreach d [split $data "\n"] {
set href ""
regexp {<\s*a\s+href\s*=\s*"([^"]+/releases/download/[^"]+.zip)"} $d match href
if { [info exists href] && $href != ""} {
return "https://github.com${href}"
}
}
error "Failed to get latest firmware download url"
}
proc ::rmupdate::download_latest_firmware {} {
variable tmp_dir
set download_url [get_latest_firmware_download_url]
write_log "Downloading latest firmware from ${download_url}."
regexp {/([^/]+)$} $download_url match archive_file
set archive_file "${tmp_dir}/${archive_file}"
file mkdir $tmp_dir
exec wget "${download_url}" --no-check-certificate -q --output-document=$archive_file
return $archive_file
}
proc ::rmupdate::get_latest_firmware_version {} {
set download_url [get_latest_firmware_download_url]
regexp {\-([\d\.]+).zip$} $download_url match latest_version
return $latest_version
}
#proc ::rmupdate::is_firmware_up_to_date {} {
# set latest_version [get_latest_firmware_version]
# write_log "Latest firmware version: ${latest_version}"
#
# set current_version [get_current_firmware_version]
# write_log "Current firmware version: ${current_version}"
#
# if {[compare_versions $current_version $latest_version] >= 0} {
# return 1
# }
# return 0
#}
proc ::rmupdate::get_current_firmware_version {} {
set fp [open "/boot/VERSION" r]
@ -191,32 +179,147 @@ proc ::rmupdate::get_current_firmware_version {} {
return $current_version
}
proc ::rmupdate::is_firmware_up_to_date {} {
set latest_version [get_latest_firmware_version]
write_log "Latest firmware version: ${latest_version}"
set current_version [get_current_firmware_version]
write_log "Current firmware version: ${current_version}"
if {[string compare $current_version $latest_version] >= 0} {
return 1
proc ::rmupdate::get_available_firmware_downloads {} {
variable release_url
set download_urls [list]
set data [exec wget "${release_url}" --no-check-certificate -q -O-]
foreach d [split $data ">"] {
set href ""
regexp {<\s*a\s+href\s*=\s*"([^"]+/releases/download/[^"]+\.zip)"} $d match href
if { [info exists href] && $href != ""} {
lappend download_urls "https://github.com${href}"
}
}
return 0
return $download_urls
}
rmupdate::download_latest_firmware
proc ::rmupdate::get_latest_firmware_version {} {
set versions [list]
foreach e [get_available_firmware_downloads] {
lappend versions [get_version_from_filename $e]
}
set versions [lsort -decreasing -command compare_versions $versions]
return [lindex $versions 0]
}
proc ::rmupdate::download_firmware {version} {
variable img_dir
set image_file "${img_dir}/RaspberryMatic-${version}.img"
set download_url ""
foreach e [get_available_firmware_downloads] {
set v [get_version_from_filename $e]
if {$v == $version} {
set download_url $e
break
}
}
if {$download_url == ""} {
error "Failed to get url for firmware ${version}"
}
write_log "Downloading firmware from ${download_url}."
regexp {/([^/]+)$} $download_url match archive_file
set archive_file "${img_dir}/${archive_file}"
file mkdir $img_dir
exec wget "${download_url}" --no-check-certificate -q --output-document=$archive_file
write_log "Extracting firmware ${archive_file}."
set data [exec unzip -ql "${archive_file}"]
set img_file ""
foreach d [split $data "\n"] {
regexp {\s+(\S+\.img)\s*$} $d match img_file
if { $img_file != "" } {
break
}
}
if { $img_file == "" } {
error "Failed to extract image from archive."
}
exec unzip "${archive_file}" "${img_file}" -o -d "${img_dir}"
set img_file "${img_dir}/${img_file}"
puts "${img_file} ${image_file}"
if {$img_file != $image_file} {
file rename $img_file $image_file
}
file delete $archive_file
return $image_file
}
proc ::rmupdate::get_available_firmware_images {} {
variable img_dir
file mkdir $img_dir
return [glob -nocomplain "${img_dir}/*.img"]
}
proc ::rmupdate::get_version_from_filename {filename} {
regexp {\-([\d\.]+)\.[^\.]+$} $filename match version
return $version
}
proc ::rmupdate::get_firmware_info {} {
set current [get_current_firmware_version]
set versions [list $current]
foreach e [get_available_firmware_downloads] {
set version [get_version_from_filename $e]
set downloads($version) $e
if {[lsearch $versions $version] == -1} {
lappend versions $version
}
}
foreach e [get_available_firmware_images] {
set version [get_version_from_filename $e]
set images($version) $e
if {[lsearch $versions $version] == -1} {
lappend versions $version
}
}
set versions [lsort -decreasing -command compare_versions $versions]
set json "\["
set latest "true"
foreach v $versions {
set installed "false"
if {$v == $current} {
set installed "true"
}
set image ""
catch { set image $images($v) }
set url ""
catch { set url $downloads($v) }
append json "\{\"version\":\"${v}\",\"installed\":${installed},\"latest\":${latest}\,\"url\":\"${url}\",\"image\":\"${image}\"\},"
set latest "false"
}
if {[llength versions] > 0} {
set json [string range $json 0 end-1]
}
append json "\]"
return $json
}
proc ::rmupdate::install_firmware_version {version} {
set firmware_image ""
#foreach e [get_available_firmware_images] {
# set v [get_version_from_filename $e]
# if {$v == $version} {
# set firmware_image $e
# break
# }
#}
if {$firmware_image == ""} {
download_firmware $version
}
}
#puts [rmupdate::get_latest_firmware_version]
#puts [rmupdate::get_firmware_info]
#puts [rmupdate::get_available_firmware_images]
#puts [rmupdate::get_available_firmware_downloads]
#rmupdate::download_latest_firmware
#puts [rmupdate::is_firmware_up_to_date]
#puts [rmupdate::get_latest_firmware_download_url]
#rmupdate::check_sizes "/usr/local/addons/raspmatic-update/tmp/RaspberryMatic-2.27.7.20170316.img"
#set res [rmupdate::get_partion_start_and_size "/dev/mmcblk0" 1]
#rmupdate::mount_image_partition "/usr/local/addons/raspmatic-update/tmp/RaspberryMatic-2.27.7.20170316.img" 1 $rmupdate::mnt_new
#rmupdate::umount $rmupdate::mnt_new
#rmupdate::mount_system_partition "/boot" $rmupdate::mnt_cur
#rmupdate::umount $rmupdate::mnt_cur

66
addon/rmupdate.tcl Normal file
View File

@ -0,0 +1,66 @@
#!/bin/tclsh
# RaspMatic update addon
#
# Copyright (C) 2017 Jan Schneider <oss@janschneider.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
source /usr/local/addons/rmupdate/lib/rmupdate.tcl
proc usage {} {
global argv0
puts stderr ""
puts stderr "usage: ${argv0} <command>"
puts stderr ""
puts stderr "possible commands:"
puts stderr " show_current : show current firmware version"
puts stderr " show_latest : show latest available firmware version"
puts stderr " install_latest : install latest available firmware version"
puts stderr " install <version> : install firmware VERSION"
}
proc main {} {
global argc
global argv
set cmd [string tolower [lindex $argv 0]]
if {$cmd == "show_current"} {
puts [rmupdate::get_current_firmware_version]
} elsif {$cmd == "show_latest"} {
puts [rmupdate::get_latest_firmware_version]
} elsif {$cmd == "install_latest"} {
rmupdate::install_firmware_version [rmupdate::get_latest_firmware_version]
} elsif {$cmd == "install"} {
if {$argc < 2} {
usage
exit 1
}
rmupdate::install_firmware_version [lindex $argv 1]
} else {
usage
exit 1
}
}
if { [ catch {
main
} err ] } {
puts stderr "ERROR: $err"
exit 1
}
exit 0

View File

@ -30,8 +30,158 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<title>RaspberryMatic Update Addon</title>
<script>
var message_timer_id = null;
function display_message(type, html, millis) {
clear_message();
$('#message').html(html);
$('#message').attr('class', 'ui ' + type + ' message visible');
$('#message-edit-bridge').html(html);
$('#message-edit-bridge').attr('class', 'ui ' + type + ' message visible');
message_timer_id = setTimeout(clear_message, millis);
}
function clear_message() {
if (message_timer_id != null) {
clearTimeout(message_timer_id);
}
message_timer_id = null;
$('#message').html('');
$('#message').attr('class', 'ui message hidden');
$('#message-edit-bridge').html('');
$('#message-edit-bridge').attr('class', 'ui message hidden');
}
function rest(method, path, data, success_callback, error_callback) {
if (!error_callback) {
error_callback = function(xhr, ajaxOptions, thrownError) {
console.error(xhr);
err = thrownError;
try {
obj = JSON.parse(xhr.responseText);
if (obj.error != null) {
err = obj.error;
}
}
catch(e) {
}
display_message('error', 'An error occurred: ' + err, 60000);
}
}
$.ajax({
url: "rest.cgi?" + path,
type: method,
data: data,
context: document.body,
success: success_callback,
error: error_callback
});
}
function install_firmware(version) {
$('#modal-log').modal('show');
//$('#log-content').append('Some text<br/>');
//$('#modal-log').modal('refresh');
//$('#modal-log').modal('hide');
var xhr = new XMLHttpRequest();
xhr.open('GET', 'rest.cgi?/install_firmware', true);
xhr.send(null);
var timer;
timer = window.setInterval(function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
window.clearTimeout(timer);
//$('body').append('done <br />');
}
//$('body').append('state: ' + xhr.readyState + '<br />');
console.log(xhr.responseText);
$('#log-content').append(xhr.responseText + '<br/>');
//$('body').append('data: ' + xhr.responseText + '<br />');
}, 1000);
/*
var last_response_len = false;
$.ajax('rest.cgi?/install_firmware', {
xhrFields: {
onprogress: function(e) {
var this_response, response = e.currentTarget.response;
if(last_response_len === false) {
this_response = response;
last_response_len = response.length;
}
else {
this_response = response.substring(last_response_len);
last_response_len = response.length;
}
console.log(this_response);
}
}
}).done(function(data) {
console.log('Complete response = ' + data);
}).fail(function(data) {
console.log('Error: ', data);
});
console.log('Request Sent');
*/
}
$(document).ready(function() {
rest("GET", "/version", null, function(version) {
document.title = document.title + " " + version;
});
rest("GET", "/get_firmware_info", null, function(data) {
$('#firmware_info tbody').empty();
data.forEach(function(fw) {
var disabled = (fw.image || fw.url ? '' : 'disabled');
var binstall = $('<button class="ui green basic '+disabled+' button">').attr('data-version', fw.version).text('install');
binstall.click(function() {
install_firmware(this.getAttribute('data-version'));
});
var latest = (fw.latest ? 'checked=""' : '')
var installed = (fw.installed ? 'checked=""' : '')
var available = (fw.url ? 'checked=""' : '')
var downloaded = (fw.image ? 'checked=""' : '')
$("#firmware_info tbody").append($('<tr>').append(
$('<td>').text(fw.version),
$('<td class="center aligned">').append($('<div class="ui disabled checkbox">').append($('<input type="checkbox" disabled="disabled" '+latest+'>'),$('<label></label>'))),
$('<td class="center aligned">').append($('<div class="ui disabled checkbox">').append($('<input type="checkbox" disabled="disabled" '+installed+'>'),$('<label></label>'))),
$('<td class="center aligned">').append($('<div class="ui disabled checkbox">').append($('<input type="checkbox" disabled="disabled" '+available+'>'),$('<label></label>'))),
$('<td class="center aligned">').append($('<div class="ui disabled checkbox">').append($('<input type="checkbox" disabled="disabled" '+downloaded+'>'),$('<label></label>'))),
$('<td class="center aligned">').append(binstall)
));
});
});
});
</script>
</head>
<body>
<div style="padding-top: 5vw" class="ui container">
<h1 class="ui header">RaspberryMatic Update</h1>
<div id="message" class="ui message hidden">
</div>
<h2 class="ui dividing header">Firmwares</h2>
<table id="firmware_info" class="ui celled stackable table">
<thead>
<tr>
<th>Version</th>
<th class="center aligned">Latest</th>
<th class="center aligned">Installed</th>
<th class="center aligned">Available</th>
<th class="center aligned">Downloaded</th>
<th class="center aligned">Action</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div id="modal-log" class="ui modal">
<i class="close icon"></i>
<div class="header">
Progress
</div>
<div id="log-content" class="content">
Line 1
</div>
</div>
</body>
</html>

View File

@ -45,7 +45,28 @@ proc process {} {
if {[lindex $path 1] == "version"} {
return "\"[rmupdate::version]\""
} elseif {[lindex $path 1] == "xy"} {
} elseif {[lindex $path 1] == "get_firmware_info"} {
return [rmupdate::get_firmware_info]
} elseif {[lindex $path 1] == "install_firmware"} {
fconfigure stdout -buffering none
#puts "Content-Type: application/octet-stream"
puts "Content-Type: text/html; charset=utf-8"
puts "Status: 200 OK";
puts ""
flush stdout
after 1000
puts "Line 1\n"
flush stdout
after 1000
puts "Line 2\n"
flush stdout
after 1000
puts "Line 3\n"
flush stdout
after 1000
puts "Line 4\n"
flush stdout
return ""
}
}
error "invalid request" "Not found" 404
@ -61,7 +82,7 @@ if [catch {process} result] {
puts ""
set result [json_string $result]
puts -nonewline "\{\"error\":\"${result}\"\}"
} else {
} elseif {$result != ""} {
puts "Content-Type: application/json"
puts "Status: 200 OK";
puts ""