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 { namespace eval rmupdate {
variable addon_dir "/usr/local/addons/raspmatic-update" variable release_url "https://github.com/jens-maus/RaspberryMatic/releases"
variable tmp_dir "/usr/local/addons/raspmatic-update/tmp" variable addon_dir "/usr/local/addons/rmupdate"
variable mnt_cur "/usr/local/addons/raspmatic-update/mnt_cur" variable img_dir "/usr/local/addons/rmupdate/var/img"
variable mnt_new "/usr/local/addons/raspmatic-update/mnt_new" 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 sys_dev "/dev/mmcblk0"
variable loop_dev "/dev/loop7" variable loop_dev "/dev/loop7"
} }
proc ::rmupdate::compare_versions {a b} {
return [package vcompare $a $b]
}
proc ::rmupdate::write_log {str} { proc ::rmupdate::write_log {str} {
puts stderr $str puts stderr $str
#set fd [open "/tmp/rmupdate.log" "a"] #set fd [open "/tmp/rmupdate.log" "a"]
@ -33,7 +38,7 @@ proc ::rmupdate::write_log {str} {
} }
proc ::rmupdate::version {} { proc ::rmupdate::version {} {
variable version_file variable addon_dir
set fp [open "${addon_dir}/VERSION" r] set fp [open "${addon_dir}/VERSION" r]
set data [read $fp] set data [read $fp]
close $fp close $fp
@ -153,35 +158,18 @@ proc ::rmupdate::update_filesystems {image} {
} }
} }
proc ::rmupdate::get_latest_firmware_download_url {} { #proc ::rmupdate::is_firmware_up_to_date {} {
set data [exec wget "https://github.com/jens-maus/RaspberryMatic/releases/latest" --no-check-certificate -q -O-] # set latest_version [get_latest_firmware_version]
foreach d [split $data "\n"] { # write_log "Latest firmware version: ${latest_version}"
set href "" #
regexp {<\s*a\s+href\s*=\s*"([^"]+/releases/download/[^"]+.zip)"} $d match href # set current_version [get_current_firmware_version]
if { [info exists href] && $href != ""} { # write_log "Current firmware version: ${current_version}"
return "https://github.com${href}" #
} # if {[compare_versions $current_version $latest_version] >= 0} {
} # return 1
error "Failed to get latest firmware download url" # }
} # return 0
#}
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::get_current_firmware_version {} { proc ::rmupdate::get_current_firmware_version {} {
set fp [open "/boot/VERSION" r] set fp [open "/boot/VERSION" r]
@ -191,32 +179,147 @@ proc ::rmupdate::get_current_firmware_version {} {
return $current_version return $current_version
} }
proc ::rmupdate::is_firmware_up_to_date {} { proc ::rmupdate::get_available_firmware_downloads {} {
set latest_version [get_latest_firmware_version] variable release_url
write_log "Latest firmware version: ${latest_version}" set download_urls [list]
set data [exec wget "${release_url}" --no-check-certificate -q -O-]
set current_version [get_current_firmware_version] foreach d [split $data ">"] {
write_log "Current firmware version: ${current_version}" set href ""
regexp {<\s*a\s+href\s*=\s*"([^"]+/releases/download/[^"]+\.zip)"} $d match href
if {[string compare $current_version $latest_version] >= 0} { if { [info exists href] && $href != ""} {
return 1 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::is_firmware_up_to_date]
#puts [rmupdate::get_latest_firmware_download_url] #puts [rmupdate::get_latest_firmware_download_url]
#rmupdate::check_sizes "/usr/local/addons/raspmatic-update/tmp/RaspberryMatic-2.27.7.20170316.img" #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] #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::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::umount $rmupdate::mnt_new
#rmupdate::mount_system_partition "/boot" $rmupdate::mnt_cur #rmupdate::mount_system_partition "/boot" $rmupdate::mnt_cur
#rmupdate::umount $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> <title>RaspberryMatic Update Addon</title>
<script> <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> </script>
</head> </head>
<body> <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> </body>
</html> </html>

View File

@ -45,7 +45,28 @@ proc process {} {
if {[lindex $path 1] == "version"} { if {[lindex $path 1] == "version"} {
return "\"[rmupdate::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 error "invalid request" "Not found" 404
@ -61,7 +82,7 @@ if [catch {process} result] {
puts "" puts ""
set result [json_string $result] set result [json_string $result]
puts -nonewline "\{\"error\":\"${result}\"\}" puts -nonewline "\{\"error\":\"${result}\"\}"
} else { } elseif {$result != ""} {
puts "Content-Type: application/json" puts "Content-Type: application/json"
puts "Status: 200 OK"; puts "Status: 200 OK";
puts "" puts ""