use super::*;
use std::error;
use std::ffi::{CStr, CString};
use std::fmt;
use std::marker;
use std::mem;
use std::os::raw::c_char;
use std::path::Path;
pub const RESULTS_PER_PAGE: u32 = sys::kNumUGCResultsPerPage as u32;
pub struct UGC<Manager> {
pub(crate) ugc: *mut sys::ISteamUGC,
pub(crate) inner: Arc<Inner<Manager>>,
}
const CALLBACK_BASE_ID: i32 = 3400;
const CALLBACK_REMOTE_STORAGE_BASE_ID: i32 = 1300;
#[allow(non_upper_case_globals)]
const UGCQueryHandleInvalid: u64 = 0xffffffffffffffff;
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PublishedFileId(pub u64);
impl From<u64> for PublishedFileId {
fn from(id: u64) -> Self {
PublishedFileId(id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UGCType {
Items,
ItemsMtx,
ItemsReadyToUse,
Collections,
Artwork,
Videos,
Screenshots,
AllGuides,
WebGuides,
IntegratedGuides,
UsableInGame,
ControllerBindings,
GameManagedItems,
All,
}
impl Into<sys::EUGCMatchingUGCType> for UGCType {
fn into(self) -> sys::EUGCMatchingUGCType {
match self {
UGCType::Items => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_Items,
UGCType::ItemsMtx => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_Items_Mtx,
UGCType::ItemsReadyToUse => {
sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_Items_ReadyToUse
}
UGCType::Collections => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_Collections,
UGCType::Artwork => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_Artwork,
UGCType::Videos => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_Videos,
UGCType::Screenshots => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_Screenshots,
UGCType::AllGuides => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_AllGuides,
UGCType::WebGuides => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_WebGuides,
UGCType::IntegratedGuides => {
sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_IntegratedGuides
}
UGCType::UsableInGame => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_UsableInGame,
UGCType::ControllerBindings => {
sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_ControllerBindings
}
UGCType::GameManagedItems => {
sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_GameManagedItems
}
UGCType::All => sys::EUGCMatchingUGCType::k_EUGCMatchingUGCType_All,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UGCQueryType {
RankedByVote,
RankedByPublicationDate,
AcceptedForGameRankedByAcceptanceDate,
RankedByTrend,
FavoritedByFriendsRankedByPublicationDate,
CreatedByFriendsRankedByPublicationDate,
RankedByNumTimesReported,
CreatedByFollowedUsersRankedByPublicationDate,
NotYetRated,
RankedByTotalVotesAsc,
RankedByVotesUp,
RankedByTextSearch,
RankedByTotalUniqueSubscriptions,
RankedByPlaytimeTrend,
RankedByTotalPlaytime,
RankedByAveragePlaytimeTrend,
RankedByLifetimeAveragePlaytime,
RankedByPlaytimeSessionsTrend,
RankedByLifetimePlaytimeSessions,
RankedByLastUpdatedDate,
}
impl Into<sys::EUGCQuery> for UGCQueryType {
fn into(self) -> sys::EUGCQuery {
match self {
UGCQueryType::RankedByVote => sys::EUGCQuery::k_EUGCQuery_RankedByVote,
UGCQueryType::RankedByPublicationDate => {
sys::EUGCQuery::k_EUGCQuery_RankedByPublicationDate
}
UGCQueryType::AcceptedForGameRankedByAcceptanceDate => {
sys::EUGCQuery::k_EUGCQuery_AcceptedForGameRankedByAcceptanceDate
}
UGCQueryType::RankedByTrend => sys::EUGCQuery::k_EUGCQuery_RankedByTrend,
UGCQueryType::FavoritedByFriendsRankedByPublicationDate => {
sys::EUGCQuery::k_EUGCQuery_FavoritedByFriendsRankedByPublicationDate
}
UGCQueryType::CreatedByFriendsRankedByPublicationDate => {
sys::EUGCQuery::k_EUGCQuery_CreatedByFriendsRankedByPublicationDate
}
UGCQueryType::RankedByNumTimesReported => {
sys::EUGCQuery::k_EUGCQuery_RankedByNumTimesReported
}
UGCQueryType::CreatedByFollowedUsersRankedByPublicationDate => {
sys::EUGCQuery::k_EUGCQuery_CreatedByFollowedUsersRankedByPublicationDate
}
UGCQueryType::NotYetRated => sys::EUGCQuery::k_EUGCQuery_NotYetRated,
UGCQueryType::RankedByTotalVotesAsc => {
sys::EUGCQuery::k_EUGCQuery_RankedByTotalVotesAsc
}
UGCQueryType::RankedByVotesUp => sys::EUGCQuery::k_EUGCQuery_RankedByVotesUp,
UGCQueryType::RankedByTextSearch => sys::EUGCQuery::k_EUGCQuery_RankedByTextSearch,
UGCQueryType::RankedByTotalUniqueSubscriptions => {
sys::EUGCQuery::k_EUGCQuery_RankedByTotalUniqueSubscriptions
}
UGCQueryType::RankedByPlaytimeTrend => {
sys::EUGCQuery::k_EUGCQuery_RankedByPlaytimeTrend
}
UGCQueryType::RankedByTotalPlaytime => {
sys::EUGCQuery::k_EUGCQuery_RankedByTotalPlaytime
}
UGCQueryType::RankedByAveragePlaytimeTrend => {
sys::EUGCQuery::k_EUGCQuery_RankedByAveragePlaytimeTrend
}
UGCQueryType::RankedByLifetimeAveragePlaytime => {
sys::EUGCQuery::k_EUGCQuery_RankedByLifetimeAveragePlaytime
}
UGCQueryType::RankedByPlaytimeSessionsTrend => {
sys::EUGCQuery::k_EUGCQuery_RankedByPlaytimeSessionsTrend
}
UGCQueryType::RankedByLifetimePlaytimeSessions => {
sys::EUGCQuery::k_EUGCQuery_RankedByLifetimePlaytimeSessions
}
UGCQueryType::RankedByLastUpdatedDate => {
sys::EUGCQuery::k_EUGCQuery_RankedByLastUpdatedDate
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileType {
Community,
Microtransaction,
Collection,
Art,
Video,
Screenshot,
Game,
Software,
Concept,
WebGuide,
IntegratedGuide,
Merch,
ControllerBinding,
SteamworksAccessInvite,
SteamVideo,
GameManagedItem,
}
impl Into<sys::EWorkshopFileType> for FileType {
fn into(self) -> sys::EWorkshopFileType {
match self {
FileType::Community => sys::EWorkshopFileType::k_EWorkshopFileTypeCommunity,
FileType::Microtransaction => {
sys::EWorkshopFileType::k_EWorkshopFileTypeMicrotransaction
}
FileType::Collection => sys::EWorkshopFileType::k_EWorkshopFileTypeCollection,
FileType::Art => sys::EWorkshopFileType::k_EWorkshopFileTypeArt,
FileType::Video => sys::EWorkshopFileType::k_EWorkshopFileTypeVideo,
FileType::Screenshot => sys::EWorkshopFileType::k_EWorkshopFileTypeScreenshot,
FileType::Game => sys::EWorkshopFileType::k_EWorkshopFileTypeGame,
FileType::Software => sys::EWorkshopFileType::k_EWorkshopFileTypeSoftware,
FileType::Concept => sys::EWorkshopFileType::k_EWorkshopFileTypeConcept,
FileType::WebGuide => sys::EWorkshopFileType::k_EWorkshopFileTypeWebGuide,
FileType::IntegratedGuide => sys::EWorkshopFileType::k_EWorkshopFileTypeIntegratedGuide,
FileType::Merch => sys::EWorkshopFileType::k_EWorkshopFileTypeMerch,
FileType::ControllerBinding => {
sys::EWorkshopFileType::k_EWorkshopFileTypeControllerBinding
}
FileType::SteamworksAccessInvite => {
sys::EWorkshopFileType::k_EWorkshopFileTypeSteamworksAccessInvite
}
FileType::SteamVideo => sys::EWorkshopFileType::k_EWorkshopFileTypeSteamVideo,
FileType::GameManagedItem => sys::EWorkshopFileType::k_EWorkshopFileTypeGameManagedItem,
}
}
}
impl From<sys::EWorkshopFileType> for FileType {
fn from(file_type: sys::EWorkshopFileType) -> FileType {
match file_type {
sys::EWorkshopFileType::k_EWorkshopFileTypeCommunity => FileType::Community,
sys::EWorkshopFileType::k_EWorkshopFileTypeMicrotransaction => {
FileType::Microtransaction
}
sys::EWorkshopFileType::k_EWorkshopFileTypeCollection => FileType::Collection,
sys::EWorkshopFileType::k_EWorkshopFileTypeArt => FileType::Art,
sys::EWorkshopFileType::k_EWorkshopFileTypeVideo => FileType::Video,
sys::EWorkshopFileType::k_EWorkshopFileTypeScreenshot => FileType::Screenshot,
sys::EWorkshopFileType::k_EWorkshopFileTypeGame => FileType::Game,
sys::EWorkshopFileType::k_EWorkshopFileTypeSoftware => FileType::Software,
sys::EWorkshopFileType::k_EWorkshopFileTypeConcept => FileType::Concept,
sys::EWorkshopFileType::k_EWorkshopFileTypeWebGuide => FileType::WebGuide,
sys::EWorkshopFileType::k_EWorkshopFileTypeIntegratedGuide => FileType::IntegratedGuide,
sys::EWorkshopFileType::k_EWorkshopFileTypeMerch => FileType::Merch,
sys::EWorkshopFileType::k_EWorkshopFileTypeControllerBinding => {
FileType::ControllerBinding
}
sys::EWorkshopFileType::k_EWorkshopFileTypeSteamworksAccessInvite => {
FileType::SteamworksAccessInvite
}
sys::EWorkshopFileType::k_EWorkshopFileTypeSteamVideo => FileType::SteamVideo,
sys::EWorkshopFileType::k_EWorkshopFileTypeGameManagedItem => FileType::GameManagedItem,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppIDs {
CreatorAppId(AppId),
ConsumerAppId(AppId),
Both { creator: AppId, consumer: AppId },
}
impl AppIDs {
pub fn creator_app_id(&self) -> Option<AppId> {
match self {
AppIDs::CreatorAppId(v) => Some(*v),
AppIDs::ConsumerAppId(_) => None,
AppIDs::Both { creator, .. } => Some(*creator),
}
}
pub fn consumer_app_id(&self) -> Option<AppId> {
match self {
AppIDs::CreatorAppId(_) => None,
AppIDs::ConsumerAppId(v) => Some(*v),
AppIDs::Both { consumer, .. } => Some(*consumer),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserListOrder {
CreationOrderAsc,
CreationOrderDesc,
TitleAsc,
LastUpdatedDesc,
SubscriptionDateDesc,
VoteScoreDesc,
ForModeration,
}
impl Into<sys::EUserUGCListSortOrder> for UserListOrder {
fn into(self) -> sys::EUserUGCListSortOrder {
match self {
UserListOrder::CreationOrderAsc => {
sys::EUserUGCListSortOrder::k_EUserUGCListSortOrder_CreationOrderAsc
}
UserListOrder::CreationOrderDesc => {
sys::EUserUGCListSortOrder::k_EUserUGCListSortOrder_CreationOrderDesc
}
UserListOrder::TitleAsc => sys::EUserUGCListSortOrder::k_EUserUGCListSortOrder_TitleAsc,
UserListOrder::LastUpdatedDesc => {
sys::EUserUGCListSortOrder::k_EUserUGCListSortOrder_LastUpdatedDesc
}
UserListOrder::SubscriptionDateDesc => {
sys::EUserUGCListSortOrder::k_EUserUGCListSortOrder_SubscriptionDateDesc
}
UserListOrder::VoteScoreDesc => {
sys::EUserUGCListSortOrder::k_EUserUGCListSortOrder_VoteScoreDesc
}
UserListOrder::ForModeration => {
sys::EUserUGCListSortOrder::k_EUserUGCListSortOrder_ForModeration
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserList {
Published,
VotedOn,
VotedUp,
VotedDown,
#[deprecated(note = "Deprecated in Steam API")]
WillVoteLater,
Favorited,
Subscribed,
UsedOrPlayed,
Followed,
}
impl Into<sys::EUserUGCList> for UserList {
#[allow(deprecated)]
fn into(self) -> sys::EUserUGCList {
match self {
UserList::Published => sys::EUserUGCList::k_EUserUGCList_Published,
UserList::VotedOn => sys::EUserUGCList::k_EUserUGCList_VotedOn,
UserList::VotedUp => sys::EUserUGCList::k_EUserUGCList_VotedUp,
UserList::VotedDown => sys::EUserUGCList::k_EUserUGCList_VotedDown,
UserList::WillVoteLater => sys::EUserUGCList::k_EUserUGCList_WillVoteLater,
UserList::Favorited => sys::EUserUGCList::k_EUserUGCList_Favorited,
UserList::Subscribed => sys::EUserUGCList::k_EUserUGCList_Subscribed,
UserList::UsedOrPlayed => sys::EUserUGCList::k_EUserUGCList_UsedOrPlayed,
UserList::Followed => sys::EUserUGCList::k_EUserUGCList_Followed,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UGCStatisticType {
Subscriptions,
Favorites,
Followers,
UniqueSubscriptions,
UniqueFavorites,
UniqueFollowers,
UniqueWebsiteViews,
Reports,
SecondsPlayed,
PlaytimeSessions,
Comments,
SecondsPlayedDuringTimePeriod,
PlaytimeSessionsDuringTimePeriod,
}
impl Into<sys::EItemStatistic> for UGCStatisticType {
fn into(self) -> sys::EItemStatistic {
match self {
UGCStatisticType::Subscriptions => {
sys::EItemStatistic::k_EItemStatistic_NumSubscriptions
}
UGCStatisticType::Favorites => sys::EItemStatistic::k_EItemStatistic_NumFavorites,
UGCStatisticType::Followers => sys::EItemStatistic::k_EItemStatistic_NumFollowers,
UGCStatisticType::UniqueSubscriptions => {
sys::EItemStatistic::k_EItemStatistic_NumUniqueSubscriptions
}
UGCStatisticType::UniqueFavorites => {
sys::EItemStatistic::k_EItemStatistic_NumUniqueFavorites
}
UGCStatisticType::UniqueFollowers => {
sys::EItemStatistic::k_EItemStatistic_NumUniqueFollowers
}
UGCStatisticType::UniqueWebsiteViews => {
sys::EItemStatistic::k_EItemStatistic_NumUniqueWebsiteViews
}
UGCStatisticType::Reports => sys::EItemStatistic::k_EItemStatistic_ReportScore,
UGCStatisticType::SecondsPlayed => {
sys::EItemStatistic::k_EItemStatistic_NumSecondsPlayed
}
UGCStatisticType::PlaytimeSessions => {
sys::EItemStatistic::k_EItemStatistic_NumPlaytimeSessions
}
UGCStatisticType::Comments => sys::EItemStatistic::k_EItemStatistic_NumComments,
UGCStatisticType::SecondsPlayedDuringTimePeriod => {
sys::EItemStatistic::k_EItemStatistic_NumSecondsPlayedDuringTimePeriod
}
UGCStatisticType::PlaytimeSessionsDuringTimePeriod => {
sys::EItemStatistic::k_EItemStatistic_NumPlaytimeSessionsDuringTimePeriod
}
}
}
}
bitflags! {
pub struct ItemState: u32 {
const NONE = 0;
const SUBSCRIBED = 1;
const LEGACY_ITEM = 2;
const INSTALLED = 4;
const NEEDS_UPDATE = 8;
const DOWNLOADING = 16;
const DOWNLOAD_PENDING = 32;
}
}
pub enum UGCContentDescriptorID {
NudityOrSexualContent = 1,
FrequentViolenceOrGore = 2,
AdultOnlySexualContent = 3,
GratuitousSexualContent = 4,
AnyMatureContent = 5,
}
impl Into<sys::EUGCContentDescriptorID> for UGCContentDescriptorID {
fn into(self) -> sys::EUGCContentDescriptorID {
match self {
UGCContentDescriptorID::NudityOrSexualContent => {
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_NudityOrSexualContent
}
UGCContentDescriptorID::FrequentViolenceOrGore => {
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_FrequentViolenceOrGore
}
UGCContentDescriptorID::AdultOnlySexualContent => {
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_AdultOnlySexualContent
}
UGCContentDescriptorID::GratuitousSexualContent => {
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_GratuitousSexualContent
}
UGCContentDescriptorID::AnyMatureContent => {
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_AnyMatureContent
}
}
}
}
impl From<sys::EUGCContentDescriptorID> for UGCContentDescriptorID {
fn from(content_descriptor_id: sys::EUGCContentDescriptorID) -> UGCContentDescriptorID {
match content_descriptor_id {
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_NudityOrSexualContent => {
UGCContentDescriptorID::NudityOrSexualContent
}
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_FrequentViolenceOrGore => {
UGCContentDescriptorID::FrequentViolenceOrGore
}
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_AdultOnlySexualContent => {
UGCContentDescriptorID::AdultOnlySexualContent
}
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_GratuitousSexualContent => {
UGCContentDescriptorID::GratuitousSexualContent
}
sys::EUGCContentDescriptorID::k_EUGCContentDescriptor_AnyMatureContent => {
UGCContentDescriptorID::AnyMatureContent
}
_ => unreachable!(),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DownloadItemResult {
pub app_id: AppId,
pub published_file_id: PublishedFileId,
pub error: Option<SteamError>,
}
unsafe impl Callback for DownloadItemResult {
const ID: i32 = CALLBACK_BASE_ID + 6;
const SIZE: i32 = ::std::mem::size_of::<sys::DownloadItemResult_t>() as i32;
unsafe fn from_raw(raw: *mut c_void) -> Self {
let val = &mut *(raw as *mut sys::DownloadItemResult_t);
DownloadItemResult {
app_id: AppId(val.m_unAppID),
published_file_id: PublishedFileId(val.m_nPublishedFileId),
error: match val.m_eResult {
sys::EResult::k_EResultOK => None,
error => Some(error.into()),
},
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct InstallInfo {
pub folder: String,
pub size_on_disk: u64,
pub timestamp: u32,
}
impl<Manager> UGC<Manager> {
pub fn suspend_downloads(&self, suspend: bool) {
unsafe {
sys::SteamAPI_ISteamUGC_SuspendDownloads(self.ugc, suspend);
}
}
pub fn create_item<F>(&self, app_id: AppId, file_type: FileType, cb: F)
where
F: FnOnce(Result<(PublishedFileId, bool), SteamError>) + 'static + Send,
{
unsafe {
let api_call = sys::SteamAPI_ISteamUGC_CreateItem(self.ugc, app_id.0, file_type.into());
register_call_result::<sys::CreateItemResult_t, _, _>(
&self.inner,
api_call,
CALLBACK_BASE_ID + 3,
move |v, io_error| {
cb(if io_error {
Err(SteamError::IOFailure)
} else if v.m_eResult != sys::EResult::k_EResultOK {
Err(v.m_eResult.into())
} else {
Ok((
PublishedFileId(v.m_nPublishedFileId),
v.m_bUserNeedsToAcceptWorkshopLegalAgreement,
))
})
},
);
}
}
#[must_use]
pub fn start_item_update(
&self,
app_id: AppId,
file_id: PublishedFileId,
) -> UpdateHandle<Manager> {
unsafe {
let handle = sys::SteamAPI_ISteamUGC_StartItemUpdate(self.ugc, app_id.0, file_id.0);
UpdateHandle {
ugc: self.ugc,
inner: self.inner.clone(),
handle,
}
}
}
pub fn subscribe_item<F>(&self, published_file_id: PublishedFileId, cb: F)
where
F: FnOnce(Result<(), SteamError>) + 'static + Send,
{
unsafe {
let api_call = sys::SteamAPI_ISteamUGC_SubscribeItem(self.ugc, published_file_id.0);
register_call_result::<sys::RemoteStorageSubscribePublishedFileResult_t, _, _>(
&self.inner,
api_call,
CALLBACK_REMOTE_STORAGE_BASE_ID + 13,
move |v, io_error| {
cb(if io_error {
Err(SteamError::IOFailure)
} else if v.m_eResult != sys::EResult::k_EResultOK {
Err(v.m_eResult.into())
} else {
Ok(())
})
},
);
}
}
pub fn unsubscribe_item<F>(&self, published_file_id: PublishedFileId, cb: F)
where
F: FnOnce(Result<(), SteamError>) + 'static + Send,
{
unsafe {
let api_call = sys::SteamAPI_ISteamUGC_UnsubscribeItem(self.ugc, published_file_id.0);
register_call_result::<sys::RemoteStorageUnsubscribePublishedFileResult_t, _, _>(
&self.inner,
api_call,
CALLBACK_REMOTE_STORAGE_BASE_ID + 15,
move |v, io_error| {
cb(if io_error {
Err(SteamError::IOFailure)
} else if v.m_eResult != sys::EResult::k_EResultOK {
Err(v.m_eResult.into())
} else {
Ok(())
})
},
);
}
}
pub fn subscribed_items(&self) -> Vec<PublishedFileId> {
unsafe {
let count = sys::SteamAPI_ISteamUGC_GetNumSubscribedItems(self.ugc);
let mut data: Vec<sys::PublishedFileId_t> = vec![0; count as usize];
let gotten_count =
sys::SteamAPI_ISteamUGC_GetSubscribedItems(self.ugc, data.as_mut_ptr(), count);
debug_assert!(count == gotten_count);
data.into_iter().map(|v| PublishedFileId(v)).collect()
}
}
pub fn item_state(&self, item: PublishedFileId) -> ItemState {
unsafe {
let state = sys::SteamAPI_ISteamUGC_GetItemState(self.ugc, item.0);
ItemState::from_bits_truncate(state)
}
}
pub fn item_download_info(&self, item: PublishedFileId) -> Option<(u64, u64)> {
unsafe {
let mut current = 0u64;
let mut total = 0u64;
if sys::SteamAPI_ISteamUGC_GetItemDownloadInfo(
self.ugc,
item.0,
&mut current,
&mut total,
) {
Some((current, total))
} else {
None
}
}
}
pub fn item_install_info(&self, item: PublishedFileId) -> Option<InstallInfo> {
unsafe {
let mut size_on_disk = 0u64;
let mut folder = [0 as c_char; 4096];
let mut timestamp = 0u32;
if sys::SteamAPI_ISteamUGC_GetItemInstallInfo(
self.ugc,
item.0,
&mut size_on_disk,
folder.as_mut_ptr(),
folder.len() as _,
&mut timestamp,
) {
Some(InstallInfo {
folder: CStr::from_ptr(folder.as_ptr() as *const _)
.to_string_lossy()
.into_owned(),
size_on_disk,
timestamp,
})
} else {
None
}
}
}
pub fn download_item(&self, item: PublishedFileId, high_priority: bool) -> bool {
unsafe { sys::SteamAPI_ISteamUGC_DownloadItem(self.ugc, item.0, high_priority) }
}
pub fn query_all(
&self,
query_type: UGCQueryType,
item_type: UGCType,
appids: AppIDs,
page: u32,
) -> Result<QueryHandle<Manager>, CreateQueryError> {
let handle = unsafe {
sys::SteamAPI_ISteamUGC_CreateQueryAllUGCRequestPage(
self.ugc,
query_type.into(),
item_type.into(),
appids.creator_app_id().unwrap_or(AppId(0)).0,
appids.consumer_app_id().unwrap_or(AppId(0)).0,
page,
)
};
if handle == UGCQueryHandleInvalid {
return Err(CreateQueryError);
}
Ok(QueryHandle {
ugc: self.ugc,
inner: Arc::clone(&self.inner),
handle: Some(handle),
})
}
pub fn query_user(
&self,
account: AccountId,
list_type: UserList,
item_type: UGCType,
sort_order: UserListOrder,
appids: AppIDs,
page: u32,
) -> Result<QueryHandle<Manager>, CreateQueryError> {
let res = unsafe {
sys::SteamAPI_ISteamUGC_CreateQueryUserUGCRequest(
self.ugc,
account.0,
list_type.into(),
item_type.into(),
sort_order.into(),
appids.creator_app_id().unwrap_or(AppId(0)).0,
appids.consumer_app_id().unwrap_or(AppId(0)).0,
page,
)
};
if res == UGCQueryHandleInvalid {
return Err(CreateQueryError);
}
Ok(QueryHandle {
ugc: self.ugc,
inner: Arc::clone(&self.inner),
handle: Some(res),
})
}
pub fn query_items(
&self,
mut items: Vec<PublishedFileId>,
) -> Result<QueryHandle<Manager>, CreateQueryError> {
debug_assert!(items.len() > 0);
let res = unsafe {
sys::SteamAPI_ISteamUGC_CreateQueryUGCDetailsRequest(
self.ugc,
items.as_mut_ptr() as _,
items.len() as _,
)
};
if res == UGCQueryHandleInvalid {
return Err(CreateQueryError);
}
Ok(QueryHandle {
ugc: self.ugc,
inner: Arc::clone(&self.inner),
handle: Some(res),
})
}
pub fn query_item(
&self,
item: PublishedFileId,
) -> Result<QueryHandle<Manager>, CreateQueryError> {
let mut items = vec![item];
let res = unsafe {
sys::SteamAPI_ISteamUGC_CreateQueryUGCDetailsRequest(
self.ugc,
items.as_mut_ptr() as _,
1 as _,
)
};
if res == UGCQueryHandleInvalid {
return Err(CreateQueryError);
}
Ok(QueryHandle {
ugc: self.ugc,
inner: Arc::clone(&self.inner),
handle: Some(res),
})
}
pub fn delete_item<F>(&self, published_file_id: PublishedFileId, cb: F)
where
F: FnOnce(Result<(), SteamError>) + 'static + Send,
{
unsafe {
let api_call = sys::SteamAPI_ISteamUGC_DeleteItem(self.ugc, published_file_id.0);
register_call_result::<sys::DownloadItemResult_t, _, _>(
&self.inner,
api_call,
CALLBACK_REMOTE_STORAGE_BASE_ID + 17,
move |v, io_error| {
cb(if io_error {
Err(SteamError::IOFailure)
} else if v.m_eResult != sys::EResult::k_EResultNone
&& v.m_eResult != sys::EResult::k_EResultOK
{
Err(v.m_eResult.into())
} else {
Ok(())
})
},
);
}
}
}
impl UGC<ServerManager> {
pub fn init_for_game_server(&self, workshop_depot: sys::DepotId_t, folder: &str) -> bool {
unsafe {
let folder = CString::new(folder).unwrap();
sys::SteamAPI_ISteamUGC_BInitWorkshopForGameServer(
self.ugc,
workshop_depot,
folder.as_ptr(),
)
}
}
}
pub struct UpdateHandle<Manager> {
ugc: *mut sys::ISteamUGC,
inner: Arc<Inner<Manager>>,
handle: sys::UGCUpdateHandle_t,
}
impl<Manager> UpdateHandle<Manager> {
#[must_use]
pub fn title(self, title: &str) -> Self {
unsafe {
let title = CString::new(title).unwrap();
assert!(sys::SteamAPI_ISteamUGC_SetItemTitle(
self.ugc,
self.handle,
title.as_ptr()
));
}
self
}
#[must_use]
pub fn description(self, description: &str) -> Self {
unsafe {
let description = CString::new(description).unwrap();
assert!(sys::SteamAPI_ISteamUGC_SetItemDescription(
self.ugc,
self.handle,
description.as_ptr()
));
}
self
}
#[must_use]
pub fn preview_path(self, path: &Path) -> Self {
unsafe {
let path = path.canonicalize().unwrap();
let preview_path = CString::new(&*path.to_string_lossy()).unwrap();
assert!(sys::SteamAPI_ISteamUGC_SetItemPreview(
self.ugc,
self.handle,
preview_path.as_ptr()
));
}
self
}
#[must_use]
pub fn content_path(self, path: &Path) -> Self {
unsafe {
let path = path.canonicalize().unwrap();
let content_path = CString::new(&*path.to_string_lossy()).unwrap();
assert!(sys::SteamAPI_ISteamUGC_SetItemContent(
self.ugc,
self.handle,
content_path.as_ptr()
));
}
self
}
#[must_use]
pub fn metadata(self, metadata: &str) -> Self {
unsafe {
let metadata = CString::new(metadata).unwrap();
assert!(sys::SteamAPI_ISteamUGC_SetItemMetadata(
self.ugc,
self.handle,
metadata.as_ptr()
));
}
self
}
pub fn visibility(self, visibility: remote_storage::PublishedFileVisibility) -> Self {
unsafe {
assert!(sys::SteamAPI_ISteamUGC_SetItemVisibility(
self.ugc,
self.handle,
visibility.into()
));
}
self
}
pub fn tags<S: AsRef<str>>(self, tags: Vec<S>, allow_admin_tags: bool) -> Self {
unsafe {
let mut tags = SteamParamStringArray::new(&tags);
assert!(sys::SteamAPI_ISteamUGC_SetItemTags(
self.ugc,
self.handle,
&tags.as_raw(),
allow_admin_tags
));
}
self
}
pub fn add_key_value_tag(self, key: &str, value: &str) -> Self {
unsafe {
let key = CString::new(key).unwrap();
let value = CString::new(value).unwrap();
assert!(sys::SteamAPI_ISteamUGC_AddItemKeyValueTag(
self.ugc,
self.handle,
key.as_ptr(),
value.as_ptr()
));
}
self
}
pub fn remove_key_value_tag(self, key: &str) -> Self {
unsafe {
let key = CString::new(key).unwrap();
assert!(sys::SteamAPI_ISteamUGC_RemoveItemKeyValueTags(
self.ugc,
self.handle,
key.as_ptr()
));
}
self
}
pub fn add_content_descriptor(self, desc_id: UGCContentDescriptorID) -> Self {
unsafe {
assert!(sys::SteamAPI_ISteamUGC_AddContentDescriptor(
self.ugc,
self.handle,
desc_id.into(),
));
}
self
}
pub fn remove_content_descriptor(self, desc_id: UGCContentDescriptorID) -> Self {
unsafe {
assert!(sys::SteamAPI_ISteamUGC_RemoveContentDescriptor(
self.ugc,
self.handle,
desc_id.into()
));
}
self
}
pub fn remove_all_key_value_tags(self) -> Self {
unsafe {
assert!(sys::SteamAPI_ISteamUGC_RemoveAllItemKeyValueTags(
self.ugc,
self.handle
));
}
self
}
pub fn submit<F>(self, change_note: Option<&str>, cb: F) -> UpdateWatchHandle<Manager>
where
F: FnOnce(Result<(PublishedFileId, bool), SteamError>) + 'static + Send,
{
use std::ptr;
unsafe {
let change_note = change_note.and_then(|v| CString::new(v).ok());
let note = change_note.as_ref().map_or(ptr::null(), |v| v.as_ptr());
let api_call = sys::SteamAPI_ISteamUGC_SubmitItemUpdate(self.ugc, self.handle, note);
register_call_result::<sys::SubmitItemUpdateResult_t, _, _>(
&self.inner,
api_call,
CALLBACK_BASE_ID + 4,
move |v, io_error| {
cb(if io_error {
Err(SteamError::IOFailure)
} else if v.m_eResult != sys::EResult::k_EResultOK {
Err(v.m_eResult.into())
} else {
Ok((
PublishedFileId(v.m_nPublishedFileId),
v.m_bUserNeedsToAcceptWorkshopLegalAgreement,
))
})
},
);
}
UpdateWatchHandle {
ugc: self.ugc,
_inner: self.inner,
handle: self.handle,
}
}
}
pub struct UpdateWatchHandle<Manager> {
ugc: *mut sys::ISteamUGC,
_inner: Arc<Inner<Manager>>,
handle: sys::UGCUpdateHandle_t,
}
unsafe impl<Manager> Send for UpdateWatchHandle<Manager> {}
unsafe impl<Manager> Sync for UpdateWatchHandle<Manager> {}
impl<Manager> UpdateWatchHandle<Manager> {
pub fn progress(&self) -> (UpdateStatus, u64, u64) {
unsafe {
let mut progress = 0;
let mut total = 0;
let status = sys::SteamAPI_ISteamUGC_GetItemUpdateProgress(
self.ugc,
self.handle,
&mut progress,
&mut total,
);
let status = match status {
sys::EItemUpdateStatus::k_EItemUpdateStatusInvalid => UpdateStatus::Invalid,
sys::EItemUpdateStatus::k_EItemUpdateStatusPreparingConfig => {
UpdateStatus::PreparingConfig
}
sys::EItemUpdateStatus::k_EItemUpdateStatusPreparingContent => {
UpdateStatus::PreparingContent
}
sys::EItemUpdateStatus::k_EItemUpdateStatusUploadingContent => {
UpdateStatus::UploadingContent
}
sys::EItemUpdateStatus::k_EItemUpdateStatusUploadingPreviewFile => {
UpdateStatus::UploadingPreviewFile
}
sys::EItemUpdateStatus::k_EItemUpdateStatusCommittingChanges => {
UpdateStatus::CommittingChanges
}
_ => unreachable!(),
};
(status, progress, total)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UpdateStatus {
Invalid,
PreparingConfig,
PreparingContent,
UploadingContent,
UploadingPreviewFile,
CommittingChanges,
}
pub struct QueryHandle<Manager> {
ugc: *mut sys::ISteamUGC,
inner: Arc<Inner<Manager>>,
handle: Option<sys::UGCQueryHandle_t>,
}
impl<Manager> Drop for QueryHandle<Manager> {
fn drop(&mut self) {
if let Some(handle) = self.handle.as_mut() {
unsafe {
sys::SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(self.ugc, *handle);
}
}
}
}
impl<Manager> QueryHandle<Manager> {
pub fn exclude_tag(self, tag: &str) -> Self {
let cstr = CString::new(tag)
.expect("String passed to exclude_tag could not be converted to a c string");
let ok = unsafe {
sys::SteamAPI_ISteamUGC_AddExcludedTag(self.ugc, self.handle.unwrap(), cstr.as_ptr())
};
debug_assert!(ok);
self
}
pub fn require_tag(self, tag: &str) -> Self {
let cstr = CString::new(tag)
.expect("String passed to require_tag could not be converted to a c string");
let ok = unsafe {
sys::SteamAPI_ISteamUGC_AddRequiredTag(self.ugc, self.handle.unwrap(), cstr.as_ptr())
};
debug_assert!(ok);
self
}
pub fn any_required(self, any: bool) -> Self {
let ok =
unsafe { sys::SteamAPI_ISteamUGC_SetMatchAnyTag(self.ugc, self.handle.unwrap(), any) };
debug_assert!(ok);
self
}
pub fn language(self, language: &str) -> Self {
let cstr = CString::new(language)
.expect("String passed to language could not be converted to a c string");
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetLanguage(self.ugc, self.handle.unwrap(), cstr.as_ptr())
};
debug_assert!(ok);
self
}
pub fn allow_cached_response(self, max_age_s: u32) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetAllowCachedResponse(
self.ugc,
self.handle.unwrap(),
max_age_s,
)
};
debug_assert!(ok);
self
}
pub fn include_long_desc(self, include: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnLongDescription(
self.ugc,
self.handle.unwrap(),
include,
)
};
debug_assert!(ok);
self
}
pub fn include_children(self, include: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnChildren(self.ugc, self.handle.unwrap(), include)
};
debug_assert!(ok);
self
}
pub fn include_metadata(self, include: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnMetadata(self.ugc, self.handle.unwrap(), include)
};
debug_assert!(ok);
self
}
pub fn include_additional_previews(self, include: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnAdditionalPreviews(
self.ugc,
self.handle.unwrap(),
include,
)
};
debug_assert!(ok);
self
}
pub fn include_key_value_tags(self, include: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnKeyValueTags(self.ugc, self.handle.unwrap(), include)
};
debug_assert!(ok);
self
}
pub fn add_required_tag(self, tag: &str) -> Self {
let cstr = CString::new(tag).unwrap();
let ok = unsafe {
sys::SteamAPI_ISteamUGC_AddRequiredTag(self.ugc, self.handle.unwrap(), cstr.as_ptr())
};
debug_assert!(ok);
self
}
pub fn add_excluded_tag(self, tag: &str) -> Self {
let cstr = CString::new(tag).unwrap();
let ok = unsafe {
sys::SteamAPI_ISteamUGC_AddExcludedTag(self.ugc, self.handle.unwrap(), cstr.as_ptr())
};
debug_assert!(ok);
self
}
pub fn set_return_only_ids(self, return_only_ids: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnOnlyIDs(
self.ugc,
self.handle.unwrap(),
return_only_ids,
)
};
debug_assert!(ok);
self
}
pub fn set_return_key_value_tags(self, return_kv_tags: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnKeyValueTags(
self.ugc,
self.handle.unwrap(),
return_kv_tags,
)
};
debug_assert!(ok);
self
}
pub fn set_return_long_description(self, return_long_desc: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnLongDescription(
self.ugc,
self.handle.unwrap(),
return_long_desc,
)
};
debug_assert!(ok);
self
}
pub fn set_return_metadata(self, return_metadata: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnMetadata(
self.ugc,
self.handle.unwrap(),
return_metadata,
)
};
debug_assert!(ok);
self
}
pub fn set_return_children(self, return_children: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnChildren(
self.ugc,
self.handle.unwrap(),
return_children,
)
};
debug_assert!(ok);
self
}
pub fn set_return_additional_previews(self, return_additional_previews: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnAdditionalPreviews(
self.ugc,
self.handle.unwrap(),
return_additional_previews,
)
};
debug_assert!(ok);
self
}
pub fn set_return_total_only(self, return_total_only: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetReturnTotalOnly(
self.ugc,
self.handle.unwrap(),
return_total_only,
)
};
debug_assert!(ok);
self
}
pub fn set_language(self, language: &str) -> Self {
let cstr = CString::new(language).unwrap();
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetLanguage(self.ugc, self.handle.unwrap(), cstr.as_ptr())
};
debug_assert!(ok);
self
}
pub fn set_allow_cached_response(self, max_age_seconds: u32) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetAllowCachedResponse(
self.ugc,
self.handle.unwrap(),
max_age_seconds,
)
};
debug_assert!(ok);
self
}
pub fn set_cloud_file_name_filter(self, file_name: &str) -> Self {
let cstr = CString::new(file_name).unwrap();
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetCloudFileNameFilter(
self.ugc,
self.handle.unwrap(),
cstr.as_ptr(),
)
};
debug_assert!(ok);
self
}
pub fn set_match_any_tag(self, match_any_tag: bool) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetMatchAnyTag(self.ugc, self.handle.unwrap(), match_any_tag)
};
debug_assert!(ok);
self
}
pub fn set_search_text(self, search_text: &str) -> Self {
let cstr = CString::new(search_text).unwrap();
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetSearchText(self.ugc, self.handle.unwrap(), cstr.as_ptr())
};
debug_assert!(ok);
self
}
pub fn set_ranked_by_trend_days(self, days: u32) -> Self {
let ok = unsafe {
sys::SteamAPI_ISteamUGC_SetRankedByTrendDays(self.ugc, self.handle.unwrap(), days)
};
debug_assert!(ok);
self
}
pub fn add_required_key_value_tag(self, key: &str, value: &str) -> Self {
let key_cstr = CString::new(key).unwrap();
let value_cstr = CString::new(value).unwrap();
let ok = unsafe {
sys::SteamAPI_ISteamUGC_AddRequiredKeyValueTag(
self.ugc,
self.handle.unwrap(),
key_cstr.as_ptr(),
value_cstr.as_ptr(),
)
};
debug_assert!(ok);
self
}
pub fn fetch<F>(mut self, cb: F)
where
F: for<'a> FnOnce(Result<QueryResults<'a>, SteamError>) + 'static + Send,
{
let ugc = self.ugc;
let inner = Arc::clone(&self.inner);
let handle = self.handle.take().unwrap();
mem::drop(self);
unsafe {
let api_call = sys::SteamAPI_ISteamUGC_SendQueryUGCRequest(ugc, handle);
register_call_result::<sys::SteamUGCQueryCompleted_t, _, _>(
&inner,
api_call,
CALLBACK_BASE_ID + 1,
move |v, io_error| {
let ugc = sys::SteamAPI_SteamUGC_v018();
if io_error {
sys::SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(ugc, handle);
cb(Err(SteamError::IOFailure));
return;
} else if v.m_eResult != sys::EResult::k_EResultOK {
sys::SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(ugc, handle);
cb(Err(v.m_eResult.into()));
return;
}
let result = QueryResults {
ugc,
handle,
num_results_returned: v.m_unNumResultsReturned,
num_results_total: v.m_unTotalMatchingResults,
was_cached: v.m_bCachedData,
_phantom: Default::default(),
};
cb(Ok(result));
},
);
}
}
pub fn fetch_total<F>(self, cb: F)
where
F: Fn(Result<u32, SteamError>) + 'static + Send,
{
unsafe {
let ok =
sys::SteamAPI_ISteamUGC_SetReturnTotalOnly(self.ugc, self.handle.unwrap(), true);
debug_assert!(ok);
}
self.fetch(move |res| cb(res.map(|qr| qr.total_results())))
}
pub fn fetch_ids<F>(self, cb: F)
where
F: Fn(Result<Vec<PublishedFileId>, SteamError>) + 'static + Send,
{
unsafe {
let ok = sys::SteamAPI_ISteamUGC_SetReturnOnlyIDs(self.ugc, self.handle.unwrap(), true);
debug_assert!(ok);
}
self.fetch(move |res| {
cb(res.map(|qr| {
qr.iter()
.filter_map(|v| v.map(|v| PublishedFileId(v.published_file_id.0)))
.collect::<Vec<_>>()
}))
})
}
}
pub struct QueryResults<'a> {
ugc: *mut sys::ISteamUGC,
handle: sys::UGCQueryHandle_t,
num_results_returned: u32,
num_results_total: u32,
was_cached: bool,
_phantom: marker::PhantomData<&'a sys::ISteamUGC>,
}
impl<'a> Drop for QueryResults<'a> {
fn drop(&mut self) {
unsafe {
sys::SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(self.ugc, self.handle);
}
}
}
impl<'a> QueryResults<'a> {
pub fn was_cached(&self) -> bool {
self.was_cached
}
pub fn total_results(&self) -> u32 {
self.num_results_total
}
pub fn returned_results(&self) -> u32 {
self.num_results_returned
}
pub fn preview_url(&self, index: u32) -> Option<String> {
let mut url = [0 as c_char; 4096];
let ok = unsafe {
sys::SteamAPI_ISteamUGC_GetQueryUGCPreviewURL(
self.ugc,
self.handle,
index,
url.as_mut_ptr(),
url.len() as _,
)
};
if ok {
Some(unsafe {
CStr::from_ptr(url.as_ptr() as *const _)
.to_string_lossy()
.into_owned()
})
} else {
None
}
}
pub fn statistic(&self, index: u32, stat_type: UGCStatisticType) -> Option<u64> {
let mut value = 0u64;
let ok = unsafe {
sys::SteamAPI_ISteamUGC_GetQueryUGCStatistic(
self.ugc,
self.handle,
index,
stat_type.into(),
&mut value,
)
};
debug_assert!(ok);
if ok {
Some(value)
} else {
None
}
}
pub fn content_descriptor(&self, index: u32) -> Vec<UGCContentDescriptorID> {
let mut descriptors: [sys::EUGCContentDescriptorID; 10] = unsafe { std::mem::zeroed() };
let max_entries = descriptors.len() as std::ffi::c_uint;
let num_descriptors = unsafe {
sys::SteamAPI_ISteamUGC_GetQueryUGCContentDescriptors(
self.ugc,
self.handle,
index,
descriptors.as_mut_ptr(),
max_entries,
)
} as usize;
Vec::from(&descriptors[..num_descriptors])
.iter()
.map(|&x| x.into())
.collect()
}
pub fn get(&self, index: u32) -> Option<QueryResult> {
if index >= self.num_results_returned {
return None;
}
unsafe {
let mut raw_details: sys::SteamUGCDetails_t = mem::zeroed();
let ok = sys::SteamAPI_ISteamUGC_GetQueryUGCResult(
self.ugc,
self.handle,
index,
&mut raw_details,
);
debug_assert!(ok);
if raw_details.m_eResult != sys::EResult::k_EResultOK {
return None;
}
let tags = CStr::from_ptr(raw_details.m_rgchTags.as_ptr())
.to_string_lossy()
.split(',')
.map(|s| String::from(s))
.collect::<Vec<_>>();
Some(QueryResult {
published_file_id: PublishedFileId(raw_details.m_nPublishedFileId),
creator_app_id: if raw_details.m_nCreatorAppID != 0 {
Some(AppId(raw_details.m_nCreatorAppID))
} else {
None
},
consumer_app_id: if raw_details.m_nConsumerAppID != 0 {
Some(AppId(raw_details.m_nConsumerAppID))
} else {
None
},
title: CStr::from_ptr(raw_details.m_rgchTitle.as_ptr())
.to_string_lossy()
.into_owned(),
description: CStr::from_ptr(raw_details.m_rgchDescription.as_ptr())
.to_string_lossy()
.into_owned(),
owner: SteamId(raw_details.m_ulSteamIDOwner),
time_created: raw_details.m_rtimeCreated,
time_updated: raw_details.m_rtimeUpdated,
time_added_to_user_list: raw_details.m_rtimeAddedToUserList,
visibility: raw_details.m_eVisibility.into(),
banned: raw_details.m_bBanned,
accepted_for_use: raw_details.m_bAcceptedForUse,
url: CStr::from_ptr(raw_details.m_rgchURL.as_ptr())
.to_string_lossy()
.into_owned(),
num_upvotes: raw_details.m_unVotesUp,
num_downvotes: raw_details.m_unVotesDown,
score: raw_details.m_flScore,
num_children: raw_details.m_unNumChildren,
tags,
tags_truncated: raw_details.m_bTagsTruncated,
file_type: raw_details.m_eFileType.into(),
file_size: raw_details.m_nFileSize.max(0) as u32,
})
}
}
pub fn iter<'b>(&'b self) -> impl Iterator<Item = Option<QueryResult>> + 'b {
(0..self.returned_results()).map(move |i| self.get(i))
}
pub fn get_children(&self, index: u32) -> Option<Vec<PublishedFileId>> {
let num_children = self.get(index)?.num_children;
let mut children: Vec<sys::PublishedFileId_t> = vec![0; num_children as usize];
let ok = unsafe {
sys::SteamAPI_ISteamUGC_GetQueryUGCChildren(
self.ugc,
self.handle,
index,
children.as_mut_ptr(),
num_children,
)
};
if ok {
Some(children.into_iter().map(Into::into).collect())
} else {
None
}
}
pub fn key_value_tags(&self, index: u32) -> u32 {
unsafe { sys::SteamAPI_ISteamUGC_GetQueryUGCNumKeyValueTags(self.ugc, self.handle, index) }
}
pub fn get_key_value_tag(&self, index: u32, kv_tag_index: u32) -> Option<(String, String)> {
let mut key = [0 as c_char; 256];
let mut value = [0 as c_char; 256];
let ok = unsafe {
sys::SteamAPI_ISteamUGC_GetQueryUGCKeyValueTag(
self.ugc,
self.handle,
index,
kv_tag_index,
key.as_mut_ptr(),
256,
value.as_mut_ptr(),
256,
)
};
if ok {
Some(unsafe {
(
CStr::from_ptr(key.as_ptr() as *const _)
.to_string_lossy()
.into_owned(),
CStr::from_ptr(value.as_ptr() as *const _)
.to_string_lossy()
.into_owned(),
)
})
} else {
None
}
}
pub fn get_metadata(&self, index: u32) -> Option<Vec<u8>> {
let mut metadata = [0 as c_char; sys::k_cchDeveloperMetadataMax as usize];
let ok = unsafe {
sys::SteamAPI_ISteamUGC_GetQueryUGCMetadata(
self.ugc,
self.handle,
index,
metadata.as_mut_ptr(),
sys::k_cchDeveloperMetadataMax,
)
};
if ok {
let metadata = unsafe { CStr::from_ptr(metadata.as_ptr() as *const _).to_bytes() };
if metadata.is_empty() {
None
} else {
Some(metadata.to_vec())
}
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct QueryResult {
pub published_file_id: PublishedFileId,
pub creator_app_id: Option<AppId>,
pub consumer_app_id: Option<AppId>,
pub title: String,
pub description: String,
pub owner: SteamId,
pub time_created: u32,
pub time_updated: u32,
pub time_added_to_user_list: u32,
pub visibility: PublishedFileVisibility,
pub banned: bool,
pub accepted_for_use: bool,
pub tags: Vec<String>,
pub tags_truncated: bool,
pub file_type: FileType,
pub file_size: u32,
pub url: String,
pub num_upvotes: u32,
pub num_downvotes: u32,
pub score: f32,
pub num_children: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct CreateQueryError;
impl fmt::Display for CreateQueryError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Could not create workshop query")
}
}
impl error::Error for CreateQueryError {}