ketcindyinstaller/
installer.rs1use crate::package::Package;
2use crate::package::PackageKind;
3use crate::package::PackageState;
4use anyhow::Context;
5
6enum BackendEvent {
7 Fetched {
8 package_kind: PackageKind,
9 versions: Vec<String>,
10 },
11 Downloading {
12 package_kind: PackageKind,
13 progress: f32,
14 },
15 Downloaded {
16 package_kind: PackageKind,
17 path: std::path::PathBuf,
18 },
19 Error {
20 package_kind: PackageKind,
21 message: String,
22 },
23}
24
25pub(crate) struct Installer {
26 packages: Vec<Package>,
27 worker_event_tx: tokio::sync::mpsc::Sender<BackendEvent>,
28 worker_event_rx: tokio::sync::mpsc::Receiver<BackendEvent>,
29 async_runtime: tokio::runtime::Runtime,
30}
31
32impl Installer {
33 pub fn new() -> Self {
34 let packages = vec![
35 Package::new(PackageKind::KeTCindy),
36 Package::new(PackageKind::Cinderella),
37 Package::new(PackageKind::R),
38 Package::new(PackageKind::Maxima),
39 ];
40 let (worker_event_tx, worker_event_rx) = tokio::sync::mpsc::channel(8);
41 let async_runtime = tokio::runtime::Builder::new_multi_thread()
42 .worker_threads(1)
43 .enable_all()
44 .build()
45 .unwrap();
46
47 Self { packages, worker_event_tx, worker_event_rx, async_runtime }
48 }
49
50 pub fn fetch_versions(&mut self, package_kind: PackageKind) {
52 use crate::worker;
53
54 self.package(package_kind).state = PackageState::Fetching;
55
56 let tx = self.worker_event_tx.clone();
57 self.async_runtime.spawn(async move {
58 let versions = worker::fetch_versions(package_kind).await.context("Failed to retrieve version list.");
59
60 let event = match versions {
61 Ok(versions) => BackendEvent::Fetched { package_kind: package_kind, versions },
62 Err(error) => BackendEvent::Error { package_kind: package_kind, message: error.to_string() },
63 };
64 tx.send(event).await.ok();
65 });
66 }
67
68 pub fn select_version(&mut self, package_kind: PackageKind, index: Option<usize>) {
70 let package = self.package(package_kind);
71 if let PackageState::Fetched { selected_index, .. } = &mut package.state {
72 *selected_index = index;
73 }
74 }
75
76 pub fn start_installation(&mut self) {
78 use crate::worker;
79
80 for package in &mut self.packages {
82 if let PackageState::Fetched { versions, selected_index: Some(selected_index) } = &package.state {
84 let package_kind = package.kind;
85 let version = versions[*selected_index].clone();
86
87 let tx = self.worker_event_tx.clone();
88 self.async_runtime.spawn(async move {
89 tx.send(BackendEvent::Downloading { package_kind, progress: 0f32 }).await.ok();
90
91 let download_result = worker::download_package(package_kind, &version, |progress| {
93 let _ = tx.try_send(BackendEvent::Downloading {
94 package_kind,
95 progress,
96 });
97 }).await;
98
99 let event = match download_result {
100 Ok(path) => BackendEvent::Downloaded { package_kind, path },
101 Err(error) => BackendEvent::Error { package_kind, message: error.to_string() },
102 };
103 tx.send(event).await.ok();
104 });
105 }
106 }
107 }
108
109 pub fn poll_events(&mut self) -> bool {
111 let mut had_events = false;
112 while let Ok(event) = self.worker_event_rx.try_recv() {
114 had_events = true;
115 self.handle_event(event);
116 }
117 had_events
118 }
119
120 fn handle_event(&mut self, event: BackendEvent) {
122 match event {
123 BackendEvent::Fetched { package_kind, versions } => {
124 self.package(package_kind).state = PackageState::Fetched {
125 versions,
126 selected_index: Some(0), };
128 },
129 BackendEvent::Downloading { package_kind, progress } => {
130 self.package(package_kind).state = PackageState::Downloading { progress };
131 },
132 BackendEvent::Downloaded { package_kind, path } => {
133 self.package(package_kind).state = PackageState::Downloaded { path };
134 },
135 BackendEvent::Error { package_kind, message } => {
136 panic!("{message}")
138 },
139 }
140 }
141
142 pub fn packages(&self) -> Vec<Package> {
144 self.packages.clone()
145 }
146
147 fn package(&mut self, package_kind: PackageKind) -> &mut Package {
149 self.packages.iter_mut().find(|package| package.kind == package_kind).unwrap()
153 }
154}