1use 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#[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 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 pub fn get(&self, id: FileId) -> FileRef {
49 FileRef::new(self.modio.clone(), self.game, self.mod_id, id)
50 }
51
52 #[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#[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 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 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 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 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#[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}