Commit b43598cefb91f90f72629d69d9eb5bd62fd7e164

Authored by Jojo-1000
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.
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&amp; path, const HueCommandAPI&amp; commands, std:: @@ -39,22 +40,37 @@ APICache::APICache(const std::string&amp; path, const HueCommandAPI&amp; 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&amp; APICache::getValue() @@ -67,24 +83,6 @@ nlohmann::json&amp; 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&amp; APICache::getCommandAPI() @@ -119,4 +117,44 @@ HueCommandAPI&amp; 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&amp; ip, const int port, const std::string&amp; username, @@ -136,12 +136,14 @@ Hue::Hue(const std::string&amp; ip, const int port, const std::string&amp; 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&amp; model_id) const @@ -419,12 +421,11 @@ std::string Hue::getPictureOfModel(const std::string&amp; 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