17#include <boost/regex.hpp>
34 RCLCPP_ERROR(
getLogger(),
"[CpCalendarPoller] CpGcalcliConnection component not found!");
41 RCLCPP_INFO(
getLogger(),
"[CpCalendarPoller] Initialized");
49 RCLCPP_WARN(
getLogger(),
"[CpCalendarPoller] Cannot refresh - not connected");
56 std::stringstream args;
57 args <<
"agenda --tsv --nocolor";
58 args <<
" --nodeclined";
61 auto now = std::chrono::system_clock::now();
62 auto end = now + std::chrono::hours(24 * config.agenda_days);
64 auto now_t = std::chrono::system_clock::to_time_t(now);
65 auto end_t = std::chrono::system_clock::to_time_t(end);
67 std::tm now_tm = *std::localtime(&now_t);
68 std::tm end_tm = *std::localtime(&end_t);
70 args <<
" \"" << std::put_time(&now_tm,
"%m/%d/%Y") <<
"\"";
71 args <<
" \"" << std::put_time(&end_tm,
"%m/%d/%Y") <<
"\"";
75 if (result.exit_code != 0 || result.timed_out)
78 getLogger(),
"[CpCalendarPoller] Agenda fetch failed: %s", result.stdout_output.c_str());
91 RCLCPP_DEBUG(
getLogger(),
"[CpCalendarPoller] Refreshed agenda: %zu events", events.size());
110 const std::string & pattern,
bool use_regex)
const
114 std::vector<CalendarEvent> matches;
120 boost::regex regex(pattern, boost::regex::icase);
123 if (boost::regex_search(event.title, regex))
125 matches.push_back(event);
129 catch (
const boost::regex_error & e)
131 RCLCPP_ERROR(
getLogger(),
"[CpCalendarPoller] Invalid regex pattern: %s", e.what());
138 if (event.title.find(pattern) != std::string::npos)
140 matches.push_back(event);
149 std::chrono::system_clock::time_point start, std::chrono::system_clock::time_point end)
const
153 std::vector<CalendarEvent> matches;
157 if (event.start_time < end && event.end_time > start)
159 matches.push_back(event);
170 std::vector<CalendarEvent> active;
173 if (event.isActiveNow())
175 active.push_back(event);
201 auto now = std::chrono::steady_clock::now();
202 auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now -
last_poll_attempt_);
204 if (elapsed >= config.poll_interval)
213 std::vector<CalendarEvent> events;
214 std::istringstream stream(output);
217 while (std::getline(stream, line))
220 if (line.empty() || line.find_first_not_of(
" \t\r\n") == std::string::npos)
226 if (event.has_value())
228 events.push_back(event.value());
238 std::vector<std::string> fields;
239 std::istringstream stream(line);
242 while (std::getline(stream, field,
'\t'))
244 fields.push_back(field);
248 if (fields.size() < 5)
251 getLogger(),
"[CpCalendarPoller] Skipping line with insufficient fields: %s", line.c_str());
259 if (!start.has_value())
262 getLogger(),
"[CpCalendarPoller] Failed to parse start time: %s %s", fields[0].c_str(),
266 event.start_time = start.value();
270 if (!end.has_value())
273 getLogger(),
"[CpCalendarPoller] Failed to parse end time: %s %s", fields[2].c_str(),
277 event.end_time = end.value();
280 event.title = fields[4];
283 if (fields.size() > 5)
event.location = fields[5];
284 if (fields.size() > 6)
event.description = fields[6];
285 if (fields.size() > 7)
event.calendar_name = fields[7];
288 event.is_all_day = (fields[1].empty() || fields[1] ==
"00:00" || fields[1] ==
"12:00am");
297 const std::string & date_str,
const std::string & time_str)
302 std::istringstream date_stream(date_str);
304 int year, month, day;
306 if (date_stream >> year >> sep1 >> month >> sep2 >> day && sep1 ==
'-' && sep2 ==
'-')
309 tm.tm_year = year - 1900;
310 tm.tm_mon = month - 1;
317 date_stream.str(date_str);
318 if (date_stream >> month >> sep1 >> day >> sep2 >> year && sep1 ==
'/' && sep2 ==
'/')
320 tm.tm_mon = month - 1;
322 tm.tm_year = year - 1900;
326 RCLCPP_DEBUG(
getLogger(),
"[CpCalendarPoller] Failed to parse date: %s", date_str.c_str());
332 if (!time_str.empty())
334 std::istringstream time_stream(time_str);
338 if (time_stream >> hour >> colon >> minute && colon ==
':')
345 std::getline(time_stream, rest);
349 size_t start = rest.find_first_not_of(
" ");
350 if (start != std::string::npos)
352 rest = rest.substr(start);
355 if (rest ==
"pm" || rest ==
"PM" || rest ==
"p" || rest ==
"P")
357 if (hour != 12) tm.tm_hour += 12;
359 else if (rest ==
"am" || rest ==
"AM" || rest ==
"a" || rest ==
"A")
361 if (hour == 12) tm.tm_hour = 0;
367 RCLCPP_DEBUG(
getLogger(),
"[CpCalendarPoller] Failed to parse time: %s", time_str.c_str());
375 std::time_t time = std::mktime(&tm);
381 return std::chrono::system_clock::from_time_t(time);
387 auto start_t = std::chrono::system_clock::to_time_t(event.
start_time);
388 std::stringstream ss;
389 ss <<
event.title <<
"_" << start_t <<
"_" <<
event.calendar_name;
std::optional< std::chrono::system_clock::time_point > parseDateTime(const std::string &date_str, const std::string &time_str)
Parse date and time strings into time_point.
smacc2::SmaccSignal< void(const std::vector< CalendarEvent > &)> onAgendaUpdated_
std::vector< CalendarEvent > getEventsInWindow(std::chrono::system_clock::time_point start, std::chrono::system_clock::time_point end) const
Get events happening within a time window.
std::chrono::system_clock::time_point last_poll_time_
bool refreshAgenda()
Force an immediate agenda refresh.
void update() override
Periodic update for polling (called by SignalDetector)
std::vector< CalendarEvent > findEvents(const std::string &pattern, bool use_regex=false) const
Get events matching a title pattern.
void onInitialize() override
CpGcalcliConnection * connection_
std::vector< CalendarEvent > getActiveEvents() const
Get currently active events.
std::vector< CalendarEvent > getEvents() const
Get cached list of calendar events.
std::string generateEventId(const CalendarEvent &event)
Generate a unique ID for an event.
std::optional< CalendarEvent > parseTsvLine(const std::string &line)
Parse a single TSV line into a CalendarEvent.
std::chrono::steady_clock::time_point last_poll_attempt_
std::function< void(const std::vector< CalendarEvent > &)> postAgendaUpdatedEvent_
std::vector< CalendarEvent > parseTsvOutput(const std::string &output)
Parse TSV output into CalendarEvent structures.
std::chrono::system_clock::time_point getLastPollTime() const
Get the time of the last successful poll.
std::vector< CalendarEvent > cached_events_
const GcalcliConfig & getConfig() const
Get the gcalcli configuration.
bool isConnected() const
Check if connected to Google Calendar.
smacc2::client_core_components::SubprocessResult executeGcalcli(const std::string &args, int timeout_ms=30000)
Execute a gcalcli command.
rclcpp::Logger getLogger() const
void requiresComponent(TComponent *&requiredComponentStorage, ComponentRequirement requirementType=ComponentRequirement::SOFT)
Represents a Google Calendar event.
std::chrono::system_clock::time_point start_time