SMACC2
Loading...
Searching...
No Matches
cp_gcalcli_connection.cpp
Go to the documentation of this file.
1// Copyright 2024 RobosoftAI Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
16
17namespace cl_gcalcli
18{
19
21: connection_state_(ConnectionState::DISCONNECTED),
22 consecutive_failures_(0),
23 initialized_(false),
24 subprocess_executor_(nullptr)
25{
26}
27
29{
30 if (!initialized_)
31 {
32 // Get the subprocess executor component
34
35 if (subprocess_executor_ == nullptr)
36 {
37 RCLCPP_ERROR(getLogger(), "[CpGcalcliConnection] CpSubprocessExecutor component not found!");
38 return;
39 }
40
41 last_heartbeat_time_ = std::chrono::steady_clock::now();
42 initialized_ = true;
43
44 RCLCPP_INFO(
45 getLogger(), "[CpGcalcliConnection] Initialized with gcalcli path: %s",
46 config_.gcalcli_path.c_str());
47 }
48}
49
51{
52 std::lock_guard<std::mutex> lock(state_mutex_);
53 config_ = config;
54 RCLCPP_DEBUG(getLogger(), "[CpGcalcliConnection] Configuration updated");
55}
56
58{
59 std::lock_guard<std::mutex> lock(state_mutex_);
60 return connection_state_;
61}
62
64{
65 std::lock_guard<std::mutex> lock(state_mutex_);
67}
68
70{
72 {
73 RCLCPP_ERROR(getLogger(), "[CpGcalcliConnection] Subprocess executor not available");
74 return false;
75 }
76
77 // Use "gcalcli list" as a lightweight connection test
78 auto result = executeGcalcli("list", 10000);
79
80 bool success = (result.exit_code == 0 && !result.timed_out);
81 handleConnectionStateChange(success, result.stdout_output);
82
83 return success;
84}
85
87{
88 std::lock_guard<std::mutex> lock(state_mutex_);
89
90 RCLCPP_INFO(getLogger(), "[CpGcalcliConnection] Restarting connection...");
91
94}
95
97 const std::string & args, int timeout_ms)
98{
100 {
102 error_result.exit_code = -1;
103 error_result.stderr_output = "Subprocess executor not available";
104 error_result.timed_out = false;
105 return error_result;
106 }
107
108 // Build the full command
109 std::string command = config_.gcalcli_path;
110
111 // Add config folder if specified
112 if (config_.config_folder.has_value())
113 {
114 command += " --config-folder \"" + config_.config_folder.value() + "\"";
115 }
116
117 // Add calendars if specified
118 for (const auto & calendar : config_.calendars)
119 {
120 command += " --calendar \"" + calendar + "\"";
121 }
122
123 // Add the command arguments
124 command += " " + args;
125
126 RCLCPP_DEBUG(getLogger(), "[CpGcalcliConnection] Executing: %s", command.c_str());
127
128 return subprocess_executor_->executeCommand(command, timeout_ms);
129}
130
132{
134 {
135 return;
136 }
137
138 auto now = std::chrono::steady_clock::now();
139 auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - last_heartbeat_time_);
140
141 if (elapsed >= config_.heartbeat_interval)
142 {
145 }
146}
147
149{
150 RCLCPP_DEBUG(getLogger(), "[CpGcalcliConnection] Performing heartbeat check...");
151
152 auto result = executeGcalcli("list", 10000);
153
154 bool success = (result.exit_code == 0 && !result.timed_out);
155 handleConnectionStateChange(success, result.stdout_output);
156}
157
158void CpGcalcliConnection::handleConnectionStateChange(bool success, const std::string & output)
159{
160 std::lock_guard<std::mutex> lock(state_mutex_);
161
162 ConnectionState previous_state = connection_state_;
163
164 if (success)
165 {
167
169 {
171 RCLCPP_INFO(getLogger(), "[CpGcalcliConnection] Connection established");
172
173 if (
174 previous_state == ConnectionState::ERROR || previous_state == ConnectionState::DISCONNECTED)
175 {
178 {
180 }
181 }
182 }
183 }
184 else
185 {
187
188 // Check if this is an authentication error
189 if (isAuthenticationError(output))
190 {
192 RCLCPP_WARN(getLogger(), "[CpGcalcliConnection] Authentication required");
193
196 {
198 }
199 }
201 {
203 {
205 RCLCPP_ERROR(
206 getLogger(), "[CpGcalcliConnection] Connection lost after %d consecutive failures",
208
211 {
213 }
214 }
215 }
216 else
217 {
218 RCLCPP_WARN(
219 getLogger(), "[CpGcalcliConnection] Connection check failed (%d/%d)", consecutive_failures_,
221 }
222 }
223}
224
225bool CpGcalcliConnection::isAuthenticationError(const std::string & output) const
226{
227 // Check for common gcalcli authentication error patterns
228 return output.find("Authentication") != std::string::npos ||
229 output.find("authorization") != std::string::npos ||
230 output.find("oauth") != std::string::npos ||
231 output.find("credentials") != std::string::npos ||
232 output.find("token") != std::string::npos;
233}
234
235} // namespace cl_gcalcli
ConnectionState getConnectionState() const
Get current connection state.
std::function< void()> postConnectionLostEvent_
bool isAuthenticationError(const std::string &output) const
Check if output indicates authentication failure.
smacc2::SmaccSignal< void()> onConnectionLost_
smacc2::SmaccSignal< void()> onAuthenticationRequired_
void restartConnection()
Restart connection after failure.
void update() override
Periodic update for heartbeat (called by SignalDetector)
bool isConnected() const
Check if connected to Google Calendar.
std::function< void()> postAuthenticationRequiredEvent_
smacc2::SmaccSignal< void()> onConnectionRestored_
smacc2::client_core_components::CpSubprocessExecutor * subprocess_executor_
smacc2::client_core_components::SubprocessResult executeGcalcli(const std::string &args, int timeout_ms=30000)
Execute a gcalcli command.
void handleConnectionStateChange(bool success, const std::string &output)
Handle connection state change.
bool checkConnection()
Manually trigger a connection check.
std::function< void()> postConnectionRestoredEvent_
void configure(const GcalcliConfig &config)
Configure the gcalcli connection parameters.
void performHeartbeat()
Perform the heartbeat check.
std::chrono::steady_clock::time_point last_heartbeat_time_
rclcpp::Logger getLogger() const
void requiresComponent(TComponent *&requiredComponentStorage, ComponentRequirement requirementType=ComponentRequirement::SOFT)
SubprocessResult executeCommand(const std::string &command, int timeout_ms=30000)
Execute a command synchronously.
ConnectionState
Connection state for gcalcli.
Definition types.hpp:29
Configuration for gcalcli client.
Definition types.hpp:102
std::chrono::seconds heartbeat_interval
How often to check connection health (heartbeat)
Definition types.hpp:116
std::string gcalcli_path
Path to gcalcli executable (default: "gcalcli" from PATH)
Definition types.hpp:104
std::vector< std::string > calendars
Calendars to monitor (empty = all calendars)
Definition types.hpp:110
std::optional< std::string > config_folder
Optional config folder for gcalcli (if not using default)
Definition types.hpp:107
int max_consecutive_failures
Number of consecutive failures before connection is considered lost.
Definition types.hpp:122