Added a run_at, run_every and run_on_start, making the app a daemon like entity.

This commit is contained in:
unrealdragon 2026-10-21 21:38:49 +05:30
commit 41c405a0c5

View file

@ -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;
}