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 pub async fn get_root(client: &Client) -> Result<Self, Error> {
29 Self::get_page(client, &[("a", "view")]).await
30 }
31
32 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 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 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 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 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