using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Facepunch.Steamworks.Data; using QueryType = Facepunch.Steamworks.Ugc.Query; namespace Facepunch.Steamworks.Ugc { public struct Item { internal SteamUGCDetails_t details; internal PublishedFileId _id; public Item( PublishedFileId id ) : this() { _id = id; } /// /// The actual ID of this file /// public PublishedFileId Id => _id; /// /// The given title of this item /// public string Title { get; internal set; } /// /// The description of this item, in your local language if available /// public string Description { get; internal set; } /// /// A list of tags for this item, all lowercase /// public string[] Tags { get; internal set; } /// /// App Id of the app that created this item /// public AppId CreatorApp => details.CreatorAppID; /// /// App Id of the app that will consume this item. /// public AppId ConsumerApp => details.ConsumerAppID; /// /// User who created this content /// public Friend Owner => new Friend( details.SteamIDOwner ); /// /// The bayesian average for up votes / total votes, between [0,1] /// public float Score => details.Score; /// /// Time when the published item was created /// public DateTime Created => Epoch.ToDateTime( details.TimeCreated ); /// /// Time when the published item was last updated /// public DateTime Updated => Epoch.ToDateTime( details.TimeUpdated ); /// /// True if this is publically visible /// public bool IsPublic => details.Visibility == RemoteStoragePublishedFileVisibility.Public; /// /// True if this item is only visible by friends of the creator /// public bool IsFriendsOnly => details.Visibility == RemoteStoragePublishedFileVisibility.FriendsOnly; /// /// True if this is only visible to the creator /// public bool IsPrivate => details.Visibility == RemoteStoragePublishedFileVisibility.Private; /// /// True if this item has been banned /// public bool IsBanned => details.Banned; /// /// Whether the developer of this app has specifically flagged this item as accepted in the Workshop /// public bool IsAcceptedForUse => details.AcceptedForUse; /// /// The number of upvotes of this item /// public uint VotesUp => details.VotesUp; /// /// The number of downvotes of this item /// public uint VotesDown => details.VotesDown; public bool IsInstalled => (State & ItemState.Installed) == ItemState.Installed; public bool IsDownloading => (State & ItemState.Downloading) == ItemState.Downloading; public bool IsDownloadPending => (State & ItemState.DownloadPending) == ItemState.DownloadPending; public bool IsSubscribed => (State & ItemState.Subscribed) == ItemState.Subscribed; public bool NeedsUpdate => (State & ItemState.NeedsUpdate) == ItemState.NeedsUpdate; public string Directory { get { ulong size = 0; uint ts = 0; if ( !SteamUGC.Internal.GetItemInstallInfo( Id, ref size, out var strVal, ref ts ) ) return null; return strVal; } } /// /// Start downloading this item. /// If this returns false the item isn't getting downloaded. /// public bool Download( bool highPriority = false ) { return SteamUGC.Download( Id, highPriority ); } /// /// If we're downloading, how big the total download is /// public long DownloadBytesTotal { get { if ( !NeedsUpdate ) return SizeBytes; ulong downloaded = 0; ulong total = 0; if ( SteamUGC.Internal.GetItemDownloadInfo( Id, ref downloaded, ref total ) ) return (long) total; return -1; } } /// /// If we're downloading, how much we've downloaded /// public long DownloadBytesDownloaded { get { if ( !NeedsUpdate ) return SizeBytes; ulong downloaded = 0; ulong total = 0; if ( SteamUGC.Internal.GetItemDownloadInfo( Id, ref downloaded, ref total ) ) return (long)downloaded; return -1; } } /// /// If we're installed, how big is the install /// public long SizeBytes { get { if ( NeedsUpdate ) return DownloadBytesDownloaded; ulong size = 0; uint ts = 0; if ( !SteamUGC.Internal.GetItemInstallInfo( Id, ref size, out _, ref ts ) ) return 0; return (long) size; } } /// /// If we're downloading our current progress as a delta betwen 0-1 /// public float DownloadAmount { get { //changed from NeedsUpdate as it's false when validating and redownloading ugc //possibly similar properties should also be changed if ( !IsDownloading ) return 1; ulong downloaded = 0; ulong total = 0; if ( SteamUGC.Internal.GetItemDownloadInfo( Id, ref downloaded, ref total ) && total > 0 ) return (float)((double)downloaded / (double)total); if ( NeedsUpdate || !IsInstalled || IsDownloading ) return 0; return 1; } } private ItemState State => (ItemState) SteamUGC.Internal.GetItemState( Id ); public static async Task GetAsync( PublishedFileId id, int maxageseconds = 60 * 30 ) { var file = await Steamworks.Ugc.Query.All .WithFileId( id ) .WithLongDescription( true ) .GetPageAsync( 1 ); if ( !file.HasValue ) return null; if ( file.Value.ResultCount == 0 ) return null; return file.Value.Entries.First(); } internal static Item From( SteamUGCDetails_t details ) { var d = new Item { _id = details.PublishedFileId, details = details, Title = details.TitleUTF8(), Description = details.DescriptionUTF8(), Tags = details.TagsUTF8().ToLower().Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries ) }; return d; } /// /// A case insensitive check for tag /// public bool HasTag( string find ) { if ( Tags.Length == 0 ) return false; return Tags.Contains( find, StringComparer.OrdinalIgnoreCase ); } /// /// Allows the user to subscribe to this item /// public async Task Subscribe () { var result = await SteamUGC.Internal.SubscribeItem( _id ); return result?.Result == Result.OK; } /// /// Allows the user to subscribe to download this item asyncronously /// If CancellationToken is default then there is 60 seconds timeout /// Progress will be set to 0-1 /// public async Task DownloadAsync( Action progress = null, int milisecondsUpdateDelay = 60, CancellationToken ct = default ) { return await SteamUGC.DownloadAsync( Id, progress, milisecondsUpdateDelay, ct ); } /// /// Allows the user to unsubscribe from this item /// public async Task Unsubscribe () { var result = await SteamUGC.Internal.UnsubscribeItem( _id ); return result?.Result == Result.OK; } /// /// Adds item to user favorite list /// public async Task AddFavorite() { var result = await SteamUGC.Internal.AddItemToFavorites(details.ConsumerAppID, _id); return result?.Result == Result.OK; } /// /// Removes item from user favorite list /// public async Task RemoveFavorite() { var result = await SteamUGC.Internal.RemoveItemFromFavorites(details.ConsumerAppID, _id); return result?.Result == Result.OK; } /// /// Allows the user to rate a workshop item up or down. /// public async Task Vote( bool up ) { var r = await SteamUGC.Internal.SetUserItemVote( Id, up ); return r?.Result; } /// /// Gets the current users vote on the item /// public async Task GetUserVote() { var result = await SteamUGC.Internal.GetUserItemVote(_id); if (!result.HasValue) return null; return UserItemVote.From(result.Value); } /// /// Return a URL to view this item online /// public string Url => $"http://steamcommunity.com/sharedfiles/filedetails/?source=Facepunch.Steamworks&id={Id}"; /// /// The URl to view this item's changelog /// public string ChangelogUrl => $"http://steamcommunity.com/sharedfiles/filedetails/changelog/{Id}"; /// /// The URL to view the comments on this item /// public string CommentsUrl => $"http://steamcommunity.com/sharedfiles/filedetails/comments/{Id}"; /// /// The URL to discuss this item /// public string DiscussUrl => $"http://steamcommunity.com/sharedfiles/filedetails/discussions/{Id}"; /// /// The URL to view this items stats online /// public string StatsUrl => $"http://steamcommunity.com/sharedfiles/filedetails/stats/{Id}"; public ulong NumSubscriptions { get; internal set; } public ulong NumFavorites { get; internal set; } public ulong NumFollowers { get; internal set; } public ulong NumUniqueSubscriptions { get; internal set; } public ulong NumUniqueFavorites { get; internal set; } public ulong NumUniqueFollowers { get; internal set; } public ulong NumUniqueWebsiteViews { get; internal set; } public ulong ReportScore { get; internal set; } public ulong NumSecondsPlayed { get; internal set; } public ulong NumPlaytimeSessions { get; internal set; } public ulong NumComments { get; internal set; } public ulong NumSecondsPlayedDuringTimePeriod { get; internal set; } public ulong NumPlaytimeSessionsDuringTimePeriod { get; internal set; } /// /// The URL to the preview image for this item /// public string PreviewImageUrl { get; internal set; } /// /// Edit this item /// public Ugc.Editor Edit() { return new Ugc.Editor( Id ); } public Result Result => details.Result; } }