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 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&amp; path, const HueCommandAPI&amp; 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&amp; 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&amp; 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&amp; ip, const int port, const std::string&amp; 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&amp; 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
... ...