From 7e3527799a67d47645b722915d687aa35fde5fb5 Mon Sep 17 00:00:00 2001 From: James King Date: Tue, 16 May 2017 16:35:37 +0100 Subject: [PATCH] Fixed downloading leaderboard file attachments --- .../Client/Leaderboard.cs | 68 ++++++++++++++++++ Facepunch.Steamworks/Client/Leaderboard.cs | 15 ++-- Facepunch.Steamworks/Client/RemoteStorage.cs | 70 ++++++++++++++++++- .../SteamNative/SteamNative.Structs.cs | 2 +- Generator/CodeWriter/Struct.cs | 7 +- 5 files changed, 149 insertions(+), 13 deletions(-) diff --git a/Facepunch.Steamworks.Test/Client/Leaderboard.cs b/Facepunch.Steamworks.Test/Client/Leaderboard.cs index 1c04a2d..48f23d8 100644 --- a/Facepunch.Steamworks.Test/Client/Leaderboard.cs +++ b/Facepunch.Steamworks.Test/Client/Leaderboard.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -169,5 +170,72 @@ namespace Facepunch.Steamworks.Test } } } + + [TestMethod] + public void AddFileAttachment() + { + using ( var client = new Steamworks.Client( 252490 ) ) + { + var board = client.GetLeaderboard( "TestLeaderboard", Steamworks.Client.LeaderboardSortMethod.Ascending, Steamworks.Client.LeaderboardDisplayType.Numeric ); + + while ( !board.IsValid ) + { + Thread.Sleep( 10 ); + client.Update(); + } + + Assert.IsTrue( board.IsValid ); + Assert.IsFalse( board.IsError ); + + var done = false; + + const int score = 5678; + const string attachment = "Hello world!"; + + var file = client.RemoteStorage.CreateFile( "score/example.txt" ); + file.WriteAllText( attachment ); + + Assert.IsTrue( board.AddScore( false, score, null, ( success, result ) => + { + Assert.IsTrue( success ); + Assert.IsTrue( result.ScoreChanged ); + + Assert.IsTrue( board.AttachRemoteFile( file, attached => + { + Assert.IsTrue( attached ); + + done = true; + } ) ); + } ) ); + + while ( !done ) + { + Thread.Sleep( 10 ); + client.Update(); + } + + done = false; + + Assert.IsTrue( board.FetchScores( Steamworks.Leaderboard.RequestType.GlobalAroundUser, 0, 0, ( success, entries ) => + { + Assert.AreEqual( 1, entries.Length ); + Assert.IsNotNull( entries[0].AttachedFile ); + + Assert.IsTrue( entries[0].AttachedFile.Download( downloaded => + { + Assert.IsTrue( downloaded ); + Assert.AreEqual( attachment, entries[0].AttachedFile.ReadAllText() ); + + done = true; + } ) ); + } ) ); + + while ( !done ) + { + Thread.Sleep( 10 ); + client.Update(); + } + } + } } } \ No newline at end of file diff --git a/Facepunch.Steamworks/Client/Leaderboard.cs b/Facepunch.Steamworks/Client/Leaderboard.cs index 6ac402b..3532e20 100644 --- a/Facepunch.Steamworks/Client/Leaderboard.cs +++ b/Facepunch.Steamworks/Client/Leaderboard.cs @@ -177,22 +177,20 @@ namespace Facepunch.Steamworks if ( file.IsShared ) { - client.native.userstats.AttachLeaderboardUGC( BoardId, file.UGCHandle, ( result, error ) => + var handle = client.native.userstats.AttachLeaderboardUGC( BoardId, file.UGCHandle, ( result, error ) => { callback?.Invoke( !error && result.Result == Result.OK ); } ); - return true; + + return handle.CallResultHandle != 0; } file.Share( success => { - if ( !success || !file.IsShared ) + if ( !success || !file.IsShared || !AttachRemoteFile( file, callback ) ) { callback?.Invoke( false ); - return; } - - AttachRemoteFile( file, callback ); } ); return true; } @@ -226,7 +224,8 @@ namespace Facepunch.Steamworks Score = entry.Score, SteamId = entry.SteamIDUser, SubScores = entry.CDetails == 0 ? null : subEntriesBuffer.Take( entry.CDetails ).ToArray(), - Name = client.Friends.GetName( entry.SteamIDUser ) + Name = client.Friends.GetName( entry.SteamIDUser ), + AttachedFile = (entry.UGC >> 32) == 0xffffffff ? null : new RemoteFile( client.RemoteStorage, entry.UGC ) } ); } } @@ -288,7 +287,7 @@ namespace Facepunch.Steamworks public int Score; public int[] SubScores; public int GlobalRank; - + public RemoteFile AttachedFile; /// /// Note that the player's name might not be immediately available. diff --git a/Facepunch.Steamworks/Client/RemoteStorage.cs b/Facepunch.Steamworks/Client/RemoteStorage.cs index e067e23..25168ce 100644 --- a/Facepunch.Steamworks/Client/RemoteStorage.cs +++ b/Facepunch.Steamworks/Client/RemoteStorage.cs @@ -125,11 +125,18 @@ namespace Facepunch.Steamworks private UGCHandle_t _handle; private ulong _ownerId; + private bool _isDownloading; + private byte[] _downloadedData; + /// /// Check if the file exists. /// public bool Exists { get; internal set; } + public bool IsDownloading { get { return _isUgc && _isDownloading && _downloadedData == null; } } + + public bool IsDownloaded { get { return !_isUgc || _downloadedData != null; } } + /// /// If true, the file is available for other users to download. /// @@ -137,6 +144,8 @@ namespace Facepunch.Steamworks internal UGCHandle_t UGCHandle { get { return _handle; } } + public ulong SharingId { get { return UGCHandle.Value; } } + /// /// Name and path of the file. /// @@ -201,7 +210,6 @@ namespace Facepunch.Steamworks /// /// Creates a used to write to this file. /// - /// public RemoteFileWriteStream OpenWrite() { return new RemoteFileWriteStream( remoteStorage, this ); @@ -227,6 +235,62 @@ namespace Facepunch.Steamworks WriteAllBytes( encoding.GetBytes( text ) ); } + /// + /// Callback invoked by when a file download is complete. + /// + public delegate void DownloadCallback( bool success ); + + /// + /// Gets the number of bytes downloaded and the total number of bytes expected while + /// this file is downloading. + /// + /// True if the file is downloading + public bool GetDownloadProgress( out int bytesDownloaded, out int bytesExpected ) + { + return remoteStorage.native.GetUGCDownloadProgress( _handle, out bytesDownloaded, out bytesExpected ); + } + + /// + /// Attempts to start downloading a shared file. + /// + /// True if the download has successfully started + public bool Download( DownloadCallback callback = null ) + { + if ( !_isUgc ) return false; + if ( _isDownloading ) return false; + if ( IsDownloaded ) return false; + + _isDownloading = true; + + remoteStorage.native.UGCDownload( _handle, 1000, ( result, error ) => + { + _isDownloading = false; + + if ( error || result.Result != Result.OK ) + { + callback?.Invoke( false ); + return; + } + + _ownerId = result.SteamIDOwner; + _sizeInBytes = result.SizeInBytes; + _fileName = result.PchFileName; + + unsafe + { + _downloadedData = new byte[_sizeInBytes]; + fixed ( byte* bufferPtr = _downloadedData ) + { + remoteStorage.native.UGCRead( _handle, (IntPtr) bufferPtr, _sizeInBytes, 0, UGCReadAction.ontinueReading ); + } + } + + callback?.Invoke( true ); + } ); + + return true; + } + /// /// Opens a stream used to read from this file. /// @@ -243,8 +307,8 @@ namespace Facepunch.Steamworks { if ( _isUgc ) { - // Need to download - throw new NotImplementedException(); + if ( !IsDownloaded ) throw new Exception( "Cannot read a file that hasn't been downloaded." ); + return _downloadedData; } var size = SizeInBytes; diff --git a/Facepunch.Steamworks/SteamNative/SteamNative.Structs.cs b/Facepunch.Steamworks/SteamNative/SteamNative.Structs.cs index 5bf8697..edb08c1 100644 --- a/Facepunch.Steamworks/SteamNative/SteamNative.Structs.cs +++ b/Facepunch.Steamworks/SteamNative/SteamNative.Structs.cs @@ -14929,7 +14929,7 @@ namespace SteamNative } } - [StructLayout( LayoutKind.Sequential, Pack = 4 )] + [StructLayout( LayoutKind.Sequential, Pack = 8 )] internal struct LeaderboardEntry_t { public ulong SteamIDUser; // m_steamIDUser class CSteamID diff --git a/Generator/CodeWriter/Struct.cs b/Generator/CodeWriter/Struct.cs index 89ce93b..4e12be4 100644 --- a/Generator/CodeWriter/Struct.cs +++ b/Generator/CodeWriter/Struct.cs @@ -30,6 +30,11 @@ namespace Generator "CCallbackBase" }; + public readonly static string[] ForceLargePackStructs = new string[] + { + "LeaderboardEntry_t" + }; + void Structs() { foreach ( var c in def.structs ) @@ -42,7 +47,7 @@ namespace Generator int defaultPack = 8; - if ( c.Fields.Any( x => x.Type.Contains( "class CSteamID" ) ) ) + if ( c.Fields.Any( x => x.Type.Contains( "class CSteamID" ) ) && !ForceLargePackStructs.Contains( c.Name ) ) defaultPack = 4; //