1use crate::api_core::common::{
2 FileIdentifier, FileRecord, FileSelection, FileServiceSelection, ServiceIdentifier,
3};
4use crate::api_core::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction};
5use crate::api_core::endpoints::searching_and_fetching_files::{FileFullMetadata, FullMetadata};
6use crate::error::{Error, Result};
7use crate::utils::tag_list_to_string_list;
8use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder;
9use crate::wrapper::builders::notes_builder::AddNotesBuilder;
10
11use crate::wrapper::tag::Tag;
12use crate::Client;
13use chrono::{NaiveDateTime, TimeZone, Utc};
14use mime::Mime;
15use std::collections::HashMap;
16
17#[derive(Clone, Debug, PartialOrd, PartialEq)]
18pub enum FileStatus {
19 ReadyForImport,
20 InDatabase,
21 Deleted,
22 Unknown,
23}
24
25impl Eq for FileStatus {}
26
27impl From<u8> for FileStatus {
28 fn from(v: u8) -> FileStatus {
29 match v {
30 3 => FileStatus::Deleted,
31 0 => FileStatus::ReadyForImport,
32 _ => FileStatus::InDatabase,
33 }
34 }
35}
36
37#[derive(Clone)]
38pub struct HydrusFile {
39 pub(crate) client: Client,
40 pub id: FileIdentifier,
41 pub status: FileStatus,
42 pub(crate) metadata: Option<FileFullMetadata>,
43}
44
45impl HydrusFile {
46 pub(crate) fn from_hash<S: ToString>(client: Client, hash: S) -> Self {
47 Self {
48 client,
49 id: FileIdentifier::Hash(hash.to_string()),
50 status: FileStatus::Unknown,
51 metadata: None,
52 }
53 }
54
55 pub(crate) fn from_raw_status_and_hash<S: ToString>(
56 client: Client,
57 status: u8,
58 hash: S,
59 ) -> Self {
60 Self {
61 client,
62 id: FileIdentifier::Hash(hash.to_string()),
63 status: status.into(),
64 metadata: None,
65 }
66 }
67
68 pub(crate) fn from_metadata(client: Client, metadata: FileFullMetadata) -> Self {
69 let status = if metadata.is_trashed {
70 FileStatus::Deleted
71 } else {
72 FileStatus::InDatabase
73 };
74
75 Self {
76 client,
77 id: FileIdentifier::Hash(metadata.basic_metadata.identifiers.hash.clone()),
78 status,
79 metadata: Some(metadata),
80 }
81 }
82
83 pub async fn update(&mut self) -> Result<()> {
85 self.metadata = None;
86 self.metadata().await?;
87 Ok(())
88 }
89
90 pub async fn hash(&mut self) -> Result<String> {
93 match &self.id {
94 FileIdentifier::ID(_) => {
95 let metadata = self.metadata().await?;
96 Ok(metadata.basic_metadata.identifiers.hash.clone())
97 }
98 FileIdentifier::Hash(hash) => Ok(hash.clone()),
99 }
100 }
101
102 pub async fn size(&mut self) -> Result<Option<u64>> {
104 let metadata = self.metadata().await?;
105
106 Ok(metadata.basic_metadata.size.clone())
107 }
108
109 pub async fn mime(&mut self) -> Result<Mime> {
111 let metadata = self.metadata().await?;
112 let mime = metadata
113 .basic_metadata
114 .mime
115 .as_str()
116 .parse()
117 .map_err(|_| Error::InvalidMime(metadata.basic_metadata.mime.clone()))?;
118
119 Ok(mime)
120 }
121
122 pub async fn ext(&mut self) -> Result<String> {
124 let metadata = self.metadata().await?;
125
126 Ok(metadata.basic_metadata.ext.clone())
127 }
128
129 pub async fn dimensions(&mut self) -> Result<Option<(u32, u32)>> {
131 let metadata = self.metadata().await?;
132 if let (Some(width), Some(height)) = (
133 &metadata.basic_metadata.width,
134 &metadata.basic_metadata.height,
135 ) {
136 Ok(Some((*width, *height)))
137 } else {
138 Ok(None)
139 }
140 }
141
142 pub async fn duration(&mut self) -> Result<Option<u64>> {
144 let metadata = self.metadata().await?;
145
146 Ok(metadata.basic_metadata.duration.clone())
147 }
148
149 pub async fn num_frames(&mut self) -> Result<Option<u64>> {
151 let metadata = self.metadata().await?;
152
153 Ok(metadata.basic_metadata.num_frames.clone())
154 }
155
156 pub async fn has_audio(&mut self) -> Result<bool> {
158 let metadata = self.metadata().await?;
159
160 Ok(metadata.basic_metadata.has_audio.unwrap_or(false))
161 }
162
163 pub async fn in_inbox(&mut self) -> Result<bool> {
165 let metadata = self.metadata().await?;
166
167 Ok(metadata.is_inbox)
168 }
169
170 pub async fn stored_locally(&mut self) -> Result<bool> {
172 let metadata = self.metadata().await?;
173
174 Ok(metadata.is_local)
175 }
176
177 pub async fn moved_to_trashed(&mut self) -> Result<bool> {
179 let metadata = self.metadata().await?;
180
181 Ok(metadata.is_trashed)
182 }
183
184 pub async fn urls(&mut self) -> Result<&Vec<String>> {
186 let metadata = self.metadata().await?;
187
188 Ok(&metadata.known_urls)
189 }
190
191 pub async fn time_modified(&mut self) -> Result<Option<NaiveDateTime>> {
193 let metadata = self.metadata().await?;
194 let naive_time_modified = metadata
195 .basic_metadata
196 .time_modified
197 .map(|m| Utc.timestamp_millis(m as i64).naive_utc());
198
199 Ok(naive_time_modified)
200 }
201
202 pub async fn time_imported<S: AsRef<str>>(
204 &mut self,
205 service_key: S,
206 ) -> Result<Option<NaiveDateTime>> {
207 let metadata = self.metadata().await?;
208 let naive_time_imported = metadata
209 .file_services
210 .current
211 .get(service_key.as_ref())
212 .map(|s| s.time_imported)
213 .or_else(|| {
214 metadata
215 .file_services
216 .deleted
217 .get(service_key.as_ref())
218 .map(|s| s.time_imported)
219 })
220 .map(|millis| Utc.timestamp_millis(millis as i64).naive_utc());
221
222 Ok(naive_time_imported)
223 }
224
225 pub async fn time_deleted<S: AsRef<str>>(
227 &mut self,
228 service_key: S,
229 ) -> Result<Option<NaiveDateTime>> {
230 let metadata = self.metadata().await?;
231 let naive_time_deleted = metadata
232 .file_services
233 .deleted
234 .get(service_key.as_ref())
235 .map(|service| service.time_deleted)
236 .map(|millis| Utc.timestamp_millis(millis as i64).naive_utc());
237
238 Ok(naive_time_deleted)
239 }
240
241 pub fn delete(&mut self) -> DeleteFilesBuilder {
243 self.metadata = None;
244 DeleteFilesBuilder::new(self.client.clone()).add_file(self.id.clone())
245 }
246
247 pub async fn undelete(&mut self, service_selection: FileServiceSelection) -> Result<()> {
250 let hash = self.hash().await?;
251 self.metadata = None;
252 self.client
253 .undelete_files(FileSelection::by_hash(hash), service_selection)
254 .await
255 }
256
257 pub async fn archive(&mut self, service_selection: FileServiceSelection) -> Result<()> {
260 let hash = self.hash().await?;
261 self.metadata = None;
262 self.client
263 .archive_files(FileSelection::by_hash(hash), service_selection)
264 .await
265 }
266
267 pub async fn unarchive(&mut self, service_selection: FileServiceSelection) -> Result<()> {
269 let hash = self.hash().await?;
270 self.metadata = None;
271 self.client
272 .unarchive_files(FileSelection::by_hash(hash), service_selection)
273 .await
274 }
275
276 pub async fn associate_urls(&mut self, urls: Vec<String>) -> Result<()> {
278 let hash = self.hash().await?;
279 self.client.associate_urls(urls, vec![hash]).await
280 }
281
282 pub async fn disassociate_urls(&mut self, urls: Vec<String>) -> Result<()> {
284 let hash = self.hash().await?;
285 self.client.disassociate_urls(urls, vec![hash]).await
286 }
287
288 #[deprecated(note = "Deprecated in the official API. Use services_with_tags instead.")]
292 pub async fn service_names_with_tags(
293 &mut self,
294 ) -> Result<HashMap<ServiceIdentifier, Vec<Tag>>> {
295 let metadata = self.metadata().await?;
296 let mut tag_mappings = HashMap::new();
297
298 for (service, service_tags) in &metadata.tags {
299 let mut tag_list = Vec::new();
300
301 for (_, tags) in &service_tags.storage_tags {
302 tag_list.append(&mut tags.into_iter().map(|t| t.into()).collect())
303 }
304 tag_mappings.insert(ServiceIdentifier::Key(service.clone()), tag_list);
305 }
306
307 Ok(tag_mappings)
308 }
309
310 pub async fn services_with_tags(&mut self) -> Result<HashMap<ServiceIdentifier, Vec<Tag>>> {
312 let metadata = self.metadata().await?;
313 let mut tag_mappings = HashMap::new();
314
315 for (service, service_tags) in &metadata.tags {
316 let mut tag_list = Vec::new();
317
318 for (_, tags) in &service_tags.storage_tags {
319 tag_list.append(&mut tags.into_iter().map(|t| t.into()).collect())
320 }
321 tag_mappings.insert(ServiceIdentifier::Key(service.clone()), tag_list);
322 }
323
324 Ok(tag_mappings)
325 }
326
327 pub async fn tags(&mut self) -> Result<Vec<Tag>> {
329 let mut tag_list = Vec::new();
330 let tag_mappings = self.services_with_tags().await?;
331
332 for (_, mut tags) in tag_mappings {
333 tag_list.append(&mut tags);
334 }
335
336 Ok(tag_list)
337 }
338
339 pub async fn add_tags(&mut self, service_key: String, tags: Vec<Tag>) -> Result<()> {
341 let hash = self.hash().await?;
342 let request = AddTagsRequestBuilder::default()
343 .add_hash(hash)
344 .add_tags(service_key, tag_list_to_string_list(tags))
345 .build();
346
347 self.client.add_tags(request).await
348 }
349
350 pub async fn modify_tags(
352 &mut self,
353 service_key: String,
354 action: TagAction,
355 tags: Vec<Tag>,
356 ) -> Result<()> {
357 let hash = self.hash().await?;
358 let mut reqwest = AddTagsRequestBuilder::default().add_hash(hash);
359
360 for tag in tags {
361 reqwest =
362 reqwest.add_tag_with_action(service_key.clone(), tag.to_string(), action.clone());
363 }
364
365 self.client.add_tags(reqwest.build()).await
366 }
367
368 pub fn add_notes(&self) -> AddNotesBuilder {
370 AddNotesBuilder::new(self.client.clone(), self.id.clone())
371 }
372
373 pub async fn delete_note<S1: ToString>(&self, name: S1) -> Result<()> {
375 self.client
376 .delete_notes(self.id.clone(), vec![name.to_string()])
377 .await
378 }
379
380 pub async fn delete_notes<I: IntoIterator<Item = S>, S: ToString>(
382 &self,
383 names: I,
384 ) -> Result<()> {
385 let names = names.into_iter().map(|n: S| n.to_string()).collect();
386 self.client.delete_notes(self.id.clone(), names).await
387 }
388
389 pub async fn retrieve(&self) -> Result<FileRecord> {
391 self.client.get_file(self.id.clone()).await
392 }
393
394 async fn metadata(&mut self) -> Result<&FileFullMetadata> {
397 if self.metadata.is_none() {
398 let metadata = self
399 .client
400 .get_file_metadata_by_identifier::<FullMetadata>(self.id.clone())
401 .await?;
402 self.status = if metadata.is_trashed {
403 FileStatus::Deleted
404 } else {
405 FileStatus::InDatabase
406 };
407 self.metadata = Some(metadata);
408 }
409
410 Ok(self.metadata.as_ref().unwrap())
411 }
412}