Browse Source

Merge pull request #90 from michaelherger/firmware-proxy

Firmware proxy
philippe44 3 năm trước cách đây
mục cha
commit
c83ddc4adc
25 tập tin đã thay đổi với 371 bổ sung132 xóa
  1. 1 1
      components/wifi-manager/webapp/.eslintcache
  2. 2 1
      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. 31 31
      components/wifi-manager/webapp/webpack.h
  6. 0 0
      components/wifi-manager/webapp/webpack/dist/index.html
  7. BIN
      components/wifi-manager/webapp/webpack/dist/index.html.br
  8. BIN
      components/wifi-manager/webapp/webpack/dist/index.html.gz
  9. BIN
      components/wifi-manager/webapp/webpack/dist/js/index.18c3b7.bundle.js.br
  10. BIN
      components/wifi-manager/webapp/webpack/dist/js/index.18c3b7.bundle.js.gz
  11. 0 0
      components/wifi-manager/webapp/webpack/dist/js/index.df6830.bundle.js
  12. BIN
      components/wifi-manager/webapp/webpack/dist/js/index.df6830.bundle.js.br
  13. BIN
      components/wifi-manager/webapp/webpack/dist/js/index.df6830.bundle.js.gz
  14. 0 0
      components/wifi-manager/webapp/webpack/dist/js/node-modules.df6830.bundle.js
  15. 0 0
      components/wifi-manager/webapp/webpack/dist/js/node-modules.df6830.bundle.js.br
  16. BIN
      components/wifi-manager/webapp/webpack/dist/js/node-modules.df6830.bundle.js.gz
  17. 0 0
      components/wifi-manager/webapp/webpack/dist/js/runtime.df6830.bundle.js
  18. 0 0
      components/wifi-manager/webapp/webpack/dist/js/runtime.df6830.bundle.js.br
  19. BIN
      components/wifi-manager/webapp/webpack/dist/js/runtime.df6830.bundle.js.gz
  20. 239 67
      plugin/SqueezeESP32/FirmwareHelper.pm
  21. 17 1
      plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html
  22. 4 0
      plugin/SqueezeESP32/Player.pm
  23. 44 6
      plugin/SqueezeESP32/PlayerSettings.pm
  24. 4 7
      plugin/SqueezeESP32/Plugin.pm
  25. 11 0
      plugin/SqueezeESP32/strings.txt

+ 1 - 1
components/wifi-manager/webapp/.eslintcache

@@ -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",[]]

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

@@ -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() {

+ 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.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)

+ 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_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
 };

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

@@ -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]

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
components/wifi-manager/webapp/webpack/dist/index.html


BIN
components/wifi-manager/webapp/webpack/dist/index.html.br


BIN
components/wifi-manager/webapp/webpack/dist/index.html.gz


BIN
components/wifi-manager/webapp/webpack/dist/js/index.18c3b7.bundle.js.br


BIN
components/wifi-manager/webapp/webpack/dist/js/index.18c3b7.bundle.js.gz


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
components/wifi-manager/webapp/webpack/dist/js/index.df6830.bundle.js


BIN
components/wifi-manager/webapp/webpack/dist/js/index.df6830.bundle.js.br


BIN
components/wifi-manager/webapp/webpack/dist/js/index.df6830.bundle.js.gz


+ 0 - 0
components/wifi-manager/webapp/webpack/dist/js/node-modules.18c3b7.bundle.js → components/wifi-manager/webapp/webpack/dist/js/node-modules.df6830.bundle.js


+ 0 - 0
components/wifi-manager/webapp/webpack/dist/js/node-modules.18c3b7.bundle.js.br → components/wifi-manager/webapp/webpack/dist/js/node-modules.df6830.bundle.js.br


BIN
components/wifi-manager/webapp/webpack/dist/js/node-modules.18c3b7.bundle.js.gz → components/wifi-manager/webapp/webpack/dist/js/node-modules.df6830.bundle.js.gz


+ 0 - 0
components/wifi-manager/webapp/webpack/dist/js/runtime.18c3b7.bundle.js → components/wifi-manager/webapp/webpack/dist/js/runtime.df6830.bundle.js


+ 0 - 0
components/wifi-manager/webapp/webpack/dist/js/runtime.18c3b7.bundle.js.br → components/wifi-manager/webapp/webpack/dist/js/runtime.df6830.bundle.js.br


BIN
components/wifi-manager/webapp/webpack/dist/js/runtime.18c3b7.bundle.js.gz → components/wifi-manager/webapp/webpack/dist/js/runtime.df6830.bundle.js.gz


+ 239 - 67
plugin/SqueezeESP32/FirmwareHelper.pm

@@ -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,16 +103,21 @@ sub prefetchFirmware {
 				}
 			}
 
-			downloadFirmwareFile(sub {
-				main::INFOLOG && $log->is_info && $log->info("Pre-cached firmware file: " . $_[0]);
-			}, sub {
-				my ($http, $error, $url, $code) = @_;
-				$error ||= ($http && $http->error) || 'unknown error';
-				$url   ||= ($http && $http->url) || 'no URL';
+			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 {
+					my ($http, $error, $url, $code) = @_;
+					$error ||= ($http && $http->error) || 'unknown error';
+					$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?/;
+					$log->error(sprintf("Failed to get firmware image from Github: %s (%s)", $error || $http->error, $url));
+				}, $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);
+}
+
+sub _gh2lmsUrl {
+	my ($url) = @_;
+	my $ghPrefix = GITHUB_DOWNLOAD_URI;
+	my $baseUrl = Slim::Utils::Network::serverURL();
+	$url =~ s/$ghPrefix/$baseUrl\/plugins\/SqueezeESP32\/firmware\//;
+	return $url;
+}
 
-	Slim::Utils::Timers::setTimer(undef, Time::HiRes::time() + FIRMWARE_POLL_INTERVAL, \&prefetchFirmware);
+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');
-			}
-
-			downloadFirmwareFile(sub {
-				my $firmwareFile = shift;
-				$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
+		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);
 		}
-	)->get(GITHUB_ASSET_URI . $id);
-
-	return;
-}
 
-sub handleFirmwareDownloadDirect {
-	my ($httpClient, $response) = @_;
-
-	my $request = $response->request;
-
-	my $_errorDownloading = sub {
-		_errorDownloading($httpClient, $response, @_);
-	};
+		main::INFOLOG && $log->is_info && $log->info("Getting custom firmware build");
 
-	my $path;
-	if (!defined $request || !(($path) = $request->uri =~ $FW_DOWNLOAD_REGEX)) {
-		return $_errorDownloading->(undef, 'Invalid request', $request->uri, 400);
+		$response->code(200);
+		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;

+ 17 - 1
plugin/SqueezeESP32/HTML/EN/plugins/SqueezeESP32/settings/player.html

@@ -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">-->
@@ -101,6 +117,6 @@
 				<input type="text" class="stdedit sliderInput_-13_20" name="pref_equalizer.9" id="pref_equalizer.9" value="[% pref_equalizer.9 %]" size="2">
 			[% END %]
 		[% END %]
-	[% END %]		
+	[% END %]
 
 [% PROCESS settings/footer.html %]

+ 4 - 0
plugin/SqueezeESP32/Player.pm

@@ -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);
 }	
 

+ 44 - 6
plugin/SqueezeESP32/PlayerSettings.pm

@@ -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);
 
@@ -62,7 +63,7 @@ sub handler {
 				x => $paramRef->{'pref_artwork_x'} || 0,
 				y => $paramRef->{'pref_artwork_y'} || 0,
 			};
-			
+
 			$cprefs->set('artwork', $artwork);
 			$client->display->modes($client->display->build_modes);
 			# the display update will be done below, after all is completed
@@ -76,14 +77,14 @@ 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;
 			}
 			$cprefs->set('equalizer', $equalizer);
 			$client->update_tones($equalizer);
-		}		
+		}
 	}
 
 	if ($client->displayWidth) {
@@ -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;
+
+	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 $class->SUPER::handler($client, $paramRef);
+	return;
 }
 
 1;

+ 4 - 7
plugin/SqueezeESP32/Plugin.pm

@@ -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 {

+ 11 - 0
plugin/SqueezeESP32/strings.txt

@@ -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

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác