1use crate::Error;
2use crate::http;
3
4use std::fmt;
5use std::io;
6use std::str::FromStr;
7
8const REPOSITORY: &str = "hecrj/llama-server";
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct Build(u32);
13
14impl Build {
15 pub async fn latest() -> Result<Self, Error> {
17 use serde::Deserialize;
18
19 #[derive(Deserialize)]
20 struct Release {
21 tag_name: String,
22 }
23
24 let client = http::client();
25
26 let latest_release_url =
27 format!("https://api.github.com/repos/{REPOSITORY}/releases/latest");
28
29 let Release { tag_name } = client
30 .get(latest_release_url)
31 .send()
32 .await?
33 .error_for_status()?
34 .json()
35 .await?;
36
37 Ok(tag_name.parse()?)
38 }
39
40 pub fn locked(number: u32) -> Self {
42 Self(number)
43 }
44
45 pub fn number(self) -> u32 {
47 self.0
48 }
49
50 pub(crate) fn url(self) -> String {
51 format!("https://github.com/{REPOSITORY}/releases/download/{self}")
52 }
53}
54
55impl FromStr for Build {
56 type Err = io::Error;
57
58 fn from_str(build: &str) -> Result<Self, Self::Err> {
59 if !build.starts_with('b') {
60 return Err(io::Error::other(format!("invalid build: {build}")));
61 }
62
63 build
64 .trim_start_matches('b')
65 .parse()
66 .map(Self)
67 .map_err(io::Error::other)
68 }
69}
70
71impl fmt::Display for Build {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(f, "b{}", self.0)
74 }
75}