Commit 12b9174988a50e8f002b8207bb453f5112ef6c62

Authored by Jojo-1000
Committed by Moritz Wirger
1 parent 972c34a0

Add possible baseCache to APICache to share cached state.

include/hueplusplus/APICache.h
... ... @@ -34,6 +34,7 @@ namespace hueplusplus
34 34 class APICache
35 35 {
36 36 public:
  37 + APICache(std::shared_ptr<APICache> baseCache, const std::string& subEntry);
37 38 //! \brief Constructs APICache
38 39 //! \param path URL appended after username, may be empty.
39 40 //! \param commands HueCommandAPI for making API requests.
... ... @@ -54,12 +55,14 @@ public:
54 55 //! \throws nlohmann::json::parse_error when response could not be parsed
55 56 nlohmann::json& getValue();
56 57 //! \brief Get cached value, does not refresh.
  58 + //! \throws HueException when no previous request was cached
57 59 const nlohmann::json& getValue() const;
58 60  
59 61 //! \brief Get duration between refreshes.
60 62 std::chrono::steady_clock::duration getRefreshDuration() const;
61 63  
62 64 private:
  65 + std::shared_ptr<APICache> base;
63 66 std::string path;
64 67 HueCommandAPI commands;
65 68 std::chrono::steady_clock::duration refreshDuration;
... ...
include/hueplusplus/Hue.h
... ... @@ -136,6 +136,8 @@ public:
136 136 Hue(const std::string& ip, const int port, const std::string& username, std::shared_ptr<const IHttpHandler> handler,
137 137 std::chrono::steady_clock::duration refreshDuration = std::chrono::seconds(10));
138 138  
  139 + void refresh();
  140 +
139 141 //! \name Configuration
140 142 ///@{
141 143  
... ... @@ -362,6 +364,7 @@ private:
362 364 HueCommandAPI commands; //!< A HueCommandAPI that is used to communicate with the bridge
363 365 std::chrono::steady_clock::duration refreshDuration;
364 366  
  367 + std::shared_ptr<APICache> stateCache;
365 368 ResourceList<HueLight, int> lights;
366 369 CreateableResourceList<Group, int, CreateGroup> groups;
367 370 CreateableResourceList<Schedule, int, CreateSchedule> schedules;
... ...
include/hueplusplus/ResourceList.h
... ... @@ -36,6 +36,10 @@ template &lt;typename Resource, typename IdType&gt;
36 36 class ResourceList
37 37 {
38 38 public:
  39 + ResourceList(const HueCommandAPI& commands, const std::string& path, std::shared_ptr<APICache> baseCache,
  40 + const std::string& cacheEntry, const std::function<Resource(int, const nlohmann::json&)>& factory = nullptr)
  41 + : commands(commands), stateCache(baseCache, cacheEntry), path(path + '/'), factory(factory)
  42 + {}
39 43 ResourceList(const HueCommandAPI& commands, const std::string& path,
40 44 std::chrono::steady_clock::duration refreshDuration,
41 45 const std::function<Resource(int, const nlohmann::json&)>& factory = nullptr)
... ... @@ -97,7 +101,7 @@ public:
97 101 std::string requestPath = path + maybeToString(id);
98 102 nlohmann::json result
99 103 = commands.DELETERequest(requestPath, nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__});
100   - bool success = utils::safeGetMember(result, 0, "success") == requestPath;
  104 + bool success = utils::safeGetMember(result, 0, "success") == requestPath + " deleted";
101 105 auto it = resources.find(id);
102 106 if (success && it != resources.end())
103 107 {
... ... @@ -113,7 +117,7 @@ protected:
113 117  
114 118 Resource construct(const IdType& id, const nlohmann::json& state)
115 119 {
116   - return construct(id, state, std::is_constructible<Resource, IdType, HueCommandAPI, nlohmann::json> {});
  120 + return construct(id, state, std::is_constructible<Resource, IdType, HueCommandAPI, std::chrono::steady_clock::duration> {});
117 121 }
118 122  
119 123 private:
... ...
src/APICache.cpp
... ... @@ -23,46 +23,95 @@
23 23  
24 24 #include "hueplusplus/HueExceptionMacro.h"
25 25  
26   -hueplusplus::APICache::APICache(
27   - const std::string& path, const HueCommandAPI& commands, std::chrono::steady_clock::duration refresh)
  26 +namespace hueplusplus
  27 +{
  28 +APICache::APICache(std::shared_ptr<APICache> baseCache, const std::string& subEntry)
  29 + : base(baseCache),
  30 + path(subEntry),
  31 + commands(baseCache->commands),
  32 + refreshDuration(baseCache->refreshDuration),
  33 + lastRefresh(baseCache->lastRefresh)
  34 +{}
  35 +
  36 +APICache::APICache(const std::string& path, const HueCommandAPI& commands, std::chrono::steady_clock::duration refresh)
28 37 : path(path), commands(commands), refreshDuration(refresh), lastRefresh(std::chrono::steady_clock::duration::zero())
29 38 {}
30 39  
31   -void hueplusplus::APICache::refresh()
  40 +void APICache::refresh()
32 41 {
33   - value = commands.GETRequest(path, nlohmann::json::object(), CURRENT_FILE_INFO);
34   - lastRefresh = std::chrono::steady_clock::now();
  42 + if (base)
  43 + {
  44 + base->refresh();
  45 + }
  46 + else
  47 + {
  48 + value = commands.GETRequest(path, nlohmann::json::object(), CURRENT_FILE_INFO);
  49 + lastRefresh = std::chrono::steady_clock::now();
  50 + }
35 51 }
36 52  
37   -nlohmann::json& hueplusplus::APICache::getValue()
  53 +nlohmann::json& APICache::getValue()
38 54 {
39   - using clock = std::chrono::steady_clock;
40   - // Explicitly check for zero in case refreshDuration is duration::max()
41   - // Negative duration causes overflow check to overflow itself
42   - if (lastRefresh.time_since_epoch().count() == 0 || refreshDuration.count() < 0)
  55 + if (base)
43 56 {
44   - // No value set yet
45   - refresh();
  57 + nlohmann::json& baseState = base->getValue();
  58 + auto pos = baseState.find(path);
  59 + if (pos != baseState.end())
  60 + {
  61 + return *pos;
  62 + }
  63 + else
  64 + {
  65 + throw HueException(CURRENT_FILE_INFO, "Child path not present in base cache");
  66 + }
46 67 }
47   - // Check if nextRefresh would overflow (assumes lastRefresh is not negative, which it should not be).
48   - // If addition would overflow, do not refresh
49   - else if (clock::duration::max() - refreshDuration > lastRefresh.time_since_epoch())
  68 + else
50 69 {
51   - clock::time_point nextRefresh = lastRefresh + refreshDuration;
52   - if (clock::now() >= nextRefresh)
  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)
53 74 {
  75 + // No value set yet
54 76 refresh();
55 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;
56 89 }
57   - return value;
58 90 }
59 91  
60   -const nlohmann::json& hueplusplus::APICache::getValue() const
  92 +const nlohmann::json& APICache::getValue() const
61 93 {
62   - return value;
  94 + if (base)
  95 + {
  96 + // Make const reference to not refresh
  97 + const APICache& b = *base;
  98 + return b.getValue().at(path);
  99 + }
  100 + else
  101 + {
  102 + if (lastRefresh.time_since_epoch().count() == 0)
  103 + {
  104 + // No value has been requested yet
  105 + throw HueException(CURRENT_FILE_INFO,
  106 + "Tried to call const getValue(), but no value was cached. "
  107 + "Call refresh() or non-const getValue() first.");
  108 + }
  109 + return value;
  110 + }
63 111 }
64 112  
65   -std::chrono::steady_clock::duration hueplusplus::APICache::getRefreshDuration() const
  113 +std::chrono::steady_clock::duration APICache::getRefreshDuration() const
66 114 {
67 115 return refreshDuration;
68 116 }
  117 +} // namespace hueplusplus
... ...
src/Hue.cpp
... ... @@ -137,13 +137,19 @@ Hue::Hue(const std::string&amp; ip, const int port, const std::string&amp; username,
137 137 http_handler(std::move(handler)),
138 138 commands(ip, port, username, http_handler),
139 139 refreshDuration(refreshDuration),
140   - lights(commands, "/lights", refreshDuration,
  140 + stateCache(std::make_shared<APICache>("", commands, refreshDuration)),
  141 + lights(commands, "/lights", stateCache, "lights",
141 142 [factory = HueLightFactory(commands, refreshDuration)](
142 143 int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }),
143   - groups(commands, "/groups", refreshDuration),
144   - schedules(commands, "/schedules", refreshDuration)
  144 + groups(commands, "/groups", stateCache, "groups"),
  145 + schedules(commands, "/schedules", stateCache, "schedules")
145 146 {}
146 147  
  148 +void Hue::refresh()
  149 +{
  150 + stateCache->refresh();
  151 +}
  152 +
147 153 std::string Hue::getBridgeIP() const
148 154 {
149 155 return ip;
... ... @@ -422,10 +428,11 @@ void Hue::setHttpHandler(std::shared_ptr&lt;const IHttpHandler&gt; handler)
422 428 {
423 429 http_handler = handler;
424 430 commands = HueCommandAPI(ip, port, username, handler);
425   - lights = ResourceList<HueLight, int>(commands, "/lights", refreshDuration,
  431 + stateCache = std::make_shared<APICache>("", commands, refreshDuration);
  432 + lights = ResourceList<HueLight, int>(commands, "/lights", stateCache, "lights",
426 433 [factory = HueLightFactory(commands, refreshDuration)](
427 434 int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); });
428   - groups = CreateableResourceList<Group, int, CreateGroup>(commands, "/groups", refreshDuration);
429   - schedules = CreateableResourceList<Schedule, int, CreateSchedule>(commands, "/schedules", refreshDuration);
  435 + groups = CreateableResourceList<Group, int, CreateGroup>(commands, "/groups", stateCache, "groups");
  436 + schedules = CreateableResourceList<Schedule, int, CreateSchedule>(commands, "/schedules", stateCache, "schedules");
430 437 }
431 438 } // namespace hueplusplus
... ...
test/test_APICache.cpp
... ... @@ -110,7 +110,19 @@ TEST(APICache, getValue)
110 110 EXPECT_EQ(value, cache.getValue());
111 111 Mock::VerifyAndClearExpectations(handler.get());
112 112 }
113   - // No refresh with const
  113 + // Only refresh once
  114 + {
  115 + std::string path = "/test/abc";
  116 + APICache cache(path, commands, std::chrono::seconds(0));
  117 + nlohmann::json value = { {"a", "b"} };
  118 + EXPECT_CALL(*handler,
  119 + GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort()))
  120 + .WillOnce(Return(value));
  121 + EXPECT_EQ(value, cache.getValue());
  122 + EXPECT_EQ(value, Const(cache).getValue());
  123 + Mock::VerifyAndClearExpectations(handler.get());
  124 + }
  125 + // No refresh with const throws exception
114 126 {
115 127 std::string path = "/test/abc";
116 128 const APICache cache(path, commands, std::chrono::steady_clock::duration::max());
... ... @@ -118,7 +130,7 @@ TEST(APICache, getValue)
118 130 EXPECT_CALL(*handler,
119 131 GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort()))
120 132 .Times(0);
121   - EXPECT_EQ(nullptr, cache.getValue());
  133 + EXPECT_THROW(cache.getValue(), HueException);
122 134 Mock::VerifyAndClearExpectations(handler.get());
123 135 }
124 136 }
125 137 \ No newline at end of file
... ...
test/test_Hue.cpp
... ... @@ -379,15 +379,13 @@ TEST(Hue, lightExists)
379 379 *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort()))
380 380 .Times(AtLeast(1))
381 381 .WillRepeatedly(Return(hue_bridge_state));
382   - EXPECT_CALL(*handler,
383   - GETJson("/api/" + getBridgeUsername() + "/lights/1", nlohmann::json::object(), getBridgeIp(), getBridgePort()))
384   - .Times(AtLeast(1))
385   - .WillRepeatedly(Return(hue_bridge_state["lights"]["1"]));
386 382  
387 383 Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler);
388 384  
389   - EXPECT_EQ(true, Const(test_bridge).lightExists(1));
390   - EXPECT_EQ(false, Const(test_bridge).lightExists(2));
  385 + test_bridge.refresh();
  386 +
  387 + EXPECT_TRUE(Const(test_bridge).lightExists(1));
  388 + EXPECT_FALSE(Const(test_bridge).lightExists(2));
391 389 }
392 390  
393 391 TEST(Hue, getGroup)
... ... @@ -492,13 +490,11 @@ TEST(Hue, groupExists)
492 490 *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort()))
493 491 .Times(AtLeast(1))
494 492 .WillRepeatedly(Return(hue_bridge_state));
495   - EXPECT_CALL(*handler,
496   - GETJson("/api/" + getBridgeUsername() + "/groups/1", nlohmann::json::object(), getBridgeIp(), getBridgePort()))
497   - .Times(AtLeast(1))
498   - .WillRepeatedly(Return(hue_bridge_state["groups"]["1"]));
499 493  
500 494 Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler);
501 495  
  496 + test_bridge.refresh();
  497 +
502 498 EXPECT_EQ(true, Const(test_bridge).groupExists(1));
503 499 EXPECT_EQ(false, Const(test_bridge).groupExists(2));
504 500 }
... ...