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 | 37 | //! \brief Constructs APICache which forwards to a base cache |
| 38 | 38 | //! \param baseCache Base cache providing a parent state, must not be nullptr |
| 39 | 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 | 47 | //! \brief Constructs APICache with an own internal json cache |
| 45 | 48 | //! \param path URL appended after username, may be empty. |
| ... | ... | @@ -52,6 +55,8 @@ public: |
| 52 | 55 | //! \throws HueException when response contained no body |
| 53 | 56 | //! \throws HueAPIResponseException when response contains an error |
| 54 | 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 | 60 | void refresh(); |
| 56 | 61 | |
| 57 | 62 | //! \brief Get cached value, refresh if necessary. |
| ... | ... | @@ -70,6 +75,13 @@ public: |
| 70 | 75 | //! \brief Get HueCommandAPI used for requests |
| 71 | 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 | 85 | private: |
| 74 | 86 | std::shared_ptr<APICache> base; |
| 75 | 87 | std::string path; | ... | ... |
include/hueplusplus/Hue.h
| ... | ... | @@ -360,7 +360,7 @@ private: |
| 360 | 360 | |
| 361 | 361 | std::shared_ptr<const IHttpHandler> http_handler; //!< A IHttpHandler that is used to communicate with the |
| 362 | 362 | //!< bridge |
| 363 | - | |
| 363 | + std::chrono::steady_clock::duration refreshDuration; | |
| 364 | 364 | std::shared_ptr<APICache> stateCache; |
| 365 | 365 | ResourceList<HueLight, int> lights; |
| 366 | 366 | CreateableResourceList<Group, int, CreateGroup> groups; | ... | ... |
include/hueplusplus/ResourceList.h
| ... | ... | @@ -27,9 +27,9 @@ |
| 27 | 27 | #include <string> |
| 28 | 28 | #include <vector> |
| 29 | 29 | |
| 30 | -#include "Utils.h" | |
| 31 | 30 | #include "APICache.h" |
| 32 | 31 | #include "HueException.h" |
| 32 | +#include "Utils.h" | |
| 33 | 33 | |
| 34 | 34 | namespace hueplusplus |
| 35 | 35 | { |
| ... | ... | @@ -48,14 +48,15 @@ public: |
| 48 | 48 | "IdType must be integral or string"); |
| 49 | 49 | |
| 50 | 50 | //! \brief Construct ResourceList using a base cache and optional factory function |
| 51 | - //! \param path Path of the resource list | |
| 52 | 51 | //! \param baseCache Base cache which holds the parent state, not nullptr |
| 53 | 52 | //! \param cacheEntry Entry name of the list state in the base cache |
| 53 | + //! \param refreshDuration Interval between refreshing the cache | |
| 54 | 54 | //! \param factory Optional factory function to create Resources. |
| 55 | 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 | 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 | 61 | //! \brief Construct ResourceList with a separate cache and optional factory function |
| 61 | 62 | //! \param commands HueCommandAPI for requests | ... | ... |
src/APICache.cpp
| ... | ... | @@ -25,11 +25,12 @@ |
| 25 | 25 | |
| 26 | 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 | 30 | : base(baseCache), |
| 30 | 31 | path(subEntry), |
| 31 | 32 | commands(baseCache->commands), |
| 32 | - refreshDuration(baseCache->refreshDuration), | |
| 33 | + refreshDuration(refresh), | |
| 33 | 34 | lastRefresh(baseCache->lastRefresh) |
| 34 | 35 | {} |
| 35 | 36 | |
| ... | ... | @@ -39,22 +40,37 @@ APICache::APICache(const std::string& path, const HueCommandAPI& commands, std:: |
| 39 | 40 | |
| 40 | 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 | 46 | base->refresh(); |
| 45 | 47 | } |
| 46 | 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 | 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 | 63 | nlohmann::json& APICache::getValue() |
| 54 | 64 | { |
| 65 | + if (needsRefresh()) | |
| 66 | + { | |
| 67 | + refresh(); | |
| 68 | + } | |
| 55 | 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 | 74 | auto pos = baseState.find(path); |
| 59 | 75 | if (pos != baseState.end()) |
| 60 | 76 | { |
| ... | ... | @@ -67,24 +83,6 @@ nlohmann::json& APICache::getValue() |
| 67 | 83 | } |
| 68 | 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 | 86 | return value; |
| 89 | 87 | } |
| 90 | 88 | } |
| ... | ... | @@ -119,4 +117,44 @@ HueCommandAPI& APICache::getCommandAPI() |
| 119 | 117 | { |
| 120 | 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 | 160 | } // namespace hueplusplus | ... | ... |
src/Hue.cpp
| ... | ... | @@ -136,12 +136,14 @@ Hue::Hue(const std::string& ip, const int port, const std::string& username, |
| 136 | 136 | username(username), |
| 137 | 137 | port(port), |
| 138 | 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 | 143 | [factory = HueLightFactory(stateCache->getCommandAPI(), refreshDuration)]( |
| 142 | 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 | 149 | void Hue::refresh() |
| ... | ... | @@ -419,12 +421,11 @@ std::string Hue::getPictureOfModel(const std::string& model_id) const |
| 419 | 421 | void Hue::setHttpHandler(std::shared_ptr<const IHttpHandler> handler) |
| 420 | 422 | { |
| 421 | 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 | 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 | 431 | } // namespace hueplusplus | ... | ... |