浏览代码

Add support for a firmware download proxy (#85)

* Add support for a firmware download proxy. This should help in situations where the player's firmware can't handle https correctly.

Two possibilities:
* full path to image: http://yourlms:9000/plugins/SqueezeESP32/firmware/ESP32-A1S.32.634.master-cmake/squeezelite-esp32-master-cmake-ESP32-A1S-32-V0.634.bin
* use Github's asset ID: http://yourlms:9000/plugins/SqueezeESP32/firmware/34298863

The former is more prone to issues related to the path. A change in the schema could break the matching regex.
The latter is simpler to use if you know the ID. But the ID is not easily available to the user. And it requires one more lookup in the plugin to get from the ID to the download path.

* Add support for proxying firmware downloads through LMS

* add magic asset ID -99 to allow the front-end to check whether the plugin does support download proxying
* web manager is expecting `lms_port` and `lms_ip` in `status.json`. If that's available, check whether plugin does support firmware downloading. If that's the case, download firmwares through LMS
* plugin would cache firmware images. In case of multiple images the file would be served directly from LMS.

Co-authored-by: Michael Herger <michael@herger.net>
Michael Herger 4 年之前
父节点
当前提交
bc0d104290
共有 26 个文件被更改,包括 235 次插入66 次删除
  1. 4 1
      components/wifi-manager/webapp/mock/status.json
  2. 23 2
      components/wifi-manager/webapp/src/js/custom.js
  3. 3 3
      components/wifi-manager/webapp/webapp.cmake
  4. 15 15
      components/wifi-manager/webapp/webpack.c
  5. 36 36
      components/wifi-manager/webapp/webpack.h
  6. 二进制
      components/wifi-manager/webapp/webpack/dist/favicon-32x32.png
  7. 0 0
      components/wifi-manager/webapp/webpack/dist/index.html
  8. 二进制
      components/wifi-manager/webapp/webpack/dist/index.html.br
  9. 二进制
      components/wifi-manager/webapp/webpack/dist/index.html.gz
  10. 0 0
      components/wifi-manager/webapp/webpack/dist/js/index.0b6890.bundle.js
  11. 二进制
      components/wifi-manager/webapp/webpack/dist/js/index.0b6890.bundle.js.br
  12. 二进制
      components/wifi-manager/webapp/webpack/dist/js/index.0b6890.bundle.js.gz
  13. 0 0
      components/wifi-manager/webapp/webpack/dist/js/index.e644c0.bundle.js
  14. 二进制
      components/wifi-manager/webapp/webpack/dist/js/index.e644c0.bundle.js.br
  15. 二进制
      components/wifi-manager/webapp/webpack/dist/js/index.e644c0.bundle.js.gz
  16. 7 0
      components/wifi-manager/webapp/webpack/dist/js/node-modules.0b6890.bundle.js
  17. 二进制
      components/wifi-manager/webapp/webpack/dist/js/node-modules.0b6890.bundle.js.br
  18. 二进制
      components/wifi-manager/webapp/webpack/dist/js/node-modules.0b6890.bundle.js.gz
  19. 0 7
      components/wifi-manager/webapp/webpack/dist/js/node-modules.e644c0.bundle.js
  20. 二进制
      components/wifi-manager/webapp/webpack/dist/js/node-modules.e644c0.bundle.js.br
  21. 二进制
      components/wifi-manager/webapp/webpack/dist/js/node-modules.e644c0.bundle.js.gz
  22. 0 0
      components/wifi-manager/webapp/webpack/dist/js/runtime.0b6890.bundle.js
  23. 0 0
      components/wifi-manager/webapp/webpack/dist/js/runtime.0b6890.bundle.js.br
  24. 二进制
      components/wifi-manager/webapp/webpack/dist/js/runtime.0b6890.bundle.js.gz
  25. 1 1
      components/wifi-manager/webapp/webpack/webpack.dev.js
  26. 146 1
      plugin/SqueezeESP32/Plugin.pm

+ 4 - 1
components/wifi-manager/webapp/mock/status.json

@@ -14,5 +14,8 @@
 	"ssid": "MyTestSSID",
 	"ip": "192.168.10.225",
 	"netmask": "255.255.255.0",
-	"gw": "192.168.10.1"
+	"gw": "192.168.10.1",
+	"lms_cport": 9090,
+	"lms_port": 9000,
+	"lms_ip": "127.0.0.1"
 }

+ 23 - 2
components/wifi-manager/webapp/src/js/custom.js

@@ -228,6 +228,7 @@ let versionName='SqueezeESP32';
 let appTitle=versionName;
 let ConnectedToSSID={};
 let ConnectingToSSID={};
+let lmsBaseUrl;
 const ConnectingToActions = {
   'CONN' : 0,'MAN' : 1,'STS' : 2,
 }
@@ -895,8 +896,7 @@ $(document).ready(function() {
 
 // eslint-disable-next-line no-unused-vars
 window.setURL = function(button) {
-  const url = button.dataset.url;
-  $('#fwurl').val(url);
+  let url = button.dataset.url;
 
   $('[data-url^="http"]')
     .addClass('btn-success')
@@ -904,6 +904,13 @@ window.setURL = function(button) {
   $('[data-url="' + url + '"]')
     .addClass('btn-danger')
     .removeClass('btn-success');
+
+  // if user can proxy download through LMS, modify the URL
+  if (lmsBaseUrl) {
+    url = url.replace(/.*\/download\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/');
+  }
+
+  $('#fwurl').val(url);
 }
 
 // function performConnect(conntype) {
@@ -1327,6 +1334,20 @@ function checkStatus() {
     } else {
       $('#battery').hide();
     }
+
+    if (typeof lmsBaseUrl == "undefined" && data.lms_ip && data.lms_port) {
+      const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port;
+      $.ajax({
+        url: baseUrl + '/plugins/SqueezeESP32/firmware/-99', 
+        error: function() {
+          // define the value, so we don't check it any more.
+          lmsBaseUrl = '';
+        },
+        success: function() {
+          lmsBaseUrl = baseUrl;
+        }
+      });
+    }
     
     $('#o_jack').attr('display', Number(data.Jack) ? 'inline' : 'none');
     blockAjax = false;

+ 3 - 3
components/wifi-manager/webapp/webapp.cmake

@@ -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.e644c0.bundle.js.gz BINARY)
-target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/node-modules.e644c0.bundle.js.gz BINARY)
-target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/runtime.e644c0.bundle.js.gz BINARY)
+target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/index.0b6890.bundle.js.gz BINARY)
+target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/node-modules.0b6890.bundle.js.gz BINARY)
+target_add_binary_data( __idf_wifi-manager ./webapp/webpack/dist/js/runtime.0b6890.bundle.js.gz BINARY)

+ 15 - 15
components/wifi-manager/webapp/webpack.c

@@ -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_e644c0_bundle_js_gz_start[] asm("_binary_index_e644c0_bundle_js_gz_start");
-extern const uint8_t _index_e644c0_bundle_js_gz_end[] asm("_binary_index_e644c0_bundle_js_gz_end");
-extern const uint8_t _node_modules_e644c0_bundle_js_gz_start[] asm("_binary_node_modules_e644c0_bundle_js_gz_start");
-extern const uint8_t _node_modules_e644c0_bundle_js_gz_end[] asm("_binary_node_modules_e644c0_bundle_js_gz_end");
-extern const uint8_t _runtime_e644c0_bundle_js_gz_start[] asm("_binary_runtime_e644c0_bundle_js_gz_start");
-extern const uint8_t _runtime_e644c0_bundle_js_gz_end[] asm("_binary_runtime_e644c0_bundle_js_gz_end");
+extern const uint8_t _index_0b6890_bundle_js_gz_start[] asm("_binary_index_0b6890_bundle_js_gz_start");
+extern const uint8_t _index_0b6890_bundle_js_gz_end[] asm("_binary_index_0b6890_bundle_js_gz_end");
+extern const uint8_t _node_modules_0b6890_bundle_js_gz_start[] asm("_binary_node_modules_0b6890_bundle_js_gz_start");
+extern const uint8_t _node_modules_0b6890_bundle_js_gz_end[] asm("_binary_node_modules_0b6890_bundle_js_gz_end");
+extern const uint8_t _runtime_0b6890_bundle_js_gz_start[] asm("_binary_runtime_0b6890_bundle_js_gz_start");
+extern const uint8_t _runtime_0b6890_bundle_js_gz_end[] asm("_binary_runtime_0b6890_bundle_js_gz_end");
 const char * resource_lookups[] = {
 	"/dist/favicon-32x32.png",
 	"/dist/index.html.gz",
-	"/js/index.e644c0.bundle.js.gz",
-	"/js/node-modules.e644c0.bundle.js.gz",
-	"/js/runtime.e644c0.bundle.js.gz",
+	"/js/index.0b6890.bundle.js.gz",
+	"/js/node-modules.0b6890.bundle.js.gz",
+	"/js/runtime.0b6890.bundle.js.gz",
 ""
 };
 const uint8_t * resource_map_start[] = {
 	_favicon_32x32_png_start,
 	_index_html_gz_start,
-	_index_e644c0_bundle_js_gz_start,
-	_node_modules_e644c0_bundle_js_gz_start,
-	_runtime_e644c0_bundle_js_gz_start
+	_index_0b6890_bundle_js_gz_start,
+	_node_modules_0b6890_bundle_js_gz_start,
+	_runtime_0b6890_bundle_js_gz_start
 };
 const uint8_t * resource_map_end[] = {
 	_favicon_32x32_png_end,
 	_index_html_gz_end,
-	_index_e644c0_bundle_js_gz_end,
-	_node_modules_e644c0_bundle_js_gz_end,
-	_runtime_e644c0_bundle_js_gz_end
+	_index_0b6890_bundle_js_gz_end,
+	_node_modules_0b6890_bundle_js_gz_end,
+	_runtime_0b6890_bundle_js_gz_end
 };

+ 36 - 36
components/wifi-manager/webapp/webpack.h

@@ -1,56 +1,56 @@
 /***********************************
 webpack_headers
-Hash: e644c04d107606ae748d
-Version: webpack 4.44.2
-Time: 6142ms
-Built at: 2020-12-21 12 h 10 min 00 s
+Hash: 0b6890f4337e767921f7
+Version: webpack 4.46.0
+Time: 273269ms
+Built at: 2021-04-03 1:28:56
                                 Asset       Size  Chunks                                Chunk Names
-          ./js/index.e644c0.bundle.js    230 KiB       0  [emitted] [immutable]         index
-       ./js/index.e644c0.bundle.js.br   31.3 KiB          [emitted]                     
-       ./js/index.e644c0.bundle.js.gz   40.9 KiB          [emitted]                     
-   ./js/node-modules.e644c0.bundle.js    265 KiB       1  [emitted] [immutable]  [big]  node-modules
-./js/node-modules.e644c0.bundle.js.br   76.2 KiB          [emitted]                     
-./js/node-modules.e644c0.bundle.js.gz   88.6 KiB          [emitted]                     
-        ./js/runtime.e644c0.bundle.js   1.46 KiB       2  [emitted] [immutable]         runtime
-     ./js/runtime.e644c0.bundle.js.br  644 bytes          [emitted]                     
-     ./js/runtime.e644c0.bundle.js.gz  722 bytes          [emitted]                     
-                    favicon-32x32.png  578 bytes          [emitted]                     
+          ./js/index.0b6890.bundle.js    231 KiB       0  [emitted] [immutable]         index
+       ./js/index.0b6890.bundle.js.br   31.5 KiB          [emitted]                     
+       ./js/index.0b6890.bundle.js.gz   41.1 KiB          [emitted]                     
+   ./js/node-modules.0b6890.bundle.js    266 KiB       1  [emitted] [immutable]  [big]  node-modules
+./js/node-modules.0b6890.bundle.js.br   76.3 KiB          [emitted]                     
+./js/node-modules.0b6890.bundle.js.gz   88.7 KiB          [emitted]                     
+        ./js/runtime.0b6890.bundle.js   1.46 KiB       2  [emitted] [immutable]         runtime
+     ./js/runtime.0b6890.bundle.js.br  644 bytes          [emitted]                     
+     ./js/runtime.0b6890.bundle.js.gz  722 bytes          [emitted]                     
+                    favicon-32x32.png  634 bytes          [emitted]                     
                            index.html   19.5 KiB          [emitted]                     
                         index.html.br   4.48 KiB          [emitted]                     
                         index.html.gz   5.46 KiB          [emitted]                     
                            sprite.svg    4.4 KiB          [emitted]                     
                         sprite.svg.br  912 bytes          [emitted]                     
-Entrypoint index [big] = ./js/runtime.e644c0.bundle.js ./js/node-modules.e644c0.bundle.js ./js/index.e644c0.bundle.js
+Entrypoint index [big] = ./js/runtime.0b6890.bundle.js ./js/node-modules.0b6890.bundle.js ./js/index.0b6890.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]
-[37] ./src/index.ts + 1 modules 52.6 KiB {0} [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]
+[37] ./src/index.ts + 1 modules 53.3 KiB {0} [built]
      | ./src/index.ts 1.36 KiB [built]
-     | ./src/js/custom.js 51.2 KiB [built]
+     | ./src/js/custom.js 51.8 KiB [built]
     + 23 hidden modules
 
 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.e644c0.bundle.js (265 KiB)
+  ./js/node-modules.0b6890.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 (497 KiB)
-      ./js/runtime.e644c0.bundle.js
-      ./js/node-modules.e644c0.bundle.js
-      ./js/index.e644c0.bundle.js
+  index (499 KiB)
+      ./js/runtime.0b6890.bundle.js
+      ./js/node-modules.0b6890.bundle.js
+      ./js/index.0b6890.bundle.js
 
 
 WARNING in webpack performance recommendations: 
@@ -60,8 +60,8 @@ Child html-webpack-plugin for "index.html":
          Asset     Size  Chunks  Chunk Names
     index.html  556 KiB       0  
     Entrypoint undefined = index.html
-    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.ejs 21.1 KiB {0} [built]
-    [1] ./node_modules/lodash/lodash.js 530 KiB {0} [built]
+    [0] ./node_modules/html-webpack-plugin/lib/loader.js!./src/index.ejs 20.3 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]
 ***********************************/

二进制
components/wifi-manager/webapp/webpack/dist/favicon-32x32.png


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/webpack/dist/index.html


二进制
components/wifi-manager/webapp/webpack/dist/index.html.br


二进制
components/wifi-manager/webapp/webpack/dist/index.html.gz


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/webpack/dist/js/index.0b6890.bundle.js


二进制
components/wifi-manager/webapp/webpack/dist/js/index.0b6890.bundle.js.br


二进制
components/wifi-manager/webapp/webpack/dist/js/index.0b6890.bundle.js.gz


文件差异内容过多而无法显示
+ 0 - 0
components/wifi-manager/webapp/webpack/dist/js/index.e644c0.bundle.js


二进制
components/wifi-manager/webapp/webpack/dist/js/index.e644c0.bundle.js.br


二进制
components/wifi-manager/webapp/webpack/dist/js/index.e644c0.bundle.js.gz


文件差异内容过多而无法显示
+ 7 - 0
components/wifi-manager/webapp/webpack/dist/js/node-modules.0b6890.bundle.js


二进制
components/wifi-manager/webapp/webpack/dist/js/node-modules.0b6890.bundle.js.br


二进制
components/wifi-manager/webapp/webpack/dist/js/node-modules.0b6890.bundle.js.gz


文件差异内容过多而无法显示
+ 0 - 7
components/wifi-manager/webapp/webpack/dist/js/node-modules.e644c0.bundle.js


二进制
components/wifi-manager/webapp/webpack/dist/js/node-modules.e644c0.bundle.js.br


二进制
components/wifi-manager/webapp/webpack/dist/js/node-modules.e644c0.bundle.js.gz


+ 0 - 0
components/wifi-manager/webapp/webpack/dist/js/runtime.e644c0.bundle.js → components/wifi-manager/webapp/webpack/dist/js/runtime.0b6890.bundle.js


+ 0 - 0
components/wifi-manager/webapp/webpack/dist/js/runtime.e644c0.bundle.js.br → components/wifi-manager/webapp/webpack/dist/js/runtime.0b6890.bundle.js.br


二进制
components/wifi-manager/webapp/webpack/dist/js/runtime.e644c0.bundle.js.gz → components/wifi-manager/webapp/webpack/dist/js/runtime.0b6890.bundle.js.gz


+ 1 - 1
components/wifi-manager/webapp/webpack/webpack.dev.js

@@ -105,7 +105,7 @@ module.exports = merge(common, {
         contentBase: path.join(__dirname, 'dist'),
         publicPath: '/',
         port: 9100,
-        host: 'desktop-n8u8515',//your ip address
+        host: '127.0.0.1',//your ip address
         disableHostCheck: true,
         overlay: true,
 

+ 146 - 1
plugin/SqueezeESP32/Plugin.pm

@@ -3,6 +3,9 @@ package Plugins::SqueezeESP32::Plugin;
 use strict;
 
 use base qw(Slim::Plugin::Base);
+use File::Basename qw(basename);
+use File::Spec::Functions qw(catfile);
+use JSON::XS::VersionOneAndTwo;
 
 use Slim::Utils::Prefs;
 use Slim::Utils::Log;
@@ -16,6 +19,12 @@ my $log = Slim::Utils::Log->addLogCategory({
 	'description'  => 'PLUGIN_SQUEEZEESP32',
 });
 
+use constant GITHUB_ASSET_URI => "https://api.github.com/repos/sle118/squeezelite-esp32/releases/assets/";
+use constant GITHUB_DOWNLOAD_URI => "https://github.com/sle118/squeezelite-esp32/releases/download/";
+my $FW_DOWNLOAD_ID_REGEX = qr|plugins/SqueezeESP32/firmware/(-?\d+)|;
+my $FW_DOWNLOAD_REGEX = qr|plugins/SqueezeESP32/firmware/([-a-z0-9-/.]+\.bin)$|i;
+my $FW_FILENAME_REGEX = qr/^squeezelite-esp32-.*\.bin(\.tmp)?$/;
+
 # migrate 'eq' pref, as that's a reserved word and could cause problems in the future
 $prefs->migrateClient(1, sub {
 	my ($cprefs, $client) = @_;
@@ -48,7 +57,7 @@ sub initPlugin {
 	$class->SUPER::initPlugin(@_);
 	# no name can be a subset of others due to a bug in addPlayerClass
 	Slim::Networking::Slimproto::addPlayerClass($class, 100, 'squeezeesp32-basic', { client => 'Plugins::SqueezeESP32::Player', display => 'Plugins::SqueezeESP32::Graphics' });
-	Slim::Networking::Slimproto::addPlayerClass($class, 101, 'squeezeesp32-graphic', { client => 'Plugins::SqueezeESP32::Player', display => 'Slim::Display::NoDisplay' });		
+	Slim::Networking::Slimproto::addPlayerClass($class, 101, 'squeezeesp32-graphic', { client => 'Plugins::SqueezeESP32::Player', display => 'Slim::Display::NoDisplay' });
 	main::INFOLOG && $log->is_info && $log->info("Added class 100 and 101 for SqueezeESP32");
 
 	# register a command to set the EQ - without saving the values! Send params as single comma separated list of values
@@ -58,6 +67,9 @@ 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'] ]);
+
+	Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_ID_REGEX, \&handleFirmwareDownload);
+	Slim::Web::Pages->addRawFunction($FW_DOWNLOAD_REGEX, \&handleFirmwareDownloadDirect);
 }
 
 sub onStopClear {
@@ -99,4 +111,137 @@ sub setEQ {
 	$client->send_equalizer(\@eqParams);
 }
 
+sub handleFirmwareDownload {
+	my ($httpClient, $response) = @_;
+
+	my $request = $response->request;
+
+	my $_errorDownloading = sub {
+		_errorDownloading($httpClient, $response, @_);
+	};
+
+	my $id;
+	if (!defined $request || !(($id) = $request->uri =~ $FW_DOWNLOAD_ID_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) {
+		$response->code(204);
+		$response->header('Access-Control-Allow-Origin' => '*');
+
+		$httpClient->send_response($response);
+		return Slim::Web::HTTP::closeHTTPSocket($httpClient);
+	}
+
+	Slim::Networking::SimpleAsyncHTTP->new(
+		sub {
+			my $http = shift;
+			my $content = eval { from_json( $http->content ) };
+
+			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');
+			}
+
+			downloadAndStreamFirmware($httpClient, $response, $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);
+	}
+
+	main::INFOLOG && $log->is_info && $log->info("Requesting firmware from: $path");
+
+	downloadAndStreamFirmware($httpClient, $response, GITHUB_DOWNLOAD_URI . $path);
+}
+
+sub downloadAndStreamFirmware {
+	my ($httpClient, $response, $url, $name) = @_;
+
+	my $_errorDownloading = sub {
+		_errorDownloading($httpClient, $response, @_);
+	};
+
+	$name ||= basename($url);
+
+	if ($name !~ $FW_FILENAME_REGEX) {
+		return $_errorDownloading->(undef, 'Unexpected firmware image name: ' . $name, $url, 400);
+	}
+
+	my $updatesDir = Slim::Utils::OSDetect::dirsFor('updates');
+	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 cached firmware version");
+		$response->code(200);
+		return Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
+	}
+
+	Slim::Networking::SimpleAsyncHTTP->new(
+		sub {
+			my $http = shift;
+
+			if ($http->code != 200 || !-e "$firmwareFile.tmp") {
+				return $_errorDownloading->($http, $http->mess);
+			}
+
+			rename "$firmwareFile.tmp", $firmwareFile or return $_errorDownloading->($http, "Unable to rename temporary $firmwareFile file" );
+
+			$response->code(200);
+			Slim::Web::HTTP::sendStreamingFile($httpClient, $response, 'application/octet-stream', $firmwareFile, undef, 1);
+		},
+		$_errorDownloading,
+		{
+			saveAs => "$firmwareFile.tmp",
+		}
+	)->get($url);
+
+	return;
+}
+
+sub _errorDownloading {
+	my ($httpClient, $response, $http, $error, $url, $code) = @_;
+
+	$error ||= ($http && $http->error) || 'unknown error';
+	$url   ||= ($http && $http->url) || 'no URL';
+	$code  ||= ($http && $http->code) || 500;
+
+	$log->error(sprintf("Failed to get data from Github: %s (%s)", $error || $http->error, $url));
+
+	$response->headers->remove_content_headers;
+	$response->code($code);
+	$response->content_type('text/plain');
+	$response->header('Connection' => 'close');
+	$response->content('');
+
+	$httpClient->send_response($response);
+	Slim::Web::HTTP::closeHTTPSocket($httpClient);
+};
+
+
 1;

部分文件因为文件数量过多而无法显示