TT#42767 Allow Range header in firmware downloads

ALE phones first download the first 256 bytes "header" of the firmware
to check if a new version is available. Support simple single Range
request in format "Range: bytes=X-Y" with X being start and Y being
end.

Also make sure to use $rs->count instead of $rs->first to only
execute the full query once and use a count query before instead,
otherwise we're fetching dozens of MB of data twice when fetching
the firmware.

Change-Id: I0c4e9f8b7d856d077eaa4ba8f2bc0aeaf42deebe
changes/12/25212/2
Andreas Granig 8 years ago
parent 83d715230e
commit bff6bda605

@ -1890,11 +1890,10 @@ sub dev_field_firmware_download :Chained('dev_field_firmware_base') :PathPart('v
$c->response->header ('Content-Disposition' => 'attachment; filename="' . $fw->filename . '"');
$c->response->content_type('application/octet-stream');
$c->response->body(
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data(
c => $c,
fw_id => $fw->id
));
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data_into_body(
c => $c,
fw_id => $fw->id
);
}
sub dev_field_firmware_version_base :Chained('dev_field_firmware_base') :PathPart('from') :CaptureArgs(1) {
@ -1941,11 +1940,10 @@ sub dev_field_firmware_next :Chained('dev_field_firmware_version_base') :PathPar
$c->response->header ('Content-Disposition' => 'attachment; filename="' . $fw->filename . '"');
$c->response->content_type('application/octet-stream');
$c->response->body(
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data(
c => $c,
fw_id => $fw->id
));
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data_into_body(
c => $c,
fw_id => $fw->id
);
}
sub dev_field_firmware_latest :Chained('dev_field_firmware_version_base') :PathPart('latest') :Args {
@ -1978,11 +1976,10 @@ sub dev_field_firmware_latest :Chained('dev_field_firmware_version_base') :PathP
$c->response->header ('Content-Disposition' => 'attachment; filename="' . $fw->filename . '"');
$c->response->content_type('application/octet-stream');
$c->response->body(
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data(
c => $c,
fw_id => $fw->id
));
NGCP::Panel::Utils::DeviceFirmware::get_firmware_data_into_body(
c => $c,
fw_id => $fw->id
);
}
sub devices_preferences_list :Chained('devmod_base') :PathPart('preferences') :CaptureArgs(0) {

@ -4,6 +4,9 @@ use warnings;
use strict;
use English;
use POSIX;
my $chunk_size = 10*1024*1024; # 10MB
sub insert_firmware_data {
my (%args) = @_;
@ -14,7 +17,6 @@ sub insert_firmware_data {
my $data_fh = $args{data_fh};
# do not set it larger than 10M
my $chunk_size = 10*1024*1024; # 10MB
$c->model('DB')->resultset('autoprov_firmwares')->find({id => $fw_id},{for => 'update'});
@ -63,7 +65,7 @@ sub get_firmware_data {
});
my $data = '';
if ($rs->first) {
if ($rs->count) {
foreach my $fw_data ($rs->all) {
$data = $data . $fw_data->data;
}
@ -71,6 +73,78 @@ sub get_firmware_data {
return $data;
}
sub get_firmware_data_into_body {
my (%args) = @_;
my $c = $args{c};
my $fw_id = $args{fw_id};
my ($range_from, $range_to, $range_len);
if ($c->request->headers->header('Range')) {
my $range = $c->request->headers->header('Range');
if ($range =~ /^bytes=(\d+)\-(\d+)$/) {
$range_from = int($1);
$range_to = int($2);
$range_len = $range_to - $range_from + 1;
if ($range_to < $range_from) {
$range_from = $range_to = undef;
}
}
}
# TODO: for now, if the requested range fits into the first chunk, request that range
# directly from db, otherwise we fetch all and extract the requested part
# later, due to the chunking in the db;
# this should be optimized to only request the range needed directly from db, but
# it must be ensured that we also handle cross-chunk ranges
my ($col, $limit);
if (defined $range_from && defined $range_to &&
$range_from < $chunk_size && ($range_from + $range_len) < $chunk_size) {
my $mysql_range_from = $range_from + 1;
$col = \"SUBSTRING(data, $mysql_range_from, $range_len)";
$limit = 1;
} else {
$col = 'data';
$limit = 0;
}
my $rs = $c->model('DB')->resultset('autoprov_firmwares_data')->search({
fw_id => $fw_id
},
{
select => [$col, \"OCTET_LENGTH(data)"],
as => [qw/body data_length/],
order_by => { -asc => 'id' }
});
my $data = '';
my $full_len = 0;
my $use_chunk = 1;
if ($rs->count) {
foreach my $fw_data ($rs->all) {
if ($use_chunk) {
$data = $data . $fw_data->get_column('body');
if ($limit) {
$use_chunk = 0;
}
}
$full_len += $fw_data->get_column('data_length');
}
}
if (defined $range_from && defined $range_to) {
unless ($limit) {
$data = substr($data, $range_from, $range_len);
}
$c->response->status(206);
$c->response->header("Content-Range" => "bytes $range_from-$range_to/$full_len");
}
$c->response->body($data);
return 0;
}
1;
# vim: set tabstop=4 expandtab:

Loading…
Cancel
Save