using System; using System.Collections.Generic; using System.Linq; namespace Facepunch.Steamworks { public partial class Workshop { public class Query : IDisposable { internal const int SteamResponseSize = 50; internal SteamNative.UGCQueryHandle_t Handle; internal SteamNative.CallbackHandle Callback; /// /// The AppId you're querying. This defaults to this appid. /// public uint AppId { get; set; } /// /// The AppId of the app used to upload the item. This defaults to 0 /// which means all/any. /// public uint UploaderAppId { get; set; } public QueryType QueryType { get; set; } = QueryType.Items; public Order Order { get; set; } = Order.RankedByVote; public string SearchText { get; set; } public Item[] Items { get; set; } public int TotalResults { get; set; } public ulong? UserId { get; set; } /// /// If order is RankedByTrend, this value represents how many days to take /// into account. /// public int RankedByTrendDays { get; set; } public UserQueryType UserQueryType { get; set; } = UserQueryType.Published; /// /// Called when the query finishes /// public Action OnResult; /// /// Page starts at 1 !! /// public int Page { get; set; } = 1; public int PerPage { get; set; } = SteamResponseSize; internal Workshop workshop; internal Friends friends; private int _resultPage = 0; private int _resultsRemain = 0; private int _resultSkip = 0; private List _results; public void Run() { if ( Callback != null ) return; if ( Page <= 0 ) throw new System.Exception( "Page should be 1 or above" ); var actualOffset = ((Page-1) * PerPage); TotalResults = 0; _resultSkip = actualOffset % SteamResponseSize; _resultsRemain = PerPage; _resultPage = (int) Math.Floor( (float) actualOffset / (float)SteamResponseSize ); _results = new List(); RunInternal(); } unsafe void RunInternal() { if ( FileId.Count != 0 ) { var fileArray = FileId.Select( x => (SteamNative.PublishedFileId_t)x ).ToArray(); _resultsRemain = fileArray.Length; Handle = workshop.ugc.CreateQueryUGCDetailsRequest( fileArray ); } else if ( UserId.HasValue ) { uint accountId = (uint)( UserId.Value & 0xFFFFFFFFul ); Handle = workshop.ugc.CreateQueryUserUGCRequest( accountId, (SteamNative.UserUGCList)( int)UserQueryType, (SteamNative.UGCMatchingUGCType)( int)QueryType, SteamNative.UserUGCListSortOrder.LastUpdatedDesc, UploaderAppId, AppId, (uint)_resultPage + 1 ); } else { Handle = workshop.ugc.CreateQueryAllUGCRequest( (SteamNative.UGCQuery)(int)Order, (SteamNative.UGCMatchingUGCType)(int)QueryType, UploaderAppId, AppId, (uint)_resultPage + 1 ); } if ( !string.IsNullOrEmpty( SearchText ) ) workshop.ugc.SetSearchText( Handle, SearchText ); foreach ( var tag in RequireTags ) workshop.ugc.AddRequiredTag( Handle, tag ); if ( RequireTags.Count > 0 ) workshop.ugc.SetMatchAnyTag( Handle, !RequireAllTags ); if ( RankedByTrendDays > 0 ) workshop.ugc.SetRankedByTrendDays( Handle, (uint) RankedByTrendDays ); foreach ( var tag in ExcludeTags ) workshop.ugc.AddExcludedTag( Handle, tag ); Callback = workshop.ugc.SendQueryUGCRequest( Handle, ResultCallback ); } void ResultCallback( SteamNative.SteamUGCQueryCompleted_t data, bool bFailed ) { if ( bFailed ) throw new System.Exception( "bFailed!" ); var gotFiles = 0; for ( int i = 0; i < data.NumResultsReturned; i++ ) { if ( _resultSkip > 0 ) { _resultSkip--; continue; } SteamNative.SteamUGCDetails_t details = new SteamNative.SteamUGCDetails_t(); if ( !workshop.ugc.GetQueryUGCResult( data.Handle, (uint)i, ref details ) ) continue; // We already have this file, so skip it if ( _results.Any( x => x.Id == details.PublishedFileId ) ) continue; var item = Item.From( details, workshop ); item.SubscriptionCount = GetStat( data.Handle, i, ItemStatistic.NumSubscriptions ); item.FavouriteCount = GetStat( data.Handle, i, ItemStatistic.NumFavorites ); item.FollowerCount = GetStat( data.Handle, i, ItemStatistic.NumFollowers ); item.WebsiteViews = GetStat( data.Handle, i, ItemStatistic.NumUniqueWebsiteViews ); item.ReportScore = GetStat( data.Handle, i, ItemStatistic.ReportScore ); string url = null; if ( workshop.ugc.GetQueryUGCPreviewURL( data.Handle, (uint)i, out url ) ) item.PreviewImageUrl = url; _results.Add( item ); _resultsRemain--; gotFiles++; if ( _resultsRemain <= 0 ) break; } TotalResults = TotalResults > data.TotalMatchingResults ? TotalResults : (int)data.TotalMatchingResults; Callback.Dispose(); Callback = null; _resultPage++; if ( _resultsRemain > 0 && gotFiles > 0 ) { RunInternal(); } else { Items = _results.ToArray(); if ( OnResult != null ) { OnResult( this ); } } } private int GetStat( ulong handle, int index, ItemStatistic stat ) { ulong val = 0; if ( !workshop.ugc.GetQueryUGCStatistic( handle, (uint)index, (SteamNative.ItemStatistic)(uint)stat, out val ) ) return 0; return (int) val; } public bool IsRunning { get { return Callback != null; } } /// /// Only return items with these tags /// public List RequireTags { get; set; } = new List(); /// /// If true, return items that have all RequireTags /// If false, return items that have any tags in RequireTags /// public bool RequireAllTags { get; set; } = false; /// /// Don't return any items with this tag /// public List ExcludeTags { get; set; } = new List(); /// /// If you're querying for a particular file or files, add them to this. /// public List FileId { get; set; } = new List(); /// /// Don't call this in production! /// public void Block() { const int sleepMs = 10; workshop.steamworks.Update(); while ( IsRunning ) { #if NET_CORE System.Threading.Tasks.Task.Delay( sleepMs ).Wait(); #else System.Threading.Thread.Sleep( sleepMs ); #endif workshop.steamworks.Update(); } } public void Dispose() { // ReleaseQueryUGCRequest } } private enum ItemStatistic : uint { NumSubscriptions = 0, NumFavorites = 1, NumFollowers = 2, NumUniqueSubscriptions = 3, NumUniqueFavorites = 4, NumUniqueFollowers = 5, NumUniqueWebsiteViews = 6, ReportScore = 7, }; } }