Commit b43598cefb91f90f72629d69d9eb5bd62fd7e164
Committed by
Moritz Wirger
1 parent
ab6b1278
Change APICache to only refresh part of base cache.
The complete state of the hue bridge should only be queried as little as possible. Now it is only done once, after that only the requested parts are refreshed.
Showing
5 changed files
with
92 additions
and
40 deletions
include/hueplusplus/APICache.h
| @@ -37,9 +37,12 @@ public: | @@ -37,9 +37,12 @@ public: | ||
| 37 | //! \brief Constructs APICache which forwards to a base cache | 37 | //! \brief Constructs APICache which forwards to a base cache |
| 38 | //! \param baseCache Base cache providing a parent state, must not be nullptr | 38 | //! \param baseCache Base cache providing a parent state, must not be nullptr |
| 39 | //! \param subEntry Key of the child to use in the base cache | 39 | //! \param subEntry Key of the child to use in the base cache |
| 40 | + //! \param refresh Interval between cache refreshing. May be 0 to always refresh. | ||
| 41 | + //! This is independent from the base cache refresh rate. | ||
| 40 | //! | 42 | //! |
| 41 | - //! Uses same refresh duration as base cache. Refresh calls are forwarded. | ||
| 42 | - APICache(std::shared_ptr<APICache> baseCache, const std::string& subEntry); | 43 | + //! Uses same refresh duration as base cache. Refreshes only part of the base cache. |
| 44 | + APICache( | ||
| 45 | + std::shared_ptr<APICache> baseCache, const std::string& subEntry, std::chrono::steady_clock::duration refresh); | ||
| 43 | 46 | ||
| 44 | //! \brief Constructs APICache with an own internal json cache | 47 | //! \brief Constructs APICache with an own internal json cache |
| 45 | //! \param path URL appended after username, may be empty. | 48 | //! \param path URL appended after username, may be empty. |
| @@ -52,6 +55,8 @@ public: | @@ -52,6 +55,8 @@ public: | ||
| 52 | //! \throws HueException when response contained no body | 55 | //! \throws HueException when response contained no body |
| 53 | //! \throws HueAPIResponseException when response contains an error | 56 | //! \throws HueAPIResponseException when response contains an error |
| 54 | //! \throws nlohmann::json::parse_error when response could not be parsed | 57 | //! \throws nlohmann::json::parse_error when response could not be parsed |
| 58 | + //! | ||
| 59 | + //! If there is a base cache, refreshes only the used part of that cache. | ||
| 55 | void refresh(); | 60 | void refresh(); |
| 56 | 61 | ||
| 57 | //! \brief Get cached value, refresh if necessary. | 62 | //! \brief Get cached value, refresh if necessary. |
| @@ -70,6 +75,13 @@ public: | @@ -70,6 +75,13 @@ public: | ||
| 70 | //! \brief Get HueCommandAPI used for requests | 75 | //! \brief Get HueCommandAPI used for requests |
| 71 | HueCommandAPI& getCommandAPI(); | 76 | HueCommandAPI& getCommandAPI(); |
| 72 | 77 | ||
| 78 | + //! \brief Get path the cache is refreshed from | ||
| 79 | + //! \returns Request path as passed to HueCommandAPI::GETRequest | ||
| 80 | + std::string getRequestPath() const; | ||
| 81 | + | ||
| 82 | +private: | ||
| 83 | + bool needsRefresh(); | ||
| 84 | + | ||
| 73 | private: | 85 | private: |
| 74 | std::shared_ptr<APICache> base; | 86 | std::shared_ptr<APICache> base; |
| 75 | std::string path; | 87 | std::string path; |
include/hueplusplus/Hue.h
| @@ -360,7 +360,7 @@ private: | @@ -360,7 +360,7 @@ private: | ||
| 360 | 360 | ||
| 361 | std::shared_ptr<const IHttpHandler> http_handler; //!< A IHttpHandler that is used to communicate with the | 361 | std::shared_ptr<const IHttpHandler> http_handler; //!< A IHttpHandler that is used to communicate with the |
| 362 | //!< bridge | 362 | //!< bridge |
| 363 | - | 363 | + std::chrono::steady_clock::duration refreshDuration; |
| 364 | std::shared_ptr<APICache> stateCache; | 364 | std::shared_ptr<APICache> stateCache; |
| 365 | ResourceList<HueLight, int> lights; | 365 | ResourceList<HueLight, int> lights; |
| 366 | CreateableResourceList<Group, int, CreateGroup> groups; | 366 | CreateableResourceList<Group, int, CreateGroup> groups; |
include/hueplusplus/ResourceList.h
| @@ -27,9 +27,9 @@ | @@ -27,9 +27,9 @@ | ||
| 27 | #include <string> | 27 | #include <string> |
| 28 | #include <vector> | 28 | #include <vector> |
| 29 | 29 | ||
| 30 | -#include "Utils.h" | ||
| 31 | #include "APICache.h" | 30 | #include "APICache.h" |
| 32 | #include "HueException.h" | 31 | #include "HueException.h" |
| 32 | +#include "Utils.h" | ||
| 33 | 33 | ||
| 34 | namespace hueplusplus | 34 | namespace hueplusplus |
| 35 | { | 35 | { |
| @@ -48,14 +48,15 @@ public: | @@ -48,14 +48,15 @@ public: | ||
| 48 | "IdType must be integral or string"); | 48 | "IdType must be integral or string"); |
| 49 | 49 | ||
| 50 | //! \brief Construct ResourceList using a base cache and optional factory function | 50 | //! \brief Construct ResourceList using a base cache and optional factory function |
| 51 | - //! \param path Path of the resource list | ||
| 52 | //! \param baseCache Base cache which holds the parent state, not nullptr | 51 | //! \param baseCache Base cache which holds the parent state, not nullptr |
| 53 | //! \param cacheEntry Entry name of the list state in the base cache | 52 | //! \param cacheEntry Entry name of the list state in the base cache |
| 53 | + //! \param refreshDuration Interval between refreshing the cache | ||
| 54 | //! \param factory Optional factory function to create Resources. | 54 | //! \param factory Optional factory function to create Resources. |
| 55 | //! Necessary if Resource is not constructible as described above. | 55 | //! Necessary if Resource is not constructible as described above. |
| 56 | - ResourceList(const std::string& path, std::shared_ptr<APICache> baseCache, const std::string& cacheEntry, | 56 | + ResourceList(std::shared_ptr<APICache> baseCache, const std::string& cacheEntry, |
| 57 | + std::chrono::steady_clock::duration refreshDuration, | ||
| 57 | const std::function<Resource(int, const nlohmann::json&)>& factory = nullptr) | 58 | const std::function<Resource(int, const nlohmann::json&)>& factory = nullptr) |
| 58 | - : stateCache(baseCache, cacheEntry), factory(factory), path(path + '/') | 59 | + : stateCache(baseCache, cacheEntry, refreshDuration), factory(factory), path(stateCache.getRequestPath() + '/') |
| 59 | {} | 60 | {} |
| 60 | //! \brief Construct ResourceList with a separate cache and optional factory function | 61 | //! \brief Construct ResourceList with a separate cache and optional factory function |
| 61 | //! \param commands HueCommandAPI for requests | 62 | //! \param commands HueCommandAPI for requests |
src/APICache.cpp
| @@ -25,11 +25,12 @@ | @@ -25,11 +25,12 @@ | ||
| 25 | 25 | ||
| 26 | namespace hueplusplus | 26 | namespace hueplusplus |
| 27 | { | 27 | { |
| 28 | -APICache::APICache(std::shared_ptr<APICache> baseCache, const std::string& subEntry) | 28 | +APICache::APICache( |
| 29 | + std::shared_ptr<APICache> baseCache, const std::string& subEntry, std::chrono::steady_clock::duration refresh) | ||
| 29 | : base(baseCache), | 30 | : base(baseCache), |
| 30 | path(subEntry), | 31 | path(subEntry), |
| 31 | commands(baseCache->commands), | 32 | commands(baseCache->commands), |
| 32 | - refreshDuration(baseCache->refreshDuration), | 33 | + refreshDuration(refresh), |
| 33 | lastRefresh(baseCache->lastRefresh) | 34 | lastRefresh(baseCache->lastRefresh) |
| 34 | {} | 35 | {} |
| 35 | 36 | ||
| @@ -39,22 +40,37 @@ APICache::APICache(const std::string& path, const HueCommandAPI& commands, std:: | @@ -39,22 +40,37 @@ APICache::APICache(const std::string& path, const HueCommandAPI& commands, std:: | ||
| 39 | 40 | ||
| 40 | void APICache::refresh() | 41 | void APICache::refresh() |
| 41 | { | 42 | { |
| 42 | - if (base) | 43 | + // Only refresh part of the cache, because that is more efficient |
| 44 | + if (base && base->needsRefresh()) | ||
| 43 | { | 45 | { |
| 44 | base->refresh(); | 46 | base->refresh(); |
| 45 | } | 47 | } |
| 46 | else | 48 | else |
| 47 | { | 49 | { |
| 48 | - value = commands.GETRequest(path, nlohmann::json::object(), CURRENT_FILE_INFO); | 50 | + nlohmann::json result = commands.GETRequest(getRequestPath(), nlohmann::json::object(), CURRENT_FILE_INFO); |
| 49 | lastRefresh = std::chrono::steady_clock::now(); | 51 | lastRefresh = std::chrono::steady_clock::now(); |
| 52 | + if (base) | ||
| 53 | + { | ||
| 54 | + base->value[path] = std::move(result); | ||
| 55 | + } | ||
| 56 | + else | ||
| 57 | + { | ||
| 58 | + value = std::move(result); | ||
| 59 | + } | ||
| 50 | } | 60 | } |
| 51 | } | 61 | } |
| 52 | 62 | ||
| 53 | nlohmann::json& APICache::getValue() | 63 | nlohmann::json& APICache::getValue() |
| 54 | { | 64 | { |
| 65 | + if (needsRefresh()) | ||
| 66 | + { | ||
| 67 | + refresh(); | ||
| 68 | + } | ||
| 55 | if (base) | 69 | if (base) |
| 56 | { | 70 | { |
| 57 | - nlohmann::json& baseState = base->getValue(); | 71 | + // Do not call getValue here, because that could cause another refresh |
| 72 | + // if base has refresh duration 0 | ||
| 73 | + nlohmann::json& baseState = base->value; | ||
| 58 | auto pos = baseState.find(path); | 74 | auto pos = baseState.find(path); |
| 59 | if (pos != baseState.end()) | 75 | if (pos != baseState.end()) |
| 60 | { | 76 | { |
| @@ -67,24 +83,6 @@ nlohmann::json& APICache::getValue() | @@ -67,24 +83,6 @@ nlohmann::json& APICache::getValue() | ||
| 67 | } | 83 | } |
| 68 | else | 84 | else |
| 69 | { | 85 | { |
| 70 | - using clock = std::chrono::steady_clock; | ||
| 71 | - // Explicitly check for zero in case refreshDuration is duration::max() | ||
| 72 | - // Negative duration causes overflow check to overflow itself | ||
| 73 | - if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0) | ||
| 74 | - { | ||
| 75 | - // No value set yet | ||
| 76 | - refresh(); | ||
| 77 | - } | ||
| 78 | - // Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be). | ||
| 79 | - // If addition would overflow, do not refresh | ||
| 80 | - else if (clock::duration::max() - refreshDuration > lastRefresh.time_since_epoch()) | ||
| 81 | - { | ||
| 82 | - clock::time_point nextRefresh = lastRefresh + refreshDuration; | ||
| 83 | - if (clock::now() >= nextRefresh) | ||
| 84 | - { | ||
| 85 | - refresh(); | ||
| 86 | - } | ||
| 87 | - } | ||
| 88 | return value; | 86 | return value; |
| 89 | } | 87 | } |
| 90 | } | 88 | } |
| @@ -119,4 +117,44 @@ HueCommandAPI& APICache::getCommandAPI() | @@ -119,4 +117,44 @@ HueCommandAPI& APICache::getCommandAPI() | ||
| 119 | { | 117 | { |
| 120 | return commands; | 118 | return commands; |
| 121 | } | 119 | } |
| 120 | + | ||
| 121 | +bool APICache::needsRefresh() | ||
| 122 | +{ | ||
| 123 | + using clock = std::chrono::steady_clock; | ||
| 124 | + if (base) | ||
| 125 | + { | ||
| 126 | + // Update lastRefresh in case base was refreshed | ||
| 127 | + lastRefresh = std::max(lastRefresh, base->lastRefresh); | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + // Explicitly check for zero in case refreshDuration is duration::max() | ||
| 131 | + // Negative duration causes overflow check to overflow itself | ||
| 132 | + if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0) | ||
| 133 | + { | ||
| 134 | + // No value set yet | ||
| 135 | + return true; | ||
| 136 | + } | ||
| 137 | + // Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be). | ||
| 138 | + // If addition would overflow, do not refresh | ||
| 139 | + else if (clock::duration::max() - refreshDuration > lastRefresh.time_since_epoch()) | ||
| 140 | + { | ||
| 141 | + clock::time_point nextRefresh = lastRefresh + refreshDuration; | ||
| 142 | + if (clock::now() >= nextRefresh) | ||
| 143 | + { | ||
| 144 | + return true; | ||
| 145 | + } | ||
| 146 | + } | ||
| 147 | + return false; | ||
| 148 | +} | ||
| 149 | +std::string APICache::getRequestPath() const | ||
| 150 | +{ | ||
| 151 | + std::string result; | ||
| 152 | + if (base) | ||
| 153 | + { | ||
| 154 | + result = base->getRequestPath(); | ||
| 155 | + result.push_back('/'); | ||
| 156 | + } | ||
| 157 | + result.append(path); | ||
| 158 | + return result; | ||
| 159 | +} | ||
| 122 | } // namespace hueplusplus | 160 | } // namespace hueplusplus |
src/Hue.cpp
| @@ -136,12 +136,14 @@ Hue::Hue(const std::string& ip, const int port, const std::string& username, | @@ -136,12 +136,14 @@ Hue::Hue(const std::string& ip, const int port, const std::string& username, | ||
| 136 | username(username), | 136 | username(username), |
| 137 | port(port), | 137 | port(port), |
| 138 | http_handler(std::move(handler)), | 138 | http_handler(std::move(handler)), |
| 139 | - stateCache(std::make_shared<APICache>("", HueCommandAPI(ip, port, username, http_handler), refreshDuration)), | ||
| 140 | - lights("/lights", stateCache, "lights", | 139 | + refreshDuration(refreshDuration), |
| 140 | + stateCache(std::make_shared<APICache>( | ||
| 141 | + "", HueCommandAPI(ip, port, username, http_handler), std::chrono::steady_clock::duration::max())), | ||
| 142 | + lights(stateCache, "lights", refreshDuration, | ||
| 141 | [factory = HueLightFactory(stateCache->getCommandAPI(), refreshDuration)]( | 143 | [factory = HueLightFactory(stateCache->getCommandAPI(), refreshDuration)]( |
| 142 | int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }), | 144 | int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }), |
| 143 | - groups("/groups", stateCache, "groups"), | ||
| 144 | - schedules("/schedules", stateCache, "schedules") | 145 | + groups(stateCache, "groups", refreshDuration), |
| 146 | + schedules(stateCache, "schedules", refreshDuration) | ||
| 145 | {} | 147 | {} |
| 146 | 148 | ||
| 147 | void Hue::refresh() | 149 | void Hue::refresh() |
| @@ -419,12 +421,11 @@ std::string Hue::getPictureOfModel(const std::string& model_id) const | @@ -419,12 +421,11 @@ std::string Hue::getPictureOfModel(const std::string& model_id) const | ||
| 419 | void Hue::setHttpHandler(std::shared_ptr<const IHttpHandler> handler) | 421 | void Hue::setHttpHandler(std::shared_ptr<const IHttpHandler> handler) |
| 420 | { | 422 | { |
| 421 | http_handler = handler; | 423 | http_handler = handler; |
| 422 | - stateCache | ||
| 423 | - = std::make_shared<APICache>("", HueCommandAPI(ip, port, username, handler), stateCache->getRefreshDuration()); | ||
| 424 | - lights = ResourceList<HueLight, int>("/lights", stateCache, "lights", | ||
| 425 | - [factory = HueLightFactory(stateCache->getCommandAPI(), stateCache->getRefreshDuration())]( | 424 | + stateCache = std::make_shared<APICache>("", HueCommandAPI(ip, port, username, handler), refreshDuration); |
| 425 | + lights = ResourceList<HueLight, int>(stateCache, "lights", refreshDuration, | ||
| 426 | + [factory = HueLightFactory(stateCache->getCommandAPI(), refreshDuration)]( | ||
| 426 | int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }); | 427 | int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }); |
| 427 | - groups = CreateableResourceList<Group, int, CreateGroup>("/groups", stateCache, "groups"); | ||
| 428 | - schedules = CreateableResourceList<Schedule, int, CreateSchedule>("/schedules", stateCache, "schedules"); | 428 | + groups = CreateableResourceList<Group, int, CreateGroup>(stateCache, "groups", refreshDuration); |
| 429 | + schedules = CreateableResourceList<Schedule, int, CreateSchedule>(stateCache, "schedules", refreshDuration); | ||
| 429 | } | 430 | } |
| 430 | } // namespace hueplusplus | 431 | } // namespace hueplusplus |