mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[online_image] Last-Modified-Date and ETag response caching (#8782)
This commit is contained in:
		| @@ -213,7 +213,7 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_DOWNLOAD_FINISHED, []): |     for conf in config.get(CONF_ON_DOWNLOAD_FINISHED, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         await automation.build_automation(trigger, [], conf) |         await automation.build_automation(trigger, [(bool, "cached")], conf) | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_ERROR, []): |     for conf in config.get(CONF_ON_ERROR, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
| @@ -3,6 +3,10 @@ | |||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| static const char *const TAG = "online_image"; | static const char *const TAG = "online_image"; | ||||||
|  | static const char *const ETAG_HEADER_NAME = "etag"; | ||||||
|  | static const char *const IF_NONE_MATCH_HEADER_NAME = "if-none-match"; | ||||||
|  | static const char *const LAST_MODIFIED_HEADER_NAME = "last-modified"; | ||||||
|  | static const char *const IF_MODIFIED_SINCE_HEADER_NAME = "if-modified-since"; | ||||||
|  |  | ||||||
| #include "image_decoder.h" | #include "image_decoder.h" | ||||||
|  |  | ||||||
| @@ -60,6 +64,8 @@ void OnlineImage::release() { | |||||||
|     this->height_ = 0; |     this->height_ = 0; | ||||||
|     this->buffer_width_ = 0; |     this->buffer_width_ = 0; | ||||||
|     this->buffer_height_ = 0; |     this->buffer_height_ = 0; | ||||||
|  |     this->last_modified_ = ""; | ||||||
|  |     this->etag_ = ""; | ||||||
|     this->end_connection_(); |     this->end_connection_(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -127,9 +133,17 @@ void OnlineImage::update() { | |||||||
|   } |   } | ||||||
|   accept_header.value = accept_mime_type + ",*/*;q=0.8"; |   accept_header.value = accept_mime_type + ",*/*;q=0.8"; | ||||||
|  |  | ||||||
|  |   if (!this->etag_.empty()) { | ||||||
|  |     headers.push_back(http_request::Header{IF_NONE_MATCH_HEADER_NAME, this->etag_}); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->last_modified_.empty()) { | ||||||
|  |     headers.push_back(http_request::Header{IF_MODIFIED_SINCE_HEADER_NAME, this->last_modified_}); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   headers.push_back(accept_header); |   headers.push_back(accept_header); | ||||||
|  |  | ||||||
|   this->downloader_ = this->parent_->get(this->url_, headers); |   this->downloader_ = this->parent_->get(this->url_, headers, {ETAG_HEADER_NAME, LAST_MODIFIED_HEADER_NAME}); | ||||||
|  |  | ||||||
|   if (this->downloader_ == nullptr) { |   if (this->downloader_ == nullptr) { | ||||||
|     ESP_LOGE(TAG, "Download failed."); |     ESP_LOGE(TAG, "Download failed."); | ||||||
| @@ -141,7 +155,9 @@ void OnlineImage::update() { | |||||||
|   int http_code = this->downloader_->status_code; |   int http_code = this->downloader_->status_code; | ||||||
|   if (http_code == HTTP_CODE_NOT_MODIFIED) { |   if (http_code == HTTP_CODE_NOT_MODIFIED) { | ||||||
|     // Image hasn't changed on server. Skip download. |     // Image hasn't changed on server. Skip download. | ||||||
|  |     ESP_LOGI(TAG, "Server returned HTTP 304 (Not Modified). Download skipped."); | ||||||
|     this->end_connection_(); |     this->end_connection_(); | ||||||
|  |     this->download_finished_callback_.call(true); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (http_code != HTTP_CODE_OK) { |   if (http_code != HTTP_CODE_OK) { | ||||||
| @@ -201,8 +217,10 @@ void OnlineImage::loop() { | |||||||
|     ESP_LOGD(TAG, "Image fully downloaded, read %zu bytes, width/height = %d/%d", this->downloader_->get_bytes_read(), |     ESP_LOGD(TAG, "Image fully downloaded, read %zu bytes, width/height = %d/%d", this->downloader_->get_bytes_read(), | ||||||
|              this->width_, this->height_); |              this->width_, this->height_); | ||||||
|     ESP_LOGD(TAG, "Total time: %lds", ::time(nullptr) - this->start_time_); |     ESP_LOGD(TAG, "Total time: %lds", ::time(nullptr) - this->start_time_); | ||||||
|  |     this->etag_ = this->downloader_->get_response_header(ETAG_HEADER_NAME); | ||||||
|  |     this->last_modified_ = this->downloader_->get_response_header(LAST_MODIFIED_HEADER_NAME); | ||||||
|  |     this->download_finished_callback_.call(false); | ||||||
|     this->end_connection_(); |     this->end_connection_(); | ||||||
|     this->download_finished_callback_.call(); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->downloader_ == nullptr) { |   if (this->downloader_ == nullptr) { | ||||||
| @@ -325,7 +343,7 @@ bool OnlineImage::validate_url_(const std::string &url) { | |||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| void OnlineImage::add_on_finished_callback(std::function<void()> &&callback) { | void OnlineImage::add_on_finished_callback(std::function<void(bool)> &&callback) { | ||||||
|   this->download_finished_callback_.add(std::move(callback)); |   this->download_finished_callback_.add(std::move(callback)); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -63,6 +63,8 @@ class OnlineImage : public PollingComponent, | |||||||
|     if (this->validate_url_(url)) { |     if (this->validate_url_(url)) { | ||||||
|       this->url_ = url; |       this->url_ = url; | ||||||
|     } |     } | ||||||
|  |     this->etag_ = ""; | ||||||
|  |     this->last_modified_ = ""; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -86,7 +88,7 @@ class OnlineImage : public PollingComponent, | |||||||
|    */ |    */ | ||||||
|   size_t resize_download_buffer(size_t size) { return this->download_buffer_.resize(size); } |   size_t resize_download_buffer(size_t size) { return this->download_buffer_.resize(size); } | ||||||
|  |  | ||||||
|   void add_on_finished_callback(std::function<void()> &&callback); |   void add_on_finished_callback(std::function<void(bool)> &&callback); | ||||||
|   void add_on_error_callback(std::function<void()> &&callback); |   void add_on_error_callback(std::function<void()> &&callback); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| @@ -131,7 +133,7 @@ class OnlineImage : public PollingComponent, | |||||||
|  |  | ||||||
|   void end_connection_(); |   void end_connection_(); | ||||||
|  |  | ||||||
|   CallbackManager<void()> download_finished_callback_{}; |   CallbackManager<void(bool)> download_finished_callback_{}; | ||||||
|   CallbackManager<void()> download_error_callback_{}; |   CallbackManager<void()> download_error_callback_{}; | ||||||
|  |  | ||||||
|   std::shared_ptr<http_request::HttpContainer> downloader_{nullptr}; |   std::shared_ptr<http_request::HttpContainer> downloader_{nullptr}; | ||||||
| @@ -173,6 +175,14 @@ class OnlineImage : public PollingComponent, | |||||||
|    * decoded images). |    * decoded images). | ||||||
|    */ |    */ | ||||||
|   int buffer_height_; |   int buffer_height_; | ||||||
|  |   /** | ||||||
|  |    * The value of the ETag HTTP header provided in the last response. | ||||||
|  |    */ | ||||||
|  |   std::string etag_ = ""; | ||||||
|  |   /** | ||||||
|  |    * The value of the Last-Modified HTTP header provided in the last response. | ||||||
|  |    */ | ||||||
|  |   std::string last_modified_ = ""; | ||||||
|  |  | ||||||
|   time_t start_time_; |   time_t start_time_; | ||||||
|  |  | ||||||
| @@ -202,10 +212,10 @@ template<typename... Ts> class OnlineImageReleaseAction : public Action<Ts...> { | |||||||
|   OnlineImage *parent_; |   OnlineImage *parent_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class DownloadFinishedTrigger : public Trigger<> { | class DownloadFinishedTrigger : public Trigger<bool> { | ||||||
|  public: |  public: | ||||||
|   explicit DownloadFinishedTrigger(OnlineImage *parent) { |   explicit DownloadFinishedTrigger(OnlineImage *parent) { | ||||||
|     parent->add_on_finished_callback([this]() { this->trigger(); }); |     parent->add_on_finished_callback([this](bool cached) { this->trigger(cached); }); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,13 @@ online_image: | |||||||
|     format: PNG |     format: PNG | ||||||
|     type: BINARY |     type: BINARY | ||||||
|     resize: 50x50 |     resize: 50x50 | ||||||
|  |     on_download_finished: | ||||||
|  |       lambda: |- | ||||||
|  |         if (cached) { | ||||||
|  |           ESP_LOGD("online_image", "Cache hit: using cached image"); | ||||||
|  |         } else { | ||||||
|  |           ESP_LOGD("online_image", "Cache miss: fresh download"); | ||||||
|  |         } | ||||||
|   - id: online_binary_transparent_image |   - id: online_binary_transparent_image | ||||||
|     url: http://www.libpng.org/pub/png/img_png/pnglogo-blk-tiny.png |     url: http://www.libpng.org/pub/png/img_png/pnglogo-blk-tiny.png | ||||||
|     type: BINARY |     type: BINARY | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user