ketcindyinstaller/
installer.rs

1use 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    // バージョン一覧の取得(UI側から呼ぶ)
51    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    // バージョン選択(UI側から呼ぶ)
69    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    // TODO: インストール処理書く
77    pub fn start_installation(&mut self) {
78        use crate::worker;
79
80        // ダウンロード処理
81        for package in &mut self.packages {
82            // バージョンが選択されているパッケージのみ処理を行う
83            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                    // 進捗を引数にとるクロージャで通知
92                    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    // workerからのイベントがあるかを確認して,ある場合は処理
110    pub fn poll_events(&mut self) -> bool {
111        let mut had_events = false;
112        // Receiverのキューを全部処理
113        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    // Eventに基づいてPackageの状態を更新する
121    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),    // デフォルトで最新を選択
127                };
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                // TODO: エラーハンドリング
137                panic!("{message}")
138            },
139        }
140    }
141
142    // packagesを複製して返す(描画用)
143    pub fn packages(&self) -> Vec<Package> {
144        self.packages.clone()
145    }
146
147    // PackageKindに対応するPackageの可変参照を返す(内部用)
148    fn package(&mut self, package_kind: PackageKind) -> &mut Package {
149        // TODO:
150        // テストを書いてunwrap()の正当性を保証するか,
151        // パッケージごとにフィールドを用意してコンパイル時に保証する
152        self.packages.iter_mut().find(|package| package.kind == package_kind).unwrap()
153    }
154}