mirror of
https://github.com/Facepunch/Facepunch.Steamworks.git
synced 2024-12-26 06:35:49 +03:00
Working on a RemoteStorage interface
This commit is contained in:
parent
549ceb0562
commit
6ea0e366f5
65
Facepunch.Steamworks.Test/Client/RemoteStorage.cs
Normal file
65
Facepunch.Steamworks.Test/Client/RemoteStorage.cs
Normal 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)" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -96,6 +96,7 @@
|
|||||||
<Compile Include="Client\Client.cs" />
|
<Compile Include="Client\Client.cs" />
|
||||||
<Compile Include="Client\Leaderboard.cs" />
|
<Compile Include="Client\Leaderboard.cs" />
|
||||||
<Compile Include="Client\App.cs" />
|
<Compile Include="Client\App.cs" />
|
||||||
|
<Compile Include="Client\RemoteStorage.cs" />
|
||||||
<Compile Include="Client\Voice.cs" />
|
<Compile Include="Client\Voice.cs" />
|
||||||
<Compile Include="Client\Inventory.cs" />
|
<Compile Include="Client\Inventory.cs" />
|
||||||
<Compile Include="Client\Workshop.cs" />
|
<Compile Include="Client\Workshop.cs" />
|
||||||
|
403
Facepunch.Steamworks/Client/RemoteStorage.cs
Normal file
403
Facepunch.Steamworks/Client/RemoteStorage.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user