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,6 +34,7 @@ namespace hueplusplus
34 class APICache 34 class APICache
35 { 35 {
36 public: 36 public:
  37 + APICache(std::shared_ptr<APICache> baseCache, const std::string& subEntry);
37 //! \brief Constructs APICache 38 //! \brief Constructs APICache
38 //! \param path URL appended after username, may be empty. 39 //! \param path URL appended after username, may be empty.
39 //! \param commands HueCommandAPI for making API requests. 40 //! \param commands HueCommandAPI for making API requests.
@@ -54,12 +55,14 @@ public: @@ -54,12 +55,14 @@ public:
54 //! \throws nlohmann::json::parse_error when response could not be parsed 55 //! \throws nlohmann::json::parse_error when response could not be parsed
55 nlohmann::json& getValue(); 56 nlohmann::json& getValue();
56 //! \brief Get cached value, does not refresh. 57 //! \brief Get cached value, does not refresh.
  58 + //! \throws HueException when no previous request was cached
57 const nlohmann::json& getValue() const; 59 const nlohmann::json& getValue() const;
58 60
59 //! \brief Get duration between refreshes. 61 //! \brief Get duration between refreshes.
60 std::chrono::steady_clock::duration getRefreshDuration() const; 62 std::chrono::steady_clock::duration getRefreshDuration() const;
61 63
62 private: 64 private:
  65 + std::shared_ptr<APICache> base;
63 std::string path; 66 std::string path;
64 HueCommandAPI commands; 67 HueCommandAPI commands;
65 std::chrono::steady_clock::duration refreshDuration; 68 std::chrono::steady_clock::duration refreshDuration;
include/hueplusplus/Hue.h
@@ -136,6 +136,8 @@ public: @@ -136,6 +136,8 @@ public:
136 Hue(const std::string& ip, const int port, const std::string& username, std::shared_ptr<const IHttpHandler> handler, 136 Hue(const std::string& ip, const int port, const std::string& username, std::shared_ptr<const IHttpHandler> handler,
137 std::chrono::steady_clock::duration refreshDuration = std::chrono::seconds(10)); 137 std::chrono::steady_clock::duration refreshDuration = std::chrono::seconds(10));
138 138
  139 + void refresh();
  140 +
139 //! \name Configuration 141 //! \name Configuration
140 ///@{ 142 ///@{
141 143
@@ -362,6 +364,7 @@ private: @@ -362,6 +364,7 @@ private:
362 HueCommandAPI commands; //!< A HueCommandAPI that is used to communicate with the bridge 364 HueCommandAPI commands; //!< A HueCommandAPI that is used to communicate with the bridge
363 std::chrono::steady_clock::duration refreshDuration; 365 std::chrono::steady_clock::duration refreshDuration;
364 366
  367 + std::shared_ptr<APICache> stateCache;
365 ResourceList<HueLight, int> lights; 368 ResourceList<HueLight, int> lights;
366 CreateableResourceList<Group, int, CreateGroup> groups; 369 CreateableResourceList<Group, int, CreateGroup> groups;
367 CreateableResourceList<Schedule, int, CreateSchedule> schedules; 370 CreateableResourceList<Schedule, int, CreateSchedule> schedules;
include/hueplusplus/ResourceList.h
@@ -36,6 +36,10 @@ template &lt;typename Resource, typename IdType&gt; @@ -36,6 +36,10 @@ template &lt;typename Resource, typename IdType&gt;
36 class ResourceList 36 class ResourceList
37 { 37 {
38 public: 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 ResourceList(const HueCommandAPI& commands, const std::string& path, 43 ResourceList(const HueCommandAPI& commands, const std::string& path,
40 std::chrono::steady_clock::duration refreshDuration, 44 std::chrono::steady_clock::duration refreshDuration,
41 const std::function<Resource(int, const nlohmann::json&)>& factory = nullptr) 45 const std::function<Resource(int, const nlohmann::json&)>& factory = nullptr)
@@ -97,7 +101,7 @@ public: @@ -97,7 +101,7 @@ public:
97 std::string requestPath = path + maybeToString(id); 101 std::string requestPath = path + maybeToString(id);
98 nlohmann::json result 102 nlohmann::json result
99 = commands.DELETERequest(requestPath, nlohmann::json::object(), FileInfo {__FILE__, __LINE__, __func__}); 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 auto it = resources.find(id); 105 auto it = resources.find(id);
102 if (success && it != resources.end()) 106 if (success && it != resources.end())
103 { 107 {
@@ -113,7 +117,7 @@ protected: @@ -113,7 +117,7 @@ protected:
113 117
114 Resource construct(const IdType& id, const nlohmann::json& state) 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 private: 123 private:
src/APICache.cpp
@@ -23,46 +23,95 @@ @@ -23,46 +23,95 @@
23 23
24 #include "hueplusplus/HueExceptionMacro.h" 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 : path(path), commands(commands), refreshDuration(refresh), lastRefresh(std::chrono::steady_clock::duration::zero()) 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 refresh(); 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 return refreshDuration; 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,13 +137,19 @@ Hue::Hue(const std::string&amp; ip, const int port, const std::string&amp; username,
137 http_handler(std::move(handler)), 137 http_handler(std::move(handler)),
138 commands(ip, port, username, http_handler), 138 commands(ip, port, username, http_handler),
139 refreshDuration(refreshDuration), 139 refreshDuration(refreshDuration),
140 - lights(commands, "/lights", refreshDuration, 140 + stateCache(std::make_shared<APICache>("", commands, refreshDuration)),
  141 + lights(commands, "/lights", stateCache, "lights",
141 [factory = HueLightFactory(commands, refreshDuration)]( 142 [factory = HueLightFactory(commands, refreshDuration)](
142 int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }), 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 std::string Hue::getBridgeIP() const 153 std::string Hue::getBridgeIP() const
148 { 154 {
149 return ip; 155 return ip;
@@ -422,10 +428,11 @@ void Hue::setHttpHandler(std::shared_ptr&lt;const IHttpHandler&gt; handler) @@ -422,10 +428,11 @@ void Hue::setHttpHandler(std::shared_ptr&lt;const IHttpHandler&gt; handler)
422 { 428 {
423 http_handler = handler; 429 http_handler = handler;
424 commands = HueCommandAPI(ip, port, username, handler); 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 [factory = HueLightFactory(commands, refreshDuration)]( 433 [factory = HueLightFactory(commands, refreshDuration)](
427 int id, const nlohmann::json& state) mutable { return factory.createLight(state, id); }); 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 } // namespace hueplusplus 438 } // namespace hueplusplus
test/test_APICache.cpp
@@ -110,7 +110,19 @@ TEST(APICache, getValue) @@ -110,7 +110,19 @@ TEST(APICache, getValue)
110 EXPECT_EQ(value, cache.getValue()); 110 EXPECT_EQ(value, cache.getValue());
111 Mock::VerifyAndClearExpectations(handler.get()); 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 std::string path = "/test/abc"; 127 std::string path = "/test/abc";
116 const APICache cache(path, commands, std::chrono::steady_clock::duration::max()); 128 const APICache cache(path, commands, std::chrono::steady_clock::duration::max());
@@ -118,7 +130,7 @@ TEST(APICache, getValue) @@ -118,7 +130,7 @@ TEST(APICache, getValue)
118 EXPECT_CALL(*handler, 130 EXPECT_CALL(*handler,
119 GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort())) 131 GETJson("/api/" + getBridgeUsername() + path, nlohmann::json::object(), getBridgeIp(), getBridgePort()))
120 .Times(0); 132 .Times(0);
121 - EXPECT_EQ(nullptr, cache.getValue()); 133 + EXPECT_THROW(cache.getValue(), HueException);
122 Mock::VerifyAndClearExpectations(handler.get()); 134 Mock::VerifyAndClearExpectations(handler.get());
123 } 135 }
124 } 136 }
125 \ No newline at end of file 137 \ No newline at end of file
test/test_Hue.cpp
@@ -379,15 +379,13 @@ TEST(Hue, lightExists) @@ -379,15 +379,13 @@ TEST(Hue, lightExists)
379 *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) 379 *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort()))
380 .Times(AtLeast(1)) 380 .Times(AtLeast(1))
381 .WillRepeatedly(Return(hue_bridge_state)); 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 Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); 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 TEST(Hue, getGroup) 391 TEST(Hue, getGroup)
@@ -492,13 +490,11 @@ TEST(Hue, groupExists) @@ -492,13 +490,11 @@ TEST(Hue, groupExists)
492 *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort())) 490 *handler, GETJson("/api/" + getBridgeUsername(), nlohmann::json::object(), getBridgeIp(), getBridgePort()))
493 .Times(AtLeast(1)) 491 .Times(AtLeast(1))
494 .WillRepeatedly(Return(hue_bridge_state)); 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 Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler); 494 Hue test_bridge(getBridgeIp(), getBridgePort(), getBridgeUsername(), handler);
501 495
  496 + test_bridge.refresh();
  497 +
502 EXPECT_EQ(true, Const(test_bridge).groupExists(1)); 498 EXPECT_EQ(true, Const(test_bridge).groupExists(1));
503 EXPECT_EQ(false, Const(test_bridge).groupExists(2)); 499 EXPECT_EQ(false, Const(test_bridge).groupExists(2));
504 } 500 }