diff --git a/include/hueplusplus/APICache.h b/include/hueplusplus/APICache.h index 9370260..15e1f26 100644 --- a/include/hueplusplus/APICache.h +++ b/include/hueplusplus/APICache.h @@ -37,9 +37,12 @@ public: //! \brief Constructs APICache which forwards to a base cache //! \param baseCache Base cache providing a parent state, must not be nullptr //! \param subEntry Key of the child to use in the base cache + //! \param refresh Interval between cache refreshing. May be 0 to always refresh. + //! This is independent from the base cache refresh rate. //! - //! Uses same refresh duration as base cache. Refresh calls are forwarded. - APICache(std::shared_ptr baseCache, const std::string& subEntry); + //! Uses same refresh duration as base cache. Refreshes only part of the base cache. + APICache( + std::shared_ptr baseCache, const std::string& subEntry, std::chrono::steady_clock::duration refresh); //! \brief Constructs APICache with an own internal json cache //! \param path URL appended after username, may be empty. @@ -52,6 +55,8 @@ public: //! \throws HueException when response contained no body //! \throws HueAPIResponseException when response contains an error //! \throws nlohmann::json::parse_error when response could not be parsed + //! + //! If there is a base cache, refreshes only the used part of that cache. void refresh(); //! \brief Get cached value, refresh if necessary. @@ -70,6 +75,13 @@ public: //! \brief Get HueCommandAPI used for requests HueCommandAPI& getCommandAPI(); + //! \brief Get path the cache is refreshed from + //! \returns Request path as passed to HueCommandAPI::GETRequest + std::string getRequestPath() const; + +private: + bool needsRefresh(); + private: std::shared_ptr base; std::string path; diff --git a/include/hueplusplus/Hue.h b/include/hueplusplus/Hue.h index 9c7d1cb..7e6ef27 100644 --- a/include/hueplusplus/Hue.h +++ b/include/hueplusplus/Hue.h @@ -360,7 +360,7 @@ private: std::shared_ptr http_handler; //!< A IHttpHandler that is used to communicate with the //!< bridge - + std::chrono::steady_clock::duration refreshDuration; std::shared_ptr stateCache; ResourceList lights; CreateableResourceList groups; diff --git a/include/hueplusplus/ResourceList.h b/include/hueplusplus/ResourceList.h index 66eb18e..f2a5394 100644 --- a/include/hueplusplus/ResourceList.h +++ b/include/hueplusplus/ResourceList.h @@ -27,9 +27,9 @@ #include #include -#include "Utils.h" #include "APICache.h" #include "HueException.h" +#include "Utils.h" namespace hueplusplus { @@ -48,14 +48,15 @@ public: "IdType must be integral or string"); //! \brief Construct ResourceList using a base cache and optional factory function - //! \param path Path of the resource list //! \param baseCache Base cache which holds the parent state, not nullptr //! \param cacheEntry Entry name of the list state in the base cache + //! \param refreshDuration Interval between refreshing the cache //! \param factory Optional factory function to create Resources. //! Necessary if Resource is not constructible as described above. - ResourceList(const std::string& path, std::shared_ptr baseCache, const std::string& cacheEntry, + ResourceList(std::shared_ptr baseCache, const std::string& cacheEntry, + std::chrono::steady_clock::duration refreshDuration, const std::function& factory = nullptr) - : stateCache(baseCache, cacheEntry), factory(factory), path(path + '/') + : stateCache(baseCache, cacheEntry, refreshDuration), factory(factory), path(stateCache.getRequestPath() + '/') {} //! \brief Construct ResourceList with a separate cache and optional factory function //! \param commands HueCommandAPI for requests diff --git a/src/APICache.cpp b/src/APICache.cpp index edf380c..7b3d151 100644 --- a/src/APICache.cpp +++ b/src/APICache.cpp @@ -25,11 +25,12 @@ namespace hueplusplus { -APICache::APICache(std::shared_ptr baseCache, const std::string& subEntry) +APICache::APICache( + std::shared_ptr baseCache, const std::string& subEntry, std::chrono::steady_clock::duration refresh) : base(baseCache), path(subEntry), commands(baseCache->commands), - refreshDuration(baseCache->refreshDuration), + refreshDuration(refresh), lastRefresh(baseCache->lastRefresh) {} @@ -39,22 +40,37 @@ APICache::APICache(const std::string& path, const HueCommandAPI& commands, std:: void APICache::refresh() { - if (base) + // Only refresh part of the cache, because that is more efficient + if (base && base->needsRefresh()) { base->refresh(); } else { - value = commands.GETRequest(path, nlohmann::json::object(), CURRENT_FILE_INFO); + nlohmann::json result = commands.GETRequest(getRequestPath(), nlohmann::json::object(), CURRENT_FILE_INFO); lastRefresh = std::chrono::steady_clock::now(); + if (base) + { + base->value[path] = std::move(result); + } + else + { + value = std::move(result); + } } } nlohmann::json& APICache::getValue() { + if (needsRefresh()) + { + refresh(); + } if (base) { - nlohmann::json& baseState = base->getValue(); + // Do not call getValue here, because that could cause another refresh + // if base has refresh duration 0 + nlohmann::json& baseState = base->value; auto pos = baseState.find(path); if (pos != baseState.end()) { @@ -67,24 +83,6 @@ nlohmann::json& APICache::getValue() } else { - using clock = std::chrono::steady_clock; - // Explicitly check for zero in case refreshDuration is duration::max() - // Negative duration causes overflow check to overflow itself - if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0) - { - // No value set yet - refresh(); - } - // Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be). - // If addition would overflow, do not refresh - else if (clock::duration::max() - refreshDuration > lastRefresh.time_since_epoch()) - { - clock::time_point nextRefresh = lastRefresh + refreshDuration; - if (clock::now() >= nextRefresh) - { - refresh(); - } - } return value; } } @@ -119,4 +117,44 @@ HueCommandAPI& APICache::getCommandAPI() { return commands; } + +bool APICache::needsRefresh() +{ + using clock = std::chrono::steady_clock; + if (base) + { + // Update lastRefresh in case base was refreshed + lastRefresh = std::max(lastRefresh, base->lastRefresh); + } + + // Explicitly check for zero in case refreshDuration is duration::max() + // Negative duration causes overflow check to overflow itself + if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0) + { + // No value set yet + return true; + } + // Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be). + // If addition would overflow, do not refresh + else if (clock::duration::max() - refreshDuration > lastRefresh.time_since_epoch()) + { + clock::time_point nextRefresh = lastRefresh + refreshDuration; + if (clock::now() >= nextRefresh) + { + return true; + } + } + return false; +} +std::string APICache::getRequestPath() const +{ + std::string result; + if (base) + { + result = base->getRequestPath(); + result.push_back('/'); + } + result.append(path); + return result; +} } // namespace hueplusplus diff --git a/src/Hue.cpp b/src/Hue.cpp index 4fc0667..8c8bb7c 100644 --- a/src/Hue.cpp +++ b/src/Hue.cpp @@ -136,12 +136,14 @@ Hue::Hue(const std::string& ip, const int port, const std::string& username, username(username), port(port), http_handler(std::move(handler)), - stateCache(std::make_shared("", HueCommandAPI(ip, port, username, http_handler), refreshDuration)), - lights("/lights", stateCache, "lights", + refreshDuration(refreshDuration), + stateCache(std::make_shared( + "", HueCommandAPI(ip, port, username, http_handler), std::chrono::steady_clock::duration::max())), + lights(stateCache, "lights", refreshDuration, [factory = HueLightFactory(stateCache->getCommandAPI(), refreshDuration)]( int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }), - groups("/groups", stateCache, "groups"), - schedules("/schedules", stateCache, "schedules") + groups(stateCache, "groups", refreshDuration), + schedules(stateCache, "schedules", refreshDuration) {} void Hue::refresh() @@ -419,12 +421,11 @@ std::string Hue::getPictureOfModel(const std::string& model_id) const void Hue::setHttpHandler(std::shared_ptr handler) { http_handler = handler; - stateCache - = std::make_shared("", HueCommandAPI(ip, port, username, handler), stateCache->getRefreshDuration()); - lights = ResourceList("/lights", stateCache, "lights", - [factory = HueLightFactory(stateCache->getCommandAPI(), stateCache->getRefreshDuration())]( + stateCache = std::make_shared("", HueCommandAPI(ip, port, username, handler), refreshDuration); + lights = ResourceList(stateCache, "lights", refreshDuration, + [factory = HueLightFactory(stateCache->getCommandAPI(), refreshDuration)]( int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }); - groups = CreateableResourceList("/groups", stateCache, "groups"); - schedules = CreateableResourceList("/schedules", stateCache, "schedules"); + groups = CreateableResourceList(stateCache, "groups", refreshDuration); + schedules = CreateableResourceList(stateCache, "schedules", refreshDuration); } } // namespace hueplusplus