use std::ffi::OsStr;
use std::path::Path;
use mime::IMAGE_STAR;
use crate::file_source::FileSource;
use crate::mods::{ModRef, Mods};
use crate::prelude::*;
use crate::types::id::{GameId, ModId};
pub use crate::types::games::{
ApiAccessOptions, CommunityOptions, CurationOption, Downloads, Game, HeaderImage, Icon,
MaturityOptions, OtherUrl, Platform, PresentationOption, Statistics, SubmissionOption,
TagOption, TagType, Theme,
};
pub use crate::types::Logo;
pub use crate::types::Status;
#[derive(Clone)]
pub struct Games {
modio: Modio,
}
impl Games {
pub(crate) fn new(modio: Modio) -> Self {
Self { modio }
}
pub fn search(&self, filter: Filter) -> Query<Game> {
let route = Route::GetGames {
show_hidden_tags: None,
};
Query::new(self.modio.clone(), route, filter)
}
pub fn get(&self, id: GameId) -> GameRef {
GameRef::new(self.modio.clone(), id)
}
}
#[derive(Clone)]
pub struct GameRef {
modio: Modio,
id: GameId,
}
impl GameRef {
pub(crate) fn new(modio: Modio, id: GameId) -> Self {
Self { modio, id }
}
pub async fn get(self) -> Result<Game> {
let route = Route::GetGame {
id: self.id,
show_hidden_tags: None,
};
self.modio.request(route).send().await
}
pub fn mod_(&self, mod_id: ModId) -> ModRef {
ModRef::new(self.modio.clone(), self.id, mod_id)
}
pub fn mods(&self) -> Mods {
Mods::new(self.modio.clone(), self.id)
}
pub async fn statistics(self) -> Result<Statistics> {
let route = Route::GetGameStats { game_id: self.id };
self.modio.request(route).send().await
}
pub fn tags(&self) -> Tags {
Tags::new(self.modio.clone(), self.id)
}
pub async fn edit_media(self, media: EditMediaOptions) -> Result<()> {
let route = Route::AddGameMedia { game_id: self.id };
self.modio
.request(route)
.multipart(Form::from(media))
.send::<Message>()
.await?;
Ok(())
}
}
#[derive(Clone)]
pub struct Tags {
modio: Modio,
game_id: GameId,
}
impl Tags {
fn new(modio: Modio, game_id: GameId) -> Self {
Self { modio, game_id }
}
pub async fn list(self) -> Result<Vec<TagOption>> {
let route = Route::GetGameTags {
game_id: self.game_id,
};
Query::new(self.modio, route, Filter::default())
.collect()
.await
}
#[allow(clippy::iter_not_returning_iterator)]
pub async fn iter(self) -> Result<impl Stream<Item = Result<TagOption>>> {
let route = Route::GetGameTags {
game_id: self.game_id,
};
let filter = Filter::default();
Query::new(self.modio, route, filter).iter().await
}
#[allow(clippy::should_implement_trait)]
pub async fn add(self, options: AddTagsOptions) -> Result<()> {
let route = Route::AddGameTags {
game_id: self.game_id,
};
self.modio
.request(route)
.form(&options)
.send::<Message>()
.await?;
Ok(())
}
pub async fn delete(self, options: DeleteTagsOptions) -> Result<Deletion> {
let route = Route::DeleteGameTags {
game_id: self.game_id,
};
self.modio.request(route).form(&options).send().await
}
pub async fn rename(self, from: String, to: String) -> Result<()> {
let route = Route::RenameGameTags {
game_id: self.game_id,
};
self.modio
.request(route)
.form(&[("from", from), ("to", to)])
.send()
.await?;
Ok(())
}
}
#[rustfmt::skip]
pub mod filters {
#[doc(inline)]
pub use crate::filter::prelude::Fulltext;
#[doc(inline)]
pub use crate::filter::prelude::Id;
#[doc(inline)]
pub use crate::filter::prelude::Name;
#[doc(inline)]
pub use crate::filter::prelude::NameId;
#[doc(inline)]
pub use crate::filter::prelude::Status;
#[doc(inline)]
pub use crate::filter::prelude::DateAdded;
#[doc(inline)]
pub use crate::filter::prelude::DateUpdated;
#[doc(inline)]
pub use crate::filter::prelude::DateLive;
#[doc(inline)]
pub use crate::filter::prelude::SubmittedBy;
filter!(Summary, SUMMARY, "summary", Eq, NotEq, Like);
filter!(InstructionsUrl, INSTRUCTIONS_URL, "instructions_url", Eq, NotEq, In, Like);
filter!(UgcName, UGC_NAME, "ugc_name", Eq, NotEq, In, Like);
filter!(PresentationOption, PRESENTATION_OPTION, "presentation_option", Eq, NotEq, In, Cmp, Bit);
filter!(SubmissionOption, SUBMISSION_OPTION, "submission_option", Eq, NotEq, In, Cmp, Bit);
filter!(CurationOption, CURATION_OPTION, "curation_option", Eq, NotEq, In, Cmp, Bit);
filter!(CommunityOptions, COMMUNITY_OPTIONS, "community_options", Eq, NotEq, In, Cmp, Bit);
filter!(RevenueOptions, REVENUE_OPTIONS, "revenue_options", Eq, NotEq, In, Cmp, Bit);
filter!(ApiAccessOptions, API_ACCESS_OPTIONS, "api_access_options", Eq, NotEq, In, Cmp, Bit);
filter!(MaturityOptions, MATURITY_OPTIONS, "maturity_options", Eq, NotEq, In, Cmp, Bit);
}
pub struct AddTagsOptions {
name: String,
kind: TagType,
hidden: bool,
locked: bool,
tags: Vec<String>,
}
impl AddTagsOptions {
pub fn new<S: Into<String>>(name: S, kind: TagType, tags: &[String]) -> Self {
Self {
name: name.into(),
kind,
hidden: false,
locked: false,
tags: tags.to_vec(),
}
}
pub fn hidden(self, value: bool) -> Self {
Self {
hidden: value,
..self
}
}
pub fn locked(self, value: bool) -> Self {
Self {
locked: value,
..self
}
}
}
#[doc(hidden)]
impl serde::ser::Serialize for AddTagsOptions {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
use serde::ser::SerializeMap;
let len = 2 + usize::from(self.hidden) + usize::from(self.locked) + self.tags.len();
let mut map = serializer.serialize_map(Some(len))?;
map.serialize_entry("name", &self.name)?;
map.serialize_entry("type", &self.kind)?;
if self.hidden {
map.serialize_entry("hidden", &self.hidden)?;
}
if self.locked {
map.serialize_entry("locked", &self.locked)?;
}
for t in &self.tags {
map.serialize_entry("tags[]", t)?;
}
map.end()
}
}
pub struct DeleteTagsOptions {
name: String,
tags: Option<Vec<String>>,
}
impl DeleteTagsOptions {
pub fn all<S: Into<String>>(name: S) -> Self {
Self {
name: name.into(),
tags: None,
}
}
pub fn some<S: Into<String>>(name: S, tags: &[String]) -> Self {
Self {
name: name.into(),
tags: if tags.is_empty() {
None
} else {
Some(tags.to_vec())
},
}
}
}
#[doc(hidden)]
impl serde::ser::Serialize for DeleteTagsOptions {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
use serde::ser::SerializeMap;
let len = self.tags.as_ref().map_or(1, Vec::len);
let mut map = serializer.serialize_map(Some(len + 1))?;
map.serialize_entry("name", &self.name)?;
if let Some(ref tags) = self.tags {
for t in tags {
map.serialize_entry("tags[]", t)?;
}
} else {
map.serialize_entry("tags[]", "")?;
}
map.end()
}
}
#[derive(Default)]
pub struct EditMediaOptions {
logo: Option<FileSource>,
icon: Option<FileSource>,
header: Option<FileSource>,
}
impl EditMediaOptions {
#[must_use]
pub fn logo<P: AsRef<Path>>(self, logo: P) -> Self {
let logo = logo.as_ref();
let filename = logo
.file_name()
.and_then(OsStr::to_str)
.map_or_else(String::new, ToString::to_string);
Self {
logo: Some(FileSource::new_from_file(logo, filename, IMAGE_STAR)),
..self
}
}
#[must_use]
pub fn icon<P: AsRef<Path>>(self, icon: P) -> Self {
let icon = icon.as_ref();
let filename = icon
.file_name()
.and_then(OsStr::to_str)
.map_or_else(String::new, ToString::to_string);
Self {
icon: Some(FileSource::new_from_file(icon, filename, IMAGE_STAR)),
..self
}
}
#[must_use]
pub fn header<P: AsRef<Path>>(self, header: P) -> Self {
let header = header.as_ref();
let filename = header
.file_name()
.and_then(OsStr::to_str)
.map_or_else(String::new, ToString::to_string);
Self {
header: Some(FileSource::new_from_file(header, filename, IMAGE_STAR)),
..self
}
}
}
#[doc(hidden)]
impl From<EditMediaOptions> for Form {
fn from(opts: EditMediaOptions) -> Form {
let mut form = Form::new();
if let Some(logo) = opts.logo {
form = form.part("logo", logo.into());
}
if let Some(icon) = opts.icon {
form = form.part("icon", icon.into());
}
if let Some(header) = opts.header {
form = form.part("header", header.into());
}
form
}
}