diff --git a/draw_tree.py b/draw_tree.py new file mode 100644 index 0000000..aaeea32 --- /dev/null +++ b/draw_tree.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +from dataclasses import dataclass + + +def main(): + activity_tree = ActivityTree() + activity_tree.activities[1] = Activity(id=1, parent=0, text="foo") + activity_tree.activities[2] = Activity(id=2, parent=0, text="bar") + activity_tree.activities[3] = Activity(id=3, parent=0, text="baz") + activity_tree.activities[4] = Activity(id=4, parent=2, text="lorem") + # activity_tree.activities[5] = Activity(id=5, parent=2, text="ipsum") + # activity_tree.activities[6] = Activity(id=6, parent=2, text="dolar") + activity_tree.activities[7] = Activity(id=7, parent=4, text="north") + activity_tree.activities[8] = Activity(id=8, parent=4, text="east") + activity_tree.activities[9] = Activity(id=9, parent=4, text="south") + activity_tree.activities[10] = Activity(id=10, parent=4, text="west") + + # For each line we need to know: + # - the depth + # - whether there are later siblings + # - whether there are earlier siblings + # + # Draw order: + # - Find all leaves + # - Start with last child of root + # - If it has children, keep going down until you hit leaves + + for entry in activity_tree.get_draw_order(): + text = activity_tree.activities[entry.id].text + leading_bars = "".join("𜹈 " if depth else " " for depth in entry.depth) + branch = "𜸨" if entry.has_later_siblings else "𜸛" + print(f"{leading_bars}{branch}𜸟 {text}") + + +class ActivityTree: + activities: dict[int, Activity] + + def __init__(self) -> None: + self.activities = {} + + def get_draw_order(self) -> list[StackEntry]: + draw_order = [] + stack = [ + StackEntry( + id=child_id, + depth=[], + has_later_siblings=True, + have_pulled_children=False, + ) + for child_id in self.get_children_in_order(0) + ] + stack[-1].has_later_siblings = False + while len(stack) > 0: + current_entry = stack.pop() + if current_entry.have_pulled_children: + if current_entry.id != 0: + draw_order.append(current_entry) + continue + + current_entry.have_pulled_children = True + stack.append(current_entry) + all_children = self.get_children_in_order(current_entry.id) + for child_id in all_children: + stack.append( + StackEntry( + id=child_id, + depth=current_entry.depth + [current_entry.has_later_siblings], + has_later_siblings=True, + have_pulled_children=False, + ) + ) + if len(all_children) > 0: + stack[-1].has_later_siblings = False + return draw_order + + def get_children_in_order(self, parent_id: int) -> list[int]: + return sorted( + child_id + for child_id, child in self.activities.items() + if child.parent == parent_id + ) + + def has_children(self, parent_id: int) -> bool: + return any( + child.parent == parent_id for _child_id, child in self.activities.items() + ) + + +@dataclass +class StackEntry: + id: int + depth: list[bool] + has_later_siblings: bool + have_pulled_children: bool # Only used when building the draw order + + +@dataclass +class Activity: + id: int + parent: int + text: str + + +if __name__ == "__main__": + main() diff --git a/src/error/error.rs b/src/error/error.rs index 41a30cf..f4d1a53 100644 --- a/src/error/error.rs +++ b/src/error/error.rs @@ -3,6 +3,9 @@ use std::num::TryFromIntError; use std::str::Utf8Error; use std::string::FromUtf8Error; +use crate::nix_util::ActivityIdAlreadyInTreeError; +use crate::nix_util::ActivityIdNotInTreeError; + #[derive(Debug)] pub(crate) enum CustomError { Static(#[allow(dead_code)] &'static str), @@ -19,6 +22,8 @@ pub(crate) enum CustomError { Migrate(#[allow(dead_code)] sqlx::migrate::MigrateError), Sql(#[allow(dead_code)] sqlx::Error), TryFromIntError(#[allow(dead_code)] TryFromIntError), + ActivityIdNotInTreeError(#[allow(dead_code)] ActivityIdNotInTreeError), + ActivityIdAlreadyInTreeError(#[allow(dead_code)] ActivityIdAlreadyInTreeError), } impl Display for CustomError { @@ -110,3 +115,15 @@ impl From for CustomError { CustomError::TryFromIntError(value) } } + +impl From for CustomError { + fn from(value: ActivityIdNotInTreeError) -> Self { + CustomError::ActivityIdNotInTreeError(value) + } +} + +impl From for CustomError { + fn from(value: ActivityIdAlreadyInTreeError) -> Self { + CustomError::ActivityIdAlreadyInTreeError(value) + } +} diff --git a/src/nix_util/activity.rs b/src/nix_util/activity.rs new file mode 100644 index 0000000..cea406f --- /dev/null +++ b/src/nix_util/activity.rs @@ -0,0 +1,471 @@ +use std::borrow::Cow; + +use tracing::warn; + +#[repr(u8)] +pub(crate) enum Activity { + Root(ActivityRoot), + Unknown(ActivityUnknown), + CopyPath(ActivityCopyPath), + FileTransfer(ActivityFileTransfer), + Realize(ActivityRealize), + CopyPaths(ActivityCopyPaths), + Builds(ActivityBuilds), + Build(ActivityBuild), + OptimizeStore(ActivityOptimizeStore), + VerifyPaths(ActivityVerifyPaths), + Substitute(ActivitySubstitute), + QueryPathInfo(ActivityQueryPathInfo), + PostBuildHook(ActivityPostBuildHook), + BuildWaiting(ActivityBuildWaiting), + FetchTree(ActivityFetchTree), +} + +impl Activity { + pub(crate) fn stop(&mut self) -> () { + match self { + Activity::Root(_activity_root) => { + panic!("Attempted to start root activity."); + } + Activity::Unknown(activity_unknown) => { + activity_unknown.state = ActivityState::Stopped; + } + Activity::CopyPath(activity_copy_path) => { + activity_copy_path.state = ActivityState::Stopped; + } + Activity::FileTransfer(activity_file_transfer) => { + activity_file_transfer.state = ActivityState::Stopped; + } + Activity::Realize(activity_realize) => { + activity_realize.state = ActivityState::Stopped; + } + Activity::CopyPaths(activity_copy_paths) => { + activity_copy_paths.state = ActivityState::Stopped; + } + Activity::Builds(activity_builds) => { + activity_builds.state = ActivityState::Stopped; + } + Activity::Build(activity_build) => { + activity_build.state = ActivityState::Stopped; + } + Activity::OptimizeStore(activity_optimize_store) => { + activity_optimize_store.state = ActivityState::Stopped; + } + Activity::VerifyPaths(activity_verify_paths) => { + activity_verify_paths.state = ActivityState::Stopped; + } + Activity::Substitute(activity_substitute) => { + activity_substitute.state = ActivityState::Stopped; + } + Activity::QueryPathInfo(activity_query_path_info) => { + activity_query_path_info.state = ActivityState::Stopped; + } + Activity::PostBuildHook(activity_post_build_hook) => { + activity_post_build_hook.state = ActivityState::Stopped; + } + Activity::BuildWaiting(activity_build_waiting) => { + activity_build_waiting.state = ActivityState::Stopped; + } + Activity::FetchTree(activity_fetch_tree) => { + activity_fetch_tree.state = ActivityState::Stopped; + } + } + } + + pub(crate) fn is_active(&self) -> bool { + match self { + Activity::Root(_activity_root) => true, + Activity::Unknown(activity_unknown) => { + matches!(activity_unknown.state, ActivityState::Started { .. }) + } + Activity::CopyPath(activity_copy_path) => { + matches!(activity_copy_path.state, ActivityState::Started { .. }) + } + Activity::FileTransfer(activity_file_transfer) => { + matches!(activity_file_transfer.state, ActivityState::Started { .. }) + } + Activity::Realize(activity_realize) => { + matches!(activity_realize.state, ActivityState::Started { .. }) + } + Activity::CopyPaths(activity_copy_paths) => { + matches!(activity_copy_paths.state, ActivityState::Started { .. }) + } + Activity::Builds(activity_builds) => { + matches!(activity_builds.state, ActivityState::Started { .. }) + } + Activity::Build(activity_build) => { + matches!(activity_build.state, ActivityState::Started { .. }) + } + Activity::OptimizeStore(activity_optimize_store) => { + matches!(activity_optimize_store.state, ActivityState::Started { .. }) + } + Activity::VerifyPaths(activity_verify_paths) => { + matches!(activity_verify_paths.state, ActivityState::Started { .. }) + } + Activity::Substitute(activity_substitute) => { + matches!(activity_substitute.state, ActivityState::Started { .. }) + } + Activity::QueryPathInfo(activity_query_path_info) => { + matches!( + activity_query_path_info.state, + ActivityState::Started { .. } + ) + } + Activity::PostBuildHook(activity_post_build_hook) => { + matches!( + activity_post_build_hook.state, + ActivityState::Started { .. } + ) + } + Activity::BuildWaiting(activity_build_waiting) => { + matches!(activity_build_waiting.state, ActivityState::Started { .. }) + } + Activity::FetchTree(activity_fetch_tree) => { + matches!(activity_fetch_tree.state, ActivityState::Started { .. }) + } + } + } + + pub(crate) fn is_transparent(&self) -> bool { + match self { + Activity::Root(_activity_root) => true, + Activity::Unknown(_activity_unknown) => false, + Activity::CopyPath(_activity_copy_path) => false, + Activity::FileTransfer(_activity_file_transfer) => false, + Activity::Realize(_activity_realize) => true, + Activity::CopyPaths(_activity_copy_paths) => true, + Activity::Builds(_activity_builds) => true, + Activity::Build(_activity_build) => false, + Activity::OptimizeStore(_activity_optimize_store) => false, + Activity::VerifyPaths(_activity_verify_paths) => false, + Activity::Substitute(_activity_substitute) => false, + Activity::QueryPathInfo(_activity_query_path_info) => false, + Activity::PostBuildHook(_activity_post_build_hook) => false, + Activity::BuildWaiting(_activity_build_waiting) => true, + Activity::FetchTree(_activity_fetch_tree) => false, + } + } + + pub(crate) fn display_name(&self) -> Option> { + match self { + Activity::Root(_activity_root) => { + warn!("Unexpected display of root activity."); + Some(Cow::Borrowed("Root")) + } + Activity::Unknown(activity_unknown) => { + Some(Cow::Owned(format!("Unknown({})", activity_unknown.text))) + } + Activity::CopyPath(activity_copy_path) => Some(Cow::Owned(format!( + "CopyPath({} | {} -> {})", + activity_copy_path.missing_path, + activity_copy_path.source, + activity_copy_path.destination + ))), + Activity::FileTransfer(activity_file_transfer) => Some(Cow::Owned(format!( + "FileTransfer({})", + activity_file_transfer.url + ))), + Activity::Realize(_activity_realize) => Some(Cow::Borrowed("Realize")), + Activity::CopyPaths(activity_copy_paths) => Some(Cow::Owned(format!( + "CopyPaths({})", + activity_copy_paths.text + ))), + Activity::Builds(_activity_builds) => Some(Cow::Borrowed("Builds")), + Activity::Build(activity_build) => { + if let Some(machine_name) = &activity_build.machine_name { + Some(Cow::Owned(format!( + "Build({}@{machine_name})", + activity_build.drv_path + ))) + } else { + Some(Cow::Owned(format!("Build({})", activity_build.drv_path))) + } + } + Activity::OptimizeStore(_activity_optimize_store) => { + Some(Cow::Borrowed("OptimizeStore")) + } + Activity::VerifyPaths(_activity_verify_paths) => Some(Cow::Borrowed("VerifyPaths")), + Activity::Substitute(_activity_substitute) => Some(Cow::Borrowed("Substitute")), + Activity::QueryPathInfo(_activity_query_path_info) => { + Some(Cow::Borrowed("QueryPathInfo")) + } + Activity::PostBuildHook(_activity_post_build_hook) => { + Some(Cow::Borrowed("PostBuildHook")) + } + Activity::BuildWaiting(activity_build_waiting) => Some(Cow::Owned(format!( + "BuildWaiting({:?} | {:?} | {})", + activity_build_waiting.drv_path, + activity_build_waiting.path_resolved, + activity_build_waiting.text + ))), + Activity::FetchTree(_activity_fetch_tree) => Some(Cow::Borrowed("FetchTree")), + } + } + + pub(crate) fn set_phase(&mut self, phase: Option) -> () { + match self { + Activity::Root(_activity_root) => { + panic!("Attempted to set the phase of a root activity."); + } + Activity::Unknown(_activity_unknown) => { + panic!("Attempted to set the phase of an unknown activity."); + } + Activity::CopyPath(_activity_copy_path) => { + panic!("Attempted to set the phase of a copy path activity."); + } + Activity::FileTransfer(_activity_file_transfer) => { + panic!("Attempted to set the phase of a file transfer activity."); + } + Activity::Realize(_activity_realize) => { + panic!("Attempted to set the phase of a realize activity."); + } + Activity::CopyPaths(_activity_copy_paths) => { + panic!("Attempted to set the phase of a copy paths activity."); + } + Activity::Builds(_activity_builds) => { + panic!("Attempted to set the phase of a builds activity."); + } + Activity::Build(activity_build) => { + activity_build.phase = phase; + } + Activity::OptimizeStore(_activity_optimize_store) => { + panic!("Attempted to set the phase of an optimize store activity."); + } + Activity::VerifyPaths(_activity_verify_paths) => { + panic!("Attempted to set the phase of a verify paths activity."); + } + Activity::Substitute(_activity_substitute) => { + panic!("Attempted to set the phase of a substitute activity."); + } + Activity::QueryPathInfo(_activity_query_path_info) => { + panic!("Attempted to set the phase of a query path info activity."); + } + Activity::PostBuildHook(_activity_post_build_hook) => { + panic!("Attempted to set the phase of a post build hook activity."); + } + Activity::BuildWaiting(_activity_build_waiting) => { + panic!("Attempted to set the phase of a build waiting activity."); + } + Activity::FetchTree(_activity_fetch_tree) => { + panic!("Attempted to set the phase of a fetch tree activity."); + } + } + } + + pub(crate) fn set_progress( + &mut self, + done: u64, + expected: u64, + running: u64, + failed: u64, + ) -> () { + match self { + Activity::Root(_activity_root) => { + panic!("Attempted to set the progress of a root activity."); + } + Activity::Unknown(_activity_unknown) => { + panic!("Attempted to set the progress of an unknown activity."); + } + Activity::CopyPath(activity_copy_path) => { + if running != 0 || failed != 0 { + panic!("Attempted to set the progress of a copy path activity."); + } + activity_copy_path.done = done; + activity_copy_path.expected = expected; + } + Activity::FileTransfer(activity_file_transfer) => { + if running != 0 || failed != 0 { + panic!("Attempted to set the progress of a file transfer activity."); + } + activity_file_transfer.done = done; + activity_file_transfer.expected = expected; + } + Activity::Realize(_activity_realize) => { + panic!("Attempted to set the progress of a realize activity."); + } + Activity::CopyPaths(activity_copy_paths) => { + activity_copy_paths.done = done; + activity_copy_paths.expected = expected; + activity_copy_paths.running = running; + activity_copy_paths.failed = failed; + } + Activity::Builds(activity_builds) => { + activity_builds.done = done; + activity_builds.expected = expected; + activity_builds.running = running; + activity_builds.failed = failed; + } + Activity::Build(_activity_build) => { + panic!("Attempted to set the progress of a build activity."); + } + Activity::OptimizeStore(_activity_optimize_store) => { + panic!("Attempted to set the progress of an optimize store activity."); + } + Activity::VerifyPaths(_activity_verify_paths) => { + panic!("Attempted to set the progress of a verify paths activity."); + } + Activity::Substitute(_activity_substitute) => { + panic!("Attempted to set the progress of a substitute activity."); + } + Activity::QueryPathInfo(_activity_query_path_info) => { + panic!("Attempted to set the progress of a query path info activity."); + } + Activity::PostBuildHook(_activity_post_build_hook) => { + panic!("Attempted to set the progress of a post build hook activity."); + } + Activity::BuildWaiting(_activity_build_waiting) => { + panic!("Attempted to set the progress of a build waiting activity."); + } + Activity::FetchTree(_activity_fetch_tree) => { + panic!("Attempted to set the progress of a fetch tree activity."); + } + } + } + + pub(crate) fn set_expected(&mut self, expected: u64) -> () { + match self { + Activity::Root(_activity_root) => { + panic!("Attempted to set the expected of a root activity."); + } + Activity::Unknown(_activity_unknown) => { + panic!("Attempted to set the expected of an unknown activity."); + } + Activity::CopyPath(_activity_copy_path) => { + panic!("Attempted to set the expected of a copy path activity."); + } + Activity::FileTransfer(_activity_file_transfer) => { + panic!("Attempted to set the expected of a file transfer activity."); + } + Activity::Realize(_activity_realize) => { + // Seems to always be zero? + if expected != 0 { + panic!("Attempted to set the expected of a realize activity."); + } + } + Activity::CopyPaths(activity_copy_paths) => { + activity_copy_paths.expected = expected; + } + Activity::Builds(_activity_builds) => { + panic!("Attempted to set the expected of a builds activity."); + } + Activity::Build(_activity_build) => { + panic!("Attempted to set the expected of a build activity."); + } + Activity::OptimizeStore(_activity_optimize_store) => { + panic!("Attempted to set the expected of an optimize store activity."); + } + Activity::VerifyPaths(_activity_verify_paths) => { + panic!("Attempted to set the expected of a verify paths activity."); + } + Activity::Substitute(_activity_substitute) => { + panic!("Attempted to set the expected of a substitute activity."); + } + Activity::QueryPathInfo(_activity_query_path_info) => { + panic!("Attempted to set the expected of a query path info activity."); + } + Activity::PostBuildHook(_activity_post_build_hook) => { + panic!("Attempted to set the expected of a post build hook activity."); + } + Activity::BuildWaiting(_activity_build_waiting) => { + panic!("Attempted to set the expected of a build waiting activity."); + } + Activity::FetchTree(_activity_fetch_tree) => { + panic!("Attempted to set the expected of a fetch tree activity."); + } + } + } +} + +pub(crate) struct ActivityRoot {} +pub(crate) struct ActivityUnknown { + pub(crate) state: ActivityState, + pub(crate) text: String, +} +pub(crate) struct ActivityCopyPath { + pub(crate) state: ActivityState, + pub(crate) missing_path: String, + + /// The machine with the file(s) + pub(crate) source: String, + + /// The machine that is receiving the file(s) + pub(crate) destination: String, + + pub(crate) done: u64, + pub(crate) expected: u64, +} +pub(crate) struct ActivityFileTransfer { + pub(crate) state: ActivityState, + pub(crate) url: String, + pub(crate) done: u64, + pub(crate) expected: u64, +} +pub(crate) struct ActivityRealize { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityCopyPaths { + pub(crate) state: ActivityState, + pub(crate) text: String, + pub(crate) done: u64, + pub(crate) expected: u64, + pub(crate) running: u64, + pub(crate) failed: u64, +} +pub(crate) struct ActivityBuilds { + pub(crate) state: ActivityState, + pub(crate) done: u64, + pub(crate) expected: u64, + pub(crate) running: u64, + pub(crate) failed: u64, +} +pub(crate) struct ActivityBuild { + pub(crate) state: ActivityState, + pub(crate) drv_path: String, + pub(crate) machine_name: Option, + pub(crate) phase: Option, +} +pub(crate) struct ActivityOptimizeStore { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityVerifyPaths { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivitySubstitute { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityQueryPathInfo { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityPostBuildHook { + pub(crate) state: ActivityState, +} +pub(crate) struct ActivityBuildWaiting { + pub(crate) state: ActivityState, + pub(crate) text: String, + pub(crate) drv_path: Option, + pub(crate) path_resolved: Option, +} +pub(crate) struct ActivityFetchTree { + pub(crate) state: ActivityState, +} + +pub(crate) enum ActivityState { + Started { + progress_numerator: u64, + progress_denominator: u64, + progress_running: u64, + progress_failed: u64, + }, + Stopped, +} + +impl Default for ActivityState { + fn default() -> Self { + ActivityState::Started { + progress_numerator: 0, + progress_denominator: 0, + progress_running: 0, + progress_failed: 0, + } + } +} diff --git a/src/nix_util/activity_tree.rs b/src/nix_util/activity_tree.rs new file mode 100644 index 0000000..2f1b909 --- /dev/null +++ b/src/nix_util/activity_tree.rs @@ -0,0 +1,180 @@ +use crate::Result; +use std::collections::BTreeMap; +use std::fmt::Display; +use std::ops::Deref; + +use super::activity::Activity; +use super::activity::ActivityRoot; + +#[derive(Debug, Clone)] +pub(crate) struct ActivityId { + id: u64, +} + +impl ActivityId { + fn new(id: u64) -> ActivityId { + ActivityId { id } + } +} + +impl Deref for ActivityId { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.id + } +} + +impl Display for ActivityId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&**self, f) + } +} + +pub(crate) struct ActivityTree { + activities: BTreeMap, +} + +impl ActivityTree { + pub(crate) fn new() -> ActivityTree { + let mut activities = BTreeMap::new(); + activities.insert( + 0, + ActivityTreeEntry { + id: ActivityId::new(0), + parent_id: ActivityId::new(0), + child_ids: Vec::new(), + activity: Activity::Root(ActivityRoot {}), + }, + ); + ActivityTree { activities } + } + + pub(crate) fn add_activity( + &mut self, + activity_id: u64, + parent_id: u64, + activity: Activity, + ) -> Result<&ActivityTreeEntry> { + // The activity_id is not yet in the tree, so we can't use get_activity_id. + let activity_id = ActivityId::new(activity_id); + let parent_id = self.get_activity_id(parent_id)?; + match self.activities.entry(activity_id.id) { + std::collections::btree_map::Entry::Vacant(vacant_entry) => { + vacant_entry.insert(ActivityTreeEntry { + id: activity_id.clone(), + parent_id: parent_id.clone(), + child_ids: Vec::new(), + activity, + }); + } + std::collections::btree_map::Entry::Occupied(_occupied_entry) => { + return Err(ActivityIdAlreadyInTreeError::new(*activity_id).into()); + } + }; + self.get_mut(&parent_id).child_ids.push(activity_id.clone()); + let tree_entry = self + .activities + .get(&*activity_id) + .expect("We just created this entry so it must exist."); + + Ok(tree_entry) + } + + pub(crate) fn get_activity_id(&self, activity_id: u64) -> Result { + if activity_id == 0 || self.activities.contains_key(&activity_id) { + Ok(ActivityId::new(activity_id)) + } else { + Err(ActivityIdNotInTreeError::new(activity_id).into()) + } + } + + pub(crate) fn get_root_id(&self) -> ActivityId { + // The root always exists, so there is no need to check. + ActivityId::new(0) + } + + pub(crate) fn get(&self, activity_id: &ActivityId) -> &ActivityTreeEntry { + self.activities + .get(activity_id) + .expect("You cannot create an ActivityId if the activity is not in the tree.") + } + + pub(crate) fn get_mut(&mut self, activity_id: &ActivityId) -> &mut ActivityTreeEntry { + self.activities + .get_mut(activity_id) + .expect("You cannot create an ActivityId if the activity is not in the tree.") + } +} + +pub(crate) struct ActivityTreeEntry { + id: ActivityId, + parent_id: ActivityId, + child_ids: Vec, + activity: Activity, +} + +impl ActivityTreeEntry { + pub(crate) fn get_activity_id(&self) -> &ActivityId { + &self.id + } + + pub(crate) fn get_parent_id(&self) -> &ActivityId { + &self.parent_id + } + + pub(crate) fn get_activity(&self) -> &Activity { + &self.activity + } + + pub(crate) fn get_mut_activity(&mut self) -> &mut Activity { + &mut self.activity + } + + pub(crate) fn get_child_ids(&self) -> &Vec { + &self.child_ids + } + + pub(crate) fn iter_children<'tree>( + &'_ self, + activity_tree: &'tree ActivityTree, + ) -> impl Iterator { + self.child_ids + .iter() + .map(|child_id| activity_tree.get(child_id)) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ActivityIdNotInTreeError { + id: u64, +} + +impl ActivityIdNotInTreeError { + fn new(id: u64) -> ActivityIdNotInTreeError { + ActivityIdNotInTreeError { id } + } +} + +impl std::fmt::Display for ActivityIdNotInTreeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Activity id {} not in the tree.", self.id) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ActivityIdAlreadyInTreeError { + id: u64, +} + +impl ActivityIdAlreadyInTreeError { + fn new(id: u64) -> ActivityIdAlreadyInTreeError { + ActivityIdAlreadyInTreeError { id } + } +} + +impl std::fmt::Display for ActivityIdAlreadyInTreeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Activity id {} already in the tree.", self.id) + } +} diff --git a/src/nix_util/mod.rs b/src/nix_util/mod.rs index caaf31b..b5d1386 100644 --- a/src/nix_util/mod.rs +++ b/src/nix_util/mod.rs @@ -1,7 +1,12 @@ +mod activity; +mod activity_tree; mod high_level; mod nix_output_stream; mod output_stream; mod running_build; +mod transparent_iter; +pub(crate) use activity_tree::ActivityIdAlreadyInTreeError; +pub(crate) use activity_tree::ActivityIdNotInTreeError; pub(crate) use high_level::*; pub(crate) use nix_output_stream::NixOutputStream; pub(crate) use output_stream::OutputLine; diff --git a/src/nix_util/nix_output_stream.rs b/src/nix_util/nix_output_stream.rs index 633e044..f4789f2 100644 --- a/src/nix_util/nix_output_stream.rs +++ b/src/nix_util/nix_output_stream.rs @@ -315,7 +315,6 @@ fn parse_action(raw: RawNixAction) -> Result { warn_if_len!(ActivityResultFileLinked, original_json, &fields, != 0); warn_if!(ActivityResultFileLinked, original_json, id, != 0); - println!("{}", original_json); Ok(NixAction::Result(ActivityResultMessage::FileLinked( ActivityResultFileLinked {}, ))) @@ -356,8 +355,8 @@ fn parse_action(raw: RawNixAction) -> Result { warn_if_len!(ActivityResultProgress, original_json, &fields, != 4); let done = number_field(&fields, 0).to_owned(); let expected = number_field(&fields, 1).to_owned(); - let running = number_field(&fields, 2).to_owned().try_into()?; - let failed = number_field(&fields, 3).to_owned().try_into()?; + let running = number_field(&fields, 2).to_owned(); + let failed = number_field(&fields, 3).to_owned(); Ok(NixAction::Result(ActivityResultMessage::Progress( ActivityResultProgress { @@ -371,7 +370,6 @@ fn parse_action(raw: RawNixAction) -> Result { } ResultType::SetExpected => { warn_if_len!(ActivityResultSetExpected, original_json, &fields, != 2); - // TODO: Maybe map activity_type to an enum? let activity_type = number_field(&fields, 0).to_owned().try_into()?; let expected = number_field(&fields, 1).to_owned(); @@ -722,6 +720,8 @@ pub(crate) struct ActivityStartCopyPaths { pub(crate) id: u64, pub(crate) parent: u64, pub(crate) level: u8, + + /// Seems to either be blank or "copying 9 paths" pub(crate) text: String, } @@ -840,8 +840,8 @@ pub(crate) struct ActivityResultProgress { pub(crate) id: u64, pub(crate) done: u64, pub(crate) expected: u64, - pub(crate) running: u8, - pub(crate) failed: u8, + pub(crate) running: u64, + pub(crate) failed: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/nix_util/running_build.rs b/src/nix_util/running_build.rs index 65a7865..481dd11 100644 --- a/src/nix_util/running_build.rs +++ b/src/nix_util/running_build.rs @@ -1,6 +1,3 @@ -use std::borrow::Cow; -use std::collections::BTreeMap; - use sqlx::Row; use tokio::process::Child; use tracing::error; @@ -11,20 +8,40 @@ use crate::nix_util::nix_output_stream::ActivityStartMessage; use crate::nix_util::nix_output_stream::NixAction; use crate::nix_util::nix_output_stream::NixOutputStream; use crate::nix_util::output_stream::OutputStream; +use crate::nix_util::transparent_iter::TransparentIter; +use super::activity::Activity; +use super::activity::ActivityBuild; +use super::activity::ActivityBuildWaiting; +use super::activity::ActivityBuilds; +use super::activity::ActivityCopyPath; +use super::activity::ActivityCopyPaths; +use super::activity::ActivityFetchTree; +use super::activity::ActivityFileTransfer; +use super::activity::ActivityOptimizeStore; +use super::activity::ActivityPostBuildHook; +use super::activity::ActivityQueryPathInfo; +use super::activity::ActivityRealize; +use super::activity::ActivityState; +use super::activity::ActivitySubstitute; +use super::activity::ActivityUnknown; +use super::activity::ActivityVerifyPaths; +use super::activity_tree::ActivityId; +use super::activity_tree::ActivityTree; +use super::activity_tree::ActivityTreeEntry; use super::nix_output_stream::ActivityResultMessage; use super::nix_output_stream::NixMessage; pub(crate) struct RunningBuild<'db> { db_handle: &'db DbHandle, - activity_tree: BTreeMap, + activity_tree: ActivityTree, } impl<'db> RunningBuild<'db> { pub(crate) fn new(db_handle: &'db DbHandle) -> Result { Ok(RunningBuild { db_handle, - activity_tree: BTreeMap::new(), + activity_tree: ActivityTree::new(), }) } @@ -96,185 +113,215 @@ impl<'db> RunningBuild<'db> { // For now we can ignore the messages. } NixAction::Start(activity_start_message) => { - let id = activity_start_message.get_id(); - let entry = self.activity_tree.entry(id); - let entry = match entry { - std::collections::btree_map::Entry::Vacant(vacant_entry) => vacant_entry, - std::collections::btree_map::Entry::Occupied(_occupied_entry) => { - panic!("Started an already started activity: {id}.") - } - }; match activity_start_message { ActivityStartMessage::Unknown(activity_start_unknown) => { - entry.insert(Activity::Unknown(ActivityUnknown { - id: activity_start_unknown.id, - parent: activity_start_unknown.parent, - state: ActivityState::default(), - level: activity_start_unknown.level, - text: activity_start_unknown.text, - })); + self.activity_tree.add_activity( + activity_start_unknown.id, + activity_start_unknown.parent, + Activity::Unknown(ActivityUnknown { + state: ActivityState::default(), + text: activity_start_unknown.text, + }), + )?; } ActivityStartMessage::CopyPath(activity_start_copy_path) => { - entry.insert(Activity::CopyPath(ActivityCopyPath { - id: activity_start_copy_path.id, - parent: activity_start_copy_path.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_copy_path.id, + activity_start_copy_path.parent, + Activity::CopyPath(ActivityCopyPath { + state: ActivityState::default(), + missing_path: activity_start_copy_path.missing_path, + source: activity_start_copy_path.source, + destination: activity_start_copy_path.destination, + done: 0, + expected: 0, + }), + )?; } ActivityStartMessage::FileTransfer(activity_start_file_transfer) => { - entry.insert(Activity::FileTransfer(ActivityFileTransfer { - id: activity_start_file_transfer.id, - parent: activity_start_file_transfer.parent, - state: ActivityState::default(), - text: activity_start_file_transfer.text, - url: activity_start_file_transfer.url, - })); + self.activity_tree.add_activity( + activity_start_file_transfer.id, + activity_start_file_transfer.parent, + Activity::FileTransfer(ActivityFileTransfer { + state: ActivityState::default(), + url: activity_start_file_transfer.url, + done: 0, + expected: 0, + }), + )?; } ActivityStartMessage::Realize(activity_start_realize) => { - entry.insert(Activity::Realize(ActivityRealize { - id: activity_start_realize.id, - parent: activity_start_realize.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_realize.id, + activity_start_realize.parent, + Activity::Realize(ActivityRealize { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::CopyPaths(activity_start_copy_paths) => { - entry.insert(Activity::CopyPaths(ActivityCopyPaths { - id: activity_start_copy_paths.id, - parent: activity_start_copy_paths.parent, - state: ActivityState::default(), - level: activity_start_copy_paths.level, - text: activity_start_copy_paths.text, - })); + self.activity_tree.add_activity( + activity_start_copy_paths.id, + activity_start_copy_paths.parent, + Activity::CopyPaths(ActivityCopyPaths { + state: ActivityState::default(), + text: activity_start_copy_paths.text, + done: 0, + expected: 0, + running: 0, + failed: 0, + }), + )?; } ActivityStartMessage::Builds(activity_start_builds) => { - entry.insert(Activity::Builds(ActivityBuilds { - id: activity_start_builds.id, - parent: activity_start_builds.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_builds.id, + activity_start_builds.parent, + Activity::Builds(ActivityBuilds { + state: ActivityState::default(), + done: 0, + expected: 0, + running: 0, + failed: 0, + }), + )?; } ActivityStartMessage::Build(activity_start_build) => { - entry.insert(Activity::Build(ActivityBuild { - id: activity_start_build.id, - parent: activity_start_build.parent, - state: ActivityState::default(), - text: activity_start_build.text, - drv_path: activity_start_build.drv_path, - })); + self.activity_tree.add_activity( + activity_start_build.id, + activity_start_build.parent, + Activity::Build(ActivityBuild { + state: ActivityState::default(), + drv_path: activity_start_build.drv_path, + machine_name: if activity_start_build.machine_name.len() > 0 { + Some(activity_start_build.machine_name) + } else { + None + }, + phase: None, + }), + )?; } ActivityStartMessage::OptimizeStore(activity_start_optimize_store) => { - entry.insert(Activity::OptimizeStore(ActivityOptimizeStore { - id: activity_start_optimize_store.id, - parent: activity_start_optimize_store.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_optimize_store.id, + activity_start_optimize_store.parent, + Activity::OptimizeStore(ActivityOptimizeStore { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::VerifyPaths(activity_start_verify_paths) => { - entry.insert(Activity::VerifyPaths(ActivityVerifyPaths { - id: activity_start_verify_paths.id, - parent: activity_start_verify_paths.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_verify_paths.id, + activity_start_verify_paths.parent, + Activity::VerifyPaths(ActivityVerifyPaths { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::Substitute(activity_start_substitute) => { - entry.insert(Activity::Substitute(ActivitySubstitute { - id: activity_start_substitute.id, - parent: activity_start_substitute.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_substitute.id, + activity_start_substitute.parent, + Activity::Substitute(ActivitySubstitute { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::QueryPathInfo(activity_start_query_path_info) => { - entry.insert(Activity::QueryPathInfo(ActivityQueryPathInfo { - id: activity_start_query_path_info.id, - parent: activity_start_query_path_info.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_query_path_info.id, + activity_start_query_path_info.parent, + Activity::QueryPathInfo(ActivityQueryPathInfo { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::PostBuildHook(activity_start_post_build_hook) => { - entry.insert(Activity::PostBuildHook(ActivityPostBuildHook { - id: activity_start_post_build_hook.id, - parent: activity_start_post_build_hook.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_post_build_hook.id, + activity_start_post_build_hook.parent, + Activity::PostBuildHook(ActivityPostBuildHook { + state: ActivityState::default(), + }), + )?; } ActivityStartMessage::BuildWaiting(activity_start_build_waiting) => { - entry.insert(Activity::BuildWaiting(ActivityBuildWaiting { - id: activity_start_build_waiting.id, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_build_waiting.id, + 0, + Activity::BuildWaiting(ActivityBuildWaiting { + state: ActivityState::default(), + text: activity_start_build_waiting.text, + drv_path: activity_start_build_waiting.drv_path, + path_resolved: activity_start_build_waiting.path_resolved, + }), + )?; } ActivityStartMessage::FetchTree(activity_start_fetch_tree) => { - entry.insert(Activity::FetchTree(ActivityFetchTree { - id: activity_start_fetch_tree.id, - parent: activity_start_fetch_tree.parent, - state: ActivityState::default(), - })); + self.activity_tree.add_activity( + activity_start_fetch_tree.id, + activity_start_fetch_tree.parent, + Activity::FetchTree(ActivityFetchTree { + state: ActivityState::default(), + }), + )?; } }; - self.print_current_status(); + // self.print_current_status(); } NixAction::Stop(stop_message) => { - let entry = self.activity_tree.entry(stop_message.id); - match entry { - std::collections::btree_map::Entry::Vacant(_vacant_entry) => { - panic!( - "Stopped an activity that is not in the tree: {}", - stop_message.id - ); - } - std::collections::btree_map::Entry::Occupied(mut occupied_entry) => { - occupied_entry.get_mut().stop(); - } - }; - self.print_current_status(); + let activity = self + .activity_tree + .get_activity_id(stop_message.id) + .map(|activity_id| self.activity_tree.get_mut(&activity_id))?; + activity.get_mut_activity().stop(); + // self.print_current_status(); // println!("{}", serde_json::to_string(&message)?); } NixAction::Result(activity_result_message) => { match activity_result_message { - ActivityResultMessage::FileLinked(activity_result_file_linked) => { - // FileLinked - // TODO: Haven't seen any of these. - // warn!("Found FileLinked: {}", serde_json::to_string(&message)?); + ActivityResultMessage::FileLinked(_activity_result_file_linked) => {} + ActivityResultMessage::BuildLogLine(_activity_result_build_log_line) => { + // These are the output from the actual build (as opposed to the output from nix). } - ActivityResultMessage::BuildLogLine(activity_result_build_log_line) => { - // BuildLogLine - // The first field is a string containing the log line + ActivityResultMessage::UntrustedPath(_activity_result_untrusted_path) => {} + ActivityResultMessage::CorruptedPath(_activity_result_corrupted_path) => {} + ActivityResultMessage::SetPhase(activity_result_set_phase) => { + let activity_id = self + .activity_tree + .get_activity_id(activity_result_set_phase.id)?; + let activity = self.activity_tree.get_mut(&activity_id); + activity + .get_mut_activity() + .set_phase(Some(activity_result_set_phase.phase)); } - ActivityResultMessage::UntrustedPath(activity_result_untrusted_path) => { - // UntrustedPath - // TODO: Haven't seen any of these. - // warn!("Found UntrustedPath: {}", serde_json::to_string(&message)?); + ActivityResultMessage::Progress(activity_result_progress) => { + let activity_id = self + .activity_tree + .get_activity_id(activity_result_progress.id)?; + let activity = self.activity_tree.get_mut(&activity_id); + activity.get_mut_activity().set_progress( + activity_result_progress.done, + activity_result_progress.expected, + activity_result_progress.running, + activity_result_progress.failed, + ); } - ActivityResultMessage::CorruptedPath(activity_result_corrupted_path) => { - // CorruptedPath - // TODO: Haven't seen any of these. - // warn!("Found CorruptedPath: {}", serde_json::to_string(&message)?); - } - ActivityResultMessage::SetPhase(activity_result_set_phase) => { // SetPhase - // The first field is the phase name - } - ActivityResultMessage::Progress(activity_result_progress) => { // Progress - // Fields numerator, denominator, running?, failed? - } - ActivityResultMessage::SetExpected(activity_result_set_expected) => { // SetExpected - // Fields activity type?, expected? + ActivityResultMessage::SetExpected(activity_result_set_expected) => { + let activity_id = self + .activity_tree + .get_activity_id(activity_result_set_expected.id)?; + let activity = self.activity_tree.get_mut(&activity_id); + activity + .get_mut_activity() + .set_expected(activity_result_set_expected.expected); } ActivityResultMessage::PostBuildLogLine( - activity_result_post_build_log_line, - ) => { - // PostBuildLogLine - // TODO: Haven't seen any of these. - // warn!( - // "Found PostBuildLogLine: {}", - // serde_json::to_string(&message)? - // ); - } - ActivityResultMessage::FetchStatus(activity_result_fetch_status) => { - // FetchStatus - // TODO: Haven't seen any of these. - // warn!("Found FetchStatus: {}", serde_json::to_string(&message)?); - // println!("{}", serde_json::to_string(&message)?); - } + _activity_result_post_build_log_line, + ) => {} + ActivityResultMessage::FetchStatus(_activity_result_fetch_status) => {} }; } }; @@ -282,295 +329,139 @@ impl<'db> RunningBuild<'db> { } fn print_current_status(&self) -> () { - let in_progress = self - .activity_tree - .iter() - .filter(|activity: &(&u64, &Activity)| activity.1.is_active()); - let names: Vec> = in_progress - .filter_map(|(id, activity)| activity.display_name()) + let mut tree = String::new(); + let draw_order = self.get_draw_order(); + for dag_entry in draw_order { + let leading_bars = { + let mut leading_bars = String::with_capacity(3 * dag_entry.depth.len()); + for leading_bar in dag_entry + .depth + .iter() + .map(|depth| if *depth { "𜹈 " } else { " " }) + { + leading_bars.push_str(leading_bar); + } + leading_bars + }; + let branch = if dag_entry.has_later_siblings { + "𜸨" + } else { + "𜸛" + }; + let activity = self.activity_tree.get(&dag_entry.activity_id); + let display_name = activity + .get_activity() + .display_name() + .expect("Currently we always return a display name."); + let activity_id = activity.get_activity_id(); + let parent_id = activity.get_parent_id(); + tree += &format!("{leading_bars}{branch}𜸟 {display_name} {parent_id} {activity_id}\n"); + } + if tree.is_empty() { + println!("No active activities."); + } else { + print!("\n{}\n", tree); + } + } + + fn get_draw_order(&self) -> Vec { + let mut draw_order: Vec = Vec::new(); + let mut stack: Vec = self + .get_children_in_order(self.activity_tree.get_root_id()) + .filter_map(|child| { + if child.get_activity().is_active() { + Some(DrawStackEntry::HasNotVisitedChildren(DrawDagEntry { + activity_id: child.get_activity_id().clone(), + depth: Vec::new(), + has_later_siblings: true, + })) + } else { + None + } + }) .collect(); - let name_list = names.join(", "); - // TODO: Make a meaningful current status. - // println!("In progress: {name_list}"); - } -} - -enum ActivityState { - Started { - progress_numerator: u64, - progress_denominator: u64, - progress_running: u64, - progress_failed: u64, - }, - Stopped, -} - -impl Default for ActivityState { - fn default() -> Self { - ActivityState::Started { - progress_numerator: 0, - progress_denominator: 0, - progress_running: 0, - progress_failed: 0, + if stack.len() == 0 { + return draw_order; } + stack + .last_mut() + .expect("If-statement ensured this is Some()") + .set_no_later_siblings(); + + while !stack.is_empty() { + let current_entry = stack.pop().expect("While-loop ensured this is Some."); + match current_entry { + DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => { + let current_id = draw_dag_entry.activity_id.clone(); + let mut current_depth = draw_dag_entry.depth.clone(); + current_depth.push(draw_dag_entry.has_later_siblings); + stack.push(DrawStackEntry::VisitedChildren(draw_dag_entry)); + let children: Vec = self + .get_children_in_order(current_id) + .filter_map(|child| { + if child.get_activity().is_active() { + Some(child.get_activity_id().clone()) + } else { + None + } + }) + .collect(); + let has_children = !children.is_empty(); + stack.extend(children.into_iter().map(|child_id| { + DrawStackEntry::HasNotVisitedChildren(DrawDagEntry { + activity_id: child_id, + depth: current_depth.clone(), + has_later_siblings: true, + }) + })); + if has_children { + stack + .last_mut() + .expect("If-statement ensured this is Some()") + .set_no_later_siblings(); + } + } + DrawStackEntry::VisitedChildren(draw_dag_entry) => { + draw_order.push(draw_dag_entry); + } + }; + } + draw_order + } + + fn get_children_in_order( + &self, + parent_id: ActivityId, + ) -> impl Iterator { + let parent = self.activity_tree.get(&parent_id); + parent + .get_child_ids() + .iter() + .map(|child_id| self.activity_tree.get(child_id)) + .flat_map(|child| TransparentIter::new(&self.activity_tree, child)) } } -#[repr(u8)] -enum Activity { - Unknown(ActivityUnknown), - CopyPath(ActivityCopyPath), - FileTransfer(ActivityFileTransfer), - Realize(ActivityRealize), - CopyPaths(ActivityCopyPaths), - Builds(ActivityBuilds), - Build(ActivityBuild), - OptimizeStore(ActivityOptimizeStore), - VerifyPaths(ActivityVerifyPaths), - Substitute(ActivitySubstitute), - QueryPathInfo(ActivityQueryPathInfo), - PostBuildHook(ActivityPostBuildHook), - BuildWaiting(ActivityBuildWaiting), - FetchTree(ActivityFetchTree), +enum DrawStackEntry { + HasNotVisitedChildren(DrawDagEntry), + VisitedChildren(DrawDagEntry), } -impl Activity { - fn stop(&mut self) -> () { +struct DrawDagEntry { + activity_id: ActivityId, + depth: Vec, + has_later_siblings: bool, +} + +impl DrawStackEntry { + fn set_no_later_siblings(&mut self) -> () { match self { - Activity::Unknown(activity_unknown) => { - activity_unknown.state = ActivityState::Stopped; + DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => { + draw_dag_entry.has_later_siblings = false } - Activity::CopyPath(activity_copy_path) => { - activity_copy_path.state = ActivityState::Stopped; + DrawStackEntry::VisitedChildren(draw_dag_entry) => { + draw_dag_entry.has_later_siblings = false } - Activity::FileTransfer(activity_file_transfer) => { - activity_file_transfer.state = ActivityState::Stopped; - } - Activity::Realize(activity_realize) => { - activity_realize.state = ActivityState::Stopped; - } - Activity::CopyPaths(activity_copy_paths) => { - activity_copy_paths.state = ActivityState::Stopped; - } - Activity::Builds(activity_builds) => { - activity_builds.state = ActivityState::Stopped; - } - Activity::Build(activity_build) => { - activity_build.state = ActivityState::Stopped; - } - Activity::OptimizeStore(activity_optimize_store) => { - activity_optimize_store.state = ActivityState::Stopped; - } - Activity::VerifyPaths(activity_verify_paths) => { - activity_verify_paths.state = ActivityState::Stopped; - } - Activity::Substitute(activity_substitute) => { - activity_substitute.state = ActivityState::Stopped; - } - Activity::QueryPathInfo(activity_query_path_info) => { - activity_query_path_info.state = ActivityState::Stopped; - } - Activity::PostBuildHook(activity_post_build_hook) => { - activity_post_build_hook.state = ActivityState::Stopped; - } - Activity::BuildWaiting(activity_build_waiting) => { - activity_build_waiting.state = ActivityState::Stopped; - } - Activity::FetchTree(activity_fetch_tree) => { - activity_fetch_tree.state = ActivityState::Stopped; - } - } - } - - fn is_active(&self) -> bool { - match self { - Activity::Unknown(activity_unknown) => { - matches!(activity_unknown.state, ActivityState::Started { .. }) - } - Activity::CopyPath(activity_copy_path) => { - matches!(activity_copy_path.state, ActivityState::Started { .. }) - } - Activity::FileTransfer(activity_file_transfer) => { - matches!(activity_file_transfer.state, ActivityState::Started { .. }) - } - Activity::Realize(activity_realize) => { - matches!(activity_realize.state, ActivityState::Started { .. }) - } - Activity::CopyPaths(activity_copy_paths) => { - matches!(activity_copy_paths.state, ActivityState::Started { .. }) - } - Activity::Builds(activity_builds) => { - matches!(activity_builds.state, ActivityState::Started { .. }) - } - Activity::Build(activity_build) => { - matches!(activity_build.state, ActivityState::Started { .. }) - } - Activity::OptimizeStore(activity_optimize_store) => { - matches!(activity_optimize_store.state, ActivityState::Started { .. }) - } - Activity::VerifyPaths(activity_verify_paths) => { - matches!(activity_verify_paths.state, ActivityState::Started { .. }) - } - Activity::Substitute(activity_substitute) => { - matches!(activity_substitute.state, ActivityState::Started { .. }) - } - Activity::QueryPathInfo(activity_query_path_info) => { - matches!( - activity_query_path_info.state, - ActivityState::Started { .. } - ) - } - Activity::PostBuildHook(activity_post_build_hook) => { - matches!( - activity_post_build_hook.state, - ActivityState::Started { .. } - ) - } - Activity::BuildWaiting(activity_build_waiting) => { - matches!(activity_build_waiting.state, ActivityState::Started { .. }) - } - Activity::FetchTree(activity_fetch_tree) => { - matches!(activity_fetch_tree.state, ActivityState::Started { .. }) - } - } - } - - fn display_name(&self) -> Option> { - match self { - Activity::Unknown(activity_unknown) => { - // TODO - Some(Cow::Borrowed("Unknown")) - } - Activity::CopyPath(activity_copy_path) => { - // TODO - Some(Cow::Borrowed("CopyPath")) - } - Activity::FileTransfer(activity_file_transfer) => { - // TODO - Some(Cow::Borrowed("FileTransfer")) - } - Activity::Realize(activity_realize) => { - // TODO - Some(Cow::Borrowed("Realize")) - } - Activity::CopyPaths(activity_copy_paths) => { - // TODO - Some(Cow::Borrowed("CopyPaths")) - } - Activity::Builds(activity_builds) => { - // TODO - Some(Cow::Borrowed("Builds")) - } - Activity::Build(activity_build) => { - // TODO - Some(Cow::Borrowed("Build")) - } - Activity::OptimizeStore(activity_optimize_store) => { - // TODO - Some(Cow::Borrowed("OptimizeStore")) - } - Activity::VerifyPaths(activity_verify_paths) => { - // TODO - Some(Cow::Borrowed("VerifyPaths")) - } - Activity::Substitute(activity_substitute) => { - // TODO - Some(Cow::Borrowed("Substitute")) - } - Activity::QueryPathInfo(activity_query_path_info) => { - // TODO - Some(Cow::Borrowed("QueryPathInfo")) - } - Activity::PostBuildHook(activity_post_build_hook) => { - // TODO - Some(Cow::Borrowed("PostBuildHook")) - } - Activity::BuildWaiting(activity_build_waiting) => { - // TODO - Some(Cow::Borrowed("BuildWaiting")) - } - Activity::FetchTree(activity_fetch_tree) => { - // TODO - Some(Cow::Borrowed("FetchTree")) - } - } + }; } } - -struct ActivityUnknown { - id: u64, - parent: u64, - state: ActivityState, - level: u8, - text: String, -} -struct ActivityCopyPath { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityFileTransfer { - id: u64, - parent: u64, - state: ActivityState, - text: String, - url: String, -} -struct ActivityRealize { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityCopyPaths { - id: u64, - parent: u64, - state: ActivityState, - level: u8, - text: String, -} -struct ActivityBuilds { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityBuild { - id: u64, - parent: u64, - state: ActivityState, - text: String, - drv_path: String, -} -struct ActivityOptimizeStore { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityVerifyPaths { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivitySubstitute { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityQueryPathInfo { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityPostBuildHook { - id: u64, - parent: u64, - state: ActivityState, -} -struct ActivityBuildWaiting { - id: u64, - state: ActivityState, -} -struct ActivityFetchTree { - id: u64, - parent: u64, - state: ActivityState, -} diff --git a/src/nix_util/transparent_iter.rs b/src/nix_util/transparent_iter.rs new file mode 100644 index 0000000..f52ab62 --- /dev/null +++ b/src/nix_util/transparent_iter.rs @@ -0,0 +1,90 @@ +use super::activity_tree::ActivityTree; +use super::activity_tree::ActivityTreeEntry; + +/// An iterator that returns either the original activity if it is not transparent, or if it is transparent, the earliest non-transparent children. +pub(crate) struct TransparentIter<'tree> { + activity_tree: &'tree ActivityTree, + origin: Option<&'tree ActivityTreeEntry>, + child_index: Vec, +} + +impl<'tree> Iterator for TransparentIter<'tree> { + type Item = &'tree ActivityTreeEntry; + + fn next(&mut self) -> Option { + let origin = match self.origin { + Some(o) => o, + None => { + return None; + } + }; + + if !origin.get_activity().is_transparent() { + let origin = self + .origin + .take() + .expect("We would have returned early if origin was None."); + return Some(origin); + } + + if self.child_index.is_empty() { + self.child_index.push(0); + } + + loop { + let current_entry = self.get_current_entry(); + if let Some(child) = current_entry { + if child.get_activity().is_transparent() { + self.child_index.push(0); + } else { + let last_index = self.child_index.last_mut().expect("Stack cannot be empty."); + *last_index += 1; + return Some(child); + } + } else { + self.child_index.pop(); + if self.child_index.is_empty() { + self.origin.take(); + return None; + } + let last_index = self.child_index.last_mut().expect("Stack cannot be empty."); + *last_index += 1; + } + } + } +} + +impl<'tree> TransparentIter<'tree> { + pub(crate) fn new( + activity_tree: &'tree ActivityTree, + origin: &'tree ActivityTreeEntry, + ) -> TransparentIter<'tree> { + TransparentIter { + activity_tree, + origin: Some(origin), + child_index: Vec::new(), + } + } + + fn get_current_entry(&mut self) -> Option<&'tree ActivityTreeEntry> { + let mut current_entry = match self.origin { + Some(o) => o, + None => { + return None; + } + }; + for ind in self.child_index.iter() { + if let Some(child) = current_entry + .get_child_ids() + .get(*ind) + .map(|child_index| self.activity_tree.get(child_index)) + { + current_entry = child; + continue; + } else { + return None; + } + } + Some(current_entry) + } +}