Skip to content

Commit a7b805c

Browse files
Merge pull request #145 from code0-tech/80-cron-adapter
Cron Adapter
2 parents 97e4fb2 + ed08997 commit a7b805c

4 files changed

Lines changed: 283 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 124 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["adapter/rest", "crates/base"]
2+
members = [ "adapter/cron","adapter/rest", "crates/base"]
33
resolver = "3"
44

55
[workspace.package]
@@ -22,6 +22,8 @@ anyhow = "1.0.98"
2222
prost = "0.14.0"
2323
tonic-health = "0.14.0"
2424
futures-lite = "2.6.1"
25+
chrono = "0.4.42"
26+
cron = "0.15.0"
2527

2628
[workspace.dependencies.base]
2729
path = "../draco/crates/base"

adapter/cron/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "cron"
3+
version.workspace = true
4+
edition.workspace = true
5+
6+
[dependencies]
7+
tokio = {workspace = true}
8+
chrono = {workspace = true}
9+
cron = {workspace = true}
10+
base = {workspace = true}
11+
tucana = {workspace = true}
12+
async-trait = {workspace = true}
13+
log = { workspace = true }
14+
anyhow = {workspace = true}

adapter/cron/src/main.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use async_trait::async_trait;
2+
use base::runner::{ServerContext, ServerRunner};
3+
use base::store::FlowIdentifyResult;
4+
use base::traits::{IdentifiableFlow, LoadConfig, Server};
5+
use chrono::{DateTime, Datelike, Timelike, Utc};
6+
use cron::Schedule;
7+
use std::str::FromStr;
8+
use tucana::shared::ValidationFlow;
9+
use tucana::shared::value::Kind;
10+
11+
#[derive(Default)]
12+
struct Cron {}
13+
14+
#[derive(Clone)]
15+
struct CronConfig {}
16+
17+
impl LoadConfig for CronConfig {
18+
fn load() -> Self {
19+
Self {}
20+
}
21+
}
22+
23+
#[tokio::main]
24+
async fn main() {
25+
let server = Cron::default();
26+
let runner = ServerRunner::new(server).await.unwrap();
27+
runner.serve().await.unwrap();
28+
}
29+
30+
struct Time {
31+
now: DateTime<Utc>,
32+
}
33+
34+
fn extract_flow_setting_field(flow: &ValidationFlow, name: &str) -> Option<String> {
35+
flow.settings
36+
.iter()
37+
.find(|s| s.flow_setting_id == name)
38+
.and_then(|s| s.value.as_ref())
39+
.and_then(|v| v.kind.as_ref())
40+
.and_then(|k| match k {
41+
Kind::StringValue(s) => Some(s.clone()),
42+
_ => None,
43+
})
44+
}
45+
46+
impl IdentifiableFlow for Time {
47+
fn identify(&self, flow: &tucana::shared::ValidationFlow) -> bool {
48+
let Some(minute) = extract_flow_setting_field(flow, "CRON_MINUTE") else {
49+
return false;
50+
};
51+
let Some(hour) = extract_flow_setting_field(flow, "CRON_HOUR") else {
52+
return false;
53+
};
54+
let Some(dom) = extract_flow_setting_field(flow, "CRON_DAY_OF_MONTH") else {
55+
return false;
56+
};
57+
let Some(month) = extract_flow_setting_field(flow, "CRON_MONTH") else {
58+
return false;
59+
};
60+
let Some(dow) = extract_flow_setting_field(flow, "CRON_DAY_OF_WEEK") else {
61+
return false;
62+
};
63+
64+
let expression = format!("* {} {} {} {} {}", minute, hour, dom, month, dow);
65+
let schedule = match Schedule::from_str(expression.as_str()) {
66+
Ok(s) => s,
67+
Err(e) => {
68+
log::error!(
69+
"Could not create schedule from expression ({}). Reason: {:?}",
70+
expression,
71+
e
72+
);
73+
return false;
74+
}
75+
};
76+
let next = match schedule.upcoming(Utc).next() {
77+
Some(n) => n,
78+
None => {
79+
log::error!("Could not find any upcomming schedules");
80+
return false;
81+
}
82+
};
83+
84+
self.now.year() == next.year()
85+
&& self.now.month() == next.month()
86+
&& self.now.day() == next.day()
87+
&& self.now.hour() == next.hour()
88+
&& self.now.minute() == next.minute()
89+
}
90+
}
91+
92+
#[async_trait]
93+
impl Server<CronConfig> for Cron {
94+
async fn init(&mut self, _ctx: &ServerContext<CronConfig>) -> anyhow::Result<()> {
95+
Ok(())
96+
}
97+
98+
async fn run(&mut self, ctx: &ServerContext<CronConfig>) -> anyhow::Result<()> {
99+
log::info!("Starting Cron adapter");
100+
let expression = "0 * * * * *";
101+
let schedule = Schedule::from_str(expression)?;
102+
let pattern = "CRON.<";
103+
104+
loop {
105+
let now = Utc::now();
106+
log::info!("Scheduled: {:?}", now);
107+
if let Some(next) = schedule.upcoming(Utc).take(1).next() {
108+
let until_next = next - now;
109+
tokio::time::sleep(until_next.to_std()?).await;
110+
111+
let time = Time { now: next };
112+
match ctx
113+
.adapter_store
114+
.get_possible_flow_match(pattern.to_string(), time)
115+
.await
116+
{
117+
FlowIdentifyResult::None => {
118+
log::debug!("No Flow identified for this schedule");
119+
}
120+
FlowIdentifyResult::Single(flow) => {
121+
log::debug!("One Flow identified for this schedule");
122+
ctx.adapter_store
123+
.validate_and_execute_flow(flow, None)
124+
.await;
125+
}
126+
FlowIdentifyResult::Multiple(flows) => {
127+
log::debug!("Multiple Flows identified for this schedule");
128+
for flow in flows {
129+
ctx.adapter_store
130+
.validate_and_execute_flow(flow, None)
131+
.await;
132+
}
133+
}
134+
}
135+
}
136+
}
137+
}
138+
139+
async fn shutdown(&mut self, _ctx: &ServerContext<CronConfig>) -> anyhow::Result<()> {
140+
Ok(())
141+
}
142+
}

0 commit comments

Comments
 (0)