Working on a RemoteStorage interface

This commit is contained in:
James King 2017-05-15 17:11:17 +01:00
parent 549ceb0562
commit 6ea0e366f5
3 changed files with 469 additions and 0 deletions

View File

@ -0,0 +1,65 @@
using System;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
[TestClass]
[DeploymentItem( "steam_api.dll" )]
[DeploymentItem( "steam_api64.dll" )]
[DeploymentItem( "steam_appid.txt" )]
public class RemoteStorage
{
[TestMethod]
public void GetQuota()
{
using ( var client = new Steamworks.Client( 252490 ) )
{
ulong total, available;
client.RemoteStorage.GetQuota( out total, out available );
Console.WriteLine( $"Total quota: {total} bytes" );
Console.WriteLine( $"Available: {available} bytes" );
}
}
[TestMethod]
public void WriteFile()
{
using ( var client = new Steamworks.Client( 252490 ) )
{
var file = client.RemoteStorage.CreateFile( "test.txt" );
const string text = "Hello world!";
file.WriteAllText( text );
Assert.IsTrue( file.Exists );
var read = file.ReadAllText();
Assert.AreEqual( text, read );
}
}
[TestMethod]
public void WriteFiles()
{
using ( var client = new Steamworks.Client( 252490 ) )
{
for ( var i = 0; i < 10; ++i )
{
client.RemoteStorage
.CreateFile( $"test_{i}/example.txt" )
.WriteAllText( Guid.NewGuid().ToString() );
}
Console.WriteLine( $"File count: {client.RemoteStorage.FileCount}" );
foreach ( var file in client.RemoteStorage.Files )
{
Console.WriteLine( $"- {file.FileName} ({file.SizeInBytes} bytes)" );
}
}
}
}
}

View File

@ -96,6 +96,7 @@
<Compile Include="Client\Client.cs" />
<Compile Include="Client\Leaderboard.cs" />
<Compile Include="Client\App.cs" />
<Compile Include="Client\RemoteStorage.cs" />
<Compile Include="Client\Voice.cs" />
<Compile Include="Client\Inventory.cs" />
<Compile Include="Client\Workshop.cs" />

View File

@ -0,0 +1,403 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using SteamNative;
namespace Facepunch.Steamworks
{
partial class Client
{
RemoteStorage _remoteStorage;
public RemoteStorage RemoteStorage
{
get
{
if ( _remoteStorage == null )
_remoteStorage = new RemoteStorage( this );
return _remoteStorage;
}
}
}
internal class RemoteFileWriteStream : Stream
{
internal readonly RemoteStorage remoteStorage;
private readonly UGCFileWriteStreamHandle_t _handle;
private readonly RemoteFile _file;
private int _written;
private bool _closed;
internal RemoteFileWriteStream( RemoteStorage r, RemoteFile file )
{
remoteStorage = r;
_handle = remoteStorage.native.FileWriteStreamOpen( file.FileName );
_file = file;
}
public override void Flush() { }
public override int Read( byte[] buffer, int offset, int count )
{
throw new NotImplementedException();
}
public override long Seek( long offset, SeekOrigin origin )
{
throw new NotImplementedException();
}
public override void SetLength( long value )
{
throw new NotImplementedException();
}
public override unsafe void Write( byte[] buffer, int offset, int count )
{
if ( _closed ) throw new ObjectDisposedException( ToString() );
fixed ( byte* bufferPtr = buffer )
{
if ( remoteStorage.native.FileWriteStreamWriteChunk( _handle, (IntPtr) (bufferPtr + offset), count ) )
{
_written += count;
}
}
}
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => _written;
public override long Position { get { return _written; } set { throw new NotImplementedException(); } }
public void Cancel()
{
if ( _closed ) return;
_closed = true;
remoteStorage.native.FileWriteStreamCancel( _handle );
}
public void Close()
{
if ( _closed ) return;
_closed = true;
remoteStorage.native.FileWriteStreamClose( _handle );
_file.remoteStorage.OnWrittenNewFile( _file );
}
protected override void Dispose( bool disposing )
{
if ( disposing ) Close();
base.Dispose( disposing );
}
}
public class RemoteFile
{
internal readonly RemoteStorage remoteStorage;
private readonly bool _isUgc;
private string _fileName;
private int _sizeInBytes = -1;
private UGCHandle_t _handle;
private ulong _ownerId;
public bool Exists { get; internal set; }
public string FileName
{
get
{
if ( _fileName != null ) return _fileName;
GetUGCDetails();
return _fileName;
}
}
public ulong OwnerId
{
get
{
if ( _ownerId != 0 ) return _ownerId;
GetUGCDetails();
return _ownerId;
}
}
public int SizeInBytes
{
get
{
if ( _sizeInBytes != -1 ) return _sizeInBytes;
if ( _isUgc ) throw new NotImplementedException();
_sizeInBytes = remoteStorage.native.GetFileSize( FileName );
return _sizeInBytes;
}
internal set { _sizeInBytes = value; }
}
internal RemoteFile( RemoteStorage r, UGCHandle_t handle )
{
Exists = true;
remoteStorage = r;
_isUgc = true;
_handle = handle;
}
internal RemoteFile( RemoteStorage r, string name, ulong ownerId, int sizeInBytes = -1 )
{
remoteStorage = r;
_isUgc = false;
_fileName = name;
_ownerId = ownerId;
_sizeInBytes = sizeInBytes;
}
public Stream OpenWrite()
{
return new RemoteFileWriteStream( remoteStorage, this );
}
public void WriteAllBytes( byte[] buffer )
{
using ( var stream = OpenWrite() )
{
stream.Write( buffer, 0, buffer.Length );
}
}
public void WriteAllText( string text, Encoding encoding = null )
{
if ( encoding == null ) encoding = Encoding.UTF8;
WriteAllBytes( encoding.GetBytes( text ) );
}
public Stream OpenRead()
{
return new MemoryStream( ReadAllBytes(), false );
}
public unsafe byte[] ReadAllBytes()
{
if ( _isUgc )
{
// Need to download
throw new NotImplementedException();
}
var size = SizeInBytes;
var buffer = new byte[size];
fixed ( byte* bufferPtr = buffer )
{
remoteStorage.native.FileRead( FileName, (IntPtr) bufferPtr, size );
}
return buffer;
}
public string ReadAllText( Encoding encoding = null )
{
if ( encoding == null ) encoding = Encoding.UTF8;
return encoding.GetString( ReadAllBytes() );
}
public delegate void ShareCallback( bool success );
public bool Share( ShareCallback callback = null )
{
if ( _isUgc ) return false;
// Already shared
if ( _handle.Value != 0 ) return false;
remoteStorage.native.FileShare( FileName, ( result, error ) =>
{
var success = !error && result.Result == Result.OK;
if ( success )
{
_handle.Value = result.File;
}
callback?.Invoke( success );
} );
return true;
}
public bool Delete()
{
if ( !Exists ) return false;
if ( _isUgc ) return false;
if ( !remoteStorage.native.FileDelete( FileName ) ) return false;
Exists = false;
remoteStorage.InvalidateFiles();
return true;
}
private void GetUGCDetails()
{
if ( !_isUgc ) throw new InvalidOperationException();
var appId = new AppId_t { Value = remoteStorage.native.steamworks.AppId };
CSteamID ownerId;
remoteStorage.native.GetUGCDetails( _handle, ref appId, out _fileName, out ownerId );
_ownerId = ownerId.Value;
}
}
/// <summary>
/// Handles Steam Cloud related actions.
/// </summary>
public class RemoteStorage
{
private static string NormalizePath( string path )
{
return new FileInfo( $"x:/{path}" ).FullName.Substring( 3 );
}
internal readonly Client client;
internal readonly SteamNative.SteamRemoteStorage native;
private bool _filesInvalid = true;
private readonly List<RemoteFile> _files = new List<RemoteFile>();
internal RemoteStorage( Client c )
{
client = c;
native = client.native.remoteStorage;
}
/// <summary>
/// True if Steam Cloud is currently enabled by the current user.
/// </summary>
public bool IsCloudEnabledForAccount
{
get { return native.IsCloudEnabledForAccount(); }
}
/// <summary>
/// True if Steam Cloud is currently enabled for this app by the current user.
/// </summary>
public bool IsCloudEnabledForApp
{
get { return native.IsCloudEnabledForApp(); }
}
public int FileCount
{
get { return native.GetFileCount(); }
}
public IEnumerable<RemoteFile> Files
{
get
{
UpdateFiles();
return _files;
}
}
public RemoteFile CreateFile( string path )
{
path = NormalizePath( path );
InvalidateFiles();
var existing = Files.FirstOrDefault( x => x.FileName == path );
return existing ?? new RemoteFile( this, path, client.SteamId, 0 );
}
internal void OnWrittenNewFile( RemoteFile file )
{
if ( _files.Any( x => x.FileName == file.FileName ) ) return;
_files.Add( file );
file.Exists = true;
InvalidateFiles();
}
internal void InvalidateFiles()
{
_filesInvalid = true;
}
private void UpdateFiles()
{
if ( !_filesInvalid ) return;
_filesInvalid = false;
foreach ( var file in _files )
{
file.Exists = false;
}
var count = FileCount;
for ( var i = 0; i < count; ++i )
{
int size;
var name = NormalizePath( GetFileNameAndSize( i, out size ) );
var existing = _files.FirstOrDefault( x => x.FileName == name );
if ( existing == null )
{
existing = new RemoteFile( this, name, client.SteamId, size );
_files.Add( existing );
}
else
{
existing.SizeInBytes = size;
}
existing.Exists = true;
}
for ( var i = _files.Count - 1; i >= 0; --i )
{
if ( !_files[i].Exists ) _files.RemoveAt( i );
}
}
public bool FileExists( string path )
{
return native.FileExists( path );
}
/// <summary>
/// Gets both the total and available remote storage in bytes for this user and app.
/// </summary>
/// <returns>True if successful</returns>
public unsafe bool GetQuota( out ulong totalBytes, out ulong availableBytes )
{
fixed ( ulong* totalPtr = &totalBytes)
fixed ( ulong* availablePtr = &availableBytes )
{
return native.GetQuota( (IntPtr) totalPtr, (IntPtr) availablePtr );
}
}
private unsafe string GetFileNameAndSize( int file, out int size )
{
fixed ( int* sizePtr = &size )
{
return native.GetFileNameAndSize( file, (IntPtr) sizePtr );
}
}
}
}