using System; using System.IO; using Godot; namespace TBL.GodotSharp.IO.File { /// /// Wrapper for that allows it to be used as a . /// /// /// Use this if really necessary, but prefer to use .NET's built in IO classes as they will be quite a bit faster. /// If you need to convert a godot path to an absolute one, use . /// public class GodotFileStream : Stream { private Godot.File _file; private Godot.File.ModeFlags _flags; /// /// Create a new instance of the class. /// /// The path to the file to open. This accepts Godot-style user:// and res:// paths. /// File flags. /// If not null, this will enable compression/decompression. public GodotFileStream(string path, Godot.File.ModeFlags flags, Godot.File.CompressionMode? compressionMode = null) { if (path == null) throw new ArgumentNullException("path"); _file = new Godot.File(); _flags = flags; Error result; if (compressionMode.HasValue) { result = _file.OpenCompressed(path, flags, compressionMode.Value); } else { result = _file.Open(path, flags); } if (result != Error.Ok) { throw new IOException($"Unable to open \"{path}\": {result}"); } } public override bool CanRead => _flags == Godot.File.ModeFlags.Read || _flags == Godot.File.ModeFlags.ReadWrite || _flags == Godot.File.ModeFlags.WriteRead; public override bool CanSeek => true; public override bool CanWrite => _flags == Godot.File.ModeFlags.Write || _flags == Godot.File.ModeFlags.ReadWrite || _flags == Godot.File.ModeFlags.WriteRead; public override long Length => checked((long)_file.GetLen()); public override long Position { get => checked((long)_file.GetPosition()); set => _file.Seek(value); } public override void Flush() { _file.Flush(); } public override int Read(byte[] buffer, int offset, int count) { if (!_file.IsOpen()) throw new ObjectDisposedException("File has been closed"); if (!CanRead) throw new NotSupportedException($"Cannot Read on a GodotFileStream with flags {_flags}"); if (count < 0) throw new ArgumentOutOfRangeException("count"); if (offset < 0) throw new ArgumentOutOfRangeException("offset"); if (count + offset > buffer.Length) throw new ArgumentException("count + offset is greater than the given buffer length"); var data = _file.GetBuffer(count); if (_file.GetError() != Error.Ok) throw new IOException($"Error reading file: {_file.GetError()}"); Array.Copy(data, 0, buffer, offset, data.Length); return data.Length; } public override long Seek(long offset, SeekOrigin origin) { if (!_file.IsOpen()) throw new ObjectDisposedException("File has been closed"); switch (origin) { case SeekOrigin.Begin: _file.Seek(offset); break; case SeekOrigin.Current: _file.Seek(checked((long)_file.GetPosition()) + offset); break; case SeekOrigin.End: _file.SeekEnd(offset); break; } if (_file.GetError() != Error.Ok) throw new IOException($"Error seeking file: {_file.GetError()}"); return checked((long)_file.GetPosition()); } public override void SetLength(long value) { throw new NotSupportedException("GodotFileStream does not support SetLength"); } public override void Write(byte[] buffer, int offset, int count) { if (!_file.IsOpen()) throw new ObjectDisposedException("File has been closed"); if (!CanWrite) throw new NotSupportedException($"Cannot Write on a GodotFileStream with flags {_flags}"); if (count < 0) throw new ArgumentOutOfRangeException("count"); if (offset < 0) throw new ArgumentOutOfRangeException("offset"); if (count + offset > buffer.Length) throw new ArgumentException("count + offset is greater than the given buffer length"); var data = new byte[count]; Array.Copy(buffer, offset, data, 0, count); _file.StoreBuffer(data); if (_file.GetError() != Error.Ok) throw new IOException($"Error writing file: {_file.GetError()}"); } protected override void Dispose(bool disposing) { _file.Close(); } } }