lanis_rs/modules/
file_storage.rs

1use crate::utils::constants::URL;
2use crate::utils::conversion::string_to_byte_size;
3use crate::utils::datetime::date_time_string_to_datetime;
4use crate::Error;
5use chrono::{DateTime, Utc};
6use markup5ever::interface::TreeSink;
7use markup5ever::tendril::fmt::Slice;
8use reqwest::Client;
9use scraper::{ElementRef, Html, Selector};
10use serde::{Deserialize, Serialize};
11use tokio::fs::File;
12
13#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
14pub struct FileStoragePage {
15    pub folder_nodes: Vec<FolderNode>,
16    pub file_nodes: Vec<FileNode>,
17}
18
19impl FileStoragePage {
20    pub fn new(folder_nodes: Vec<FolderNode>, file_nodes: Vec<FileNode>) -> Self {
21        Self {
22            folder_nodes,
23            file_nodes,
24        }
25    }
26
27    /// Get the root [FileStoragePage]
28    pub async fn get_root(client: &Client) -> Result<Self, Error> {
29        Self::get_page(client, &[("a", "view")]).await
30    }
31
32    /// Get a [FileStoragePage] for a specific folder node
33    pub async fn get(node_id: i32, client: &Client) -> Result<Self, Error> {
34        Self::get_page(client, &[("a", "view"), ("folder", &node_id.to_string())]).await
35    }
36
37    async fn get_page<T: Serialize>(client: &Client, query_parameter: &T) -> Result<Self, Error> {
38        match client
39            .get(URL::DATA_STORAGE)
40            .query(query_parameter)
41            .send()
42            .await
43        {
44            Ok(response) => {
45                async fn string_or_none<'a>(option: Option<ElementRef<'a>>) -> Option<String> {
46                    match option {
47                        Some(element) => {
48                            Some(element.text().collect::<String>().trim().to_string())
49                        }
50                        None => None,
51                    }
52                }
53
54                let text = response.text().await.map_err(|e| {
55                    Error::Parsing(format!("failed to parse response as text '{}'", e))
56                })?;
57                let html = Html::parse_document(&text);
58
59                let mut folder_nodes: Vec<FolderNode> = Vec::new();
60
61                let folder_selector = Selector::parse(".folder").unwrap();
62                let folder_name_selector = Selector::parse(".caption").unwrap();
63                let folder_description_selector = Selector::parse(".desc").unwrap();
64                let folder_subfolders_selector =
65                    Selector::parse("div.row>div.col-md-12>small>span.label.label-info").unwrap();
66
67                let folders = html.select(&folder_selector);
68                for folder in folders {
69                    let id = folder
70                        .attr("data-id")
71                        .unwrap_or("0")
72                        .trim()
73                        .parse::<i32>()
74                        .map_err(|e| {
75                            Error::Parsing(format!(
76                                "failed to parse id of folder node as i32 '{}'",
77                                e
78                            ))
79                        })?;
80                    let name_future = string_or_none(folder.select(&folder_name_selector).nth(0));
81                    let description_future =
82                        string_or_none(folder.select(&folder_description_selector).nth(0));
83                    let subfolders =
84                        string_or_none(folder.select(&folder_subfolders_selector).nth(0))
85                            .await
86                            .unwrap_or(String::from("0"))
87                            .replace(" Ordner", "")
88                            .replace("Keine Dateien", "0")
89                            .parse::<i32>()
90                            .map_err(|e| {
91                                Error::Parsing(format!(
92                                    "failed to parse subfolder count as i32 '{}'",
93                                    e
94                                ))
95                            })?;
96
97                    let name = match name_future.await {
98                        Some(name) => name,
99                        None => {
100                            return Err(Error::Parsing(String::from(
101                                "failed to parse name of folder node 'name is None'",
102                            )))
103                        }
104                    };
105                    let description = description_future.await;
106
107                    folder_nodes.push(FolderNode::new(id, name, description, subfolders))
108                }
109
110                let mut file_nodes: Vec<FileNode> = Vec::new();
111
112                let file_selector = Selector::parse("table#files>tbody>tr").unwrap();
113                let td_selector = Selector::parse("td").unwrap();
114                let small_selector = Selector::parse("small").unwrap();
115
116                let file_header_selector = Selector::parse("table#files thead th").unwrap();
117
118                let file_headers = html
119                    .select(&file_header_selector)
120                    .map(|element| element.text().collect::<String>().trim().to_string())
121                    .collect::<Vec<_>>();
122
123                for file_element in html.select(&file_selector) {
124                    let mut file = Html::parse_document(&format!(
125                        "<body><table><tbody>{}</tbody></table></body>",
126                        &file_element.html()
127                    ));
128
129                    let fields = file.select(&td_selector).collect::<Vec<ElementRef>>();
130
131                    let id = file_element
132                        .attr("data-id")
133                        .unwrap_or("0")
134                        .trim()
135                        .parse::<i32>()
136                        .map_err(|e| {
137                            Error::Parsing(format!(
138                                "failed to parse id of file node as i32 '{}'",
139                                e
140                            ))
141                        })?;
142
143                    let name_pos = file_headers.iter().position(|r| r == "Name");
144
145                    // TODO: Test this
146                    let notice = {
147                        match fields.get(name_pos.unwrap_or(fields.len())) {
148                            Some(element) => match element.select(&small_selector).nth(0) {
149                                Some(element) => {
150                                    let text =
151                                        element.text().collect::<String>().trim().to_string();
152                                    file.remove_from_parent(&element.id());
153                                    Some(text)
154                                }
155                                None => None,
156                            },
157                            None => None,
158                        }
159                    };
160
161                    let fields = file.select(&td_selector).collect::<Vec<ElementRef>>();
162
163                    let name_future =
164                        string_or_none(fields.get(name_pos.unwrap_or(fields.len())).copied());
165
166                    let changed_pos = file_headers.iter().position(|r| r == "Änderung");
167                    let changed_future =
168                        string_or_none(fields.get(changed_pos.unwrap_or(fields.len())).copied());
169
170                    let size_pos = file_headers.iter().position(|r| r == "Größe");
171                    let size_future =
172                        string_or_none(fields.get(size_pos.unwrap_or(fields.len())).copied());
173
174                    let name = match name_future.await {
175                        Some(name) => name,
176                        None => {
177                            return Err(Error::Parsing(String::from(
178                                "failed to parse name of file node 'name is None'",
179                            )))
180                        }
181                    };
182
183                    let changed = match changed_future.await {
184                        Some(changed) => {
185                            let mut split = changed.split(' ');
186                            let date = split
187                                .nth(0)
188                                .ok_or_else(|| {
189                                    Error::Parsing(String::from(
190                                        "failed to parse date for file node 'not found'",
191                                    ))
192                                })?
193                                .to_string();
194                            let time = split
195                                .nth(0)
196                                .ok_or_else(|| {
197                                    Error::Parsing(String::from(
198                                        "failed to parse time for file node 'not found'",
199                                    ))
200                                })?
201                                .to_string();
202
203                            date_time_string_to_datetime(&date, &time).map_err(|e| Error::DateTime(format!("failed to convert file node changed date & time to DateTime '{:?}'", e)))?.to_utc()
204                        }
205                        None => DateTime::from_timestamp_nanos(0).into(),
206                    };
207
208                    let size = match size_future.await {
209                        Some(size) => string_to_byte_size(size).await.map_err(|e| {
210                            Error::Parsing(format!("failed to convert size into u64 '{:?}'", e))
211                        })?,
212                        None => {
213                            return Err(Error::Parsing(String::from(
214                                "failed to parse size of file node 'size is None'",
215                            )))
216                        }
217                    };
218
219                    file_nodes.push(FileNode::new(id, name, changed, size, notice));
220                }
221
222                Ok(Self {
223                    folder_nodes,
224                    file_nodes,
225                })
226            }
227            Err(e) => Err(Error::Network(format!(
228                "failed to get page: '{}'",
229                e.to_string()
230            )))?,
231        }
232    }
233}
234
235#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
236pub struct FileNode {
237    pub id: i32,
238    pub name: String,
239    /// The last time the file was changed
240    pub changed: DateTime<Utc>,
241    pub size: u64,
242    pub notice: Option<String>,
243}
244
245impl FileNode {
246    pub fn new(
247        id: i32,
248        name: String,
249        changed: DateTime<Utc>,
250        size: u64,
251        notice: Option<String>,
252    ) -> Self {
253        Self {
254            id,
255            name,
256            changed,
257            size,
258            notice,
259        }
260    }
261
262    /// Downloads the file to the given location. <br>
263    /// Please note that the given file path will be overwritten if there is already a file
264    pub async fn download(&self, path: &str, client: &Client) -> Result<(), Error> {
265        match client
266            .get(URL::DATA_STORAGE)
267            .query(&[("a", "download"), ("f", &self.id.to_string())])
268            .send()
269            .await
270        {
271            Ok(response) => {
272                let bytes = response
273                    .bytes()
274                    .await
275                    .map_err(|e| {
276                        Error::Parsing(format!("failed to convert response to bytes '{}'", e))
277                    })?
278                    .as_bytes()
279                    .to_vec();
280
281                let mut file = File::create(path).await.map_err(|e| {
282                    Error::FileSystem(format!("failed to create file in desired path '{}'", e))
283                })?;
284                tokio::io::copy(&mut bytes.as_ref(), &mut file)
285                    .await
286                    .map_err(|e| {
287                        Error::FileSystem(format!(
288                            "failed to copy downloaded file to desired path '{}'",
289                            e
290                        ))
291                    })?;
292
293                Ok(())
294            }
295            Err(e) => Err(Error::Network(format!("download failed '{}'", e))),
296        }
297    }
298}
299
300#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
301pub struct FolderNode {
302    pub id: i32,
303    pub name: String,
304    pub description: Option<String>,
305    /// The amount of subfolders
306    pub subfolders: i32,
307}
308
309impl FolderNode {
310    pub fn new(id: i32, name: String, description: Option<String>, subfolders: i32) -> Self {
311        Self {
312            id,
313            name,
314            description,
315            subfolders,
316        }
317    }
318}
319