Rename EtcTree -> FileTree.
This commit is contained in:
parent
a642fe94dd
commit
af853c014c
5 changed files with 85 additions and 79 deletions
|
|
@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::{fs, io, process};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::activate::etc_files::EtcTree;
|
||||
use crate::activate::etc_files::FileTree;
|
||||
use crate::{StorePath, STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
|
@ -34,7 +34,7 @@ pub type ActivationResult<R> = Result<R, ActivationError<R>>;
|
|||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct State {
|
||||
etc_tree: EtcTree,
|
||||
file_tree: FileTree,
|
||||
services: services::Services,
|
||||
}
|
||||
|
||||
|
|
@ -78,15 +78,18 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
|||
|
||||
log::info!("Activating etc files...");
|
||||
|
||||
match etc_files::activate(store_path, old_state.etc_tree, ephemeral) {
|
||||
match etc_files::activate(store_path, old_state.file_tree, ephemeral) {
|
||||
Ok(etc_tree) => {
|
||||
log::info!("Activating systemd services...");
|
||||
match services::activate(store_path, old_state.services, ephemeral) {
|
||||
Ok(services) => State { etc_tree, services },
|
||||
Ok(services) => State {
|
||||
file_tree: etc_tree,
|
||||
services,
|
||||
},
|
||||
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||
log::error!("Error during activation: {source:?}");
|
||||
State {
|
||||
etc_tree,
|
||||
file_tree: etc_tree,
|
||||
services: result,
|
||||
}
|
||||
}
|
||||
|
|
@ -95,7 +98,7 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
|||
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||
log::error!("Error during activation: {source:?}");
|
||||
State {
|
||||
etc_tree: result,
|
||||
file_tree: result,
|
||||
..old_state
|
||||
}
|
||||
}
|
||||
|
|
@ -111,15 +114,18 @@ pub fn deactivate() -> Result<()> {
|
|||
let old_state = State::from_file(state_file)?;
|
||||
log::debug!("{old_state:?}");
|
||||
|
||||
match etc_files::deactivate(old_state.etc_tree) {
|
||||
match etc_files::deactivate(old_state.file_tree) {
|
||||
Ok(etc_tree) => {
|
||||
log::info!("Deactivating systemd services...");
|
||||
match services::deactivate(old_state.services) {
|
||||
Ok(services) => State { etc_tree, services },
|
||||
Ok(services) => State {
|
||||
file_tree: etc_tree,
|
||||
services,
|
||||
},
|
||||
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||
log::error!("Error during deactivation: {source:?}");
|
||||
State {
|
||||
etc_tree,
|
||||
file_tree: etc_tree,
|
||||
services: result,
|
||||
}
|
||||
}
|
||||
|
|
@ -128,7 +134,7 @@ pub fn deactivate() -> Result<()> {
|
|||
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||
log::error!("Error during deactivation: {source:?}");
|
||||
State {
|
||||
etc_tree: result,
|
||||
file_tree: result,
|
||||
..old_state
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use std::path;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
use self::etc_tree::EtcFileStatus;
|
||||
use self::etc_tree::FileStatus;
|
||||
use super::ActivationResult;
|
||||
use crate::activate::ActivationError;
|
||||
use crate::{
|
||||
|
|
@ -17,9 +17,9 @@ use crate::{
|
|||
SYSTEM_MANAGER_STATIC_NAME,
|
||||
};
|
||||
|
||||
pub use etc_tree::EtcTree;
|
||||
pub use etc_tree::FileTree;
|
||||
|
||||
type EtcActivationResult = ActivationResult<EtcTree>;
|
||||
type EtcActivationResult = ActivationResult<FileTree>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
@ -81,7 +81,7 @@ fn read_config(store_path: &StorePath) -> anyhow::Result<EtcFilesConfig> {
|
|||
|
||||
pub fn activate(
|
||||
store_path: &StorePath,
|
||||
old_state: EtcTree,
|
||||
old_state: FileTree,
|
||||
ephemeral: bool,
|
||||
) -> EtcActivationResult {
|
||||
let config = read_config(store_path)
|
||||
|
|
@ -90,7 +90,7 @@ pub fn activate(
|
|||
let etc_dir = etc_dir(ephemeral);
|
||||
log::info!("Creating /etc entries in {}", etc_dir.display());
|
||||
|
||||
let initial_state = EtcTree::root_node();
|
||||
let initial_state = FileTree::root_node();
|
||||
|
||||
let state = create_etc_static_link(
|
||||
SYSTEM_MANAGER_STATIC_NAME,
|
||||
|
|
@ -108,15 +108,15 @@ pub fn activate(
|
|||
Ok(final_state)
|
||||
}
|
||||
|
||||
pub fn deactivate(old_state: EtcTree) -> EtcActivationResult {
|
||||
pub fn deactivate(old_state: FileTree) -> EtcActivationResult {
|
||||
let final_state = old_state.deactivate(&try_delete_path).unwrap_or_default();
|
||||
|
||||
log::info!("Done");
|
||||
Ok(final_state)
|
||||
}
|
||||
|
||||
fn try_delete_path(path: &Path, status: &EtcFileStatus) -> bool {
|
||||
fn do_try_delete(path: &Path, status: &EtcFileStatus) -> anyhow::Result<()> {
|
||||
fn try_delete_path(path: &Path, status: &FileStatus) -> bool {
|
||||
fn do_try_delete(path: &Path, status: &FileStatus) -> anyhow::Result<()> {
|
||||
// exists() returns false for broken symlinks
|
||||
if path.exists() || path.is_symlink() {
|
||||
if path.is_symlink() {
|
||||
|
|
@ -127,7 +127,7 @@ fn try_delete_path(path: &Path, status: &EtcFileStatus) -> bool {
|
|||
if path.read_dir()?.next().is_none() {
|
||||
remove_dir(path)
|
||||
} else {
|
||||
if let EtcFileStatus::Managed = status {
|
||||
if let FileStatus::Managed = status {
|
||||
log::warn!("Managed directory not empty, ignoring: {}", path.display());
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -153,9 +153,9 @@ fn try_delete_path(path: &Path, status: &EtcFileStatus) -> bool {
|
|||
fn create_etc_links<'a, E>(
|
||||
entries: E,
|
||||
etc_dir: &Path,
|
||||
state: EtcTree,
|
||||
old_state: &EtcTree,
|
||||
) -> EtcTree
|
||||
state: FileTree,
|
||||
old_state: &FileTree,
|
||||
) -> FileTree
|
||||
where
|
||||
E: Iterator<Item = &'a EtcFile>,
|
||||
{
|
||||
|
|
@ -178,7 +178,7 @@ fn create_etc_static_link(
|
|||
static_dir_name: &str,
|
||||
store_path: &StorePath,
|
||||
etc_dir: &Path,
|
||||
state: EtcTree,
|
||||
state: FileTree,
|
||||
) -> EtcActivationResult {
|
||||
let static_path = etc_dir.join(static_dir_name);
|
||||
let new_state = create_dir_recursively(static_path.parent().unwrap(), state);
|
||||
|
|
@ -193,8 +193,8 @@ fn create_etc_static_link(
|
|||
fn create_etc_link<P>(
|
||||
link_target: &P,
|
||||
etc_dir: &Path,
|
||||
state: EtcTree,
|
||||
old_state: &EtcTree,
|
||||
state: FileTree,
|
||||
old_state: &FileTree,
|
||||
) -> EtcActivationResult
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
|
|
@ -203,13 +203,13 @@ where
|
|||
link_target: &Path,
|
||||
absolute_target: &Path,
|
||||
etc_dir: &Path,
|
||||
state: EtcTree,
|
||||
old_state: &EtcTree,
|
||||
state: FileTree,
|
||||
old_state: &FileTree,
|
||||
upwards_path: &Path,
|
||||
) -> EtcActivationResult {
|
||||
let link_path = etc_dir.join(link_target);
|
||||
if link_path.is_dir() && absolute_target.is_dir() {
|
||||
log::info!("Entering into directory...");
|
||||
log::debug!("Entering into directory {}...", link_path.display());
|
||||
Ok(absolute_target
|
||||
.read_dir()
|
||||
.expect("Error reading the directory.")
|
||||
|
|
@ -250,8 +250,8 @@ where
|
|||
fn go(
|
||||
link_target: &Path,
|
||||
etc_dir: &Path,
|
||||
state: EtcTree,
|
||||
old_state: &EtcTree,
|
||||
state: FileTree,
|
||||
old_state: &FileTree,
|
||||
upwards_path: &Path,
|
||||
) -> EtcActivationResult {
|
||||
let link_path = etc_dir.join(link_target);
|
||||
|
|
@ -272,6 +272,7 @@ where
|
|||
} else if link_path.is_symlink()
|
||||
&& link_path.read_link().expect("Error reading link.") == target
|
||||
{
|
||||
log::debug!("Link {} up to date.", link_path.display());
|
||||
Ok(dir_state.register_managed_entry(&link_path))
|
||||
} else {
|
||||
let result = if link_path.exists() {
|
||||
|
|
@ -304,8 +305,8 @@ where
|
|||
fn create_etc_entry(
|
||||
entry: &EtcFile,
|
||||
etc_dir: &Path,
|
||||
state: EtcTree,
|
||||
old_state: &EtcTree,
|
||||
state: FileTree,
|
||||
old_state: &FileTree,
|
||||
) -> EtcActivationResult {
|
||||
if entry.mode == "symlink" {
|
||||
if let Some(path::Component::Normal(link_target)) = entry.target.components().next() {
|
||||
|
|
@ -331,7 +332,7 @@ fn create_etc_entry(
|
|||
}
|
||||
}
|
||||
|
||||
fn create_dir_recursively(dir: &Path, state: EtcTree) -> EtcActivationResult {
|
||||
fn create_dir_recursively(dir: &Path, state: FileTree) -> EtcActivationResult {
|
||||
use itertools::FoldWhile::{Continue, Done};
|
||||
use path::Component;
|
||||
|
||||
|
|
@ -382,7 +383,7 @@ fn create_dir_recursively(dir: &Path, state: EtcTree) -> EtcActivationResult {
|
|||
new_state
|
||||
}
|
||||
|
||||
fn copy_file(source: &Path, target: &Path, mode: &str, old_state: &EtcTree) -> anyhow::Result<()> {
|
||||
fn copy_file(source: &Path, target: &Path, mode: &str, old_state: &FileTree) -> anyhow::Result<()> {
|
||||
let exists = target.try_exists()?;
|
||||
if !exists || old_state.is_managed(target) {
|
||||
log::debug!(
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EtcFileStatus {
|
||||
pub enum FileStatus {
|
||||
Managed,
|
||||
Unmanaged,
|
||||
}
|
||||
|
||||
impl EtcFileStatus {
|
||||
impl FileStatus {
|
||||
fn merge(&self, other: &Self) -> Self {
|
||||
use EtcFileStatus::*;
|
||||
use FileStatus::*;
|
||||
|
||||
match (self, other) {
|
||||
(Unmanaged, Unmanaged) => Unmanaged,
|
||||
|
|
@ -25,22 +25,22 @@ impl EtcFileStatus {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EtcTree {
|
||||
status: EtcFileStatus,
|
||||
pub struct FileTree {
|
||||
status: FileStatus,
|
||||
path: PathBuf,
|
||||
// TODO directories and files are now both represented as a string associated with a nested
|
||||
// map. For files the nested map is simple empty.
|
||||
// We could potentially optimise this.
|
||||
nested: HashMap<String, EtcTree>,
|
||||
nested: HashMap<String, FileTree>,
|
||||
}
|
||||
|
||||
impl AsRef<EtcTree> for EtcTree {
|
||||
fn as_ref(&self) -> &EtcTree {
|
||||
impl AsRef<FileTree> for FileTree {
|
||||
fn as_ref(&self) -> &FileTree {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EtcTree {
|
||||
impl Default for FileTree {
|
||||
fn default() -> Self {
|
||||
Self::root_node()
|
||||
}
|
||||
|
|
@ -56,12 +56,12 @@ impl Default for EtcTree {
|
|||
/// 3. Import from a file
|
||||
/// 4. Add a path to the tree, that will from then on be considered as managed
|
||||
/// 5.
|
||||
impl EtcTree {
|
||||
impl FileTree {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
Self::with_status(path, EtcFileStatus::Unmanaged)
|
||||
Self::with_status(path, FileStatus::Unmanaged)
|
||||
}
|
||||
|
||||
fn with_status(path: PathBuf, status: EtcFileStatus) -> Self {
|
||||
fn with_status(path: PathBuf, status: FileStatus) -> Self {
|
||||
Self {
|
||||
status,
|
||||
path,
|
||||
|
|
@ -73,8 +73,8 @@ impl EtcTree {
|
|||
Self::new(PathBuf::from(path::MAIN_SEPARATOR_STR))
|
||||
}
|
||||
|
||||
pub fn get_status<'a>(&'a self, path: &Path) -> &'a EtcFileStatus {
|
||||
fn go<'a, 'b, C>(tree: &'a EtcTree, mut components: C, path: &Path) -> &'a EtcFileStatus
|
||||
pub fn get_status<'a>(&'a self, path: &Path) -> &'a FileStatus {
|
||||
fn go<'a, 'b, C>(tree: &'a FileTree, mut components: C, path: &Path) -> &'a FileStatus
|
||||
where
|
||||
C: Iterator<Item = path::Component<'b>>,
|
||||
{
|
||||
|
|
@ -84,7 +84,7 @@ impl EtcTree {
|
|||
.nested
|
||||
.get(name.to_string_lossy().as_ref())
|
||||
.map(|subtree| go(subtree, components, path))
|
||||
.unwrap_or(&EtcFileStatus::Unmanaged),
|
||||
.unwrap_or(&FileStatus::Unmanaged),
|
||||
path::Component::RootDir => go(tree, components, path),
|
||||
_ => todo!(),
|
||||
}
|
||||
|
|
@ -97,13 +97,13 @@ impl EtcTree {
|
|||
}
|
||||
|
||||
pub fn is_managed(&self, path: &Path) -> bool {
|
||||
*self.get_status(path) == EtcFileStatus::Managed
|
||||
*self.get_status(path) == FileStatus::Managed
|
||||
}
|
||||
|
||||
// TODO is recursion OK here?
|
||||
// Should we convert to CPS and use a crate like tramp to TCO this?
|
||||
pub fn register_managed_entry(self, path: &Path) -> Self {
|
||||
fn go<'a, C>(mut tree: EtcTree, mut components: Peekable<C>, path: PathBuf) -> EtcTree
|
||||
fn go<'a, C>(mut tree: FileTree, mut components: Peekable<C>, path: PathBuf) -> FileTree
|
||||
where
|
||||
C: Iterator<Item = path::Component<'a>>,
|
||||
{
|
||||
|
|
@ -115,15 +115,13 @@ impl EtcTree {
|
|||
|maybe_subtree| {
|
||||
Some(go(
|
||||
maybe_subtree.unwrap_or_else(|| {
|
||||
EtcTree::with_status(
|
||||
FileTree::with_status(
|
||||
new_path.to_owned(),
|
||||
// We only label as managed the final path entry,
|
||||
// to label intermediate nodes as managed, we should
|
||||
// call this function for every one of them separately.
|
||||
components
|
||||
.peek()
|
||||
.map_or(EtcFileStatus::Managed, |_| {
|
||||
EtcFileStatus::Unmanaged
|
||||
components.peek().map_or(FileStatus::Managed, |_| {
|
||||
FileStatus::Unmanaged
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
@ -151,9 +149,9 @@ impl EtcTree {
|
|||
go(self, path.components().peekable(), PathBuf::new())
|
||||
}
|
||||
|
||||
pub fn deactivate<F>(self, delete_action: &F) -> Option<EtcTree>
|
||||
pub fn deactivate<F>(self, delete_action: &F) -> Option<FileTree>
|
||||
where
|
||||
F: Fn(&Path, &EtcFileStatus) -> bool,
|
||||
F: Fn(&Path, &FileStatus) -> bool,
|
||||
{
|
||||
let new_tree = self.nested.keys().fold(self.clone(), |mut new_tree, name| {
|
||||
new_tree.nested = new_tree.nested.alter(
|
||||
|
|
@ -168,7 +166,7 @@ impl EtcTree {
|
|||
// are not responsible for cleaning them up (we don't run the delete_action
|
||||
// closure on their paths).
|
||||
if new_tree.nested.is_empty() {
|
||||
if let EtcFileStatus::Managed = new_tree.status {
|
||||
if let FileStatus::Managed = new_tree.status {
|
||||
if delete_action(&new_tree.path, &new_tree.status) {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -184,7 +182,7 @@ impl EtcTree {
|
|||
|
||||
pub fn update_state<F>(self, other: Self, delete_action: &F) -> Option<Self>
|
||||
where
|
||||
F: Fn(&Path, &EtcFileStatus) -> bool,
|
||||
F: Fn(&Path, &FileStatus) -> bool,
|
||||
{
|
||||
let to_deactivate = other
|
||||
.nested
|
||||
|
|
@ -225,7 +223,7 @@ impl EtcTree {
|
|||
|
||||
// If our invariants are properly maintained, then we should never end up
|
||||
// here with dangling unmanaged nodes.
|
||||
debug_assert!(!merged.nested.is_empty() || merged.status == EtcFileStatus::Managed);
|
||||
debug_assert!(!merged.nested.is_empty() || merged.status == FileStatus::Managed);
|
||||
|
||||
Some(merged)
|
||||
}
|
||||
|
|
@ -236,20 +234,20 @@ mod tests {
|
|||
use super::*;
|
||||
use itertools::Itertools;
|
||||
|
||||
impl EtcTree {
|
||||
impl FileTree {
|
||||
pub fn deactivate_managed_entry<F>(self, path: &Path, delete_action: &F) -> Self
|
||||
where
|
||||
F: Fn(&Path, &EtcFileStatus) -> bool,
|
||||
F: Fn(&Path, &FileStatus) -> bool,
|
||||
{
|
||||
fn go<'a, C, F>(
|
||||
mut tree: EtcTree,
|
||||
mut tree: FileTree,
|
||||
path: PathBuf,
|
||||
mut components: Peekable<C>,
|
||||
delete_action: &F,
|
||||
) -> EtcTree
|
||||
) -> FileTree
|
||||
where
|
||||
C: Iterator<Item = path::Component<'a>>,
|
||||
F: Fn(&Path, &EtcFileStatus) -> bool,
|
||||
F: Fn(&Path, &FileStatus) -> bool,
|
||||
{
|
||||
log::debug!("Deactivating {}", path.display());
|
||||
|
||||
|
|
@ -296,8 +294,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn etc_tree_get_status() {
|
||||
let tree1 = EtcTree::root_node()
|
||||
fn get_status() {
|
||||
let tree1 = FileTree::root_node()
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz"))
|
||||
|
|
@ -319,8 +317,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn etc_tree_register() {
|
||||
let tree = EtcTree::root_node()
|
||||
fn register() {
|
||||
let tree = FileTree::root_node()
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz2").join("bar"));
|
||||
|
|
@ -345,8 +343,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn etc_tree_deactivate() {
|
||||
let tree1 = EtcTree::root_node()
|
||||
fn deactivate() {
|
||||
let tree1 = FileTree::root_node()
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz"))
|
||||
|
|
@ -406,8 +404,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn etc_tree_update_state() {
|
||||
let tree1 = EtcTree::root_node()
|
||||
fn update_state() {
|
||||
let tree1 = FileTree::root_node()
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz"))
|
||||
|
|
@ -415,7 +413,7 @@ mod tests {
|
|||
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz2"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz2").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo3").join("baz2").join("bar"));
|
||||
let tree2 = EtcTree::root_node()
|
||||
let tree2 = FileTree::root_node()
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo3").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo4"))
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ where
|
|||
.iter()
|
||||
.fold(HashSet::new(), |mut set, unit| match action(unit) {
|
||||
Ok(_) => {
|
||||
log::info!("Unit {}: {}...", unit, log_action);
|
||||
log::debug!("Unit {}: {}...", unit, log_action);
|
||||
set.insert(JobId {
|
||||
id: (*unit).to_owned(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -201,7 +201,8 @@ impl ServiceManager {
|
|||
let total_jobs = waiting_for.len();
|
||||
|
||||
if total_jobs > 0 {
|
||||
log::info!("Waiting for jobs to finish... (0/{})", total_jobs);
|
||||
log::info!("Waiting for jobs to finish...");
|
||||
log::debug!("Waiting for jobs to finish... (0/{})", total_jobs);
|
||||
}
|
||||
|
||||
while !waiting_for.is_empty() {
|
||||
|
|
@ -218,7 +219,7 @@ impl ServiceManager {
|
|||
waiting_for = waiting_for.relative_complement(job_names.clone());
|
||||
*job_names = im::HashSet::new();
|
||||
if !waiting_for.is_empty() {
|
||||
log::info!(
|
||||
log::debug!(
|
||||
"Waiting for jobs to finish... ({}/{})",
|
||||
total_jobs - waiting_for.len(),
|
||||
total_jobs
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue