mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
stability fixes
This commit is contained in:
committed by
Aiden McClelland
parent
7696ec9a13
commit
071f6cec03
2
Makefile
2
Makefile
@@ -11,6 +11,8 @@ PATCH_DB_CLIENT_SRC = $(shell find patch-db/client -not -path patch-db/client/di
|
|||||||
GIT_REFS := $(shell find .git/refs/heads)
|
GIT_REFS := $(shell find .git/refs/heads)
|
||||||
TMP_FILE := $(shell mktemp)
|
TMP_FILE := $(shell mktemp)
|
||||||
|
|
||||||
|
.DELETE_ON_ERROR:
|
||||||
|
|
||||||
all: eos.img
|
all: eos.img
|
||||||
|
|
||||||
gzip: eos.img
|
gzip: eos.img
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ use std::collections::BTreeMap;
|
|||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bollard::container::StopContainerOptions;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
@@ -46,17 +48,39 @@ impl DockerAction {
|
|||||||
volumes: &Volumes,
|
volumes: &Volumes,
|
||||||
input: Option<I>,
|
input: Option<I>,
|
||||||
allow_inject: bool,
|
allow_inject: bool,
|
||||||
|
timeout: Option<Duration>,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
let mut cmd = tokio::process::Command::new("docker");
|
let mut cmd = tokio::process::Command::new("docker");
|
||||||
|
let mut timeout_fut =
|
||||||
|
futures::future::Either::Left(futures::future::pending::<Result<_, Error>>());
|
||||||
if self.inject && allow_inject {
|
if self.inject && allow_inject {
|
||||||
cmd.arg("exec");
|
cmd.arg("exec");
|
||||||
} else {
|
} else {
|
||||||
|
let container_name = Self::container_name(pkg_id, name);
|
||||||
cmd.arg("run")
|
cmd.arg("run")
|
||||||
.arg("--rm")
|
.arg("--rm")
|
||||||
.arg("--network=start9")
|
.arg("--network=start9")
|
||||||
.arg(format!("--add-host=embassy:{}", Ipv4Addr::from(HOST_IP)))
|
.arg(format!("--add-host=embassy:{}", Ipv4Addr::from(HOST_IP)))
|
||||||
.arg("--name")
|
.arg("--name")
|
||||||
.arg(Self::container_name(pkg_id, name));
|
.arg(&container_name);
|
||||||
|
if let Some(timeout) = timeout {
|
||||||
|
timeout_fut = futures::future::Either::Right(async move {
|
||||||
|
tokio::time::sleep(timeout).await;
|
||||||
|
|
||||||
|
match ctx
|
||||||
|
.docker
|
||||||
|
.stop_container(&container_name, Some(StopContainerOptions { t: 30 }))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(bollard::errors::Error::DockerResponseNotFoundError { .. })
|
||||||
|
| Err(bollard::errors::Error::DockerResponseConflictError { .. })
|
||||||
|
| Err(bollard::errors::Error::DockerResponseNotModifiedError { .. }) => (), // Already stopped
|
||||||
|
a => a?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(futures::future::pending().await)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cmd.args(
|
cmd.args(
|
||||||
self.docker_args(ctx, pkg_id, pkg_version, volumes, allow_inject)
|
self.docker_args(ctx, pkg_id, pkg_version, volumes, allow_inject)
|
||||||
@@ -85,10 +109,13 @@ impl DockerAction {
|
|||||||
.await
|
.await
|
||||||
.with_kind(crate::ErrorKind::Docker)?;
|
.with_kind(crate::ErrorKind::Docker)?;
|
||||||
}
|
}
|
||||||
let res = handle
|
let res = tokio::select! {
|
||||||
|
res = handle
|
||||||
.wait_with_output()
|
.wait_with_output()
|
||||||
.await
|
=> res
|
||||||
.with_kind(crate::ErrorKind::Docker)?;
|
.with_kind(crate::ErrorKind::Docker)?,
|
||||||
|
res = timeout_fut => res?,
|
||||||
|
};
|
||||||
Ok(if res.status.success() || res.status.code() == Some(143) {
|
Ok(if res.status.success() || res.status.code() == Some(143) {
|
||||||
Ok(if let Some(format) = &self.io_format {
|
Ok(if let Some(format) = &self.io_format {
|
||||||
match format.from_slice(&res.stdout) {
|
match format.from_slice(&res.stdout) {
|
||||||
@@ -125,6 +152,7 @@ impl DockerAction {
|
|||||||
pkg_version: &Version,
|
pkg_version: &Version,
|
||||||
volumes: &Volumes,
|
volumes: &Volumes,
|
||||||
input: Option<I>,
|
input: Option<I>,
|
||||||
|
timeout: Option<Duration>,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
let mut cmd = tokio::process::Command::new("docker");
|
let mut cmd = tokio::process::Command::new("docker");
|
||||||
cmd.arg("run").arg("--rm").arg("--network=none");
|
cmd.arg("run").arg("--rm").arg("--network=none");
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
@@ -131,6 +132,7 @@ impl Action {
|
|||||||
volumes,
|
volumes,
|
||||||
input,
|
input,
|
||||||
true,
|
true,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::Action))
|
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::Action))
|
||||||
@@ -154,11 +156,21 @@ impl ActionImplementation {
|
|||||||
volumes: &Volumes,
|
volumes: &Volumes,
|
||||||
input: Option<I>,
|
input: Option<I>,
|
||||||
allow_inject: bool,
|
allow_inject: bool,
|
||||||
|
timeout: Option<Duration>,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
match self {
|
match self {
|
||||||
ActionImplementation::Docker(action) => {
|
ActionImplementation::Docker(action) => {
|
||||||
action
|
action
|
||||||
.execute(ctx, pkg_id, pkg_version, name, volumes, input, allow_inject)
|
.execute(
|
||||||
|
ctx,
|
||||||
|
pkg_id,
|
||||||
|
pkg_version,
|
||||||
|
name,
|
||||||
|
volumes,
|
||||||
|
input,
|
||||||
|
allow_inject,
|
||||||
|
timeout,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,11 +183,12 @@ impl ActionImplementation {
|
|||||||
pkg_version: &Version,
|
pkg_version: &Version,
|
||||||
volumes: &Volumes,
|
volumes: &Volumes,
|
||||||
input: Option<I>,
|
input: Option<I>,
|
||||||
|
timeout: Option<Duration>,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
match self {
|
match self {
|
||||||
ActionImplementation::Docker(action) => {
|
ActionImplementation::Docker(action) => {
|
||||||
action
|
action
|
||||||
.sandboxed(ctx, pkg_id, pkg_version, volumes, input)
|
.sandboxed(ctx, pkg_id, pkg_version, volumes, input, timeout)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ impl BackupActions {
|
|||||||
&volumes,
|
&volumes,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| eyre!("{}", e.1))
|
.map_err(|e| eyre!("{}", e.1))
|
||||||
@@ -167,6 +168,7 @@ impl BackupActions {
|
|||||||
&volumes,
|
&volumes,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| eyre!("{}", e.1))
|
.map_err(|e| eyre!("{}", e.1))
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ impl ConfigActions {
|
|||||||
volumes,
|
volumes,
|
||||||
None::<()>,
|
None::<()>,
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.and_then(|res| {
|
.and_then(|res| {
|
||||||
@@ -73,6 +74,7 @@ impl ConfigActions {
|
|||||||
volumes,
|
volumes,
|
||||||
Some(input),
|
Some(input),
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.and_then(|res| {
|
.and_then(|res| {
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ use crate::dependencies::{
|
|||||||
};
|
};
|
||||||
use crate::install::cleanup::remove_current_dependents;
|
use crate::install::cleanup::remove_current_dependents;
|
||||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||||
use crate::util::{
|
use crate::util::{display_none, display_serializable, parse_stdin_deserializable, IoFormat};
|
||||||
display_none, display_serializable, parse_duration, parse_stdin_deserializable, IoFormat,
|
|
||||||
};
|
|
||||||
use crate::{Error, ResultExt as _};
|
use crate::{Error, ResultExt as _};
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
@@ -190,11 +188,11 @@ pub fn set(
|
|||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[arg(long = "format")]
|
#[arg(long = "format")]
|
||||||
format: Option<IoFormat>,
|
format: Option<IoFormat>,
|
||||||
#[arg(long = "timeout", parse(parse_duration))] timeout: Option<Duration>,
|
#[arg(long = "timeout")] timeout: Option<crate::util::Duration>,
|
||||||
#[arg(stdin, parse(parse_stdin_deserializable))] config: Option<Config>,
|
#[arg(stdin, parse(parse_stdin_deserializable))] config: Option<Config>,
|
||||||
#[arg(rename = "expire-id", long = "expire-id")] expire_id: Option<String>,
|
#[arg(rename = "expire-id", long = "expire-id")] expire_id: Option<String>,
|
||||||
) -> Result<(PackageId, Option<Config>, Option<Duration>, Option<String>), Error> {
|
) -> Result<(PackageId, Option<Config>, Option<Duration>, Option<String>), Error> {
|
||||||
Ok((id, config, timeout, expire_id))
|
Ok((id, config, timeout.map(|d| *d), expire_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command(rename = "dry", display(display_serializable))]
|
#[command(rename = "dry", display(display_serializable))]
|
||||||
|
|||||||
@@ -421,6 +421,7 @@ impl DependencyConfig {
|
|||||||
dependent_version,
|
dependent_version,
|
||||||
dependent_volumes,
|
dependent_volumes,
|
||||||
Some(dependency_config),
|
Some(dependency_config),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|(_, e)| e))
|
.map_err(|(_, e)| e))
|
||||||
@@ -440,6 +441,7 @@ impl DependencyConfig {
|
|||||||
dependent_version,
|
dependent_version,
|
||||||
dependent_volumes,
|
dependent_volumes,
|
||||||
Some(old),
|
Some(old),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))
|
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))
|
||||||
@@ -472,7 +474,7 @@ pub async fn configure_impl(
|
|||||||
&mut db,
|
&mut db,
|
||||||
&dep_id,
|
&dep_id,
|
||||||
Some(new_config),
|
Some(new_config),
|
||||||
&Some(Duration::from_secs(3)),
|
&Some(Duration::from_secs(3).into()),
|
||||||
false,
|
false,
|
||||||
&mut BTreeMap::new(),
|
&mut BTreeMap::new(),
|
||||||
&mut BTreeMap::new(),
|
&mut BTreeMap::new(),
|
||||||
@@ -598,7 +600,14 @@ pub async fn configure_logic(
|
|||||||
|
|
||||||
let new_config = dependency
|
let new_config = dependency
|
||||||
.auto_configure
|
.auto_configure
|
||||||
.sandboxed(&ctx, &pkg_id, &pkg_version, &pkg_volumes, Some(&old_config))
|
.sandboxed(
|
||||||
|
&ctx,
|
||||||
|
&pkg_id,
|
||||||
|
&pkg_version,
|
||||||
|
&pkg_volumes,
|
||||||
|
Some(&old_config),
|
||||||
|
None,
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))?;
|
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))?;
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ pub async fn get_vendor<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error
|
|||||||
.join("device")
|
.join("device")
|
||||||
.join("vendor"),
|
.join("vendor"),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?
|
||||||
|
.trim()
|
||||||
|
.to_owned();
|
||||||
Ok(if vendor.is_empty() {
|
Ok(if vendor.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -104,7 +106,9 @@ pub async fn get_model<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error>
|
|||||||
.join("device")
|
.join("device")
|
||||||
.join("model"),
|
.join("model"),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?
|
||||||
|
.trim()
|
||||||
|
.to_owned();
|
||||||
Ok(if model.is_empty() { None } else { Some(model) })
|
Ok(if model.is_empty() { None } else { Some(model) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ async fn run_main(
|
|||||||
&rt_state.manifest.volumes,
|
&rt_state.manifest.volumes,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ impl Migrations {
|
|||||||
volumes,
|
volumes,
|
||||||
Some(version),
|
Some(version),
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -78,6 +79,7 @@ impl Migrations {
|
|||||||
volumes,
|
volumes,
|
||||||
Some(version),
|
Some(version),
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
|||||||
@@ -92,6 +92,13 @@ impl NginxControllerInner {
|
|||||||
if lan_port_config.ssl {
|
if lan_port_config.ssl {
|
||||||
// these have already been written by the net controller
|
// these have already been written by the net controller
|
||||||
let package_path = nginx_root.join(format!("ssl/{}", package));
|
let package_path = nginx_root.join(format!("ssl/{}", package));
|
||||||
|
if tokio::fs::metadata(&package_path).await.is_err() {
|
||||||
|
tokio::fs::create_dir_all(&package_path)
|
||||||
|
.await
|
||||||
|
.with_ctx(|_| {
|
||||||
|
(ErrorKind::Filesystem, package_path.display().to_string())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
let ssl_path_key = package_path.join(format!("{}.key.pem", id));
|
let ssl_path_key = package_path.join(format!("{}.key.pem", id));
|
||||||
let ssl_path_cert = package_path.join(format!("{}.cert.pem", id));
|
let ssl_path_cert = package_path.join(format!("{}.cert.pem", id));
|
||||||
let (key, chain) = ssl_manager.certificate_for(&meta.dns_base).await?;
|
let (key, chain) = ssl_manager.certificate_for(&meta.dns_base).await?;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ pub async fn fetch_properties(ctx: RpcContext, id: PackageId) -> Result<Value, E
|
|||||||
&manifest.volumes,
|
&manifest.volumes,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map_err(|(_, e)| Error::new(eyre!("{}", e), ErrorKind::Docker))
|
.map_err(|(_, e)| Error::new(eyre!("{}", e), ErrorKind::Docker))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::action::{ActionImplementation, NoOutput};
|
|||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::id::Id;
|
use crate::id::Id;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::Version;
|
use crate::util::{Duration, Version};
|
||||||
use crate::volume::Volumes;
|
use crate::volume::Volumes;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ pub struct HealthCheck {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
implementation: ActionImplementation,
|
implementation: ActionImplementation,
|
||||||
pub critical: bool,
|
pub critical: bool,
|
||||||
|
pub timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
impl HealthCheck {
|
impl HealthCheck {
|
||||||
#[instrument(skip(ctx))]
|
#[instrument(skip(ctx))]
|
||||||
@@ -94,6 +95,10 @@ impl HealthCheck {
|
|||||||
volumes,
|
volumes,
|
||||||
Some(Utc::now().signed_duration_since(started).num_milliseconds()),
|
Some(Utc::now().signed_duration_since(started).num_milliseconds()),
|
||||||
true,
|
true,
|
||||||
|
Some(
|
||||||
|
self.timeout
|
||||||
|
.map_or(std::time::Duration::from_secs(30), |d| *d),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(match res {
|
Ok(match res {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::process::{exit, Stdio};
|
use std::process::{exit, Stdio};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
@@ -651,30 +650,98 @@ pub fn parse_stdin_deserializable<T: for<'de> Deserialize<'de>>(
|
|||||||
format.from_reader(stdin)
|
format.from_reader(stdin)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_duration(arg: &str, _: &ArgMatches<'_>) -> Result<Duration, Error> {
|
#[derive(Debug, Clone, Copy)]
|
||||||
let units_idx = arg.find(|c: char| c.is_alphabetic()).ok_or_else(|| {
|
pub struct Duration(std::time::Duration);
|
||||||
Error::new(
|
impl Deref for Duration {
|
||||||
eyre!("Must specify units for duration"),
|
type Target = std::time::Duration;
|
||||||
crate::ErrorKind::Deserialization,
|
fn deref(&self) -> &Self::Target {
|
||||||
)
|
&self.0
|
||||||
})?;
|
}
|
||||||
let (num, units) = arg.split_at(units_idx);
|
}
|
||||||
match units {
|
impl From<std::time::Duration> for Duration {
|
||||||
"d" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse::<f64>()? * 86400_f64)),
|
fn from(t: std::time::Duration) -> Self {
|
||||||
"d" => Ok(Duration::from_secs(num.parse::<u64>()? * 86400)),
|
Duration(t)
|
||||||
"h" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse::<f64>()? * 3600_f64)),
|
}
|
||||||
"h" => Ok(Duration::from_secs(num.parse::<u64>()? * 3600)),
|
}
|
||||||
"m" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse::<f64>()? * 60_f64)),
|
impl std::str::FromStr for Duration {
|
||||||
"m" => Ok(Duration::from_secs(num.parse::<u64>()? * 60)),
|
type Err = Error;
|
||||||
"s" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse()?)),
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
"s" => Ok(Duration::from_secs(num.parse()?)),
|
let units_idx = s.find(|c: char| c.is_alphabetic()).ok_or_else(|| {
|
||||||
"ms" => Ok(Duration::from_millis(num.parse()?)),
|
Error::new(
|
||||||
"us" => Ok(Duration::from_micros(num.parse()?)),
|
eyre!("Must specify units for duration"),
|
||||||
"ns" => Ok(Duration::from_nanos(num.parse()?)),
|
crate::ErrorKind::Deserialization,
|
||||||
_ => Err(Error::new(
|
)
|
||||||
eyre!("Invalid units for duration"),
|
})?;
|
||||||
crate::ErrorKind::Deserialization,
|
let (num, units) = s.split_at(units_idx);
|
||||||
)),
|
use std::time::Duration;
|
||||||
|
Ok(Duration(match units {
|
||||||
|
"d" if num.contains(".") => Duration::from_secs_f64(num.parse::<f64>()? * 86_400_f64),
|
||||||
|
"d" => Duration::from_secs(num.parse::<u64>()? * 86_400),
|
||||||
|
"h" if num.contains(".") => Duration::from_secs_f64(num.parse::<f64>()? * 3_600_f64),
|
||||||
|
"h" => Duration::from_secs(num.parse::<u64>()? * 3_600),
|
||||||
|
"m" if num.contains(".") => Duration::from_secs_f64(num.parse::<f64>()? * 60_f64),
|
||||||
|
"m" => Duration::from_secs(num.parse::<u64>()? * 60),
|
||||||
|
"s" if num.contains(".") => Duration::from_secs_f64(num.parse()?),
|
||||||
|
"s" => Duration::from_secs(num.parse()?),
|
||||||
|
"ms" if num.contains(".") => Duration::from_secs_f64(num.parse::<f64>()? / 1_000_f64),
|
||||||
|
"ms" => {
|
||||||
|
let millis: u128 = num.parse()?;
|
||||||
|
Duration::new((millis / 1_000) as u64, (millis % 1_000) as u32)
|
||||||
|
}
|
||||||
|
"us" | "µs" if num.contains(".") => {
|
||||||
|
Duration::from_secs_f64(num.parse::<f64>()? / 1_000_000_f64)
|
||||||
|
}
|
||||||
|
"us" | "µs" => {
|
||||||
|
let micros: u128 = num.parse()?;
|
||||||
|
Duration::new((micros / 1_000_000) as u64, (micros % 1_000_000) as u32)
|
||||||
|
}
|
||||||
|
"ns" if num.contains(".") => {
|
||||||
|
Duration::from_secs_f64(num.parse::<f64>()? / 1_000_000_000_f64)
|
||||||
|
}
|
||||||
|
"ns" => {
|
||||||
|
let nanos: u128 = num.parse()?;
|
||||||
|
Duration::new(
|
||||||
|
(nanos / 1_000_000_000) as u64,
|
||||||
|
(nanos % 1_000_000_000) as u32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Invalid units for duration"),
|
||||||
|
crate::ErrorKind::Deserialization,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Duration {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let nanos = self.as_nanos();
|
||||||
|
match () {
|
||||||
|
_ if nanos % 86_400_000_000_000 == 0 => write!(f, "{}d", nanos / 86_400_000_000_000),
|
||||||
|
_ if nanos % 3_600_000_000_000 == 0 => write!(f, "{}h", nanos / 3_600_000_000_000),
|
||||||
|
_ if nanos % 60_000_000_000 == 0 => write!(f, "{}m", nanos / 60_000_000_000),
|
||||||
|
_ if nanos % 1_000_000_000 == 0 => write!(f, "{}s", nanos / 1_000_000_000),
|
||||||
|
_ if nanos % 1_000_000 == 0 => write!(f, "{}ms", nanos / 1_000_000),
|
||||||
|
_ if nanos % 1_000 == 0 => write!(f, "{}µs", nanos / 1_000),
|
||||||
|
_ => write!(f, "{}ns", nanos),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'de> Deserialize<'de> for Duration {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserialize_from_str(deserializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Serialize for Duration {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serialize_display(self, serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user