modio/
files.rs

1//! Modfile interface
2use std::ffi::OsStr;
3use std::path::Path;
4
5use mime::APPLICATION_OCTET_STREAM;
6use serde::ser::{Serialize, SerializeMap, Serializer};
7use tokio::io::AsyncRead;
8
9use crate::file_source::FileSource;
10use crate::prelude::*;
11use crate::types::id::{FileId, GameId, ModId};
12use crate::TargetPlatform;
13
14pub use crate::types::files::{
15    Download, File, FileHash, Platform, PlatformStatus, VirusResult, VirusScan, VirusStatus,
16};
17
18/// Interface for the modfiles of a mod.
19#[derive(Clone)]
20pub struct Files {
21    modio: Modio,
22    game: GameId,
23    mod_id: ModId,
24}
25
26impl Files {
27    pub(crate) fn new(modio: Modio, game: GameId, mod_id: ModId) -> Self {
28        Self {
29            modio,
30            game,
31            mod_id,
32        }
33    }
34
35    /// Returns a `Query` interface to retrieve all files that are published
36    /// for a mod this `Files` refers to.
37    ///
38    /// See [Filters and sorting](filters).
39    pub fn search(&self, filter: Filter) -> Query<File> {
40        let route = Route::GetFiles {
41            game_id: self.game,
42            mod_id: self.mod_id,
43        };
44        Query::new(self.modio.clone(), route, filter)
45    }
46
47    /// Return a reference to a file.
48    pub fn get(&self, id: FileId) -> FileRef {
49        FileRef::new(self.modio.clone(), self.game, self.mod_id, id)
50    }
51
52    /// Add a file for a mod that this `Files` refers to. [required: token]
53    #[allow(clippy::should_implement_trait)]
54    pub async fn add(self, options: AddFileOptions) -> Result<File> {
55        let route = Route::AddFile {
56            game_id: self.game,
57            mod_id: self.mod_id,
58        };
59        self.modio
60            .request(route)
61            .multipart(Form::from(options))
62            .send()
63            .await
64    }
65}
66
67/// Reference interface of a modfile.
68#[derive(Clone)]
69pub struct FileRef {
70    modio: Modio,
71    game: GameId,
72    mod_id: ModId,
73    id: FileId,
74}
75
76impl FileRef {
77    pub(crate) fn new(modio: Modio, game: GameId, mod_id: ModId, id: FileId) -> Self {
78        Self {
79            modio,
80            game,
81            mod_id,
82            id,
83        }
84    }
85
86    /// Get a reference to the Modio modfile object that this `FileRef` refers to.
87    pub async fn get(self) -> Result<File> {
88        let route = Route::GetFile {
89            game_id: self.game,
90            mod_id: self.mod_id,
91            file_id: self.id,
92        };
93        self.modio.request(route).send().await
94    }
95
96    /// Edit details of a modfile. [required: token]
97    pub async fn edit(self, options: EditFileOptions) -> Result<Editing<File>> {
98        let route = Route::EditFile {
99            game_id: self.game,
100            mod_id: self.mod_id,
101            file_id: self.id,
102        };
103        self.modio.request(route).form(&options).send().await
104    }
105
106    /// Delete a modfile. [required: token]
107    pub async fn delete(self) -> Result<()> {
108        let route = Route::DeleteFile {
109            game_id: self.game,
110            mod_id: self.mod_id,
111            file_id: self.id,
112        };
113        self.modio.request(route).send().await
114    }
115
116    /// Edit the platform status of a modfile. [required: token]
117    pub async fn edit_platform_status(self, options: EditPlatformStatusOptions) -> Result<File> {
118        let route = Route::ManagePlatformStatus {
119            game_id: self.game,
120            mod_id: self.mod_id,
121            file_id: self.id,
122        };
123        self.modio.request(route).form(&options).send().await
124    }
125}
126
127/// Modfile filters and sorting.
128///
129/// # Filters
130/// - `Fulltext`
131/// - `Id`
132/// - `ModId`
133/// - `DateAdded`
134/// - `DateScanned`
135/// - `VirusStatus`
136/// - `VirusPositive`
137/// - `Filesize`
138/// - `Filehash`
139/// - `Filename`
140/// - `Version`
141/// - `Changelog`
142///
143/// # Sorting
144/// - `Id`
145/// - `ModId`
146/// - `DateAdded`
147/// - `Version`
148///
149/// See [modio docs](https://docs.mod.io/restapiref/#get-modfiles) for more information.
150///
151/// By default this returns up to `100` items. You can limit the result by using `limit` and
152/// `offset`.
153///
154/// # Example
155/// ```
156/// use modio::filter::prelude::*;
157/// use modio::files::filters::Id;
158///
159/// let filter = Id::_in(vec![1, 2]).order_by(Id::desc());
160/// ```
161#[rustfmt::skip]
162pub mod filters {
163    #[doc(inline)]
164    pub use crate::filter::prelude::Fulltext;
165    #[doc(inline)]
166    pub use crate::filter::prelude::Id;
167    #[doc(inline)]
168    pub use crate::filter::prelude::ModId;
169    #[doc(inline)]
170    pub use crate::filter::prelude::DateAdded;
171
172    filter!(DateScanned, DATE_SCANNED, "date_scanned", Eq, NotEq, In, Cmp);
173    filter!(VirusStatus, VIRUS_STATUS, "virus_status", Eq, NotEq, In, Cmp);
174    filter!(VirusPositive, VIRUS_POSITIVE, "virus_positive", Eq, NotEq, In, Cmp);
175    filter!(Filesize, FILESIZE, "filesize", Eq, NotEq, In, Cmp, OrderBy);
176    filter!(Filehash, FILEHASH, "filehash", Eq, NotEq, In, Like);
177    filter!(Filename, FILENAME, "filename", Eq, NotEq, In, Like);
178    filter!(Version, VERSION, "version", Eq, NotEq, In, Like, OrderBy);
179    filter!(Changelog, CHANGELOG, "changelog", Eq, NotEq, In, Like);
180}
181
182pub struct AddFileOptions {
183    source: FileSource,
184    version: Option<String>,
185    changelog: Option<String>,
186    active: Option<bool>,
187    filehash: Option<String>,
188    metadata_blob: Option<String>,
189}
190
191impl AddFileOptions {
192    pub fn with_read<R, S>(inner: R, filename: S) -> AddFileOptions
193    where
194        R: AsyncRead + Send + Sync + Unpin + 'static,
195        S: Into<String>,
196    {
197        AddFileOptions {
198            source: FileSource::new_from_read(inner, filename.into(), APPLICATION_OCTET_STREAM),
199            version: None,
200            changelog: None,
201            active: None,
202            filehash: None,
203            metadata_blob: None,
204        }
205    }
206
207    pub fn with_file<P: AsRef<Path>>(file: P) -> AddFileOptions {
208        let file = file.as_ref();
209        let filename = file
210            .file_name()
211            .and_then(OsStr::to_str)
212            .map_or_else(String::new, ToString::to_string);
213
214        Self::with_file_name(file, filename)
215    }
216
217    pub fn with_file_name<P, S>(file: P, filename: S) -> AddFileOptions
218    where
219        P: AsRef<Path>,
220        S: Into<String>,
221    {
222        let file = file.as_ref();
223
224        AddFileOptions {
225            source: FileSource::new_from_file(file, filename.into(), APPLICATION_OCTET_STREAM),
226            version: None,
227            changelog: None,
228            active: None,
229            filehash: None,
230            metadata_blob: None,
231        }
232    }
233
234    option!(version);
235    option!(changelog);
236    option!(active: bool);
237    option!(filehash);
238    option!(metadata_blob);
239}
240
241#[doc(hidden)]
242impl From<AddFileOptions> for Form {
243    fn from(opts: AddFileOptions) -> Form {
244        let mut form = Form::new();
245        if let Some(version) = opts.version {
246            form = form.text("version", version);
247        }
248        if let Some(changelog) = opts.changelog {
249            form = form.text("changelog", changelog);
250        }
251        if let Some(active) = opts.active {
252            form = form.text("active", active.to_string());
253        }
254        if let Some(filehash) = opts.filehash {
255            form = form.text("filehash", filehash);
256        }
257        if let Some(metadata_blob) = opts.metadata_blob {
258            form = form.text("metadata_blob", metadata_blob);
259        }
260        form.part("filedata", opts.source.into())
261    }
262}
263
264#[derive(Default)]
265pub struct EditFileOptions {
266    params: std::collections::BTreeMap<&'static str, String>,
267}
268
269impl EditFileOptions {
270    option!(version >> "version");
271    option!(changelog >> "changelog");
272    option!(active: bool >> "active");
273    option!(metadata_blob >> "metadata_blob");
274}
275
276impl_serialize_params!(EditFileOptions >> params);
277
278pub struct EditPlatformStatusOptions {
279    approved: Vec<TargetPlatform>,
280    denied: Vec<TargetPlatform>,
281}
282
283impl EditPlatformStatusOptions {
284    pub fn new(approved: &[TargetPlatform], denied: &[TargetPlatform]) -> Self {
285        Self {
286            approved: approved.to_vec(),
287            denied: denied.to_vec(),
288        }
289    }
290}
291
292impl Serialize for EditPlatformStatusOptions {
293    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
294        let mut s = serializer.serialize_map(Some(self.approved.len() + self.denied.len()))?;
295        for target in &self.approved {
296            s.serialize_entry("approved[]", target)?;
297        }
298        for target in &self.denied {
299            s.serialize_entry("denied[]", target)?;
300        }
301        s.end()
302    }
303}