Skip to content

Commit ead243c

Browse files
authored
Merge pull request #71 from serpent-os/feat/explicit-selection
Mark selections as explicit
2 parents 6855941 + e745266 commit ead243c

File tree

10 files changed

+168
-71
lines changed

10 files changed

+168
-71
lines changed

moss/src/cli/install.rs

+13-6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use moss::{
1010
client::{self, Client},
1111
package::{self, Flags},
1212
registry::transaction,
13+
state::Selection,
1314
Package,
1415
};
1516
use thiserror::Error;
@@ -87,14 +88,20 @@ pub async fn handle(args: &ArgMatches, root: &Path) -> Result<(), Error> {
8788

8889
// Calculate the new state of packages (old_state + missing)
8990
let new_state_pkgs = {
90-
let previous_state_pkgs = match active_state {
91-
Some(id) => client.state_db.get(&id).await?.packages,
91+
let previous_selections = match active_state {
92+
Some(id) => client.state_db.get(&id).await?.selections,
9293
None => vec![],
9394
};
94-
missing
95-
.iter()
96-
.map(|p| p.id.clone())
97-
.chain(previous_state_pkgs)
95+
let missing_selections = missing.iter().map(|p| Selection {
96+
package: p.id.clone(),
97+
// Package is explicit if it was one of the input
98+
// packages provided by the user
99+
explicit: input.iter().any(|id| *id == p.id),
100+
reason: None,
101+
});
102+
103+
missing_selections
104+
.chain(previous_selections)
98105
.collect::<Vec<_>>()
99106
};
100107

moss/src/cli/remove.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use moss::{
1111
client::{self, Client},
1212
package::Flags,
1313
registry::transaction,
14+
state::Selection,
1415
};
1516
use thiserror::Error;
1617
use tui::{pretty::print_to_columns, Stylize};
@@ -93,10 +94,38 @@ pub async fn handle(args: &ArgMatches, root: &Path) -> Result<(), Error> {
9394
);
9495
}
9596

97+
// Map finalized state to a [`Selection`] by referencing
98+
// it's value from the previous state
99+
let new_state_pkgs = {
100+
let previous_selections = match client.installation.active_state {
101+
Some(id) => client.state_db.get(&id).await?.selections,
102+
None => vec![],
103+
};
104+
105+
finalized
106+
.into_iter()
107+
.map(|id| {
108+
previous_selections
109+
.iter()
110+
.find(|s| s.package == id)
111+
.cloned()
112+
// Should be unreachable since new state from removal
113+
// is always a subset of the previous state
114+
.unwrap_or_else(|| {
115+
eprintln!("Unreachable: previous selection not found during removal for package {id:?}, marking as not explicit");
116+
117+
Selection {
118+
package: id,
119+
explicit: false,
120+
reason: None,
121+
}
122+
})
123+
})
124+
.collect::<Vec<_>>()
125+
};
126+
96127
// Apply state
97-
client
98-
.apply_state(&finalized.into_iter().collect::<Vec<_>>(), "Remove")
99-
.await?;
128+
client.apply_state(&new_state_pkgs, "Remove").await?;
100129

101130
Ok(())
102131
}

moss/src/cli/state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fn print_state(state: state::State) {
7777
);
7878
// TODO: List packages?
7979
// TODO: Start with normal list, compute diff, reverse to print ?
80-
println!("{} {}", "Packages:".bold(), state.packages.len());
80+
println!("{} {}", "Packages:".bold(), state.selections.len());
8181
println!();
8282
}
8383

moss/src/client/mod.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ use self::prune::prune;
2222
use crate::{
2323
db, environment, package,
2424
registry::plugin::{self, Plugin},
25-
repository, state, Installation, Package, Registry, State,
25+
repository,
26+
state::{self, Selection},
27+
Installation, Package, Registry, State,
2628
};
2729

2830
pub mod cache;
@@ -137,16 +139,17 @@ impl Client {
137139
/// Then blit the filesystem, promote it, finally archiving the active ID
138140
pub async fn apply_state(
139141
&self,
140-
packages: &[package::Id],
142+
selections: &[Selection],
141143
summary: impl ToString,
142144
) -> Result<State, Error> {
143145
let old_state = self.installation.active_state;
144-
self.blit_root(packages).await?;
146+
self.blit_root(selections.iter().map(|s| &s.package))
147+
.await?;
145148

146149
// Add to db
147150
let state = self
148151
.state_db
149-
.add(packages, Some(summary.to_string()), None)
152+
.add(selections, Some(summary.to_string()), None)
150153
.await?;
151154

152155
// Write state id
@@ -362,7 +365,10 @@ impl Client {
362365
}
363366

364367
/// Blit the packages to a filesystem root
365-
async fn blit_root(&self, packages: &[package::Id]) -> Result<(), Error> {
368+
async fn blit_root(
369+
&self,
370+
packages: impl IntoIterator<Item = &package::Id>,
371+
) -> Result<(), Error> {
366372
let progress = ProgressBar::new(1).with_style(
367373
ProgressStyle::with_template("\n|{bar:20.red/blue}| {pos}/{len} {msg}")
368374
.unwrap()
@@ -373,7 +379,7 @@ impl Client {
373379
progress.tick();
374380

375381
let mut tbuild = TreeBuilder::new();
376-
for id in packages {
382+
for id in packages.into_iter() {
377383
let layouts = self.layout_db.query(id).await?;
378384
for layout in layouts {
379385
tbuild.push(PendingFile {

moss/src/client/prune.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,18 @@ pub async fn prune(
102102
let state = state_db.get(status.id()).await?;
103103

104104
// Increment each package
105-
state.packages.iter().for_each(|pkg| {
106-
*packages_counts.entry(pkg.clone()).or_default() += 1;
105+
state.selections.iter().for_each(|selection| {
106+
*packages_counts
107+
.entry(selection.package.clone())
108+
.or_default() += 1;
107109
});
108110

109111
// Decrement if removal
110112
if status.is_removal() {
111-
state.packages.iter().for_each(|pkg| {
112-
*packages_counts.entry(pkg.clone()).or_default() -= 1;
113+
state.selections.iter().for_each(|selection| {
114+
*packages_counts
115+
.entry(selection.package.clone())
116+
.or_default() -= 1;
113117
});
114118
removals.push(state);
115119
}

moss/src/db/state/migrations/20230912172712_init.sql

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ CREATE TABLE IF NOT EXISTS state (
77
description TEXT NULL
88
);
99

10-
CREATE TABLE IF NOT EXISTS state_packages (
10+
CREATE TABLE IF NOT EXISTS state_selections (
1111
state_id INTEGER NOT NULL,
1212
package_id TEXT NOT NULL,
13+
explicit BOOLEAN NOT NULL,
1314
reason TEXT NULL,
1415
FOREIGN KEY(state_id) REFERENCES state(id) ON DELETE CASCADE
1516
);

moss/src/db/state/mod.rs

+33-24
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ use sqlx::{Acquire, Executor, Pool, Sqlite};
88
use thiserror::Error;
99

1010
use crate::db::Encoding;
11-
use crate::state::{self, Id};
12-
use crate::Installation;
13-
use crate::{package, State};
11+
use crate::state::{self, Id, Selection};
12+
use crate::{Installation, State};
1413

1514
#[derive(Debug)]
1615
pub struct Database {
@@ -63,38 +62,44 @@ impl Database {
6362
",
6463
)
6564
.bind(id.encode());
66-
let packages_query = sqlx::query_as::<_, encoding::Package>(
65+
let selections_query = sqlx::query_as::<_, encoding::Selection>(
6766
"
68-
SELECT package_id
69-
FROM state_packages
67+
SELECT package_id,
68+
explicit,
69+
reason
70+
FROM state_selections
7071
WHERE state_id = ?;
7172
",
7273
)
7374
.bind(id.encode());
7475

75-
let (state, package_rows) = futures::try_join!(
76+
let (state, selections_rows) = futures::try_join!(
7677
state_query.fetch_one(&self.pool),
77-
packages_query.fetch_all(&self.pool)
78+
selections_query.fetch_all(&self.pool)
7879
)?;
7980

80-
let packages = package_rows
81+
let selections = selections_rows
8182
.into_iter()
82-
.map(|row| row.package_id.0)
83+
.map(|row| Selection {
84+
package: row.package_id.0,
85+
explicit: row.explicit,
86+
reason: row.reason,
87+
})
8388
.collect();
8489

8590
Ok(State {
8691
id: state.id.0,
8792
summary: state.summary,
8893
description: state.description,
89-
packages,
94+
selections,
9095
created: state.created,
9196
kind: state.kind.0,
9297
})
9398
}
9499

95100
pub async fn add(
96101
&self,
97-
packages: &[package::Id],
102+
selections: &[Selection],
98103
summary: Option<String>,
99104
description: Option<String>,
100105
) -> Result<State, Error> {
@@ -113,18 +118,19 @@ impl Database {
113118
.fetch_one(transaction.acquire().await?)
114119
.await?;
115120

116-
if !packages.is_empty() {
121+
if !selections.is_empty() {
117122
transaction
118123
.execute(
119124
sqlx::QueryBuilder::new(
120125
"
121-
INSERT INTO state_packages (state_id, package_id, reason)
126+
INSERT INTO state_selections (state_id, package_id, explicit, reason)
122127
",
123128
)
124-
.push_values(packages, |mut b, package| {
129+
.push_values(selections, |mut b, selection| {
125130
b.push_bind(id.0.encode())
126-
.push_bind(package.encode())
127-
.push_bind(Option::<String>::None);
131+
.push_bind(selection.package.encode())
132+
.push_bind(selection.explicit)
133+
.push_bind(selection.reason.as_ref());
128134
})
129135
.build(),
130136
)
@@ -202,8 +208,10 @@ mod encoding {
202208
}
203209

204210
#[derive(FromRow)]
205-
pub struct Package {
211+
pub struct Selection {
206212
pub package_id: Decoder<package::Id>,
213+
pub explicit: bool,
214+
pub reason: Option<String>,
207215
}
208216
}
209217

@@ -214,6 +222,7 @@ mod test {
214222
use chrono::Utc;
215223

216224
use super::*;
225+
use crate::package;
217226

218227
#[tokio::test]
219228
async fn create_insert_select() {
@@ -222,15 +231,15 @@ mod test {
222231
.await
223232
.unwrap();
224233

225-
let packages = vec![
226-
package::Id::from("pkg a".to_string()),
227-
package::Id::from("pkg b".to_string()),
228-
package::Id::from("pkg c".to_string()),
234+
let selections = vec![
235+
Selection::explicit(package::Id::from("pkg a".to_string())),
236+
Selection::explicit(package::Id::from("pkg a".to_string())),
237+
Selection::explicit(package::Id::from("pkg a".to_string())),
229238
];
230239

231240
let state = database
232241
.add(
233-
&packages,
242+
&selections,
234243
Some("test".to_string()),
235244
Some("test".to_string()),
236245
)
@@ -248,6 +257,6 @@ mod test {
248257
assert_eq!(state.summary.as_deref(), Some("test"));
249258
assert_eq!(state.description.as_deref(), Some("test"));
250259

251-
assert_eq!(state.packages, packages);
260+
assert_eq!(state.selections, selections);
252261
}
253262
}

moss/src/package/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ bitflags! {
9090
const INSTALLED = 1 << 2;
9191
/// Available as from-source build
9292
const SOURCE = 1 << 3;
93+
/// Package is explicitly installed (use with [`Flags::INSTALLED`])
94+
const EXPLICIT = 1 << 4;
9395
}
9496
}
9597

0 commit comments

Comments
 (0)