1use std::{
2 ffi::OsStr,
3 path::{self, Path, PathBuf},
4};
5
6use myko_macros::{myko_query, myko_query_handler, myko_report, Eventable};
7use partially::Partial;
8use serde::{Deserialize, Serialize};
9
10use myko_rs::query::QueryResult;
11#[derive(Partial, PartialEq, Eventable, Clone, Serialize, Deserialize, Debug)]
12#[serde(rename_all = "camelCase")]
13#[partially(derive(Clone, Serialize, Deserialize, Default))]
14pub struct File {
15 pub id: String,
16 pub hash: String,
17 pub machine_id: String,
18 pub asset_path: String,
19 pub local_path: String,
20 pub version: u32,
21}
22
23fn get_version(path: &Path) -> u32 {
24 let filename = path
25 .file_stem()
26 .expect("could not get file name")
27 .to_string_lossy()
28 .to_string()
29 .to_owned();
30
31 let version_tags = filename
32 .split('_')
33 .filter(|s| s.starts_with("v"))
34 .map(|s| s.replace("v", ""))
35 .filter_map(|s| s.parse::<u32>().ok())
36 .collect::<Vec<u32>>();
37 let version = version_tags.first();
38
39 match version {
40 Some(v) => v.to_owned(),
41 None => 0,
42 }
43}
44
45impl File {
46 pub fn new(path: &Path, machine_id: &String, base_path: &Path) -> Result<Self, String> {
47 let asset_path = path.strip_prefix(base_path).map_err(|e| e.to_string())?;
48
49 let file_name = asset_path
50 .file_stem()
51 .ok_or("File name is not valid")?
52 .to_string_lossy()
53 .to_string();
54
55 let file_name = file_name
56 .split('_')
57 .filter(|s| !s.starts_with("v"))
58 .collect::<Vec<&str>>()
59 .join("_");
60
61 let asset_path = change_file_name(asset_path, &file_name);
62
63 let asset_path = get_normal_path(&asset_path);
64 let local_path = get_normal_path(path);
65
66 Ok(File {
67 id: format!("{machine_id}:{local_path}"),
68 hash: uuid::Uuid::new_v4().to_string(),
69 machine_id: machine_id.to_string(),
70 local_path,
71 asset_path,
72 version: get_version(path),
73 })
74 }
75
76 pub fn local_path(&self) -> PathBuf {
77 get_local_path(&self.local_path)
78 }
79}
80
81fn change_file_name(path: impl AsRef<Path>, name: &str) -> PathBuf {
82 let path = path.as_ref();
83 let mut result = path.to_owned();
84 result.set_file_name(name);
85 if let Some(ext) = path.extension() {
86 result.set_extension(ext);
87 }
88 result
89}
90
91fn get_normal_path(path: &Path) -> String {
92 path.to_string_lossy()
93 .to_string()
94 .replace(path::MAIN_SEPARATOR, "/")
95}
96
97fn get_local_path(path: &str) -> PathBuf {
98 PathBuf::from(path.replace("/", std::path::MAIN_SEPARATOR_STR))
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103#[myko_query(File)]
104#[myko_query_handler(get_files_by_machine_id)]
105pub struct GetFilesByMachineId {
106 pub machine_id: String,
107}
108
109fn get_files_by_machine_id(
110 _q: GetFilesByMachineId,
111 tx: String,
112) -> impl tokio_stream::Stream<Item = QueryResult<File>> {
113 let files: Vec<File> = vec![];
114
115 let response = myko_rs::query::QueryResult::<File>::new(tx, files);
116 async_stream::stream! {
117 yield response;
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct AvailableVersionData {
123 pub min: u32,
124 pub max: u32,
125}
126
127#[cfg(feature = "mods")]
128#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130#[myko_report(AvailableVersionData)]
131pub struct AvailableVersions {
132 pub asset_path: String,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
136#[serde(rename_all = "camelCase")]
137pub struct MissingAsset {
138 pub asset_path: String,
139 pub version: u32,
140 pub available_from: Vec<String>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct Missing {
146 pub missing: Vec<MissingAsset>,
147}
148
149#[cfg(feature = "mods")]
150#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152#[myko_report(Missing)]
153pub struct MissingFiles {
154 pub machine_id: String,
155}