409 lines
14 KiB
C++
409 lines
14 KiB
C++
#include "schedule_manager.h"
|
|
#include <esp_log.h>
|
|
#include <esp_timer.h>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
|
|
const char* ScheduleManager::TAG = "ScheduleManager";
|
|
|
|
ScheduleManager::ScheduleManager() {
|
|
ESP_LOGI(TAG, "ScheduleManager initialized");
|
|
}
|
|
|
|
ScheduleManager::~ScheduleManager() {
|
|
ESP_LOGI(TAG, "ScheduleManager destroyed");
|
|
}
|
|
|
|
std::string ScheduleManager::CreateEvent(const std::string& title,
|
|
const std::string& description,
|
|
time_t start_time,
|
|
time_t end_time,
|
|
const std::string& category,
|
|
bool is_all_day,
|
|
int reminder_minutes) {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
|
|
if (title.empty()) {
|
|
ESP_LOGE(TAG, "Event title cannot be empty");
|
|
return "";
|
|
}
|
|
|
|
if (!IsEventTimeValid(start_time, end_time)) {
|
|
ESP_LOGE(TAG, "Invalid event time");
|
|
return "";
|
|
}
|
|
|
|
ScheduleEvent event;
|
|
event.id = GenerateEventId();
|
|
event.title = title;
|
|
event.description = description;
|
|
event.start_time = start_time;
|
|
event.end_time = end_time;
|
|
event.is_all_day = is_all_day;
|
|
event.reminder_minutes = reminder_minutes;
|
|
event.created_time = time(nullptr);
|
|
event.updated_time = event.created_time;
|
|
|
|
// 智能分类
|
|
if (category.empty()) {
|
|
event.category = CategorizeEvent(title, description);
|
|
} else {
|
|
event.category = category;
|
|
}
|
|
|
|
events_[event.id] = event;
|
|
|
|
ESP_LOGI(TAG, "Created event: %s (ID: %s)", title.c_str(), event.id.c_str());
|
|
return event.id;
|
|
}
|
|
|
|
bool ScheduleManager::UpdateEvent(const std::string& event_id,
|
|
const std::string& title,
|
|
const std::string& description,
|
|
time_t start_time,
|
|
time_t end_time,
|
|
const std::string& category,
|
|
bool is_all_day,
|
|
int reminder_minutes) {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
|
|
auto it = events_.find(event_id);
|
|
if (it == events_.end()) {
|
|
ESP_LOGE(TAG, "Event not found: %s", event_id.c_str());
|
|
return false;
|
|
}
|
|
|
|
ScheduleEvent& event = it->second;
|
|
|
|
if (!title.empty()) {
|
|
event.title = title;
|
|
}
|
|
if (!description.empty()) {
|
|
event.description = description;
|
|
}
|
|
if (start_time > 0) {
|
|
event.start_time = start_time;
|
|
}
|
|
if (end_time > 0) {
|
|
event.end_time = end_time;
|
|
}
|
|
if (!category.empty()) {
|
|
event.category = category;
|
|
}
|
|
if (reminder_minutes >= 0) {
|
|
event.reminder_minutes = reminder_minutes;
|
|
}
|
|
|
|
event.is_all_day = is_all_day;
|
|
event.updated_time = time(nullptr);
|
|
|
|
ESP_LOGI(TAG, "Updated event: %s", event_id.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool ScheduleManager::DeleteEvent(const std::string& event_id) {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
|
|
auto it = events_.find(event_id);
|
|
if (it == events_.end()) {
|
|
ESP_LOGE(TAG, "Event not found: %s", event_id.c_str());
|
|
return false;
|
|
}
|
|
|
|
events_.erase(it);
|
|
ESP_LOGI(TAG, "Deleted event: %s", event_id.c_str());
|
|
return true;
|
|
}
|
|
|
|
ScheduleEvent* ScheduleManager::GetEvent(const std::string& event_id) {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
|
|
auto it = events_.find(event_id);
|
|
if (it == events_.end()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return &it->second;
|
|
}
|
|
|
|
std::vector<ScheduleEvent> ScheduleManager::GetEventsByDate(time_t date) {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
std::vector<ScheduleEvent> result;
|
|
|
|
struct tm date_tm = *localtime(&date);
|
|
|
|
for (const auto& pair : events_) {
|
|
const ScheduleEvent& event = pair.second;
|
|
struct tm event_tm = *localtime(&event.start_time);
|
|
|
|
if (event_tm.tm_year == date_tm.tm_year &&
|
|
event_tm.tm_mon == date_tm.tm_mon &&
|
|
event_tm.tm_mday == date_tm.tm_mday) {
|
|
result.push_back(event);
|
|
}
|
|
}
|
|
|
|
// 按开始时间排序
|
|
std::sort(result.begin(), result.end(),
|
|
[](const ScheduleEvent& a, const ScheduleEvent& b) {
|
|
return a.start_time < b.start_time;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<ScheduleEvent> ScheduleManager::GetEventsByCategory(const std::string& category) {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
std::vector<ScheduleEvent> result;
|
|
|
|
for (const auto& pair : events_) {
|
|
const ScheduleEvent& event = pair.second;
|
|
if (event.category == category) {
|
|
result.push_back(event);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<ScheduleEvent> ScheduleManager::GetUpcomingEvents(int days) {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
std::vector<ScheduleEvent> result;
|
|
|
|
time_t now = time(nullptr);
|
|
time_t future_time = now + (days * 24 * 60 * 60);
|
|
|
|
for (const auto& pair : events_) {
|
|
const ScheduleEvent& event = pair.second;
|
|
if (event.start_time >= now && event.start_time <= future_time && !event.is_completed) {
|
|
result.push_back(event);
|
|
}
|
|
}
|
|
|
|
// 按开始时间排序
|
|
std::sort(result.begin(), result.end(),
|
|
[](const ScheduleEvent& a, const ScheduleEvent& b) {
|
|
return a.start_time < b.start_time;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<ScheduleEvent> ScheduleManager::GetEventsByKeyword(const std::string& keyword) {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
std::vector<ScheduleEvent> result;
|
|
|
|
std::string lower_keyword = keyword;
|
|
std::transform(lower_keyword.begin(), lower_keyword.end(), lower_keyword.begin(), ::tolower);
|
|
|
|
for (const auto& pair : events_) {
|
|
const ScheduleEvent& event = pair.second;
|
|
|
|
std::string lower_title = event.title;
|
|
std::string lower_description = event.description;
|
|
std::transform(lower_title.begin(), lower_title.end(), lower_title.begin(), ::tolower);
|
|
std::transform(lower_description.begin(), lower_description.end(), lower_description.begin(), ::tolower);
|
|
|
|
if (lower_title.find(lower_keyword) != std::string::npos ||
|
|
lower_description.find(lower_keyword) != std::string::npos) {
|
|
result.push_back(event);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string ScheduleManager::CategorizeEvent(const std::string& title, const std::string& description) {
|
|
std::string text = title + " " + description;
|
|
std::transform(text.begin(), text.end(), text.begin(), ::tolower);
|
|
|
|
// 工作相关关键词
|
|
if (text.find("会议") != std::string::npos || text.find("工作") != std::string::npos ||
|
|
text.find("项目") != std::string::npos || text.find("报告") != std::string::npos ||
|
|
text.find("deadline") != std::string::npos || text.find("meeting") != std::string::npos) {
|
|
return CategoryToString(EventCategory::WORK);
|
|
}
|
|
|
|
// 学习相关关键词
|
|
if (text.find("学习") != std::string::npos || text.find("课程") != std::string::npos ||
|
|
text.find("考试") != std::string::npos || text.find("作业") != std::string::npos ||
|
|
text.find("study") != std::string::npos || text.find("exam") != std::string::npos) {
|
|
return CategoryToString(EventCategory::STUDY);
|
|
}
|
|
|
|
// 健康相关关键词
|
|
if (text.find("运动") != std::string::npos || text.find("健身") != std::string::npos ||
|
|
text.find("医院") != std::string::npos || text.find("体检") != std::string::npos ||
|
|
text.find("exercise") != std::string::npos || text.find("doctor") != std::string::npos) {
|
|
return CategoryToString(EventCategory::HEALTH);
|
|
}
|
|
|
|
// 娱乐相关关键词
|
|
if (text.find("电影") != std::string::npos || text.find("游戏") != std::string::npos ||
|
|
text.find("聚会") != std::string::npos || text.find("娱乐") != std::string::npos ||
|
|
text.find("movie") != std::string::npos || text.find("party") != std::string::npos) {
|
|
return CategoryToString(EventCategory::ENTERTAINMENT);
|
|
}
|
|
|
|
// 旅行相关关键词
|
|
if (text.find("旅行") != std::string::npos || text.find("旅游") != std::string::npos ||
|
|
text.find("出差") != std::string::npos || text.find("travel") != std::string::npos ||
|
|
text.find("trip") != std::string::npos) {
|
|
return CategoryToString(EventCategory::TRAVEL);
|
|
}
|
|
|
|
// 家庭相关关键词
|
|
if (text.find("家庭") != std::string::npos || text.find("家人") != std::string::npos ||
|
|
text.find("孩子") != std::string::npos || text.find("family") != std::string::npos ||
|
|
text.find("child") != std::string::npos) {
|
|
return CategoryToString(EventCategory::FAMILY);
|
|
}
|
|
|
|
return CategoryToString(EventCategory::OTHER);
|
|
}
|
|
|
|
void ScheduleManager::SetReminderCallback(ReminderCallback callback) {
|
|
reminder_callback_ = callback;
|
|
}
|
|
|
|
void ScheduleManager::CheckReminders() {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
|
|
time_t now = time(nullptr);
|
|
|
|
for (const auto& pair : events_) {
|
|
const ScheduleEvent& event = pair.second;
|
|
|
|
if (event.is_completed || event.reminder_minutes <= 0) {
|
|
continue;
|
|
}
|
|
|
|
time_t reminder_time = event.start_time - (event.reminder_minutes * 60);
|
|
|
|
if (now >= reminder_time && now < event.start_time) {
|
|
ESP_LOGI(TAG, "Reminder triggered for event: %s", event.title.c_str());
|
|
if (reminder_callback_) {
|
|
reminder_callback_(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int ScheduleManager::GetEventCount() {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
return events_.size();
|
|
}
|
|
|
|
int ScheduleManager::GetEventCountByCategory(const std::string& category) {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
int count = 0;
|
|
|
|
for (const auto& pair : events_) {
|
|
if (pair.second.category == category) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
std::map<std::string, int> ScheduleManager::GetCategoryStatistics() {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
std::map<std::string, int> stats;
|
|
|
|
for (const auto& pair : events_) {
|
|
stats[pair.second.category]++;
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
|
|
bool ScheduleManager::SaveToStorage() {
|
|
// TODO: 实现数据持久化到NVS或SPIFFS
|
|
ESP_LOGW(TAG, "SaveToStorage not implemented yet");
|
|
return true;
|
|
}
|
|
|
|
bool ScheduleManager::LoadFromStorage() {
|
|
// TODO: 实现从NVS或SPIFFS加载数据
|
|
ESP_LOGW(TAG, "LoadFromStorage not implemented yet");
|
|
return true;
|
|
}
|
|
|
|
std::string ScheduleManager::ExportToJson() {
|
|
std::lock_guard<std::mutex> lock(events_mutex_);
|
|
|
|
std::stringstream json;
|
|
json << "{\"events\":[";
|
|
|
|
bool first = true;
|
|
for (const auto& pair : events_) {
|
|
if (!first) json << ",";
|
|
first = false;
|
|
|
|
const ScheduleEvent& event = pair.second;
|
|
json << "{"
|
|
<< "\"id\":\"" << event.id << "\","
|
|
<< "\"title\":\"" << event.title << "\","
|
|
<< "\"description\":\"" << event.description << "\","
|
|
<< "\"category\":\"" << event.category << "\","
|
|
<< "\"start_time\":" << event.start_time << ","
|
|
<< "\"end_time\":" << event.end_time << ","
|
|
<< "\"is_all_day\":" << (event.is_all_day ? "true" : "false") << ","
|
|
<< "\"reminder_minutes\":" << event.reminder_minutes << ","
|
|
<< "\"is_completed\":" << (event.is_completed ? "true" : "false") << ","
|
|
<< "\"created_time\":" << event.created_time << ","
|
|
<< "\"updated_time\":" << event.updated_time
|
|
<< "}";
|
|
}
|
|
|
|
json << "]}";
|
|
return json.str();
|
|
}
|
|
|
|
bool ScheduleManager::ImportFromJson(const std::string& json_data) {
|
|
// TODO: 实现JSON导入功能
|
|
ESP_LOGW(TAG, "ImportFromJson not implemented yet");
|
|
return false;
|
|
}
|
|
|
|
std::string ScheduleManager::GenerateEventId() {
|
|
static int counter = 0;
|
|
return "event_" + std::to_string(++counter) + "_" + std::to_string(time(nullptr));
|
|
}
|
|
|
|
std::string ScheduleManager::CategoryToString(EventCategory category) {
|
|
switch (category) {
|
|
case EventCategory::WORK: return "工作";
|
|
case EventCategory::LIFE: return "生活";
|
|
case EventCategory::STUDY: return "学习";
|
|
case EventCategory::HEALTH: return "健康";
|
|
case EventCategory::ENTERTAINMENT: return "娱乐";
|
|
case EventCategory::TRAVEL: return "旅行";
|
|
case EventCategory::FAMILY: return "家庭";
|
|
case EventCategory::OTHER: return "其他";
|
|
default: return "其他";
|
|
}
|
|
}
|
|
|
|
EventCategory ScheduleManager::StringToCategory(const std::string& category_str) {
|
|
if (category_str == "工作") return EventCategory::WORK;
|
|
if (category_str == "生活") return EventCategory::LIFE;
|
|
if (category_str == "学习") return EventCategory::STUDY;
|
|
if (category_str == "健康") return EventCategory::HEALTH;
|
|
if (category_str == "娱乐") return EventCategory::ENTERTAINMENT;
|
|
if (category_str == "旅行") return EventCategory::TRAVEL;
|
|
if (category_str == "家庭") return EventCategory::FAMILY;
|
|
return EventCategory::OTHER;
|
|
}
|
|
|
|
bool ScheduleManager::IsEventTimeValid(time_t start_time, time_t end_time) {
|
|
if (start_time <= 0) return false;
|
|
if (end_time > 0 && end_time <= start_time) return false;
|
|
return true;
|
|
}
|
|
|
|
void ScheduleManager::UpdateEventTimestamp(ScheduleEvent& event) {
|
|
event.updated_time = time(nullptr);
|
|
} |