Added a run_at, run_every and run_on_start, making the app a daemon like entity.
This commit is contained in:
parent
111c2487d8
commit
41c405a0c5
1 changed files with 694 additions and 46 deletions
463
appinfo.cpp
463
appinfo.cpp
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <re2/re2.h>
|
||||
|
||||
AppInfo::~AppInfo() {
|
||||
std::cout << "Module: " << getModuleName() << " finished ops." << std::endl;
|
||||
|
|
@ -57,6 +58,11 @@ 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;
|
||||
|
|
@ -67,6 +73,11 @@ 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<AppInfo> parent, State state)
|
||||
{
|
||||
this->parents[parent] = state;
|
||||
|
|
@ -131,6 +142,11 @@ std::string AppInfo::getAfterKeys() const
|
|||
return after;
|
||||
}
|
||||
|
||||
std::string AppInfo::getStringRunMode() const
|
||||
{
|
||||
return runmodeToString();
|
||||
}
|
||||
|
||||
AppInfo::RunMode AppInfo::getRunMode() const
|
||||
{
|
||||
return this->mode;
|
||||
|
|
@ -152,6 +168,9 @@ void AppInfo::copyFromData(AppInfo::AppData &other)
|
|||
}
|
||||
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);
|
||||
}
|
||||
|
|
@ -171,6 +190,26 @@ bool AppInfo::getCreateLog()
|
|||
return create_log;
|
||||
}
|
||||
|
||||
bool AppInfo::getScheduled()
|
||||
{
|
||||
return this->scheduled;
|
||||
}
|
||||
|
||||
bool AppInfo::getRunOnStart()
|
||||
{
|
||||
return this->run_on_start;
|
||||
}
|
||||
|
||||
std::vector<pid_t> 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<std::string> AppInfo::getArgs() const
|
||||
{
|
||||
return args;
|
||||
|
|
@ -244,9 +283,369 @@ void AppInfo::printInfo(std::ostream &out)
|
|||
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()
|
||||
{
|
||||
bool got_run_every = false;
|
||||
bool got_run_at = 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<std::chrono::system_clock::time_point> 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<std::chrono::system_clock::time_point> {
|
||||
using timepoint = std::chrono::system_clock::time_point;
|
||||
std::vector<timepoint> possible_time_points;
|
||||
std::vector<std::string> 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<std::chrono::system_clock::time_point> {
|
||||
using timepoint = std::chrono::system_clock::time_point;
|
||||
std::vector<timepoint> possible_time_points;
|
||||
std::vector<std::string> time_list = tokenize(temp_run_every, ",");
|
||||
std::vector<std::string> 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<int> {
|
||||
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<std::chrono::system_clock::time_point> {
|
||||
using timepoint = std::chrono::system_clock::time_point;
|
||||
std::vector<timepoint> possible_time_points;
|
||||
std::vector<std::string> time_list = tokenize(temp_run_every, ",");
|
||||
std::vector<std::string> 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<std::chrono::days>(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<unsigned>(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) {
|
||||
std::chrono::year_month_day ymd_next = (current_ymd.year() / current_ymd.month() / target_day) + std::chrono::months(1);
|
||||
if (!ymd_next.ok()) {
|
||||
ymd_next = ymd_next.year() / ymd_next.month() / 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<std::chrono::system_clock::time_point> 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<std::chrono::days>(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) {
|
||||
|
|
@ -259,6 +658,67 @@ std::string AppInfo::runmodeToString() const
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> AppInfo::tokenize(std::string main_string, std::string sub_string)
|
||||
{
|
||||
std::vector<std::string> 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<str.size(); i++) {
|
||||
if(!front_found) {
|
||||
if(str[i] == ' ' || str[i] == '\t' || str[i] == '\n' || str[i] == '\v' || str[i] == '\r')
|
||||
continue;
|
||||
else {
|
||||
front_found = true;
|
||||
ch += str[i];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
ch += str[i];
|
||||
}
|
||||
auto pos = ch.find_last_not_of(" \t\n\v\r");
|
||||
if(pos == std::string::npos)
|
||||
return ch;
|
||||
ch.erase(pos+1);
|
||||
return ch;
|
||||
}
|
||||
|
||||
int AppInfo::integer(const std::string str)
|
||||
{
|
||||
std::stringstream ss;
|
||||
int i = -1;
|
||||
ss << str;
|
||||
if (!(ss >> 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<char>(std::tolower(c));
|
||||
});
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string AppInfo::stateToString() const
|
||||
{
|
||||
return stateToString(this->state);
|
||||
|
|
@ -309,6 +769,9 @@ void AppInfo::AppData::printInfo(std::ostream &out)
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue