pitboard_core/switch/
uninstall.rs1use super::{Result, Settled, purge};
4use crate::context::Context;
5use crate::error::Error;
6use crate::service::Permit;
7use crate::state::Account;
8use crate::{home, schedule, state};
9
10#[non_exhaustive]
13pub struct Removed {
14 pub parks: usize,
16 pub pending: usize,
18 pub left: usize,
23 pub home_removed: bool,
25 pub schedule_removed: bool,
27}
28
29pub fn uninstall(settled: Settled) -> Result<Removed> {
42 let Settled {
43 _exclusive,
44 mut state,
45 ctx,
46 permit,
47 } = settled;
48 let schedule_removed = remove_schedule(&ctx, permit)?;
49 let held = state.accounts.iter().filter_map(|a| a.parked.as_ref());
50 let left = held
51 .clone()
52 .filter(|park| state.is_foreign(&park.service))
53 .count();
54 let parks = held.count() - left;
55 for key in state.accounts.iter().map(Account::key).collect::<Vec<_>>() {
56 state.remove(&key);
57 }
58 state.active.clear();
59 state::save(&ctx, permit, &state)?;
60 let pending = purge(&ctx, permit, &mut state);
61 if pending == 0 {
64 crate::pending::clear(&ctx, permit);
65 }
66 let home_removed = pending == 0 && remove_home(&ctx, permit);
67 Ok(Removed {
68 parks: parks.saturating_sub(pending),
69 pending,
70 left,
71 home_removed,
72 schedule_removed,
73 })
74}
75
76fn remove_schedule(ctx: &Context, permit: Permit) -> Result<bool> {
79 if !schedule::serves(ctx) {
80 return Ok(false);
81 }
82 match schedule::uninstall(ctx, permit) {
83 Err(Error::ScheduleUnsupported) => Ok(false),
84 removed => removed,
85 }
86}
87
88fn remove_home(ctx: &Context, permit: Permit) -> bool {
91 crate::host::fs::remove_dir_all(permit, &home::dir(ctx)).is_ok()
92}
93
94#[cfg(test)]
95mod tests {
96 use super::super::harness::{codex_machine, machine};
97 use super::super::settle;
98 use super::*;
99
100 #[test]
101 fn uninstalling_takes_the_renewal_schedule_with_it() {
102 for make in [machine, codex_machine] {
103 let m = make("uninstall-schedule");
104 schedule::install(&m.ctx, Permit::for_a_test()).expect("scheduled");
105
106 let removed = uninstall(
107 settle(&m.ctx, Permit::for_a_test(), None)
108 .expect("nothing to recover")
109 .0,
110 )
111 .expect("uninstalled");
112
113 assert!(removed.schedule_removed);
114 assert!(removed.home_removed);
115 assert_eq!(schedule::status(&m.ctx), schedule::Installed::No);
116 }
117 }
118
119 #[test]
122 fn a_schedule_that_cannot_be_taken_away_leaves_every_login_where_it_was() {
123 use crate::host::fs::testing;
124 for make in [machine, codex_machine] {
125 let m = make("uninstall-stuck");
126 schedule::install(&m.ctx, Permit::for_a_test()).expect("scheduled");
127 let dir = schedule::path(&m.ctx)
128 .and_then(|p| p.parent().map(std::path::Path::to_path_buf))
129 .expect("where the scheduler keeps it");
130 testing::deny_changes(&dir).expect("read-only");
131 let refused = uninstall(
132 settle(&m.ctx, Permit::for_a_test(), None)
133 .expect("nothing to recover")
134 .0,
135 );
136 testing::allow_changes(&dir).expect("changeable again");
137
138 let Err(error) = refused else {
139 panic!("{:?}: uninstalled with the schedule still there", m.which);
140 };
141 assert_eq!(error.code(), "home_unwritable");
142 let state = state::load(&m.ctx).expect("the account list");
143 assert_eq!(state.accounts.len(), 2, "{:?}", m.which);
144 let there = state.get(&m.key("there")).expect("`there` is enrolled");
145 let park = there.parked.as_ref().expect("`there` is still parked");
146 crate::park::load(&m.ctx, &m.key("there"), park).expect("its parked login is kept");
147 assert!(home::dir(&m.ctx).is_dir());
148 assert!(matches!(
149 schedule::status(&m.ctx),
150 schedule::Installed::Yes { .. }
151 ));
152 }
153 }
154
155 #[test]
156 fn uninstalling_where_nothing_is_scheduled_is_not_a_failure() {
157 let m = machine("uninstall-unscheduled");
158 let removed = uninstall(
159 settle(&m.ctx, Permit::for_a_test(), None)
160 .expect("nothing to recover")
161 .0,
162 )
163 .expect("uninstalled");
164 assert!(!removed.schedule_removed);
165 assert!(removed.home_removed);
166 }
167
168 #[test]
171 fn uninstalling_another_home_leaves_the_schedule_alone() {
172 let m = machine("uninstall-elsewhere");
173 schedule::install(&m.ctx, Permit::for_a_test()).expect("scheduled");
174 let elsewhere = m
175 .ctx
176 .clone()
177 .with_pitboard_home(m.ctx_home().join("elsewhere"));
178
179 let removed = uninstall(
180 settle(&elsewhere, Permit::for_a_test(), None)
181 .expect("nothing to recover")
182 .0,
183 )
184 .expect("uninstalled");
185
186 assert!(!removed.schedule_removed);
187 assert!(matches!(
188 schedule::status(&m.ctx),
189 schedule::Installed::Yes { .. }
190 ));
191 }
192}