Merge in-progress activity tracking.

This commit is contained in:
Tom Alexander
2026-02-28 20:15:48 -05:00
8 changed files with 1189 additions and 429 deletions

106
draw_tree.py Normal file
View File

@@ -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()

View File

@@ -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<TryFromIntError> for CustomError {
CustomError::TryFromIntError(value)
}
}
impl From<ActivityIdNotInTreeError> for CustomError {
fn from(value: ActivityIdNotInTreeError) -> Self {
CustomError::ActivityIdNotInTreeError(value)
}
}
impl From<ActivityIdAlreadyInTreeError> for CustomError {
fn from(value: ActivityIdAlreadyInTreeError) -> Self {
CustomError::ActivityIdAlreadyInTreeError(value)
}
}

471
src/nix_util/activity.rs Normal file
View File

@@ -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<Cow<'_, str>> {
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<String>) -> () {
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<String>,
pub(crate) phase: Option<String>,
}
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<String>,
pub(crate) path_resolved: Option<String>,
}
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,
}
}
}

View File

@@ -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<u64, ActivityTreeEntry>,
}
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<ActivityId> {
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<ActivityId>,
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<ActivityId> {
&self.child_ids
}
pub(crate) fn iter_children<'tree>(
&'_ self,
activity_tree: &'tree ActivityTree,
) -> impl Iterator<Item = &'tree ActivityTreeEntry> {
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)
}
}

View File

@@ -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;

View File

@@ -315,7 +315,6 @@ fn parse_action(raw: RawNixAction) -> Result<NixAction> {
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<NixAction> {
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<NixAction> {
}
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)]

View File

@@ -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<u64, Activity>,
activity_tree: ActivityTree,
}
impl<'db> RunningBuild<'db> {
pub(crate) fn new(db_handle: &'db DbHandle) -> Result<Self> {
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,
self.activity_tree.add_activity(
activity_start_unknown.id,
activity_start_unknown.parent,
Activity::Unknown(ActivityUnknown {
state: ActivityState::default(),
level: activity_start_unknown.level,
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,
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,
self.activity_tree.add_activity(
activity_start_file_transfer.id,
activity_start_file_transfer.parent,
Activity::FileTransfer(ActivityFileTransfer {
state: ActivityState::default(),
text: activity_start_file_transfer.text,
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,
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,
self.activity_tree.add_activity(
activity_start_copy_paths.id,
activity_start_copy_paths.parent,
Activity::CopyPaths(ActivityCopyPaths {
state: ActivityState::default(),
level: activity_start_copy_paths.level,
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,
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,
self.activity_tree.add_activity(
activity_start_build.id,
activity_start_build.parent,
Activity::Build(ActivityBuild {
state: ActivityState::default(),
text: activity_start_build.text,
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,
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,
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,
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,
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,
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,
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,
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
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()
.filter(|activity: &(&u64, &Activity)| activity.1.is_active());
let names: Vec<Cow<'_, str>> = in_progress
.filter_map(|(id, activity)| activity.display_name())
.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<DrawDagEntry> {
let mut draw_order: Vec<DrawDagEntry> = Vec::new();
let mut stack: Vec<DrawStackEntry> = 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}");
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<ActivityId> = 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<Item = &ActivityTreeEntry> {
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))
}
}
enum ActivityState {
Started {
progress_numerator: u64,
progress_denominator: u64,
progress_running: u64,
progress_failed: u64,
},
Stopped,
enum DrawStackEntry {
HasNotVisitedChildren(DrawDagEntry),
VisitedChildren(DrawDagEntry),
}
impl Default for ActivityState {
fn default() -> Self {
ActivityState::Started {
progress_numerator: 0,
progress_denominator: 0,
progress_running: 0,
progress_failed: 0,
}
}
struct DrawDagEntry {
activity_id: ActivityId,
depth: Vec<bool>,
has_later_siblings: bool,
}
#[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),
}
impl Activity {
fn stop(&mut self) -> () {
impl DrawStackEntry {
fn set_no_later_siblings(&mut self) -> () {
match self {
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;
}
}
}
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<Cow<'_, str>> {
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"))
DrawStackEntry::HasNotVisitedChildren(draw_dag_entry) => {
draw_dag_entry.has_later_siblings = false
}
DrawStackEntry::VisitedChildren(draw_dag_entry) => {
draw_dag_entry.has_later_siblings = false
}
};
}
}
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,
}

View File

@@ -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<usize>,
}
impl<'tree> Iterator for TransparentIter<'tree> {
type Item = &'tree ActivityTreeEntry;
fn next(&mut self) -> Option<Self::Item> {
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)
}
}