Merge pull request #90 from michaelherger/firmware-proxy

Firmware proxy
This commit is contained in:
philippe44
2021-04-25 22:12:15 -07:00
committed by GitHub
25 changed files with 374 additions and 135 deletions

View File

@@ -1 +1 @@
[{"C:\\Users\\sle11\\Documents\\VSCode\\squeezelite-esp32\\components\\wifi-manager\\webapp\\src\\js\\test.js":"1","C:\\Users\\sle11\\Documents\\VSCode\\squeezelite-esp32\\components\\wifi-manager\\webapp\\src\\js\\custom.js":"2"},{"size":4775,"mtime":1608244817341,"results":"3","hashOfConfig":"4"},{"size":61704,"mtime":1618438544167,"results":"5","hashOfConfig":"4"},{"filePath":"6","messages":"7","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"8"},"1275pne",{"filePath":"9","messages":"10","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"C:\\Users\\sle11\\Documents\\VSCode\\squeezelite-esp32\\components\\wifi-manager\\webapp\\src\\js\\test.js",[],[],"C:\\Users\\sle11\\Documents\\VSCode\\squeezelite-esp32\\components\\wifi-manager\\webapp\\src\\js\\custom.js",[]]
[{"/Users/mh/SynologyDrive/git/squeezelite-esp32/components/wifi-manager/webapp/src/js/custom.js":"1"},{"size":59815,"mtime":1618633783112,"results":"2","hashOfConfig":"3"},{"filePath":"4","messages":"5","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"15w6qa4","/Users/mh/SynologyDrive/git/squeezelite-esp32/components/wifi-manager/webapp/src/js/custom.js",[]]

View File

@@ -1496,7 +1496,8 @@ function checkStatus() {
const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port;
prevLMSIP=data.lms_ip;
$.ajax({
url: baseUrl + '/plugins/SqueezeESP32/firmware/-99',
url: baseUrl + '/plugins/SqueezeESP32/firmware/-check.bin',
type: 'HEAD',
dataType: 'text',
cache: false,
error: function() {

View File

@@ -1,5 +1,5 @@
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/favicon-32x32.png BINARY)
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/index.html.gz BINARY)
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/index.18c3b7.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/node-modules.18c3b7.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/runtime.18c3b7.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/index.df6830.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/node-modules.df6830.bundle.js.gz BINARY)
target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/runtime.df6830.bundle.js.gz BINARY)

View File

@@ -4,31 +4,31 @@ extern const uint8_t _favicon_32x32_png_start[] asm("_binary_favicon_32x32_png_s
extern const uint8_t _favicon_32x32_png_end[] asm("_binary_favicon_32x32_png_end");
extern const uint8_t _index_html_gz_start[] asm("_binary_index_html_gz_start");
extern const uint8_t _index_html_gz_end[] asm("_binary_index_html_gz_end");
extern const uint8_t _index_18c3b7_bundle_js_gz_start[] asm("_binary_index_18c3b7_bundle_js_gz_start");
extern const uint8_t _index_18c3b7_bundle_js_gz_end[] asm("_binary_index_18c3b7_bundle_js_gz_end");
extern const uint8_t _node_modules_18c3b7_bundle_js_gz_start[] asm("_binary_node_modules_18c3b7_bundle_js_gz_start");
extern const uint8_t _node_modules_18c3b7_bundle_js_gz_end[] asm("_binary_node_modules_18c3b7_bundle_js_gz_end");
extern const uint8_t _runtime_18c3b7_bundle_js_gz_start[] asm("_binary_runtime_18c3b7_bundle_js_gz_start");
extern const uint8_t _runtime_18c3b7_bundle_js_gz_end[] asm("_binary_runtime_18c3b7_bundle_js_gz_end");
extern const uint8_t _index_df6830_bundle_js_gz_start[] asm("_binary_index_df6830_bundle_js_gz_start");
extern const uint8_t _index_df6830_bundle_js_gz_end[] asm("_binary_index_df6830_bundle_js_gz_end");
extern const uint8_t _node_modules_df6830_bundle_js_gz_start[] asm("_binary_node_modules_df6830_bundle_js_gz_start");
extern const uint8_t _node_modules_df6830_bundle_js_gz_end[] asm("_binary_node_modules_df6830_bundle_js_gz_end");
extern const uint8_t _runtime_df6830_bundle_js_gz_start[] asm("_binary_runtime_df6830_bundle_js_gz_start");
extern const uint8_t _runtime_df6830_bundle_js_gz_end[] asm("_binary_runtime_df6830_bundle_js_gz_end");
const char * resource_lookups[] = {
"/favicon-32x32.png",
"/index.html.gz",
"/js/index.18c3b7.bundle.js.gz",
"/js/node-modules.18c3b7.bundle.js.gz",
"/js/runtime.18c3b7.bundle.js.gz",
"/js/index.df6830.bundle.js.gz",
"/js/node-modules.df6830.bundle.js.gz",
"/js/runtime.df6830.bundle.js.gz",
""
};
const uint8_t * resource_map_start[] = {
_favicon_32x32_png_start,
_index_html_gz_start,
_index_18c3b7_bundle_js_gz_start,
_node_modules_18c3b7_bundle_js_gz_start,
_runtime_18c3b7_bundle_js_gz_start
_index_df6830_bundle_js_gz_start,
_node_modules_df6830_bundle_js_gz_start,
_runtime_df6830_bundle_js_gz_start
};
const uint8_t * resource_map_end[] = {
_favicon_32x32_png_end,
_index_html_gz_end,
_index_18c3b7_bundle_js_gz_end,
_node_modules_18c3b7_bundle_js_gz_end,
_runtime_18c3b7_bundle_js_gz_end
_index_df6830_bundle_js_gz_end,
_node_modules_df6830_bundle_js_gz_end,
_runtime_df6830_bundle_js_gz_end
};

View File

@@ -1,40 +1,40 @@
/***********************************
webpack_headers
Hash: 18c3b78fe9dd6db2c31d
Hash: df683065b9a62ef5a0ce
Version: webpack 4.46.0
Time: 8782ms
Built at: 2021-04-21 12 h 01 min 40 s
Time: 2739ms
Built at: 26.04.2021 07:00:49
Asset Size Chunks Chunk Names
./js/index.18c3b7.bundle.js 232 KiB 0 [emitted] [immutable] index
./js/index.18c3b7.bundle.js.br 32.5 KiB [emitted]
./js/index.18c3b7.bundle.js.gz 41.9 KiB [emitted]
./js/node-modules.18c3b7.bundle.js 266 KiB 1 [emitted] [immutable] [big] node-modules
./js/node-modules.18c3b7.bundle.js.br 76.3 KiB [emitted]
./js/node-modules.18c3b7.bundle.js.gz 88.7 KiB [emitted]
./js/runtime.18c3b7.bundle.js 1.46 KiB 2 [emitted] [immutable] runtime
./js/runtime.18c3b7.bundle.js.br 644 bytes [emitted]
./js/runtime.18c3b7.bundle.js.gz 722 bytes [emitted]
./js/index.df6830.bundle.js 232 KiB 0 [emitted] [immutable] index
./js/index.df6830.bundle.js.br 32.5 KiB [emitted]
./js/index.df6830.bundle.js.gz 41.9 KiB [emitted]
./js/node-modules.df6830.bundle.js 266 KiB 1 [emitted] [immutable] [big] node-modules
./js/node-modules.df6830.bundle.js.br 76.3 KiB [emitted]
./js/node-modules.df6830.bundle.js.gz 88.7 KiB [emitted]
./js/runtime.df6830.bundle.js 1.46 KiB 2 [emitted] [immutable] runtime
./js/runtime.df6830.bundle.js.br 644 bytes [emitted]
./js/runtime.df6830.bundle.js.gz 722 bytes [emitted]
favicon-32x32.png 634 bytes [emitted]
index.html 21.7 KiB [emitted]
index.html.br 4.74 KiB [emitted]
index.html.gz 5.75 KiB [emitted]
sprite.svg 4.4 KiB [emitted]
sprite.svg.br 898 bytes [emitted]
Entrypoint index [big] = ./js/runtime.18c3b7.bundle.js ./js/node-modules.18c3b7.bundle.js ./js/index.18c3b7.bundle.js
Entrypoint index [big] = ./js/runtime.df6830.bundle.js ./js/node-modules.df6830.bundle.js ./js/index.df6830.bundle.js
[6] ./node_modules/bootstrap/dist/js/bootstrap-exposed.js 437 bytes {1} [built]
[11] ./src/sass/main.scss 1.55 KiB {0} [built]
[16] ./node_modules/remixicon/icons/Device/signal-wifi-fill.svg 340 bytes {1} [built]
[17] ./node_modules/remixicon/icons/Device/signal-wifi-3-fill.svg 344 bytes {1} [built]
[18] ./node_modules/remixicon/icons/Device/signal-wifi-2-fill.svg 344 bytes {1} [built]
[19] ./node_modules/remixicon/icons/Device/signal-wifi-1-fill.svg 344 bytes {1} [built]
[20] ./node_modules/remixicon/icons/Device/signal-wifi-line.svg 340 bytes {1} [built]
[21] ./node_modules/remixicon/icons/Device/battery-line.svg 332 bytes {1} [built]
[22] ./node_modules/remixicon/icons/Device/battery-low-line.svg 340 bytes {1} [built]
[23] ./node_modules/remixicon/icons/Device/battery-fill.svg 332 bytes {1} [built]
[24] ./node_modules/remixicon/icons/Media/headphone-fill.svg 335 bytes {1} [built]
[25] ./node_modules/remixicon/icons/Device/device-recover-fill.svg 346 bytes {1} [built]
[26] ./node_modules/remixicon/icons/Device/bluetooth-fill.svg 336 bytes {1} [built]
[27] ./node_modules/remixicon/icons/Device/bluetooth-connect-fill.svg 352 bytes {1} [built]
[16] ./node_modules/remixicon/icons/Device/signal-wifi-fill.svg 323 bytes {1} [built]
[17] ./node_modules/remixicon/icons/Device/signal-wifi-3-fill.svg 327 bytes {1} [built]
[18] ./node_modules/remixicon/icons/Device/signal-wifi-2-fill.svg 327 bytes {1} [built]
[19] ./node_modules/remixicon/icons/Device/signal-wifi-1-fill.svg 327 bytes {1} [built]
[20] ./node_modules/remixicon/icons/Device/signal-wifi-line.svg 323 bytes {1} [built]
[21] ./node_modules/remixicon/icons/Device/battery-line.svg 315 bytes {1} [built]
[22] ./node_modules/remixicon/icons/Device/battery-low-line.svg 323 bytes {1} [built]
[23] ./node_modules/remixicon/icons/Device/battery-fill.svg 315 bytes {1} [built]
[24] ./node_modules/remixicon/icons/Media/headphone-fill.svg 318 bytes {1} [built]
[25] ./node_modules/remixicon/icons/Device/device-recover-fill.svg 329 bytes {1} [built]
[26] ./node_modules/remixicon/icons/Device/bluetooth-fill.svg 319 bytes {1} [built]
[27] ./node_modules/remixicon/icons/Device/bluetooth-connect-fill.svg 335 bytes {1} [built]
[38] ./src/index.ts + 1 modules 62.5 KiB {0} [built]
| ./src/index.ts 1.4 KiB [built]
| ./src/js/custom.js 61 KiB [built]
@@ -43,14 +43,14 @@ Entrypoint index [big] = ./js/runtime.18c3b7.bundle.js ./js/node-modules.18c3b7.
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
./js/node-modules.18c3b7.bundle.js (266 KiB)
./js/node-modules.df6830.bundle.js (266 KiB)
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
index (499 KiB)
./js/runtime.18c3b7.bundle.js
./js/node-modules.18c3b7.bundle.js
./js/index.18c3b7.bundle.js
./js/runtime.df6830.bundle.js
./js/node-modules.df6830.bundle.js
./js/index.df6830.bundle.js
WARNING in webpack performance recommendations:
@@ -58,9 +58,9 @@ You can limit the size of your bundles by using import() or require.ensure to la
For more info visit https://webpack.js.org/guides/code-splitting/
Child html-webpack-plugin for "index.html":
Asset Size Chunks Chunk Names
index.html 560 KiB 0
index.html 559 KiB 0
Entrypoint undefined = index.html
[0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.ejs 23.9 KiB {0} [built]
[0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.ejs 22.9 KiB {0} [built]
[1] ./node_modules/lodash/lodash.js 531 KiB {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {0} [built]
[3] (webpack)/buildin/module.js 497 bytes {0} [built]

File diff suppressed because one or more lines are too long

View File

@@ -13,25 +13,74 @@ use constant FIRMWARE_POLL_INTERVAL => 3600 * (5 + rand());
use constant GITHUB_RELEASES_URI => "https://api.github.com/repos/sle118/squeezelite-esp32/releases";
use constant GITHUB_ASSET_URI => GITHUB_RELEASES_URI . "/assets/";
use constant GITHUB_DOWNLOAD_URI => "https://github.com/sle118/squeezelite-esp32/releases/download/";
my $FW_DOWNLOAD_ID_REGEX = qr|plugins/SqueezeESP32/firmware/(-?\d+)|;
use constant ESP32_STATUS_URI => "http://%s/status.json";
my $FW_DOWNLOAD_REGEX = qr|plugins/SqueezeESP32/firmware/([-a-z0-9-/.]+\.bin)$|i;
my $FW_CUSTOM_REGEX = qr/^((?:squeezelite-esp32-)?custom\.bin)$/;
my $FW_FILENAME_REGEX = qr/^squeezelite-esp32-.*\.bin(\.tmp)?$/;
my $FW_TAG_REGEX = qr/\/(ESP32-A1S|SqueezeAmp|I2S-4MFlash)\.(16|32)\.(\d+)\.(.*)\//;
my $FW_TAG_REGEX = qr/\b(ESP32-A1S|SqueezeAmp|I2S-4MFlash)\.(16|32)\.(\d+)\.([-a-zA-Z0-9]+)\b/;
use constant MAX_FW_IMAGE_SIZE => 10 * 1024 * 1024;
my $prefs = preferences('plugin.squeezeesp32');
my $log = logger('plugin.squeezeesp32');
my $initialized;
sub init {
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_ID_REGEX, \&handleFirmwareDownload);
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_REGEX, \&handleFirmwareDownloadDirect);
my ($client) = @_;
if (!$initialized) {
$initialized = 1;
Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_REGEX, \&handleFirmwareDownload);
Slim::Web::Pages->addRawFunction('plugins/SqueezeESP32/firmware/upload', \&handleFirmwareUpload);
}
# start checking for firmware updates
Slim::Utils::Timers::setTimer(undef, Time::HiRes::time() + 30 + rand(30), \&prefetchFirmware);
Slim::Utils::Timers::setTimer($client, Time::HiRes::time() + 3.0 + rand(3.0), \&initFirmwareDownload);
}
sub initFirmwareDownload {
my ($client, $cb) = @_;
Slim::Utils::Timers::killTimers($client, \&initFirmwareDownload);
return unless preferences('server')->get('checkVersion') || $cb;
Slim::Networking::SimpleAsyncHTTP->new(
sub {
my $http = shift;
my $content = eval { from_json( $http->content ) };
if ($content && ref $content) {
my $releaseInfo = getFirmwareTag($content->{version});
if ($releaseInfo && ref $releaseInfo) {
prefetchFirmware($releaseInfo, $cb);
}
else {
$cb->() if $cb;
}
}
},
sub {
my ($http, $error) = @_;
$log->error("Failed to get releases from Github: $error");
$cb->() if $cb;
},
{
timeout => 10
}
)->get(sprintf(ESP32_STATUS_URI, $client->ip));
Slim::Utils::Timers::setTimer($client, Time::HiRes::time() + FIRMWARE_POLL_INTERVAL, \&initFirmwareDownload);
}
sub prefetchFirmware {
Slim::Utils::Timers::killTimers(undef, \&prefetchFirmware);
my $releaseInfo = $prefs->get('lastReleaseTagUsed');
my ($releaseInfo, $cb) = @_;
return unless $releaseInfo;
Slim::Networking::SimpleAsyncHTTP->new(
sub {
@@ -54,6 +103,9 @@ sub prefetchFirmware {
}
}
my $customFwUrl = _urlFromPath('custom.bin') if $cb && -f _customFirmwareFile();
if ( ($url && $url =~ /^https?/) || $customFwUrl ) {
downloadFirmwareFile(sub {
main::INFOLOG && $log->is_info && $log->info("Pre-cached firmware file: " . $_[0]);
}, sub {
@@ -62,8 +114,10 @@ sub prefetchFirmware {
$url ||= ($http && $http->url) || 'no URL';
$log->error(sprintf("Failed to get firmware image from Github: %s (%s)", $error || $http->error, $url));
}, $url) if $url && $url =~ /^https?/;
}, $url) if $url;
$cb->($releaseInfo, _gh2lmsUrl($url), $customFwUrl) if $cb;
}
},
sub {
my ($http, $error) = @_;
@@ -74,9 +128,23 @@ sub prefetchFirmware {
cache => 1,
expires => 3600
}
)->get(GITHUB_RELEASES_URI) if $releaseInfo;
)->get(GITHUB_RELEASES_URI);
}
Slim::Utils::Timers::setTimer(undef, Time::HiRes::time() + FIRMWARE_POLL_INTERVAL, \&prefetchFirmware);
sub _gh2lmsUrl {
my ($url) = @_;
my $ghPrefix = GITHUB_DOWNLOAD_URI;
my $baseUrl = Slim::Utils::Network::serverURL();
$url =~ s/$ghPrefix/$baseUrl\/plugins\/SqueezeESP32\/firmware\//;
return $url;
}
sub _urlFromPath {
return sprintf('%s/plugins/SqueezeESP32/firmware/%s', Slim::Utils::Network::serverURL(), basename(shift));
}
sub _customFirmwareFile {
return catfile(scalar Slim::Utils::OSDetect::dirsFor('updates'), 'squeezelite-esp32-custom.bin');
}
sub handleFirmwareDownload {
@@ -88,13 +156,13 @@ sub handleFirmwareDownload {
_errorDownloading($httpClient, $response, @_);
};
my $id;
if (!defined $request || !(($id) = $request->uri =~ $FW_DOWNLOAD_ID_REGEX)) {
my $path;
if (!defined $request || !(($path) = $request->uri =~ $FW_DOWNLOAD_REGEX)) {
return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400);
}
# this is the magic number used on the client to figure out whether the plugin does support download proxying
if ($id == -99) {
if ($path eq '-check.bin' && $request->method eq 'HEAD') {
$response->code(204);
$response->header('Access-Control-Allow-Origin' => '*');
@@ -102,48 +170,20 @@ sub handleFirmwareDownload {
return Slim::Web::HTTP::closeHTTPSocket($httpClient);
}
Slim::Networking::SimpleAsyncHTTP->new(
sub {
my $http = shift;
my $content = eval { from_json( $http->content ) };
if ($path =~ $FW_CUSTOM_REGEX) {
my $firmwareFile = _customFirmwareFile();
if (!$content || !ref $content) {
$@ && $log->error("Failed to parse response: $@");
return $_errorDownloading->($http);
}
elsif (!$content->{browser_download_url} || !$content->{name}) {
return $_errorDownloading->($http, 'No download URL found');
if (! -f $firmwareFile) {
main::INFOLOG && $log->is_info && $log->info("Failed to find custom firmware build: $firmwareFile");
$response->code(404);
$httpClient->send_response($response);
return Slim::Web::HTTP::closeHTTPSocket($httpClient);
}
downloadFirmwareFile(sub {
my $firmwareFile = shift;
main::INFOLOG && $log->is_info && $log->info("Getting custom firmware build");
$response->code(200);
Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
}, $_errorDownloading, $content->{browser_download_url}, $content->{name});
},
$_errorDownloading,
{
timeout => 10,
cache => 1,
expires => 86400
}
)->get(GITHUB_ASSET_URI . $id);
return;
}
sub handleFirmwareDownloadDirect {
my ($httpClient, $response) = @_;
my $request = $response->request;
my $_errorDownloading = sub {
_errorDownloading($httpClient, $response, @_);
};
my $path;
if (!defined $request || !(($path) = $request->uri =~ $FW_DOWNLOAD_REGEX)) {
return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400);
return Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
}
main::INFOLOG && $log->is_info && $log->info("Requesting firmware from: $path");
@@ -159,7 +199,7 @@ sub downloadFirmwareFile {
my ($cb, $ecb, $url, $name) = @_;
# keep track of the last firmware we requested, to prefetch it in the future
_getFirmwareTag($url);
my $releaseInfo = getFirmwareTag($url);
$name ||= basename($url);
@@ -167,9 +207,21 @@ sub downloadFirmwareFile {
return $ecb->(undef, 'Unexpected firmware image name: ' . $name, $url, 400);
}
my $updatesDir = Slim::Utils::OSDetect::dirsFor('updates');
my $updatesDir = _getTempDir();
my $firmwareFile = catfile($updatesDir, $name);
Slim::Utils::Misc::deleteFiles($updatesDir, $FW_FILENAME_REGEX, $firmwareFile);
if (-f $firmwareFile) {
main::INFOLOG && $log->is_info && $log->info("Found uploaded firmware file $name");
return $cb->($firmwareFile);
}
$updatesDir = Slim::Utils::OSDetect::dirsFor('updates');
$firmwareFile = catfile($updatesDir, $name);
if ($releaseInfo) {
my $fileMatchRegex = join('-', '', $releaseInfo->{branch}, $releaseInfo->{model}, $releaseInfo->{res});
Slim::Utils::Misc::deleteFiles($updatesDir, $fileMatchRegex, $firmwareFile);
}
if (-f $firmwareFile) {
main::INFOLOG && $log->is_info && $log->info("Found cached firmware file");
@@ -188,7 +240,11 @@ sub downloadFirmwareFile {
return $cb->($firmwareFile);
},
$ecb,
sub {
my ($http, $error) = @_;
$http->code(404) if $error =~ /\b404\b/;
$ecb->(@_);
},
{
saveAs => "$firmwareFile.tmp",
}
@@ -197,10 +253,10 @@ sub downloadFirmwareFile {
return;
}
sub _getFirmwareTag {
my ($url) = @_;
sub getFirmwareTag {
my ($info) = @_;
if (my ($model, $resolution, $version, $branch) = $url =~ $FW_TAG_REGEX) {
if (my ($model, $resolution, $version, $branch) = $info =~ $FW_TAG_REGEX) {
my $releaseInfo = {
model => $model,
res => $resolution,
@@ -208,8 +264,6 @@ sub _getFirmwareTag {
branch => $branch
};
$prefs->set('lastReleaseTagUsed', $releaseInfo);
return $releaseInfo;
}
}
@@ -233,5 +287,123 @@ sub _errorDownloading {
Slim::Web::HTTP::closeHTTPSocket($httpClient);
};
sub handleFirmwareUpload {
my ($httpClient, $response) = @_;
my $request = $response->request;
my $result = {};
my $t = Time::HiRes::time();
main::INFOLOG && $log->is_info && $log->info("New firmware image to upload. Size: " . formatMB($request->content_length));
if ( $request->method !~ /HEAD|OPTIONS|POST/ ) {
$log->error("Invalid HTTP verb: " . $request->method);
$result = {
error => 'Invalid request.',
code => 400,
};
}
elsif ( $request->content_length > MAX_FW_IMAGE_SIZE ) {
$log->error("Upload data is too large: " . $request->content_length);
$result = {
error => string('PLUGIN_DNDPLAY_FILE_TOO_LARGE', formatMB($request->content_length), formatMB(MAX_FW_IMAGE_SIZE)),
code => 413,
};
}
else {
my $ct = $request->header('Content-Type');
my ($boundary) = $ct =~ /boundary=(.*)/;
my ($uploadedFwFh, $filename, $inUpload, $buf);
# open a pseudo-filehandle to the uploaded data ref for further processing
open TEMP, '<', $request->content_ref;
while (<TEMP>) {
if ( Time::HiRes::time - $t > 0.2 ) {
main::idleStreams();
$t = Time::HiRes::time();
}
# a new part starts - reset some variables
if ( /--\Q$boundary\E/i ) {
$filename = '';
if ($buf) {
$buf =~ s/\r\n$//;
print $uploadedFwFh $buf if $uploadedFwFh;
}
close $uploadedFwFh if $uploadedFwFh;
$inUpload = undef;
}
# write data to file handle
elsif ( $inUpload && $uploadedFwFh ) {
print $uploadedFwFh $buf if defined $buf;
$buf = $_;
}
# we got an uploaded file name
elsif ( /filename="(.+?)"/i ) {
$filename = $1;
main::INFOLOG && $log->is_info && $log->info("New file to upload: $filename")
}
# we got the separator after the upload file name: file data comes next. Open a file handle to write the data to.
elsif ( $filename && /^\s*$/ ) {
$inUpload = 1;
$uploadedFwFh = File::Temp->new(
DIR => _getTempDir(),
SUFFIX => '.bin',
TEMPLATE => 'squeezelite-esp32-upload-XXXXXX',
UNLINK => 0,
) or $log->warn("Failed to open file: $@");
binmode $uploadedFwFh;
# remove file after a few minutes
Slim::Utils::Timers::setTimer($uploadedFwFh->filename, Time::HiRes::time() + 15 * 60, sub { unlink shift });
}
}
close TEMP;
close $uploadedFwFh if $uploadedFwFh;
main::idleStreams();
if (!$result->{error}) {
$result->{url} = _urlFromPath($uploadedFwFh->filename);
$result->{size} = -s $uploadedFwFh->filename;
}
}
$log->error($result->{error}) if $result->{error};
my $content = to_json($result);
$response->header( 'Content-Length' => length($content) );
$response->code($result->{code} || 200);
$response->header('Connection' => 'close');
$response->content_type('application/json');
Slim::Web::HTTP::addHTTPResponse( $httpClient, $response, \$content );
}
my $tempDir;
sub _getTempDir {
return $tempDir if $tempDir;
eval { $tempDir = Slim::Utils::Misc::getTempDir() }; # LMS 8.2+ only
$tempDir ||= File::Temp::tempdir(CLEANUP => 1, DIR => preferences('server')->get('cachedir'));
return $tempDir;
}
sub formatMB {
return Slim::Utils::Misc::delimitThousands(int($_[0] / 1024 / 1024)) . 'MB';
}
1;

View File

@@ -1,5 +1,21 @@
[% PROCESS settings/header.html %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_FIRMWARE" desc="" %]
<div><a href="http://[% player_ip %]" target="_blank">[% "PLUGIN_SQUEEZEESP32_PLAYERSETTINGS" | string %] ([% player_ip %])</a></div>
[% IF fwUpdateAvailable %]
<div>
<input type="submit" name="installUpdate" class="stdclick" value="[% "CONTROLPANEL_INSTALL_UPDATE" | string %]"/>
[% fwUpdateAvailable %]
</div>
[% END %]
[% IF fwCustomUpdateAvailable %]
<div>
<input type="submit" name="installCustomUpdate" class="stdclick" value="[% "CONTROLPANEL_INSTALL_UPDATE" | string %]"/>
[% fwCustomUpdateAvailable | string %]
</div>
[% END %]
[% END %]
[% IF prefs.pref_width %]
[% WRAPPER setting title="PLUGIN_SQUEEZEESP32_WIDTH" desc="PLUGIN_SQUEEZEESP32_WIDTH_DESC" %]
<!--<input type="text" readonly class="stdedit" name="pref_width" id="width" value="[% prefs.pref_width %]" size="3">-->

View File

@@ -9,6 +9,8 @@ use List::Util qw(min);
use Slim::Utils::Log;
use Slim::Utils::Prefs;
use Plugins::SqueezeESP32::FirmwareHelper;
my $sprefs = preferences('server');
my $prefs = preferences('plugin.squeezeesp32');
my $log = logger('plugin.squeezeesp32');
@@ -95,6 +97,8 @@ sub init {
}
$client->SUPER::init(@_);
Plugins::SqueezeESP32::FirmwareHelper::init($client);
main::INFOLOG && $log->is_info && $log->info("SqueezeESP player connected: " . $client->id);
}

View File

@@ -2,6 +2,7 @@ package Plugins::SqueezeESP32::PlayerSettings;
use strict;
use base qw(Slim::Web::Settings);
use JSON::XS::VersionOneAndTwo;
use List::Util qw(first);
use Slim::Utils::Log;
@@ -36,7 +37,7 @@ sub prefs {
}
sub handler {
my ($class, $client, $paramRef) = @_;
my ($class, $client, $paramRef, $callback, @args) = @_;
my ($cprefs, @prefs) = $class->prefs($client);
@@ -76,7 +77,7 @@ sub handler {
}
if ($client->depth == 16) {
if ($client->can('depth') && $client->depth == 16) {
my $equalizer = $cprefs->get('equalizer');
for my $i (0 .. $#{$equalizer}) {
$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
@@ -93,9 +94,46 @@ sub handler {
$paramRef->{'pref_artwork'} = $cprefs->get('artwork');
}
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer') if $client->depth == 16;
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer') if $client->can('depth') && $client->depth == 16;
$paramRef->{'player_ip'} = $client->ip;
return $class->SUPER::handler($client, $paramRef);
Plugins::SqueezeESP32::FirmwareHelper::initFirmwareDownload($client, sub {
my ($currentFWInfo, $newFWUrl, $customFwUrl) = @_;
$currentFWInfo ||= {};
my $newFWInfo = Plugins::SqueezeESP32::FirmwareHelper::getFirmwareTag($newFWUrl) || {};
if ($paramRef->{installUpdate} || $paramRef->{installCustomUpdate}) {
my $http = Slim::Networking::SimpleAsyncHTTP->new(sub {
main::INFOLOG && $log->is_info && $log->info("Firmware update triggered");
}, sub {
main::INFOLOG && $log->is_info && $log->info("Failed to trigger firmware update");
main::DEBUGLOG && $log->is_debug && $log->debug(Data::Dump::dump(@_));
})->post(sprintf('http://%s/config.json', $client->ip), to_json({
timestamp => int(Time::HiRes::time() * 1000) * 1,
config => {
fwurl => {
value => $paramRef->{installCustomUpdate} ? $customFwUrl : $newFWUrl,
type => 33
}
}
}));
}
else {
if ($currentFWInfo->{version} && $newFWInfo->{version} && $currentFWInfo->{version} > $newFWInfo->{version}) {
main::INFOLOG && $log->is_info && $log->info("There's an update for your SqueezeESP32 player: $newFWUrl");
$paramRef->{fwUpdateAvailable} = sprintf($client->string('PLUGIN_SQUEEZEESP32_FIRMWARE_AVAILABLE'), $newFWInfo->{version}, $currentFWInfo->{version});
}
if ($customFwUrl) {
main::INFOLOG && $log->is_info && $log->info("There's a custom firmware for your SqueezeESP32 player: $customFwUrl");
$paramRef->{fwCustomUpdateAvailable} = 'PLUGIN_SQUEEZEESP32_CUSTOM_FIRMWARE_AVAILABLE';
}
}
$callback->( $client, $paramRef, $class->SUPER::handler($client, $paramRef), @args );
});
return;
}
1;

View File

@@ -8,8 +8,6 @@ use Slim::Utils::Prefs;
use Slim::Utils::Log;
use Slim::Web::ImageProxy;
use Plugins::SqueezeESP32::FirmwareHelper;
my $prefs = preferences('plugin.squeezeesp32');
my $log = Slim::Utils::Log->addLogCategory({
@@ -39,12 +37,13 @@ $prefs->setChange(sub {
sub initPlugin {
my $class = shift;
# enable the following to test the firmware downloading code without a SqueezeliteESP32 player
# require Plugins::SqueezeESP32::FirmwareHelper;
# Plugins::SqueezeESP32::FirmwareHelper::init();
if ( main::WEBUI ) {
require Plugins::SqueezeESP32::PlayerSettings;
Plugins::SqueezeESP32::PlayerSettings->new;
# require Plugins::SqueezeESP32::Settings;
# Plugins::SqueezeESP32::Settings->new;
}
$class->SUPER::initPlugin(@_);
@@ -60,8 +59,6 @@ sub initPlugin {
Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['newmetadata'] ] );
Slim::Control::Request::subscribe( sub { onNotification(@_) }, [ ['playlist'], ['open', 'newsong'] ]);
Slim::Control::Request::subscribe( \&onStopClear, [ ['playlist'], ['stop', 'clear'] ]);
Plugins::SqueezeESP32::FirmwareHelper->init();
}
sub onStopClear {

View File

@@ -21,6 +21,17 @@ PLUGIN_SQUEEZEESP32_PLAYERSETTINGS
DE ESP32 Einstellungen
EN ESP32 settings
PLUGIN_SQUEEZEESP32_FIRMWARE
EN Firmware
PLUGIN_SQUEEZEESP32_FIRMWARE_AVAILABLE
DE Es steht eine neue Firmware Version v%s zur Verfügung (aktuell installiert: v%s).
EN A new firmware version v%s is available (currently installed: v%s).
PLUGIN_SQUEEZEESP32_CUSTOM_FIRMWARE_AVAILABLE
DE Es steht eine benutzerdefinierte Firmware Version zur Verfügung.
EN A custom firmware image is available for installation.
PLUGIN_SQUEEZEESP32_WIDTH
DE Displaybreite
EN Screen width