#include "appinfo.h" #include #include #include AppInfo::~AppInfo() { std::cout << "Module: " << getModuleName() << " finished ops." << std::endl; } void AppInfo::setProgramName(std::string name) { this->program_name = name; } void AppInfo::setModuleName(std::string name) { this->module_name = name; } void AppInfo::setArgs(std::deque args) { this->args = args; } void AppInfo::setWorkingDir(std::string working_dir) { this->working_dir = working_dir; } void AppInfo::setEnv(string_map env) { this->env = env; } void AppInfo::setAfter(std::unordered_map after) { this->after = after; } void AppInfo::setCreateLog(bool create_log) { this->create_log = create_log; } void AppInfo::setRunMode(std::string mode) { if(mode == "script") { this->mode = RunMode::script; } else if(mode == "daemon") { this->mode = RunMode::daemon; } } void AppInfo::setRunAs(std::string run_as) { this->run_as = run_as; } void AppInfo::setScheduled(bool val) { this->scheduled = val; } void AppInfo::setFinished() { this->state = State::exited; } void AppInfo::setRunning() { this->state = State::running; } void AppInfo::addToPreviousExecutionList(pid_t new_process) { this->previous_execution_list.push_back(new_process); } void AppInfo::insertParent(std::shared_ptr parent, State state) { this->parents[parent] = state; } bool AppInfo::isParentDependencyCleared(std::osyncstream &out) const { for(auto &[key, value]: parents) { if(key->getState() != value) { out << key->getModuleName() << " not at state: " << stateToString(value) << " but is at:" << stateToString(key->getState()) << std::endl; return false; } } return true; } std::string AppInfo::getModuleName() const { return module_name; } std::string AppInfo::getProgramName() const { return program_name; } std::string AppInfo::getRunAs() const { return run_as; } std::string AppInfo::getWorkingDir() const { return working_dir; } AppInfo::string_map AppInfo::getEnv() const { return env; } std::string AppInfo::getAfterString() const { std::stringstream ss; for(auto &[key,value]: after) { ss << key << "[" << value << "], "; } std::string after_str = ss.str(); if(after_str.size() >= 2) after_str = after_str.substr(0, after_str.size() - 2); return after_str; } std::string AppInfo::getAfterKeys() const { std::stringstream ss; for(auto &[key,value]: after) { ss << key << ", "; } std::string after_str = ss.str(); if(after_str.size() >= 2) after_str = after_str.substr(0, after_str.size() - 2); return after_str; } std::string AppInfo::getStringRunMode() const { return runmodeToString(); } AppInfo::RunMode AppInfo::getRunMode() const { return this->mode; } AppInfo::State AppInfo::getState() const { return this->state; } void AppInfo::copyFromData(AppInfo::AppData &other) { this->module_name = other.module_name; this->program_name = other.program_name; this->create_log = other.create_log; this->working_dir = other.working_dir; for(auto &i: other.args) { this->args.push_back(i); } this->after = other.after; this->env = other.env; this->run_at = other.run_at; this->run_every = other.run_every; this->run_on_start = other.run_on_start; setRunMode(other.run_mode); setRunAs(other.run_as); } std::unordered_map, AppInfo::State> AppInfo::getParents() const { return this->parents; } std::unordered_map AppInfo::getAfter() const { return after; } bool AppInfo::getCreateLog() { return create_log; } bool AppInfo::getScheduled() { return this->scheduled; } bool AppInfo::getRunOnStart() { return this->run_on_start; } std::vector AppInfo::getPreviousExecutionList() const { return this->previous_execution_list; } std::chrono::system_clock::time_point AppInfo::getNextTimeToRun() const { return this->next_time_to_run; } std::deque AppInfo::getArgs() const { return args; } AppInfo::AppInfo() { } bool AppInfo::operator==(const AppInfo &other) { if(this->module_name == other.module_name && this->program_name == other.program_name && this->args == other.args && this->working_dir == other.working_dir && this->after == other.after && this->create_log == other.create_log ) return true; else return false; } void AppInfo::printInfo(std::ostream &out) { out << "************************************App************************************" << std::endl; out << "Module: " << module_name << std::endl; out << "App Name: " << program_name << std::endl; out << "Arg List: " << std::endl; int index = 0; for(auto &i: args) { out << "Arg[" << index++ << "]: " << i << std::endl; } out << "Run Mode: " << runmodeToString() << std::endl; out << "Working Directory: " << working_dir << std::endl; std::stringstream ss; for(auto &[key,value]: after) { ss << key << "[" << value << "], "; } std::string after = ss.str(); if(!after.empty() && after.size() >= 2) after = after.substr(0,after.size() - 2); out << "After: " << after << std::endl; std::stringstream ss2, ss3; for(auto &[key, value]: parents) { if(!key) { ss2 << "Invalid Parent" << std::endl; } else if(value == State::running) { ss2 << key->getModuleName() << " At: " << key << " with state: running." << std::endl; } else if(value == State::exited) { ss2 << key->getModuleName() << " At: " << key << " with state: exited." << std::endl; } else { ss2 << key->getModuleName() << " At: " << key << " with state: unknown." << std::endl; } } if(parents.size() != 0) { out << "Parents: " << parents.size() << " modules." << std::endl; out << ss2.str(); } else { out << "Parents: None." << std::endl; } for(auto &[key,value]: env) { ss3 << key << "=" << value << ", "; } std::string env = ss3.str(); if(!env.empty() && env.size() >= 2) env = env.substr(0,env.size() - 2); out << "Environment: " << env << std::endl; out << "Create Log: " << create_log << std::endl; out << "Run as: " << run_as << std::endl; out << "Run on start: " << run_on_start << std::endl; const auto* zone = std::chrono::current_zone(); auto local_now = zone->to_local(this->next_time_to_run); auto local_now_string = std::format("{:%F %T}", local_now); if((run_at.size() != 0 || run_every.size() != 0)) { out << "Next run scheduled at: " << local_now_string << std::endl; } else { out << "Normal scheduled run." << std::endl; } out << "************************************End************************************" << std::endl; } bool AppInfo::checkValidity() { // The json is wrong is run on start is set when run_every and run_at are not set if(this->run_every.size() == 0 && this->run_at.size() == 0 && this->run_on_start) return false; bool got_run_every = false; bool got_run_at = false; // The json is wrong if the format of run_every is wrong std::chrono::system_clock::time_point every_time_point, at_time_point; if(this->run_every.size() != 0) { auto next_time = parseEveryTimeFormat(run_every); if(next_time.has_value()) { every_time_point = next_time.value(); got_run_every = true; } else { return false; } } // The json is wrong if the format of run_at is wrong if(this->run_at.size() != 0) { auto next_time = parseAtTimeFormat(run_at); if(next_time.has_value()) { at_time_point = next_time.value(); got_run_at = true; } else { return false; } } // Update next time to run if(got_run_every && !got_run_at) { scheduled = true; next_time_to_run = every_time_point; } if(!got_run_every && got_run_at) { scheduled = true; next_time_to_run = at_time_point; } if(got_run_every && got_run_at) { scheduled = true; next_time_to_run = std::min(every_time_point, at_time_point); } return true; } void AppInfo::updateNextTimeToRun() { next_time_to_run_parse_failed = false; bool got_run_every = false; bool got_run_at = false; scheduled = false; std::chrono::system_clock::time_point every_time_point, at_time_point; if(this->run_every.size() != 0) { auto next_time = parseEveryTimeFormat(run_every); if(next_time.has_value()) { every_time_point = next_time.value(); got_run_every = true; } else { next_time_to_run_parse_failed = true; } } if(this->run_at.size() != 0) { auto next_time = parseAtTimeFormat(run_at); if(next_time.has_value()) { at_time_point = next_time.value(); got_run_at = true; } else { next_time_to_run_parse_failed = true; } } if(next_time_to_run_parse_failed) return; if(got_run_every && !got_run_at) next_time_to_run = every_time_point; if(!got_run_every && got_run_at) next_time_to_run = at_time_point; if(got_run_every && got_run_at) next_time_to_run = std::min(every_time_point, at_time_point); scheduled = true; } std::optional AppInfo::parseAtTimeFormat(std::string run_every) { /* format should be like: * "[time] Daily": for running daily at that time * "[day] [time] Weekly": for running every week on those days at that time. * "[day_month] [time] Monthly": for running every month on those days at that time. * [time] format is 24Hrs with [HH:MM:SS] * [day] format is any of [Mon, Tue, Wed, Thu, Fri, Sat, Sun] * [day_month] format is 1-31. * For Weekly, [day] can be a list of days seperated by commas. * For Monthly, [day_month] can be a list of days seperated by commas. * For Any, [time] can be a list of timestamps seperated by commas. */ auto timeParseDaily = [this](std::string temp_run_every, std::chrono::system_clock::time_point now) -> std::optional { using timepoint = std::chrono::system_clock::time_point; std::vector possible_time_points; std::vector time_list = tokenize(temp_run_every, ","); const auto* zone = std::chrono::current_zone(); auto local_now = zone->to_local(now); std::string date_part = std::format("{:%F}", local_now); for(auto &time: time_list) { std::string string_time_next = date_part + " " + time; std::chrono::local_seconds local_next; std::istringstream is{string_time_next}; is >> std::chrono::parse("%F %T", local_next); if(is.fail()) { return std::nullopt; } if(local_now >= local_next) { local_next = local_next + std::chrono::days(1); } timepoint next_time_point = zone->to_sys(local_next, std::chrono::choose::earliest); possible_time_points.push_back(next_time_point); } if(possible_time_points.size() == 0) { return std::nullopt; } return *std::ranges::min_element(possible_time_points); }; auto timeParseWeekly = [this](std::string temp_run_every, std::string week_part, std::chrono::system_clock::time_point now) -> std::optional { using timepoint = std::chrono::system_clock::time_point; std::vector possible_time_points; std::vector time_list = tokenize(temp_run_every, ","); std::vector week_list = tokenize(week_part, ","); const auto* zone = std::chrono::current_zone(); auto local_now = zone->to_local(now); int weekday_of_local_now = integer(std::format("{:%w}", local_now)); auto getDayOfWeekInteger = [this](std::string day) -> std::optional { if(tolower(day) == "sun") { return 0; } else if(tolower(day) == "mon") { return 1; } else if(tolower(day) == "tue") { return 2; } else if(tolower(day) == "wed") { return 3; } else if(tolower(day) == "thu") { return 4; } else if(tolower(day) == "fri") { return 5; } else if(tolower(day) == "sat") { return 6; } else { return std::nullopt; } }; for(auto &day_of_week: week_list) { auto temp_local_now = local_now; auto day_of_week_integer_ret = getDayOfWeekInteger(day_of_week); if(!day_of_week_integer_ret.has_value()) { return std::nullopt; } int day_of_week_integer = day_of_week_integer_ret.value(); if(day_of_week_integer != weekday_of_local_now) { temp_local_now = temp_local_now + std::chrono::days( (day_of_week_integer - weekday_of_local_now + 7) % 7 ); } std::string date_part = std::format("{:%F}", temp_local_now); for(auto &time: time_list) { std::string string_time_next = date_part + " " + time; std::chrono::local_seconds local_next; std::istringstream is{string_time_next}; is >> std::chrono::parse("%F %T", local_next); if(is.fail()) { return std::nullopt; } if(temp_local_now >= local_next && day_of_week_integer == weekday_of_local_now) { local_next = local_next + std::chrono::days(7); } timepoint next_time_point = zone->to_sys(local_next, std::chrono::choose::earliest); possible_time_points.push_back(next_time_point); } } if(possible_time_points.size() == 0) { return std::nullopt; } return *std::ranges::min_element(possible_time_points); }; auto timeParseMonthly = [this](std::string temp_run_every, std::string month_part, std::chrono::system_clock::time_point now) -> std::optional { using timepoint = std::chrono::system_clock::time_point; std::vector possible_time_points; std::vector time_list = tokenize(temp_run_every, ","); std::vector month_days_list = tokenize(month_part, ","); const auto* zone = std::chrono::current_zone(); auto local_now = zone->to_local(now); std::chrono::year_month_day current_ymd{std::chrono::floor(local_now)}; for(auto &day_of_month: month_days_list) { int day_int = integer(day_of_month); if (day_int < 1 || day_int > 31) { return std::nullopt; } std::chrono::day target_day{static_cast(day_int)}; std::chrono::year_month_day ymd = current_ymd.year() / current_ymd.month() / target_day; if (!ymd.ok()) { ymd = ymd.year() / ymd.month() / std::chrono::last; } for(auto &time: time_list) { std::chrono::seconds time_of_day; std::istringstream is{time}; is >> std::chrono::parse("%T", time_of_day); if (is.fail()) { return std::nullopt; } auto local_next = std::chrono::local_days{ymd} + time_of_day; if (local_next <= local_now) { auto next_ym = current_ymd.year() / current_ymd.month() + std::chrono::months(1); std::chrono::year_month_day ymd_next = next_ym / target_day; if (!ymd_next.ok()) { ymd_next = next_ym / std::chrono::last; } local_next = std::chrono::local_days{ymd_next} + time_of_day; } timepoint next_time_point = zone->to_sys(local_next, std::chrono::choose::earliest); possible_time_points.push_back(next_time_point); } } if(possible_time_points.size() == 0) { return std::nullopt; } return *std::ranges::min_element(possible_time_points); }; using timepoint = std::chrono::system_clock::time_point; std::chrono::time_point now = std::chrono::system_clock::now(); if(run_every.contains("Daily")) { std::string temp_run_every = run_every.erase(run_every.find("Daily")); temp_run_every = stringStrip(temp_run_every); auto options = timeParseDaily(temp_run_every, now); if(!options.has_value()) { return std::nullopt; } return options.value(); } else if(run_every.contains("Weekly")) { std::string temp_run_weekly = run_every.erase(run_every.find("Weekly")); temp_run_weekly = stringStrip(temp_run_weekly); std::string week_part; std::string time_part; if(!RE2::FullMatch(temp_run_weekly,R"(([a-zA-Z ,]+)([0-9: ,]+))", &week_part, &time_part)) { return std::nullopt; } week_part = stringStrip(week_part); time_part = stringStrip(time_part); auto options = timeParseWeekly(time_part, week_part, now); if(!options.has_value()) { return std::nullopt; } return options.value(); } else if(run_every.contains("Monthly")) { std::string temp_run_monthly = run_every.erase(run_every.find("Monthly")); temp_run_monthly = stringStrip(temp_run_monthly); std::string_view view_for_temp_run_monthy(temp_run_monthly); std::string temp_time_part; std::string time_part = ""; re2::RE2 r(R"((\d\d:\d\d:\d\d[, ]?)+)"); while (RE2::FindAndConsume(&view_for_temp_run_monthy, r, &temp_time_part)) { time_part += temp_time_part; } RE2::GlobalReplace(&temp_run_monthly,R"((\d\d:\d\d:\d\d[, ]?)+)", ""); std::string month_part = stringStrip(temp_run_monthly); time_part = stringStrip(time_part); auto options = timeParseMonthly(time_part, month_part, now); if(!options.has_value()) { return std::nullopt; } return options.value(); } else { return std::nullopt; } } std::optional AppInfo::parseEveryTimeFormat(std::string run_every) { /* format should be like: * [digits][unit] * valid units are: * s or S: seconds * m: minutes * h or H: hours * d or D: days * w or W: weeks * M: months * Example: 15s * Example: 2h */ int duration = 0; std::string unit; if(!RE2::FullMatch(run_every,R"((\d+)([sSmMhHdDwW]))", &duration, &unit)) { return std::nullopt; } if(duration <= 0) { return std::nullopt; } if(unit.size() <= 0) { return std::nullopt; } char ch = unit[0]; std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); const auto* zone = std::chrono::current_zone(); auto local_now = zone->to_local(now); switch(ch) { case 's': case 'S': { return zone->to_sys(local_now + std::chrono::seconds(duration), std::chrono::choose::earliest); } case 'm': { return zone->to_sys(local_now + std::chrono::minutes(duration), std::chrono::choose::earliest); } case 'h': case 'H': { return zone->to_sys(local_now + std::chrono::hours(duration), std::chrono::choose::earliest); } case 'd': case 'D': { return zone->to_sys(local_now + std::chrono::days(duration), std::chrono::choose::earliest); } case 'w': case 'W': { return zone->to_sys(local_now + std::chrono::weeks(duration), std::chrono::choose::earliest); } case 'M': { auto today = std::chrono::floor(local_now); auto time_of_day = local_now - today; std::chrono::year_month_day ymd{today}; ymd += std::chrono::months(duration); if (!ymd.ok()) { ymd = ymd.year() / ymd.month() / std::chrono::last; } auto next_local = std::chrono::local_days{ymd} + time_of_day; return zone->to_sys(next_local, std::chrono::choose::earliest); } default: { return std::nullopt; } } } std::string AppInfo::runmodeToString() const { switch(this->mode) { case RunMode::script: return "script"; case RunMode::daemon: return "daemon"; default: return "unknown"; } } std::vector AppInfo::tokenize(std::string main_string, std::string sub_string) { std::vector tokens; size_t pos = 0; while ((pos = main_string.find(sub_string)) != std::string::npos) { std::string token = stringStrip(main_string.substr(0, pos)); if (!token.empty()) { tokens.push_back(token); } main_string.erase(0, pos + sub_string.length()); } std::string last_token = stringStrip(main_string); if (!last_token.empty()) { tokens.push_back(last_token); } return tokens; } std::string AppInfo::stringStrip(const std::string str) { std::string ch; bool front_found = false; for(int i=0; i> i) || !(ss >> std::ws).eof()) return -1; return i; } std::string AppInfo::tolower(const std::string str) const { std::string s; std::transform(str.begin(), str.end(), std::back_inserter(s), [](unsigned char c){ return static_cast(std::tolower(c)); }); return s; } std::string AppInfo::stateToString() const { return stateToString(this->state); } std::string AppInfo::stateToString(State s) const { switch(s) { case State::initial: return "initial"; case State::running: return "running"; case State::exited: return "exited"; default: return "unknown"; } } void AppInfo::AppData::printInfo(std::ostream &out) { out << "************************************App************************************" << std::endl; out << "Module: " << AppData::module_name << std::endl; out << "App Name: " << AppData::program_name << std::endl; out << "Arg List: " << std::endl; int index = 0; for(auto &i: AppData::args) { out << "Arg[" << index++ << "]: " << i << std::endl; } out << "Run Mode: " << run_mode << std::endl; out << "Working Directory: " << AppData::working_dir << std::endl; std::stringstream ss, ss2; for(auto &[key,value]: AppData::after) { ss << key << "[" << value << "], "; } std::string after = ss.str(); if(!after.empty() && after.size() >= 2) after = after.substr(0,after.size() - 2); out << "After: " << after << std::endl; ss.clear(); ss.flush(); for(auto &[key,value]: AppData::env) { ss2 << key << "=" << value << ", "; } std::string env = ss2.str(); if(!env.empty() && env.size() >= 2) env = env.substr(0,env.size() - 2); out << "Environment: " << env << std::endl; out << "Create Log: " << AppData::create_log << std::endl; out << "Run as: " << run_as << std::endl; out << "Run on start: " << run_on_start << std::endl; out << "Run At: " << run_at << std::endl; out << "Run Every: " << run_every << std::endl; out << "************************************End************************************" << std::endl; } std::string AppInfo::AppData::getAfterString() const { std::stringstream ss; for(auto &[key,value]: after) { ss << key << "[" << value << "], "; } std::string after_str = ss.str(); if(after_str.size() >= 2) after_str = after_str.substr(0, after_str.size() - 2); return after_str; } std::string AppInfo::AppData::getAfterKeys() const { std::stringstream ss; for(auto &[key,value]: after) { ss << key << ", "; } std::string after_str = ss.str(); if(after_str.size() >= 2) after_str = after_str.substr(0, after_str.size() - 2); return after_str; }