kernel/install/
ollama_pull.rs1use std::collections::BTreeMap;
6
7use serde::Deserialize;
8
9use crate::install::bytes::saturating_sum;
10use crate::install::error::InstallError;
11use crate::install::event::InstallProgress;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum Outcome {
16 Ignored,
18 Status(String),
20 Progress(InstallProgress),
22 Success,
24}
25
26#[derive(Debug, Default)]
28pub struct Aggregator {
29 totals: BTreeMap<String, i64>,
30 completed: BTreeMap<String, i64>,
31 last_status: Option<String>,
32}
33
34impl Aggregator {
35 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn fold(&mut self, line: &str) -> Result<Outcome, InstallError> {
43 let Ok(decoded) = serde_json::from_str::<Line>(line) else {
44 return Ok(Outcome::Ignored);
45 };
46 if let Some(error) = decoded.error.as_deref().filter(|error| !error.is_empty()) {
47 return Err(InstallError::TransferFailed(format!("ollama: {error}")));
48 }
49 let Some(status) = decoded
50 .status
51 .as_deref()
52 .filter(|status| !status.is_empty())
53 else {
54 return Ok(Outcome::Ignored);
55 };
56 if status == "success" {
57 return Ok(Outcome::Success);
58 }
59 if let (Some(digest), Some(total)) = (decoded.digest.as_ref(), decoded.total) {
60 self.totals.insert(digest.clone(), total);
61 let completed = decoded
63 .completed
64 .or_else(|| self.completed.get(digest).copied())
65 .unwrap_or(0);
66 self.completed.insert(digest.clone(), completed);
67 return Ok(Outcome::Progress(self.aggregate()));
68 }
69 if self.last_status.as_deref() == Some(status) {
70 return Ok(Outcome::Ignored);
71 }
72 self.last_status = Some(status.to_owned());
73 Ok(Outcome::Status(status.to_owned()))
74 }
75
76 fn aggregate(&self) -> InstallProgress {
77 InstallProgress {
78 bytes_downloaded: saturating_sum(self.completed.values().copied()),
79 total_bytes: Some(saturating_sum(self.totals.values().copied())),
80 total_is_partial: true,
81 current_file: None,
82 }
83 }
84}
85
86#[derive(Deserialize)]
87struct Line {
88 status: Option<String>,
89 digest: Option<String>,
90 total: Option<i64>,
91 completed: Option<i64>,
92 error: Option<String>,
93}