using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Facepunch.Steamworks.Callbacks; using SteamNative; using Result = SteamNative.Result; namespace Facepunch.Steamworks { /// /// Represents a file stored in a user's Steam Cloud. /// public class RemoteFile { internal readonly RemoteStorage remoteStorage; private readonly bool _isUgc; private string _fileName; private int _sizeInBytes = -1; private long _timestamp = 0; 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. /// public bool IsShared { get { return _handle.Value != 0; } } internal UGCHandle_t UGCHandle { get { return _handle; } } public ulong SharingId { get { return UGCHandle.Value; } } /// /// Name and path of the file. /// public string FileName { get { if ( _fileName != null ) return _fileName; GetUGCDetails(); return _fileName; } } /// /// Steam ID of the file's owner. /// public ulong OwnerId { get { if ( _ownerId != 0 ) return _ownerId; GetUGCDetails(); return _ownerId; } } /// /// Total size of the file in bytes. /// 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; } } /// /// Date modified timestamp in epoch format. /// public long FileTimestamp { get { if ( _timestamp != 0 ) return _timestamp; if (_isUgc) throw new NotImplementedException(); _timestamp = remoteStorage.native.GetFileTimestamp(FileName); return _timestamp; } internal set { _timestamp = 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, long timestamp = 0 ) { remoteStorage = r; _isUgc = false; _fileName = name; _ownerId = ownerId; _sizeInBytes = sizeInBytes; _timestamp = timestamp; } /// /// Creates a used to write to this file. /// public RemoteFileWriteStream OpenWrite() { if (_isUgc) throw new InvalidOperationException("Cannot write to a shared file."); return new RemoteFileWriteStream( remoteStorage, this ); } /// /// Write a byte array to this file, replacing any existing contents. /// public void WriteAllBytes( byte[] buffer ) { using ( var stream = OpenWrite() ) { stream.Write( buffer, 0, buffer.Length ); } } /// /// Write a string to this file, replacing any existing contents. /// public void WriteAllText( string text, Encoding encoding = null ) { if ( encoding == null ) encoding = Encoding.UTF8; WriteAllBytes( encoding.GetBytes( text ) ); } /// /// Callback invoked by when a file download is complete. /// public delegate void DownloadCallback(); /// /// 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 onSuccess = null, FailureCallback onFailure = 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 ) { onFailure?.Invoke( result.Result == 0 ? Callbacks.Result.IOFailure : (Callbacks.Result) result.Result ); 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 ); } } onSuccess?.Invoke(); } ); return true; } /// /// Opens a stream used to read from this file. /// /// public Stream OpenRead() { return new MemoryStream( ReadAllBytes(), false ); } /// /// Reads the entire contents of the file as a byte array. /// public unsafe byte[] ReadAllBytes() { if ( _isUgc ) { if ( !IsDownloaded ) throw new Exception( "Cannot read a file that hasn't been downloaded." ); return _downloadedData; } var size = SizeInBytes; var buffer = new byte[size]; fixed ( byte* bufferPtr = buffer ) { remoteStorage.native.FileRead( FileName, (IntPtr) bufferPtr, size ); } return buffer; } /// /// Reads the entire contents of the file as a string. /// public string ReadAllText( Encoding encoding = null ) { if ( encoding == null ) encoding = Encoding.UTF8; return encoding.GetString( ReadAllBytes() ); } /// /// Callback invoked by when file sharing is complete. /// public delegate void ShareCallback(); /// /// Attempt to publish this file for other users to download. /// /// True if we have started attempting to share public bool Share( ShareCallback onSuccess = null, FailureCallback onFailure = null ) { if ( _isUgc ) return false; // Already shared if ( _handle.Value != 0 ) return false; remoteStorage.native.FileShare( FileName, ( result, error ) => { if ( !error && result.Result == Result.OK ) { _handle.Value = result.File; onSuccess?.Invoke(); } else { onFailure?.Invoke( result.Result == 0 ? Callbacks.Result.IOFailure : (Callbacks.Result) result.Result ); } } ); return true; } /// /// Delete this file from remote storage. /// /// True if the file could be deleted public bool Delete() { if ( !Exists ) return false; if ( _isUgc ) return false; if ( !remoteStorage.native.FileDelete( FileName ) ) return false; Exists = false; remoteStorage.InvalidateFiles(); return true; } /// /// Remove this file from remote storage, while keeping a local copy. /// Writing to this file again will re-add it to the cloud. /// /// True if the file was forgotten public bool Forget() { if ( !Exists ) return false; if ( _isUgc ) return false; return remoteStorage.native.FileForget( FileName ); } 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; } } }