proxmox-systemd: remove crate, use new proxmox-time 1.1.0 instead
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
This commit is contained in:
parent
729bd1fd16
commit
15cc41b6cb
|
@ -28,7 +28,6 @@ members = [
|
||||||
"pbs-runtime",
|
"pbs-runtime",
|
||||||
"proxmox-rest-server",
|
"proxmox-rest-server",
|
||||||
"proxmox-rrd",
|
"proxmox-rrd",
|
||||||
"proxmox-systemd",
|
|
||||||
"pbs-tape",
|
"pbs-tape",
|
||||||
"pbs-tools",
|
"pbs-tools",
|
||||||
|
|
||||||
|
@ -120,7 +119,6 @@ pbs-datastore = { path = "pbs-datastore" }
|
||||||
pbs-runtime = { path = "pbs-runtime" }
|
pbs-runtime = { path = "pbs-runtime" }
|
||||||
proxmox-rest-server = { path = "proxmox-rest-server" }
|
proxmox-rest-server = { path = "proxmox-rest-server" }
|
||||||
proxmox-rrd = { path = "proxmox-rrd" }
|
proxmox-rrd = { path = "proxmox-rrd" }
|
||||||
proxmox-systemd = { path = "proxmox-systemd" }
|
|
||||||
pbs-tools = { path = "pbs-tools" }
|
pbs-tools = { path = "pbs-tools" }
|
||||||
pbs-tape = { path = "pbs-tape" }
|
pbs-tape = { path = "pbs-tape" }
|
||||||
|
|
||||||
|
|
1
Makefile
1
Makefile
|
@ -41,7 +41,6 @@ SUBCRATES := \
|
||||||
pbs-runtime \
|
pbs-runtime \
|
||||||
proxmox-rest-server \
|
proxmox-rest-server \
|
||||||
proxmox-rrd \
|
proxmox-rrd \
|
||||||
proxmox-systemd \
|
|
||||||
pbs-tape \
|
pbs-tape \
|
||||||
pbs-tools \
|
pbs-tools \
|
||||||
proxmox-backup-banner \
|
proxmox-backup-banner \
|
||||||
|
|
|
@ -18,7 +18,5 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
proxmox = "0.15.0"
|
proxmox = "0.15.0"
|
||||||
proxmox-lang = "1.0.0"
|
proxmox-lang = "1.0.0"
|
||||||
proxmox-schema = { version = "1.0.1", features = [ "api-macro" ] }
|
proxmox-schema = { version = "1.0.1", features = [ "api-macro" ] }
|
||||||
proxmox-time = "1.0.0"
|
proxmox-time = "1.1"
|
||||||
proxmox-uuid = { version = "1.0.0", features = [ "serde" ] }
|
proxmox-uuid = { version = "1.0.0", features = [ "serde" ] }
|
||||||
|
|
||||||
proxmox-systemd = { path = "../proxmox-systemd" }
|
|
||||||
|
|
|
@ -23,25 +23,25 @@ pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
|
||||||
|
|
||||||
pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
|
pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
|
||||||
"Run sync job at specified schedule.")
|
"Run sync job at specified schedule.")
|
||||||
.format(&ApiStringFormat::VerifyFn(proxmox_systemd::time::verify_calendar_event))
|
.format(&ApiStringFormat::VerifyFn(proxmox_time::verify_calendar_event))
|
||||||
.type_text("<calendar-event>")
|
.type_text("<calendar-event>")
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
pub const GC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
|
pub const GC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
|
||||||
"Run garbage collection job at specified schedule.")
|
"Run garbage collection job at specified schedule.")
|
||||||
.format(&ApiStringFormat::VerifyFn(proxmox_systemd::time::verify_calendar_event))
|
.format(&ApiStringFormat::VerifyFn(proxmox_time::verify_calendar_event))
|
||||||
.type_text("<calendar-event>")
|
.type_text("<calendar-event>")
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new(
|
pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new(
|
||||||
"Run prune job at specified schedule.")
|
"Run prune job at specified schedule.")
|
||||||
.format(&ApiStringFormat::VerifyFn(proxmox_systemd::time::verify_calendar_event))
|
.format(&ApiStringFormat::VerifyFn(proxmox_time::verify_calendar_event))
|
||||||
.type_text("<calendar-event>")
|
.type_text("<calendar-event>")
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
pub const VERIFICATION_SCHEDULE_SCHEMA: Schema = StringSchema::new(
|
pub const VERIFICATION_SCHEDULE_SCHEMA: Schema = StringSchema::new(
|
||||||
"Run verify job at specified schedule.")
|
"Run verify job at specified schedule.")
|
||||||
.format(&ApiStringFormat::VerifyFn(proxmox_systemd::time::verify_calendar_event))
|
.format(&ApiStringFormat::VerifyFn(proxmox_time::verify_calendar_event))
|
||||||
.type_text("<calendar-event>")
|
.type_text("<calendar-event>")
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use proxmox_schema::{
|
||||||
api, const_regex, ApiStringFormat, ApiType, ArraySchema, Schema, StringSchema, ReturnType,
|
api, const_regex, ApiStringFormat, ApiType, ArraySchema, Schema, StringSchema, ReturnType,
|
||||||
};
|
};
|
||||||
use proxmox::{IPRE, IPRE_BRACKET, IPV4OCTET, IPV4RE, IPV6H16, IPV6LS32, IPV6RE};
|
use proxmox::{IPRE, IPRE_BRACKET, IPV4OCTET, IPV4RE, IPV6H16, IPV6LS32, IPV6RE};
|
||||||
use proxmox_systemd::daily_duration::parse_daily_duration;
|
use proxmox_time::parse_daily_duration;
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
|
@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use proxmox_schema::{api, Schema, StringSchema, ApiStringFormat, Updater};
|
use proxmox_schema::{api, Schema, StringSchema, ApiStringFormat, Updater};
|
||||||
|
|
||||||
use proxmox_systemd::time::{parse_calendar_event, parse_time_span, CalendarEvent, TimeSpan};
|
use proxmox_time::{parse_calendar_event, parse_time_span, CalendarEvent, TimeSpan};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PROXMOX_SAFE_ID_FORMAT,
|
PROXMOX_SAFE_ID_FORMAT,
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "proxmox-systemd"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Proxmox Support Team <support@proxmox.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
description = "common systemd-related helpers, but no unit parsing"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0"
|
|
||||||
bitflags = "1.2.1"
|
|
||||||
lazy_static = "1.4"
|
|
||||||
nom = "5.1"
|
|
||||||
|
|
||||||
proxmox-time = "1"
|
|
||||||
proxmox-lang = "1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
proxmox = "0.15.0"
|
|
|
@ -1,174 +0,0 @@
|
||||||
use std::cmp::{Ordering, PartialOrd};
|
|
||||||
use std::convert::{TryFrom, TryInto};
|
|
||||||
|
|
||||||
use anyhow::Error;
|
|
||||||
|
|
||||||
use proxmox_time::TmEditor;
|
|
||||||
|
|
||||||
use super::time::{WeekDays};
|
|
||||||
|
|
||||||
pub use super::parse_time::parse_daily_duration;
|
|
||||||
|
|
||||||
/// Time of Day (hour with minute)
|
|
||||||
#[derive(Default, PartialEq, Clone, Debug)]
|
|
||||||
pub struct HmTime {
|
|
||||||
pub hour: u32,
|
|
||||||
pub minute: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for HmTime {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
let mut order = self.hour.cmp(&other.hour);
|
|
||||||
if order == Ordering::Equal {
|
|
||||||
order = self.minute.cmp(&other.minute);
|
|
||||||
}
|
|
||||||
Some(order)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
|
||||||
pub struct DailyDuration {
|
|
||||||
/// the days in a week this duration should trigger
|
|
||||||
pub days: WeekDays,
|
|
||||||
pub start: HmTime,
|
|
||||||
pub end: HmTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DailyDuration {
|
|
||||||
|
|
||||||
/// Test it time is within this frame
|
|
||||||
pub fn time_match(&self, epoch: i64, utc: bool) -> Result<bool, Error> {
|
|
||||||
|
|
||||||
let t = TmEditor::with_epoch(epoch, utc)?;
|
|
||||||
|
|
||||||
Ok(self.time_match_with_tm_editor(&t))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like time_match, but use [TmEditor] to specify the time
|
|
||||||
///
|
|
||||||
/// Note: This function returns bool (not Result<bool, Error>). It
|
|
||||||
/// simply returns ''false' if passed time 't' contains invalid values.
|
|
||||||
pub fn time_match_with_tm_editor(&self, t: &TmEditor) -> bool {
|
|
||||||
let all_days = self.days.is_empty() || self.days.is_all();
|
|
||||||
|
|
||||||
if !all_days { // match day first
|
|
||||||
match u32::try_from(t.day_num()) {
|
|
||||||
Ok(day_num) => {
|
|
||||||
match WeekDays::from_bits(1<<day_num) {
|
|
||||||
Some(day) => {
|
|
||||||
if !self.days.contains(day) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let hour = t.hour().try_into();
|
|
||||||
let minute = t.min().try_into();
|
|
||||||
|
|
||||||
match (hour, minute) {
|
|
||||||
(Ok(hour), Ok(minute)) => {
|
|
||||||
let ctime = HmTime { hour, minute };
|
|
||||||
ctime >= self.start && ctime < self.end
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
|
|
||||||
use anyhow::{bail, Error};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn test_parse(
|
|
||||||
duration_str: &str,
|
|
||||||
start_h: u32, start_m: u32,
|
|
||||||
end_h: u32, end_m: u32,
|
|
||||||
days: &[usize],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut day_bits = 0;
|
|
||||||
for day in days { day_bits |= 1<<day; }
|
|
||||||
let expected_days = WeekDays::from_bits(day_bits).unwrap();
|
|
||||||
|
|
||||||
let duration = parse_daily_duration(duration_str)?;
|
|
||||||
|
|
||||||
if duration.start.hour != start_h {
|
|
||||||
bail!("start hour missmatch, extected {}, got {:?}", start_h, duration);
|
|
||||||
}
|
|
||||||
if duration.start.minute != start_m {
|
|
||||||
bail!("start minute missmatch, extected {}, got {:?}", start_m, duration);
|
|
||||||
}
|
|
||||||
if duration.end.hour != end_h {
|
|
||||||
bail!("end hour missmatch, extected {}, got {:?}", end_h, duration);
|
|
||||||
}
|
|
||||||
if duration.end.minute != end_m {
|
|
||||||
bail!("end minute missmatch, extected {}, got {:?}", end_m, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
if duration.days != expected_days {
|
|
||||||
bail!("weekday missmatch, extected {:?}, got {:?}", expected_days, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn make_test_time(mday: i32, hour: i32, min: i32) -> i64 {
|
|
||||||
(mday*3600*24 + hour*3600 + min*60) as i64
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_daily_duration_parser() -> Result<(), Error> {
|
|
||||||
|
|
||||||
assert!(parse_daily_duration("").is_err());
|
|
||||||
assert!(parse_daily_duration(" 8-12").is_err());
|
|
||||||
assert!(parse_daily_duration("8:60-12").is_err());
|
|
||||||
assert!(parse_daily_duration("8-25").is_err());
|
|
||||||
assert!(parse_daily_duration("12-8").is_err());
|
|
||||||
|
|
||||||
test_parse("8-12", 8, 0, 12, 0, &[])?;
|
|
||||||
test_parse("8:0-12:0", 8, 0, 12, 0, &[])?;
|
|
||||||
test_parse("8:00-12:00", 8, 0, 12, 0, &[])?;
|
|
||||||
test_parse("8:05-12:20", 8, 5, 12, 20, &[])?;
|
|
||||||
test_parse("8:05 - 12:20", 8, 5, 12, 20, &[])?;
|
|
||||||
|
|
||||||
test_parse("mon 8-12", 8, 0, 12, 0, &[0])?;
|
|
||||||
test_parse("tue..fri 8-12", 8, 0, 12, 0, &[1,2,3,4])?;
|
|
||||||
test_parse("sat,tue..thu,fri 8-12", 8, 0, 12, 0, &[1,2,3,4,5])?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_time_match() -> Result<(), Error> {
|
|
||||||
const THURSDAY_80_00: i64 = make_test_time(0, 8, 0);
|
|
||||||
const THURSDAY_12_00: i64 = make_test_time(0, 12, 0);
|
|
||||||
const DAY: i64 = 3600*24;
|
|
||||||
|
|
||||||
let duration = parse_daily_duration("thu..fri 8:05-12")?;
|
|
||||||
|
|
||||||
assert!(!duration.time_match(THURSDAY_80_00, true)?);
|
|
||||||
assert!(!duration.time_match(THURSDAY_80_00 + DAY, true)?);
|
|
||||||
assert!(!duration.time_match(THURSDAY_80_00 + 2*DAY, true)?);
|
|
||||||
|
|
||||||
assert!(duration.time_match(THURSDAY_80_00 + 5*60, true)?);
|
|
||||||
assert!(duration.time_match(THURSDAY_80_00 + 5*60 + DAY, true)?);
|
|
||||||
assert!(!duration.time_match(THURSDAY_80_00 + 5*60 + 2*DAY, true)?);
|
|
||||||
|
|
||||||
assert!(duration.time_match(THURSDAY_12_00 - 1, true)?);
|
|
||||||
assert!(duration.time_match(THURSDAY_12_00 - 1 + DAY, true)?);
|
|
||||||
assert!(!duration.time_match(THURSDAY_12_00 - 1 + 2*DAY, true)?);
|
|
||||||
|
|
||||||
assert!(!duration.time_match(THURSDAY_12_00, true)?);
|
|
||||||
assert!(!duration.time_match(THURSDAY_12_00 + DAY, true)?);
|
|
||||||
assert!(!duration.time_match(THURSDAY_12_00 + 2*DAY, true)?);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
pub mod time;
|
|
||||||
pub mod daily_duration;
|
|
||||||
|
|
||||||
mod parse_time;
|
|
||||||
mod unit;
|
|
||||||
pub use unit::*;
|
|
|
@ -1,510 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use anyhow::{bail, Error};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
use super::time::*;
|
|
||||||
use super::daily_duration::*;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
error::{context, ParseError, VerboseError},
|
|
||||||
bytes::complete::{tag, take_while1},
|
|
||||||
combinator::{map_res, all_consuming, opt, recognize},
|
|
||||||
sequence::{pair, preceded, tuple},
|
|
||||||
character::complete::{alpha1, space0, digit1},
|
|
||||||
multi::separated_nonempty_list,
|
|
||||||
};
|
|
||||||
|
|
||||||
type IResult<I, O, E = VerboseError<I>> = Result<(I, O), nom::Err<E>>;
|
|
||||||
|
|
||||||
fn parse_error<'a>(i: &'a str, context: &'static str) -> nom::Err<VerboseError<&'a str>> {
|
|
||||||
let err = VerboseError { errors: Vec::new() };
|
|
||||||
let err = VerboseError::add_context(i, context, err);
|
|
||||||
nom::Err::Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse a 64 bit unsigned integer
|
|
||||||
fn parse_u64(i: &str) -> IResult<&str, u64> {
|
|
||||||
map_res(recognize(digit1), str::parse)(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse complete input, generate simple error message (use this for sinple line input).
|
|
||||||
fn parse_complete_line<'a, F, O>(what: &str, i: &'a str, parser: F) -> Result<O, Error>
|
|
||||||
where F: Fn(&'a str) -> IResult<&'a str, O>,
|
|
||||||
{
|
|
||||||
match all_consuming(parser)(i) {
|
|
||||||
Err(nom::Err::Error(VerboseError { errors })) |
|
|
||||||
Err(nom::Err::Failure(VerboseError { errors })) => {
|
|
||||||
if errors.is_empty() {
|
|
||||||
bail!("unable to parse {}", what);
|
|
||||||
} else {
|
|
||||||
bail!("unable to parse {} at '{}' - {:?}", what, errors[0].0, errors[0].1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
bail!("unable to parse {} - {}", what, err);
|
|
||||||
}
|
|
||||||
Ok((_, data)) => Ok(data),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref TIME_SPAN_UNITS: HashMap<&'static str, f64> = {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
|
|
||||||
let second = 1.0;
|
|
||||||
|
|
||||||
map.insert("seconds", second);
|
|
||||||
map.insert("second", second);
|
|
||||||
map.insert("sec", second);
|
|
||||||
map.insert("s", second);
|
|
||||||
|
|
||||||
let msec = second / 1000.0;
|
|
||||||
|
|
||||||
map.insert("msec", msec);
|
|
||||||
map.insert("ms", msec);
|
|
||||||
|
|
||||||
let usec = msec / 1000.0;
|
|
||||||
|
|
||||||
map.insert("usec", usec);
|
|
||||||
map.insert("us", usec);
|
|
||||||
map.insert("µs", usec);
|
|
||||||
|
|
||||||
let nsec = usec / 1000.0;
|
|
||||||
|
|
||||||
map.insert("nsec", nsec);
|
|
||||||
map.insert("ns", nsec);
|
|
||||||
|
|
||||||
let minute = second * 60.0;
|
|
||||||
|
|
||||||
map.insert("minutes", minute);
|
|
||||||
map.insert("minute", minute);
|
|
||||||
map.insert("min", minute);
|
|
||||||
map.insert("m", minute);
|
|
||||||
|
|
||||||
let hour = minute * 60.0;
|
|
||||||
|
|
||||||
map.insert("hours", hour);
|
|
||||||
map.insert("hour", hour);
|
|
||||||
map.insert("hr", hour);
|
|
||||||
map.insert("h", hour);
|
|
||||||
|
|
||||||
let day = hour * 24.0 ;
|
|
||||||
|
|
||||||
map.insert("days", day);
|
|
||||||
map.insert("day", day);
|
|
||||||
map.insert("d", day);
|
|
||||||
|
|
||||||
let week = day * 7.0;
|
|
||||||
|
|
||||||
map.insert("weeks", week);
|
|
||||||
map.insert("week", week);
|
|
||||||
map.insert("w", week);
|
|
||||||
|
|
||||||
let month = 30.44 * day;
|
|
||||||
|
|
||||||
map.insert("months", month);
|
|
||||||
map.insert("month", month);
|
|
||||||
map.insert("M", month);
|
|
||||||
|
|
||||||
let year = 365.25 * day;
|
|
||||||
|
|
||||||
map.insert("years", year);
|
|
||||||
map.insert("year", year);
|
|
||||||
map.insert("y", year);
|
|
||||||
|
|
||||||
map
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TimeSpec {
|
|
||||||
hour: Vec<DateTimeValue>,
|
|
||||||
minute: Vec<DateTimeValue>,
|
|
||||||
second: Vec<DateTimeValue>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DateSpec {
|
|
||||||
year: Vec<DateTimeValue>,
|
|
||||||
month: Vec<DateTimeValue>,
|
|
||||||
day: Vec<DateTimeValue>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_time_comp(max: usize) -> impl Fn(&str) -> IResult<&str, u32> {
|
|
||||||
move |i: &str| {
|
|
||||||
let (i, v) = map_res(recognize(digit1), str::parse)(i)?;
|
|
||||||
if (v as usize) >= max {
|
|
||||||
return Err(parse_error(i, "time value too large"));
|
|
||||||
}
|
|
||||||
Ok((i, v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_weekday(i: &str) -> IResult<&str, WeekDays> {
|
|
||||||
let (i, text) = alpha1(i)?;
|
|
||||||
|
|
||||||
match text.to_ascii_lowercase().as_str() {
|
|
||||||
"monday" | "mon" => Ok((i, WeekDays::MONDAY)),
|
|
||||||
"tuesday" | "tue" => Ok((i, WeekDays::TUESDAY)),
|
|
||||||
"wednesday" | "wed" => Ok((i, WeekDays::WEDNESDAY)),
|
|
||||||
"thursday" | "thu" => Ok((i, WeekDays::THURSDAY)),
|
|
||||||
"friday" | "fri" => Ok((i, WeekDays::FRIDAY)),
|
|
||||||
"saturday" | "sat" => Ok((i, WeekDays::SATURDAY)),
|
|
||||||
"sunday" | "sun" => Ok((i, WeekDays::SUNDAY)),
|
|
||||||
_ => return Err(parse_error(text, "weekday")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_weekdays_range(i: &str) -> IResult<&str, WeekDays> {
|
|
||||||
let (i, startday) = parse_weekday(i)?;
|
|
||||||
|
|
||||||
let generate_range = |start, end| {
|
|
||||||
let mut res = 0;
|
|
||||||
let mut pos = start;
|
|
||||||
loop {
|
|
||||||
res |= pos;
|
|
||||||
if pos >= end { break; }
|
|
||||||
pos <<= 1;
|
|
||||||
}
|
|
||||||
WeekDays::from_bits(res).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let (i, Some((_, endday))) = opt(pair(tag(".."),parse_weekday))(i)? {
|
|
||||||
let start = startday.bits();
|
|
||||||
let end = endday.bits();
|
|
||||||
if start > end {
|
|
||||||
let set1 = generate_range(start, WeekDays::SUNDAY.bits());
|
|
||||||
let set2 = generate_range(WeekDays::MONDAY.bits(), end);
|
|
||||||
Ok((i, set1 | set2))
|
|
||||||
} else {
|
|
||||||
Ok((i, generate_range(start, end)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok((i, startday))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_date_time_comp(max: usize) -> impl Fn(&str) -> IResult<&str, DateTimeValue> {
|
|
||||||
move |i: &str| {
|
|
||||||
let (i, value) = parse_time_comp(max)(i)?;
|
|
||||||
|
|
||||||
if let (i, Some(end)) = opt(preceded(tag(".."), parse_time_comp(max)))(i)? {
|
|
||||||
if value > end {
|
|
||||||
return Err(parse_error(i, "range start is bigger than end"));
|
|
||||||
}
|
|
||||||
return Ok((i, DateTimeValue::Range(value, end)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(time) = i.strip_prefix('/') {
|
|
||||||
let (time, repeat) = parse_time_comp(max)(time)?;
|
|
||||||
Ok((time, DateTimeValue::Repeated(value, repeat)))
|
|
||||||
} else {
|
|
||||||
Ok((i, DateTimeValue::Single(value)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_date_time_comp_list(start: u32, max: usize) -> impl Fn(&str) -> IResult<&str, Vec<DateTimeValue>> {
|
|
||||||
move |i: &str| {
|
|
||||||
if let Some(rest) = i.strip_prefix('*') {
|
|
||||||
if let Some(time) = rest.strip_prefix('/') {
|
|
||||||
let (n, repeat) = parse_time_comp(max)(time)?;
|
|
||||||
if repeat > 0 {
|
|
||||||
return Ok((n, vec![DateTimeValue::Repeated(start, repeat)]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok((rest, Vec::new()));
|
|
||||||
}
|
|
||||||
|
|
||||||
separated_nonempty_list(tag(","), parse_date_time_comp(max))(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_time_spec(i: &str) -> IResult<&str, TimeSpec> {
|
|
||||||
|
|
||||||
let (i, (hour, minute, opt_second)) = tuple((
|
|
||||||
parse_date_time_comp_list(0, 24),
|
|
||||||
preceded(tag(":"), parse_date_time_comp_list(0, 60)),
|
|
||||||
opt(preceded(tag(":"), parse_date_time_comp_list(0, 60))),
|
|
||||||
))(i)?;
|
|
||||||
|
|
||||||
if let Some(second) = opt_second {
|
|
||||||
Ok((i, TimeSpec { hour, minute, second }))
|
|
||||||
} else {
|
|
||||||
Ok((i, TimeSpec { hour, minute, second: vec![DateTimeValue::Single(0)] }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_date_spec(i: &str) -> IResult<&str, DateSpec> {
|
|
||||||
|
|
||||||
// TODO: implement ~ for days (man systemd.time)
|
|
||||||
if let Ok((i, (year, month, day))) = tuple((
|
|
||||||
parse_date_time_comp_list(0, 2200), // the upper limit for systemd, stay compatible
|
|
||||||
preceded(tag("-"), parse_date_time_comp_list(1, 13)),
|
|
||||||
preceded(tag("-"), parse_date_time_comp_list(1, 32)),
|
|
||||||
))(i) {
|
|
||||||
Ok((i, DateSpec { year, month, day }))
|
|
||||||
} else if let Ok((i, (month, day))) = tuple((
|
|
||||||
parse_date_time_comp_list(1, 13),
|
|
||||||
preceded(tag("-"), parse_date_time_comp_list(1, 32)),
|
|
||||||
))(i) {
|
|
||||||
Ok((i, DateSpec { year: Vec::new(), month, day }))
|
|
||||||
} else {
|
|
||||||
Err(parse_error(i, "invalid date spec"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_calendar_event(i: &str) -> Result<CalendarEvent, Error> {
|
|
||||||
parse_complete_line("calendar event", i, parse_calendar_event_incomplete)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_calendar_event_incomplete(mut i: &str) -> IResult<&str, CalendarEvent> {
|
|
||||||
|
|
||||||
let mut has_dayspec = false;
|
|
||||||
let mut has_timespec = false;
|
|
||||||
let mut has_datespec = false;
|
|
||||||
|
|
||||||
let mut event = CalendarEvent::default();
|
|
||||||
|
|
||||||
if i.starts_with(|c: char| char::is_ascii_alphabetic(&c)) {
|
|
||||||
|
|
||||||
match i {
|
|
||||||
"minutely" => {
|
|
||||||
return Ok(("", CalendarEvent {
|
|
||||||
second: vec![DateTimeValue::Single(0)],
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
"hourly" => {
|
|
||||||
return Ok(("", CalendarEvent {
|
|
||||||
minute: vec![DateTimeValue::Single(0)],
|
|
||||||
second: vec![DateTimeValue::Single(0)],
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
"daily" => {
|
|
||||||
return Ok(("", CalendarEvent {
|
|
||||||
hour: vec![DateTimeValue::Single(0)],
|
|
||||||
minute: vec![DateTimeValue::Single(0)],
|
|
||||||
second: vec![DateTimeValue::Single(0)],
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
"weekly" => {
|
|
||||||
return Ok(("", CalendarEvent {
|
|
||||||
hour: vec![DateTimeValue::Single(0)],
|
|
||||||
minute: vec![DateTimeValue::Single(0)],
|
|
||||||
second: vec![DateTimeValue::Single(0)],
|
|
||||||
days: WeekDays::MONDAY,
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
"monthly" => {
|
|
||||||
return Ok(("", CalendarEvent {
|
|
||||||
hour: vec![DateTimeValue::Single(0)],
|
|
||||||
minute: vec![DateTimeValue::Single(0)],
|
|
||||||
second: vec![DateTimeValue::Single(0)],
|
|
||||||
day: vec![DateTimeValue::Single(1)],
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
"yearly" | "annually" => {
|
|
||||||
return Ok(("", CalendarEvent {
|
|
||||||
hour: vec![DateTimeValue::Single(0)],
|
|
||||||
minute: vec![DateTimeValue::Single(0)],
|
|
||||||
second: vec![DateTimeValue::Single(0)],
|
|
||||||
day: vec![DateTimeValue::Single(1)],
|
|
||||||
month: vec![DateTimeValue::Single(1)],
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
"quarterly" => {
|
|
||||||
return Ok(("", CalendarEvent {
|
|
||||||
hour: vec![DateTimeValue::Single(0)],
|
|
||||||
minute: vec![DateTimeValue::Single(0)],
|
|
||||||
second: vec![DateTimeValue::Single(0)],
|
|
||||||
day: vec![DateTimeValue::Single(1)],
|
|
||||||
month: vec![
|
|
||||||
DateTimeValue::Single(1),
|
|
||||||
DateTimeValue::Single(4),
|
|
||||||
DateTimeValue::Single(7),
|
|
||||||
DateTimeValue::Single(10),
|
|
||||||
],
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
"semiannually" | "semi-annually" => {
|
|
||||||
return Ok(("", CalendarEvent {
|
|
||||||
hour: vec![DateTimeValue::Single(0)],
|
|
||||||
minute: vec![DateTimeValue::Single(0)],
|
|
||||||
second: vec![DateTimeValue::Single(0)],
|
|
||||||
day: vec![DateTimeValue::Single(1)],
|
|
||||||
month: vec![
|
|
||||||
DateTimeValue::Single(1),
|
|
||||||
DateTimeValue::Single(7),
|
|
||||||
],
|
|
||||||
..Default::default()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
_ => { /* continue */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
let (n, range_list) = context(
|
|
||||||
"weekday range list",
|
|
||||||
separated_nonempty_list(tag(","), parse_weekdays_range)
|
|
||||||
)(i)?;
|
|
||||||
|
|
||||||
has_dayspec = true;
|
|
||||||
|
|
||||||
i = space0(n)?.0;
|
|
||||||
|
|
||||||
for range in range_list { event.days.insert(range); }
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (n, Some(date)) = opt(parse_date_spec)(i)? {
|
|
||||||
event.year = date.year;
|
|
||||||
event.month = date.month;
|
|
||||||
event.day = date.day;
|
|
||||||
has_datespec = true;
|
|
||||||
i = space0(n)?.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (n, Some(time)) = opt(parse_time_spec)(i)? {
|
|
||||||
event.hour = time.hour;
|
|
||||||
event.minute = time.minute;
|
|
||||||
event.second = time.second;
|
|
||||||
has_timespec = true;
|
|
||||||
i = n;
|
|
||||||
} else {
|
|
||||||
event.hour = vec![DateTimeValue::Single(0)];
|
|
||||||
event.minute = vec![DateTimeValue::Single(0)];
|
|
||||||
event.second = vec![DateTimeValue::Single(0)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(has_dayspec || has_timespec || has_datespec) {
|
|
||||||
return Err(parse_error(i, "date or time specification"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((i, event))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_time_unit(i: &str) -> IResult<&str, &str> {
|
|
||||||
let (n, text) = take_while1(|c: char| char::is_ascii_alphabetic(&c) || c == 'µ')(i)?;
|
|
||||||
if TIME_SPAN_UNITS.contains_key(&text) {
|
|
||||||
Ok((n, text))
|
|
||||||
} else {
|
|
||||||
Err(parse_error(text, "time unit"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn parse_time_span(i: &str) -> Result<TimeSpan, Error> {
|
|
||||||
parse_complete_line("time span", i, parse_time_span_incomplete)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_time_span_incomplete(mut i: &str) -> IResult<&str, TimeSpan> {
|
|
||||||
|
|
||||||
let mut ts = TimeSpan::default();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
i = space0(i)?.0;
|
|
||||||
if i.is_empty() { break; }
|
|
||||||
let (n, num) = parse_u64(i)?;
|
|
||||||
i = space0(n)?.0;
|
|
||||||
|
|
||||||
if let (n, Some(unit)) = opt(parse_time_unit)(i)? {
|
|
||||||
i = n;
|
|
||||||
match unit {
|
|
||||||
"seconds" | "second" | "sec" | "s" => {
|
|
||||||
ts.seconds += num;
|
|
||||||
}
|
|
||||||
"msec" | "ms" => {
|
|
||||||
ts.msec += num;
|
|
||||||
}
|
|
||||||
"usec" | "us" | "µs" => {
|
|
||||||
ts.usec += num;
|
|
||||||
}
|
|
||||||
"nsec" | "ns" => {
|
|
||||||
ts.nsec += num;
|
|
||||||
}
|
|
||||||
"minutes" | "minute" | "min" | "m" => {
|
|
||||||
ts.minutes += num;
|
|
||||||
}
|
|
||||||
"hours" | "hour" | "hr" | "h" => {
|
|
||||||
ts.hours += num;
|
|
||||||
}
|
|
||||||
"days" | "day" | "d" => {
|
|
||||||
ts.days += num;
|
|
||||||
}
|
|
||||||
"weeks" | "week" | "w" => {
|
|
||||||
ts.weeks += num;
|
|
||||||
}
|
|
||||||
"months" | "month" | "M" => {
|
|
||||||
ts.months += num;
|
|
||||||
}
|
|
||||||
"years" | "year" | "y" => {
|
|
||||||
ts.years += num;
|
|
||||||
}
|
|
||||||
_ => return Err(parse_error(unit, "internal error")),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ts.seconds += num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((i, ts))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_daily_duration(i: &str) -> Result<DailyDuration, Error> {
|
|
||||||
parse_complete_line("daily duration", i, parse_daily_duration_incomplete)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_daily_duration_incomplete(mut i: &str) -> IResult<&str, DailyDuration> {
|
|
||||||
|
|
||||||
let mut duration = DailyDuration::default();
|
|
||||||
|
|
||||||
if i.starts_with(|c: char| char::is_ascii_alphabetic(&c)) {
|
|
||||||
|
|
||||||
let (n, range_list) = context(
|
|
||||||
"weekday range list",
|
|
||||||
separated_nonempty_list(tag(","), parse_weekdays_range)
|
|
||||||
)(i)?;
|
|
||||||
|
|
||||||
i = space0(n)?.0;
|
|
||||||
|
|
||||||
for range in range_list { duration.days.insert(range); }
|
|
||||||
}
|
|
||||||
|
|
||||||
let (i, start) = parse_hm_time(i)?;
|
|
||||||
|
|
||||||
let i = space0(i)?.0;
|
|
||||||
|
|
||||||
let (i, _) = tag("-")(i)?;
|
|
||||||
|
|
||||||
let i = space0(i)?.0;
|
|
||||||
|
|
||||||
let end_time_start = i;
|
|
||||||
|
|
||||||
let (i, end) = parse_hm_time(i)?;
|
|
||||||
|
|
||||||
if start > end {
|
|
||||||
return Err(parse_error(end_time_start, "end time before start time"));
|
|
||||||
}
|
|
||||||
|
|
||||||
duration.start = start;
|
|
||||||
duration.end = end;
|
|
||||||
|
|
||||||
Ok((i, duration))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_hm_time(i: &str) -> IResult<&str, HmTime> {
|
|
||||||
|
|
||||||
let (i, (hour, opt_minute)) = tuple((
|
|
||||||
parse_time_comp(24),
|
|
||||||
opt(preceded(tag(":"), parse_time_comp(60))),
|
|
||||||
))(i)?;
|
|
||||||
|
|
||||||
match opt_minute {
|
|
||||||
Some(minute) => Ok((i, HmTime { hour, minute })),
|
|
||||||
None => Ok((i, HmTime { hour, minute: 0})),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,586 +0,0 @@
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
use anyhow::Error;
|
|
||||||
use bitflags::bitflags;
|
|
||||||
|
|
||||||
use proxmox_time::TmEditor;
|
|
||||||
|
|
||||||
pub use super::parse_time::{parse_calendar_event, parse_time_span};
|
|
||||||
|
|
||||||
bitflags!{
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct WeekDays: u8 {
|
|
||||||
const MONDAY = 1;
|
|
||||||
const TUESDAY = 2;
|
|
||||||
const WEDNESDAY = 4;
|
|
||||||
const THURSDAY = 8;
|
|
||||||
const FRIDAY = 16;
|
|
||||||
const SATURDAY = 32;
|
|
||||||
const SUNDAY = 64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum DateTimeValue {
|
|
||||||
Single(u32),
|
|
||||||
Range(u32, u32),
|
|
||||||
Repeated(u32, u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DateTimeValue {
|
|
||||||
// Test if the entry contains the value
|
|
||||||
pub fn contains(&self, value: u32) -> bool {
|
|
||||||
match self {
|
|
||||||
DateTimeValue::Single(v) => *v == value,
|
|
||||||
DateTimeValue::Range(start, end) => value >= *start && value <= *end,
|
|
||||||
DateTimeValue::Repeated(start, repetition) => {
|
|
||||||
if value >= *start {
|
|
||||||
if *repetition > 0 {
|
|
||||||
let offset = value - start;
|
|
||||||
offset % repetition == 0
|
|
||||||
} else {
|
|
||||||
*start == value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_contains(list: &[DateTimeValue], value: u32) -> bool {
|
|
||||||
list.iter().any(|spec| spec.contains(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find an return an entry greater than value
|
|
||||||
pub fn find_next(list: &[DateTimeValue], value: u32) -> Option<u32> {
|
|
||||||
let mut next: Option<u32> = None;
|
|
||||||
let mut set_next = |v: u32| {
|
|
||||||
if let Some(n) = next {
|
|
||||||
if v < n { next = Some(v); }
|
|
||||||
} else {
|
|
||||||
next = Some(v);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for spec in list {
|
|
||||||
match spec {
|
|
||||||
DateTimeValue::Single(v) => {
|
|
||||||
if *v > value { set_next(*v); }
|
|
||||||
}
|
|
||||||
DateTimeValue::Range(start, end) => {
|
|
||||||
if value < *start {
|
|
||||||
set_next(*start);
|
|
||||||
} else {
|
|
||||||
let n = value + 1;
|
|
||||||
if n >= *start && n <= *end {
|
|
||||||
set_next(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DateTimeValue::Repeated(start, repetition) => {
|
|
||||||
if value < *start {
|
|
||||||
set_next(*start);
|
|
||||||
} else if *repetition > 0 {
|
|
||||||
set_next(start + ((value - start + repetition) / repetition) * repetition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calendar events may be used to refer to one or more points in time in a
|
|
||||||
/// single expression. They are designed after the systemd.time Calendar Events
|
|
||||||
/// specification, but are not guaranteed to be 100% compatible.
|
|
||||||
#[derive(Default, Clone, Debug)]
|
|
||||||
pub struct CalendarEvent {
|
|
||||||
/// the days in a week this event should trigger
|
|
||||||
pub days: WeekDays,
|
|
||||||
/// the second(s) this event should trigger
|
|
||||||
pub second: Vec<DateTimeValue>, // todo: support float values
|
|
||||||
/// the minute(s) this event should trigger
|
|
||||||
pub minute: Vec<DateTimeValue>,
|
|
||||||
/// the hour(s) this event should trigger
|
|
||||||
pub hour: Vec<DateTimeValue>,
|
|
||||||
/// the day(s) in a month this event should trigger
|
|
||||||
pub day: Vec<DateTimeValue>,
|
|
||||||
/// the month(s) in a year this event should trigger
|
|
||||||
pub month: Vec<DateTimeValue>,
|
|
||||||
/// the years(s) this event should trigger
|
|
||||||
pub year: Vec<DateTimeValue>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
|
||||||
pub struct TimeSpan {
|
|
||||||
pub nsec: u64,
|
|
||||||
pub usec: u64,
|
|
||||||
pub msec: u64,
|
|
||||||
pub seconds: u64,
|
|
||||||
pub minutes: u64,
|
|
||||||
pub hours: u64,
|
|
||||||
pub days: u64,
|
|
||||||
pub weeks: u64,
|
|
||||||
pub months: u64,
|
|
||||||
pub years: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TimeSpan> for f64 {
|
|
||||||
fn from(ts: TimeSpan) -> Self {
|
|
||||||
(ts.seconds as f64) +
|
|
||||||
((ts.nsec as f64) / 1_000_000_000.0) +
|
|
||||||
((ts.usec as f64) / 1_000_000.0) +
|
|
||||||
((ts.msec as f64) / 1_000.0) +
|
|
||||||
((ts.minutes as f64) * 60.0) +
|
|
||||||
((ts.hours as f64) * 3600.0) +
|
|
||||||
((ts.days as f64) * 3600.0 * 24.0) +
|
|
||||||
((ts.weeks as f64) * 3600.0 * 24.0 * 7.0) +
|
|
||||||
((ts.months as f64) * 3600.0 * 24.0 * 30.44) +
|
|
||||||
((ts.years as f64) * 3600.0 * 24.0 * 365.25)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::time::Duration> for TimeSpan {
|
|
||||||
fn from(duration: std::time::Duration) -> Self {
|
|
||||||
let mut duration = duration.as_nanos();
|
|
||||||
let nsec = (duration % 1000) as u64;
|
|
||||||
duration /= 1000;
|
|
||||||
let usec = (duration % 1000) as u64;
|
|
||||||
duration /= 1000;
|
|
||||||
let msec = (duration % 1000) as u64;
|
|
||||||
duration /= 1000;
|
|
||||||
let seconds = (duration % 60) as u64;
|
|
||||||
duration /= 60;
|
|
||||||
let minutes = (duration % 60) as u64;
|
|
||||||
duration /= 60;
|
|
||||||
let hours = (duration % 24) as u64;
|
|
||||||
duration /= 24;
|
|
||||||
let years = (duration as f64 / 365.25) as u64;
|
|
||||||
let ydays = (duration as f64 % 365.25) as u64;
|
|
||||||
let months = (ydays as f64 / 30.44) as u64;
|
|
||||||
let mdays = (ydays as f64 % 30.44) as u64;
|
|
||||||
let weeks = mdays / 7;
|
|
||||||
let days = mdays % 7;
|
|
||||||
Self {
|
|
||||||
nsec,
|
|
||||||
usec,
|
|
||||||
msec,
|
|
||||||
seconds,
|
|
||||||
minutes,
|
|
||||||
hours,
|
|
||||||
days,
|
|
||||||
weeks,
|
|
||||||
months,
|
|
||||||
years,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for TimeSpan {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
|
||||||
let mut first = true;
|
|
||||||
{ // block scope for mutable borrows
|
|
||||||
let mut do_write = |v: u64, unit: &str| -> Result<(), std::fmt::Error> {
|
|
||||||
if !first {
|
|
||||||
write!(f, " ")?;
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
write!(f, "{}{}", v, unit)
|
|
||||||
};
|
|
||||||
if self.years > 0 {
|
|
||||||
do_write(self.years, "y")?;
|
|
||||||
}
|
|
||||||
if self.months > 0 {
|
|
||||||
do_write(self.months, "m")?;
|
|
||||||
}
|
|
||||||
if self.weeks > 0 {
|
|
||||||
do_write(self.weeks, "w")?;
|
|
||||||
}
|
|
||||||
if self.days > 0 {
|
|
||||||
do_write(self.days, "d")?;
|
|
||||||
}
|
|
||||||
if self.hours > 0 {
|
|
||||||
do_write(self.hours, "h")?;
|
|
||||||
}
|
|
||||||
if self.minutes > 0 {
|
|
||||||
do_write(self.minutes, "min")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !first {
|
|
||||||
write!(f, " ")?;
|
|
||||||
}
|
|
||||||
let seconds = self.seconds as f64 + (self.msec as f64 / 1000.0);
|
|
||||||
if seconds >= 0.1 {
|
|
||||||
if seconds >= 1.0 || !first {
|
|
||||||
write!(f, "{:.0}s", seconds)?;
|
|
||||||
} else {
|
|
||||||
write!(f, "{:.1}s", seconds)?;
|
|
||||||
}
|
|
||||||
} else if first {
|
|
||||||
write!(f, "<0.1s")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_time_span(i: &str) -> Result<(), Error> {
|
|
||||||
parse_time_span(i)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_calendar_event(i: &str) -> Result<(), Error> {
|
|
||||||
parse_calendar_event(i)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compute_next_event(
|
|
||||||
event: &CalendarEvent,
|
|
||||||
last: i64,
|
|
||||||
utc: bool,
|
|
||||||
) -> Result<Option<i64>, Error> {
|
|
||||||
|
|
||||||
let last = last + 1; // at least one second later
|
|
||||||
|
|
||||||
let all_days = event.days.is_empty() || event.days.is_all();
|
|
||||||
|
|
||||||
let mut t = TmEditor::with_epoch(last, utc)?;
|
|
||||||
|
|
||||||
let mut count = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// cancel after 1000 loops
|
|
||||||
if count > 1000 {
|
|
||||||
return Ok(None);
|
|
||||||
} else {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !event.year.is_empty() {
|
|
||||||
let year: u32 = t.year().try_into()?;
|
|
||||||
if !DateTimeValue::list_contains(&event.year, year) {
|
|
||||||
if let Some(n) = DateTimeValue::find_next(&event.year, year) {
|
|
||||||
t.add_years((n - year).try_into()?)?;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// if we have no valid year, we cannot find a correct timestamp
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !event.month.is_empty() {
|
|
||||||
let month: u32 = t.month().try_into()?;
|
|
||||||
if !DateTimeValue::list_contains(&event.month, month) {
|
|
||||||
if let Some(n) = DateTimeValue::find_next(&event.month, month) {
|
|
||||||
t.add_months((n - month).try_into()?)?;
|
|
||||||
} else {
|
|
||||||
// if we could not find valid month, retry next year
|
|
||||||
t.add_years(1)?;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !event.day.is_empty() {
|
|
||||||
let day: u32 = t.day().try_into()?;
|
|
||||||
if !DateTimeValue::list_contains(&event.day, day) {
|
|
||||||
if let Some(n) = DateTimeValue::find_next(&event.day, day) {
|
|
||||||
t.add_days((n - day).try_into()?)?;
|
|
||||||
} else {
|
|
||||||
// if we could not find valid mday, retry next month
|
|
||||||
t.add_months(1)?;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !all_days { // match day first
|
|
||||||
let day_num: u32 = t.day_num().try_into()?;
|
|
||||||
let day = WeekDays::from_bits(1<<day_num).unwrap();
|
|
||||||
if !event.days.contains(day) {
|
|
||||||
if let Some(n) = ((day_num+1)..7)
|
|
||||||
.find(|d| event.days.contains(WeekDays::from_bits(1<<d).unwrap()))
|
|
||||||
{
|
|
||||||
// try next day
|
|
||||||
t.add_days((n - day_num).try_into()?)?;
|
|
||||||
} else {
|
|
||||||
// try next week
|
|
||||||
t.add_days((7 - day_num).try_into()?)?;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this day
|
|
||||||
if !event.hour.is_empty() {
|
|
||||||
let hour = t.hour().try_into()?;
|
|
||||||
if !DateTimeValue::list_contains(&event.hour, hour) {
|
|
||||||
if let Some(n) = DateTimeValue::find_next(&event.hour, hour) {
|
|
||||||
// test next hour
|
|
||||||
t.set_time(n.try_into()?, 0, 0)?;
|
|
||||||
} else {
|
|
||||||
// test next day
|
|
||||||
t.add_days(1)?;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this hour
|
|
||||||
if !event.minute.is_empty() {
|
|
||||||
let minute = t.min().try_into()?;
|
|
||||||
if !DateTimeValue::list_contains(&event.minute, minute) {
|
|
||||||
if let Some(n) = DateTimeValue::find_next(&event.minute, minute) {
|
|
||||||
// test next minute
|
|
||||||
t.set_min_sec(n.try_into()?, 0)?;
|
|
||||||
} else {
|
|
||||||
// test next hour
|
|
||||||
t.set_time(t.hour() + 1, 0, 0)?;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this minute
|
|
||||||
if !event.second.is_empty() {
|
|
||||||
let second = t.sec().try_into()?;
|
|
||||||
if !DateTimeValue::list_contains(&event.second, second) {
|
|
||||||
if let Some(n) = DateTimeValue::find_next(&event.second, second) {
|
|
||||||
// test next second
|
|
||||||
t.set_sec(n.try_into()?)?;
|
|
||||||
} else {
|
|
||||||
// test next min
|
|
||||||
t.set_min_sec(t.min() + 1, 0)?;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let next = t.into_epoch()?;
|
|
||||||
return Ok(Some(next))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
//use proxmox_time::*;
|
|
||||||
|
|
||||||
fn test_event(v: &'static str) -> Result<(), Error> {
|
|
||||||
match parse_calendar_event(v) {
|
|
||||||
Ok(event) => println!("CalendarEvent '{}' => {:?}", v, event),
|
|
||||||
Err(err) => bail!("parsing '{}' failed - {}", v, err),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn make_test_time(mday: i32, hour: i32, min: i32) -> i64 {
|
|
||||||
(mday*3600*24 + hour*3600 + min*60) as i64
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_compute_next_event() -> Result<(), Error> {
|
|
||||||
|
|
||||||
let test_value = |v: &'static str, last: i64, expect: i64| -> Result<i64, Error> {
|
|
||||||
let event = match parse_calendar_event(v) {
|
|
||||||
Ok(event) => event,
|
|
||||||
Err(err) => bail!("parsing '{}' failed - {}", v, err),
|
|
||||||
};
|
|
||||||
|
|
||||||
match compute_next_event(&event, last, true) {
|
|
||||||
Ok(Some(next)) => {
|
|
||||||
if next == expect {
|
|
||||||
println!("next {:?} => {}", event, next);
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"next {:?} failed\nnext: {:?}\nexpect: {:?}",
|
|
||||||
event,
|
|
||||||
proxmox_time::gmtime(next),
|
|
||||||
proxmox_time::gmtime(expect),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None) => bail!("next {:?} failed to find a timestamp", event),
|
|
||||||
Err(err) => bail!("compute next for '{}' failed - {}", v, err),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(expect)
|
|
||||||
};
|
|
||||||
|
|
||||||
let test_never = |v: &'static str, last: i64| -> Result<(), Error> {
|
|
||||||
let event = match parse_calendar_event(v) {
|
|
||||||
Ok(event) => event,
|
|
||||||
Err(err) => bail!("parsing '{}' failed - {}", v, err),
|
|
||||||
};
|
|
||||||
|
|
||||||
match compute_next_event(&event, last, true)? {
|
|
||||||
None => Ok(()),
|
|
||||||
Some(next) => bail!("compute next for '{}' succeeded, but expected fail - result {}", v, next),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const MIN: i64 = 60;
|
|
||||||
const HOUR: i64 = 3600;
|
|
||||||
const DAY: i64 = 3600*24;
|
|
||||||
|
|
||||||
const THURSDAY_00_00: i64 = make_test_time(0, 0, 0);
|
|
||||||
const THURSDAY_15_00: i64 = make_test_time(0, 15, 0);
|
|
||||||
|
|
||||||
const JUL_31_2020: i64 = 1596153600; // Friday, 2020-07-31 00:00:00
|
|
||||||
const DEC_31_2020: i64 = 1609372800; // Thursday, 2020-12-31 00:00:00
|
|
||||||
|
|
||||||
test_value("*:0", THURSDAY_00_00, THURSDAY_00_00 + HOUR)?;
|
|
||||||
test_value("*:*", THURSDAY_00_00, THURSDAY_00_00 + MIN)?;
|
|
||||||
test_value("*:*:*", THURSDAY_00_00, THURSDAY_00_00 + 1)?;
|
|
||||||
test_value("*:3:5", THURSDAY_00_00, THURSDAY_00_00 + 3*MIN + 5)?;
|
|
||||||
|
|
||||||
test_value("mon *:*", THURSDAY_00_00, THURSDAY_00_00 + 4*DAY)?;
|
|
||||||
test_value("mon 2:*", THURSDAY_00_00, THURSDAY_00_00 + 4*DAY + 2*HOUR)?;
|
|
||||||
test_value("mon 2:50", THURSDAY_00_00, THURSDAY_00_00 + 4*DAY + 2*HOUR + 50*MIN)?;
|
|
||||||
|
|
||||||
test_value("tue", THURSDAY_00_00, THURSDAY_00_00 + 5*DAY)?;
|
|
||||||
test_value("wed", THURSDAY_00_00, THURSDAY_00_00 + 6*DAY)?;
|
|
||||||
test_value("thu", THURSDAY_00_00, THURSDAY_00_00 + 7*DAY)?;
|
|
||||||
test_value("fri", THURSDAY_00_00, THURSDAY_00_00 + 1*DAY)?;
|
|
||||||
test_value("sat", THURSDAY_00_00, THURSDAY_00_00 + 2*DAY)?;
|
|
||||||
test_value("sun", THURSDAY_00_00, THURSDAY_00_00 + 3*DAY)?;
|
|
||||||
|
|
||||||
// test multiple values for a single field
|
|
||||||
// and test that the order does not matter
|
|
||||||
test_value("5,10:4,8", THURSDAY_00_00, THURSDAY_00_00 + 5*HOUR + 4*MIN)?;
|
|
||||||
test_value("10,5:8,4", THURSDAY_00_00, THURSDAY_00_00 + 5*HOUR + 4*MIN)?;
|
|
||||||
test_value("6,4..10:23,5/5", THURSDAY_00_00, THURSDAY_00_00 + 4*HOUR + 5*MIN)?;
|
|
||||||
test_value("4..10,6:5/5,23", THURSDAY_00_00, THURSDAY_00_00 + 4*HOUR + 5*MIN)?;
|
|
||||||
|
|
||||||
// test month wrapping
|
|
||||||
test_value("sat", JUL_31_2020, JUL_31_2020 + 1*DAY)?;
|
|
||||||
test_value("sun", JUL_31_2020, JUL_31_2020 + 2*DAY)?;
|
|
||||||
test_value("mon", JUL_31_2020, JUL_31_2020 + 3*DAY)?;
|
|
||||||
test_value("tue", JUL_31_2020, JUL_31_2020 + 4*DAY)?;
|
|
||||||
test_value("wed", JUL_31_2020, JUL_31_2020 + 5*DAY)?;
|
|
||||||
test_value("thu", JUL_31_2020, JUL_31_2020 + 6*DAY)?;
|
|
||||||
test_value("fri", JUL_31_2020, JUL_31_2020 + 7*DAY)?;
|
|
||||||
|
|
||||||
// test year wrapping
|
|
||||||
test_value("fri", DEC_31_2020, DEC_31_2020 + 1*DAY)?;
|
|
||||||
test_value("sat", DEC_31_2020, DEC_31_2020 + 2*DAY)?;
|
|
||||||
test_value("sun", DEC_31_2020, DEC_31_2020 + 3*DAY)?;
|
|
||||||
test_value("mon", DEC_31_2020, DEC_31_2020 + 4*DAY)?;
|
|
||||||
test_value("tue", DEC_31_2020, DEC_31_2020 + 5*DAY)?;
|
|
||||||
test_value("wed", DEC_31_2020, DEC_31_2020 + 6*DAY)?;
|
|
||||||
test_value("thu", DEC_31_2020, DEC_31_2020 + 7*DAY)?;
|
|
||||||
|
|
||||||
test_value("daily", THURSDAY_00_00, THURSDAY_00_00 + DAY)?;
|
|
||||||
test_value("daily", THURSDAY_00_00+1, THURSDAY_00_00 + DAY)?;
|
|
||||||
|
|
||||||
let n = test_value("5/2:0", THURSDAY_00_00, THURSDAY_00_00 + 5*HOUR)?;
|
|
||||||
let n = test_value("5/2:0", n, THURSDAY_00_00 + 7*HOUR)?;
|
|
||||||
let n = test_value("5/2:0", n, THURSDAY_00_00 + 9*HOUR)?;
|
|
||||||
test_value("5/2:0", n, THURSDAY_00_00 + 11*HOUR)?;
|
|
||||||
|
|
||||||
let mut n = test_value("*:*", THURSDAY_00_00, THURSDAY_00_00 + MIN)?;
|
|
||||||
for i in 2..100 {
|
|
||||||
n = test_value("*:*", n, THURSDAY_00_00 + i*MIN)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut n = test_value("*:0", THURSDAY_00_00, THURSDAY_00_00 + HOUR)?;
|
|
||||||
for i in 2..100 {
|
|
||||||
n = test_value("*:0", n, THURSDAY_00_00 + i*HOUR)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut n = test_value("1:0", THURSDAY_15_00, THURSDAY_00_00 + DAY + HOUR)?;
|
|
||||||
for i in 2..100 {
|
|
||||||
n = test_value("1:0", n, THURSDAY_00_00 + i*DAY + HOUR)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// test date functionality
|
|
||||||
|
|
||||||
test_value("2020-07-31", 0, JUL_31_2020)?;
|
|
||||||
test_value("02-28", 0, (31+27)*DAY)?;
|
|
||||||
test_value("02-29", 0, 2*365*DAY + (31+28)*DAY)?; // 1972-02-29
|
|
||||||
test_value("1965/5-01-01", -1, THURSDAY_00_00)?;
|
|
||||||
test_value("2020-7..9-2/2", JUL_31_2020, JUL_31_2020 + 2*DAY)?;
|
|
||||||
test_value("2020,2021-12-31", JUL_31_2020, DEC_31_2020)?;
|
|
||||||
|
|
||||||
test_value("monthly", 0, 31*DAY)?;
|
|
||||||
test_value("quarterly", 0, (31+28+31)*DAY)?;
|
|
||||||
test_value("semiannually", 0, (31+28+31+30+31+30)*DAY)?;
|
|
||||||
test_value("yearly", 0, (365)*DAY)?;
|
|
||||||
|
|
||||||
test_never("2021-02-29", 0)?;
|
|
||||||
test_never("02-30", 0)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_calendar_event_weekday() -> Result<(), Error> {
|
|
||||||
test_event("mon,wed..fri")?;
|
|
||||||
test_event("fri..mon")?;
|
|
||||||
|
|
||||||
test_event("mon")?;
|
|
||||||
test_event("MON")?;
|
|
||||||
test_event("monDay")?;
|
|
||||||
test_event("tue")?;
|
|
||||||
test_event("Tuesday")?;
|
|
||||||
test_event("wed")?;
|
|
||||||
test_event("wednesday")?;
|
|
||||||
test_event("thu")?;
|
|
||||||
test_event("thursday")?;
|
|
||||||
test_event("fri")?;
|
|
||||||
test_event("friday")?;
|
|
||||||
test_event("sat")?;
|
|
||||||
test_event("saturday")?;
|
|
||||||
test_event("sun")?;
|
|
||||||
test_event("sunday")?;
|
|
||||||
|
|
||||||
test_event("mon..fri")?;
|
|
||||||
test_event("mon,tue,fri")?;
|
|
||||||
test_event("mon,tue..wednesday,fri..sat")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_time_span_parser() -> Result<(), Error> {
|
|
||||||
|
|
||||||
let test_value = |ts_str: &str, expect: f64| -> Result<(), Error> {
|
|
||||||
let ts = parse_time_span(ts_str)?;
|
|
||||||
assert_eq!(f64::from(ts), expect, "{}", ts_str);
|
|
||||||
Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
test_value("2", 2.0)?;
|
|
||||||
test_value("2s", 2.0)?;
|
|
||||||
test_value("2sec", 2.0)?;
|
|
||||||
test_value("2second", 2.0)?;
|
|
||||||
test_value("2seconds", 2.0)?;
|
|
||||||
|
|
||||||
test_value(" 2s 2 s 2", 6.0)?;
|
|
||||||
|
|
||||||
test_value("1msec 1ms", 0.002)?;
|
|
||||||
test_value("1usec 1us 1µs", 0.000_003)?;
|
|
||||||
test_value("1nsec 1ns", 0.000_000_002)?;
|
|
||||||
test_value("1minutes 1minute 1min 1m", 4.0*60.0)?;
|
|
||||||
test_value("1hours 1hour 1hr 1h", 4.0*3600.0)?;
|
|
||||||
test_value("1days 1day 1d", 3.0*86400.0)?;
|
|
||||||
test_value("1weeks 1 week 1w", 3.0*86400.0*7.0)?;
|
|
||||||
test_value("1months 1month 1M", 3.0*86400.0*30.44)?;
|
|
||||||
test_value("1years 1year 1y", 3.0*86400.0*365.25)?;
|
|
||||||
|
|
||||||
test_value("2h", 7200.0)?;
|
|
||||||
test_value(" 2 h", 7200.0)?;
|
|
||||||
test_value("2hours", 7200.0)?;
|
|
||||||
test_value("48hr", 48.0*3600.0)?;
|
|
||||||
test_value("1y 12month", 365.25*24.0*3600.0 + 12.0*30.44*24.0*3600.0)?;
|
|
||||||
test_value("55s500ms", 55.5)?;
|
|
||||||
test_value("300ms20s 5day", 5.0*24.0*3600.0 + 20.0 + 0.3)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -187,9 +187,9 @@ pub fn create_datastore_disk(
|
||||||
|
|
||||||
let mount_unit_name = create_datastore_mount_unit(&name, &mount_point, filesystem, &uuid_path)?;
|
let mount_unit_name = create_datastore_mount_unit(&name, &mount_point, filesystem, &uuid_path)?;
|
||||||
|
|
||||||
proxmox_systemd::reload_daemon()?;
|
crate::tools::systemd::reload_daemon()?;
|
||||||
proxmox_systemd::enable_unit(&mount_unit_name)?;
|
crate::tools::systemd::enable_unit(&mount_unit_name)?;
|
||||||
proxmox_systemd::start_unit(&mount_unit_name)?;
|
crate::tools::systemd::start_unit(&mount_unit_name)?;
|
||||||
|
|
||||||
if add_datastore {
|
if add_datastore {
|
||||||
let lock = pbs_config::datastore::lock_config()?;
|
let lock = pbs_config::datastore::lock_config()?;
|
||||||
|
@ -245,7 +245,7 @@ pub fn delete_datastore_disk(name: String) -> Result<(), Error> {
|
||||||
// disable systemd mount-unit
|
// disable systemd mount-unit
|
||||||
let mut mount_unit_name = proxmox::tools::systemd::escape_unit(&path, true);
|
let mut mount_unit_name = proxmox::tools::systemd::escape_unit(&path, true);
|
||||||
mount_unit_name.push_str(".mount");
|
mount_unit_name.push_str(".mount");
|
||||||
proxmox_systemd::disable_unit(&mount_unit_name)?;
|
crate::tools::systemd::disable_unit(&mount_unit_name)?;
|
||||||
|
|
||||||
// delete .mount-file
|
// delete .mount-file
|
||||||
let mount_unit_path = format!("/etc/systemd/system/{}", mount_unit_name);
|
let mount_unit_path = format!("/etc/systemd/system/{}", mount_unit_name);
|
||||||
|
|
|
@ -270,7 +270,7 @@ pub fn create_zpool(
|
||||||
|
|
||||||
if std::path::Path::new("/lib/systemd/system/zfs-import@.service").exists() {
|
if std::path::Path::new("/lib/systemd/system/zfs-import@.service").exists() {
|
||||||
let import_unit = format!("zfs-import@{}.service", proxmox::tools::systemd::escape_unit(&name, false));
|
let import_unit = format!("zfs-import@{}.service", proxmox::tools::systemd::escape_unit(&name, false));
|
||||||
proxmox_systemd::enable_unit(&import_unit)?;
|
crate::tools::systemd::enable_unit(&import_unit)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(compression) = compression {
|
if let Some(compression) = compression {
|
||||||
|
|
|
@ -46,7 +46,7 @@ use proxmox_backup::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use pbs_buildcfg::configdir;
|
use pbs_buildcfg::configdir;
|
||||||
use proxmox_systemd::time::{compute_next_event, parse_calendar_event};
|
use proxmox_time::{compute_next_event, parse_calendar_event};
|
||||||
use pbs_tools::logrotate::LogRotate;
|
use pbs_tools::logrotate::LogRotate;
|
||||||
|
|
||||||
use pbs_api_types::{
|
use pbs_api_types::{
|
||||||
|
|
|
@ -11,8 +11,7 @@ use cidr::IpInet;
|
||||||
use proxmox_http::client::{ShareableRateLimit, RateLimiter};
|
use proxmox_http::client::{ShareableRateLimit, RateLimiter};
|
||||||
use proxmox_section_config::SectionConfigData;
|
use proxmox_section_config::SectionConfigData;
|
||||||
|
|
||||||
use proxmox_systemd::daily_duration::{parse_daily_duration, DailyDuration};
|
use proxmox_time::{parse_daily_duration, DailyDuration, TmEditor};
|
||||||
use proxmox_time::TmEditor;
|
|
||||||
|
|
||||||
use pbs_api_types::TrafficControlRule;
|
use pbs_api_types::TrafficControlRule;
|
||||||
|
|
||||||
|
|
|
@ -431,7 +431,7 @@ pub fn send_tape_backup_status(
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
let (fqdn, port) = get_server_url();
|
let (fqdn, port) = get_server_url();
|
||||||
let duration: proxmox_systemd::time::TimeSpan = summary.duration.into();
|
let duration: proxmox_time::TimeSpan = summary.duration.into();
|
||||||
let mut data = json!({
|
let mut data = json!({
|
||||||
"job": job,
|
"job": job,
|
||||||
"fqdn": fqdn,
|
"fqdn": fqdn,
|
||||||
|
|
|
@ -46,7 +46,7 @@ use proxmox::tools::fs::{
|
||||||
create_path, file_read_optional_string, replace_file, CreateOptions,
|
create_path, file_read_optional_string, replace_file, CreateOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
use proxmox_systemd::time::{compute_next_event, parse_calendar_event};
|
use proxmox_time::{compute_next_event, parse_calendar_event};
|
||||||
|
|
||||||
use pbs_buildcfg::PROXMOX_BACKUP_STATE_DIR_M;
|
use pbs_buildcfg::PROXMOX_BACKUP_STATE_DIR_M;
|
||||||
use pbs_config::{open_backup_lockfile, BackupLockGuard};
|
use pbs_config::{open_backup_lockfile, BackupLockGuard};
|
||||||
|
|
|
@ -33,7 +33,7 @@ use serde_json::json;
|
||||||
use proxmox::tools::fs::{replace_file, file_get_json, CreateOptions};
|
use proxmox::tools::fs::{replace_file, file_get_json, CreateOptions};
|
||||||
use proxmox_uuid::Uuid;
|
use proxmox_uuid::Uuid;
|
||||||
|
|
||||||
use proxmox_systemd::time::compute_next_event;
|
use proxmox_time::compute_next_event;
|
||||||
use pbs_config::BackupLockGuard;
|
use pbs_config::BackupLockGuard;
|
||||||
use pbs_api_types::{MediaSetPolicy, RetentionPolicy, MediaStatus, MediaLocation};
|
use pbs_api_types::{MediaSetPolicy, RetentionPolicy, MediaStatus, MediaLocation};
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ use pbs_api_types::{
|
||||||
Fingerprint, MediaStatus, MediaLocation, MediaSetPolicy, RetentionPolicy,
|
Fingerprint, MediaStatus, MediaLocation, MediaSetPolicy, RetentionPolicy,
|
||||||
MediaPoolConfig,
|
MediaPoolConfig,
|
||||||
};
|
};
|
||||||
use proxmox_systemd::time::compute_next_event;
|
use proxmox_time::compute_next_event;
|
||||||
use pbs_config::BackupLockGuard;
|
use pbs_config::BackupLockGuard;
|
||||||
|
|
||||||
use crate::tape::{
|
use crate::tape::{
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
|
|
||||||
use proxmox_systemd::time::parse_time_span;
|
use proxmox_time::parse_time_span;
|
||||||
use pbs_api_types::{RetentionPolicy, MediaSetPolicy};
|
use pbs_api_types::{RetentionPolicy, MediaSetPolicy};
|
||||||
|
|
||||||
use crate::tape::{Inventory, MediaPool};
|
use crate::tape::{Inventory, MediaPool};
|
||||||
|
|
|
@ -100,8 +100,8 @@ fn test_media_expire_time() -> Result<(), Error> {
|
||||||
let sl2= MediaSetLabel::with_data("p1", Uuid::generate(), 0, ctime + 120, None);
|
let sl2= MediaSetLabel::with_data("p1", Uuid::generate(), 0, ctime + 120, None);
|
||||||
let tape2_uuid = inventory.generate_used_tape("tape2", sl2, 0);
|
let tape2_uuid = inventory.generate_used_tape("tape2", sl2, 0);
|
||||||
|
|
||||||
let event = proxmox_systemd::time::parse_calendar_event("*:0/2")?;
|
let event = proxmox_time::parse_calendar_event("*:0/2")?;
|
||||||
let span = proxmox_systemd::time::parse_time_span("120 seconds")?;
|
let span = proxmox_time::parse_time_span("120 seconds")?;
|
||||||
|
|
||||||
let pool = MediaPool::new(
|
let pool = MediaPool::new(
|
||||||
"p1",
|
"p1",
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
|
mod unit;
|
||||||
|
pub use unit::*;
|
||||||
|
|
|
@ -248,10 +248,10 @@ pub enum ServiceStartup {
|
||||||
|
|
||||||
pub const SYSTEMD_TIMESPAN_SCHEMA: Schema = StringSchema::new(
|
pub const SYSTEMD_TIMESPAN_SCHEMA: Schema = StringSchema::new(
|
||||||
"systemd time span")
|
"systemd time span")
|
||||||
.format(&ApiStringFormat::VerifyFn(proxmox_systemd::time::verify_time_span))
|
.format(&ApiStringFormat::VerifyFn(proxmox_time::verify_time_span))
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
pub const SYSTEMD_CALENDAR_EVENT_SCHEMA: Schema = StringSchema::new(
|
pub const SYSTEMD_CALENDAR_EVENT_SCHEMA: Schema = StringSchema::new(
|
||||||
"systemd calendar event")
|
"systemd calendar event")
|
||||||
.format(&ApiStringFormat::VerifyFn(proxmox_systemd::time::verify_calendar_event))
|
.format(&ApiStringFormat::VerifyFn(proxmox_time::verify_calendar_event))
|
||||||
.schema();
|
.schema();
|
||||||
|
|
Loading…
Reference in New Issue