Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/Lua/IO/BinaryData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Buffers;

namespace Lua.IO;

public class BinaryData(ReadOnlyMemory<byte> bytes) : IBinaryData
{
public ReadOnlyMemory<byte> Memory => bytes;
}

public interface IBinaryData
{
/// <summary>
/// Gets the bytes of the binary data.
/// </summary>
public ReadOnlyMemory<byte> Memory { get; }
}
134 changes: 134 additions & 0 deletions src/Lua/IO/BinaryLuaIOStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
namespace Lua.IO;

internal sealed class BinaryLuaIOStream(LuaFileOpenMode mode, Stream innerStream) : ILuaIOStream
{
ulong flushSize = ulong.MaxValue;
ulong nextFlushSize = ulong.MaxValue;

public LuaFileOpenMode Mode => mode;
public LuaFileContentType ContentType => LuaFileContentType.Binary;

public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
{
throw new InvalidOperationException("Cannot read lines from a binary stream. Use a text stream instead.");
}

public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
{
ThrowIfNotReadable();
using var memoryStream = new MemoryStream();
innerStream.CopyTo(memoryStream);
var bytes = memoryStream.ToArray();
return new(new LuaFileContent(bytes));
}

public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)
{
throw new InvalidOperationException("Cannot read strings from a binary stream. Use a text stream instead.");
}

public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken)
{
if (content.Type != LuaFileContentType.Binary)
{
throw new InvalidOperationException("Cannot write string to a binary stream.");
}

return WriteBytesAsync(content.ReadBytes().Span, cancellationToken);
}

public ValueTask<byte[]?> ReadBytesAsync(int count, CancellationToken cancellationToken)
{
ThrowIfNotReadable();

if (count <= 0) return new((byte[]?)null);

var buffer = new byte[count];
var totalRead = 0;

while (totalRead < count)
{
var bytesRead = innerStream.Read(buffer, totalRead, count - totalRead);
if (bytesRead == 0) break; // End of stream
totalRead += bytesRead;
}

if (totalRead == 0) return new((byte[]?)null);
if (totalRead < count)
{
Array.Resize(ref buffer, totalRead);
}

return new(buffer);
}


public ValueTask WriteBytesAsync(ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
{
ThrowIfNotWritable();

if (mode is LuaFileOpenMode.Append or LuaFileOpenMode.ReadAppend)
{
innerStream.Seek(0, SeekOrigin.End);
}

innerStream.Write(buffer);

if (nextFlushSize < (ulong)buffer.Length)
{
innerStream.Flush();
nextFlushSize = flushSize;
}

return new();
}

public ValueTask FlushAsync(CancellationToken cancellationToken)
{
innerStream.Flush();
nextFlushSize = flushSize;
return new();
}

public void SetVBuf(LuaFileBufferingMode mode, int size)
{
// Ignore size parameter
if (mode is LuaFileBufferingMode.NoBuffering or LuaFileBufferingMode.LineBuffering)
{
nextFlushSize = 0;
flushSize = 0;
}
else
{
nextFlushSize = (ulong)size;
flushSize = (ulong)size;
}
}

public long Seek(long offset, SeekOrigin origin)
{
return innerStream.Seek(offset, origin);
}

void ThrowIfNotReadable()
{
if (!innerStream.CanRead)
{
throw new IOException("Stream is not readable.");
}
}

void ThrowIfNotWritable()
{
if (!innerStream.CanWrite)
{
throw new IOException("Stream is not writable.");
}
}

public void Dispose()
{
if (innerStream.CanWrite) innerStream.Flush();
innerStream.Dispose();
}
}
180 changes: 50 additions & 130 deletions src/Lua/IO/ILuaFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ namespace Lua.IO;
public interface ILuaFileSystem
{
public bool IsReadable(string path);
public ValueTask<LuaFileContent> ReadFileContentAsync(string path, CancellationToken cancellationToken);
public ILuaIOStream Open(string path, LuaFileOpenMode mode);
public ILuaIOStream Open(string path, LuaFileMode mode);
public void Rename(string oldName, string newName);
public void Remove(string path);
public string DirectorySeparator { get; }
Expand All @@ -18,17 +17,26 @@ public interface ILuaFileSystem
public interface ILuaIOStream : IDisposable
{
public LuaFileOpenMode Mode { get; }

public LuaFileContentType ContentType { get; }
public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken);
public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken);
public ValueTask<string> ReadToEndAsync(CancellationToken cancellationToken);
public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken);
public ValueTask WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken);
public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken);
public ValueTask FlushAsync(CancellationToken cancellationToken);
public void SetVBuf(LuaFileBufferingMode mode, int size);
public long Seek(long offset, SeekOrigin origin);

public static ILuaIOStream CreateStreamWrapper(LuaFileOpenMode mode, Stream stream)
public static ILuaIOStream CreateStreamWrapper(Stream stream, LuaFileOpenMode mode, LuaFileContentType contentType = LuaFileContentType.Text)
{
return new LuaIOStreamWrapper(mode, stream);
return contentType == LuaFileContentType.Binary
? new BinaryLuaIOStream(mode, stream)
: new TextLuaIOStream(mode, stream);
}

public void Close()
{
Dispose();
}
}

Expand Down Expand Up @@ -64,161 +72,73 @@ public bool IsReadable(string path)
}
}

public ValueTask<LuaFileContent> ReadFileContentAsync(string path, CancellationToken cancellationToken)
{
var bytes = File.ReadAllBytes(path);
return new(new LuaFileContent(bytes));
}

public ILuaIOStream Open(string path, LuaFileOpenMode luaMode)
ILuaIOStream Open(string path, LuaFileOpenMode luaMode, LuaFileContentType contentType)
{
var (mode, access) = GetFileMode(luaMode);
Stream stream;

if (luaMode == LuaFileOpenMode.ReadAppend)
{
var s = new LuaIOStreamWrapper(luaMode, File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete));
s.Seek(0, SeekOrigin.End);
return s;
stream = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete);
}

return new LuaIOStreamWrapper(luaMode, File.Open(path, mode, access, FileShare.ReadWrite | FileShare.Delete));
}

public void Rename(string oldName, string newName)
{
if (oldName == newName) return;
File.Move(oldName, newName);
File.Delete(oldName);
}

public void Remove(string path)
{
File.Delete(path);
}

static readonly string directorySeparator = Path.DirectorySeparatorChar.ToString();
public string DirectorySeparator => directorySeparator;

public string GetTempFileName()
{
return Path.GetTempFileName();
}

public ILuaIOStream OpenTempFileStream()
{
return new LuaIOStreamWrapper(LuaFileOpenMode.ReadAppend, File.Open(Path.GetTempFileName(), FileMode.Open, FileAccess.ReadWrite));
}
}

internal sealed class LuaIOStreamWrapper(LuaFileOpenMode mode, Stream innerStream) : ILuaIOStream
{
public LuaFileOpenMode Mode => mode;
Utf8Reader? reader;
ulong flushSize = ulong.MaxValue;
ulong nextFlushSize = ulong.MaxValue;

public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
{
ThrowIfNotReadable();
reader ??= new();
return new(reader.ReadLine(innerStream));
}

public ValueTask<string> ReadToEndAsync(CancellationToken cancellationToken)
{
ThrowIfNotReadable();
reader ??= new();
return new(reader.ReadToEnd(innerStream));
}

public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)
{
ThrowIfNotReadable();
reader ??= new();
return new(reader.Read(innerStream, count));
}

public ValueTask WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken)
{
ThrowIfNotWritable();
if (mode is LuaFileOpenMode.Append or LuaFileOpenMode.ReadAppend)
else
{
innerStream.Seek(0, SeekOrigin.End);
stream = File.Open(path, mode, access, FileShare.ReadWrite | FileShare.Delete);
}

using var byteBuffer = new PooledArray<byte>(4096);
var encoder = Encoding.UTF8.GetEncoder();
var totalBytes = encoder.GetByteCount(buffer.Span, true);
var remainingBytes = totalBytes;
while (0 < remainingBytes)
{
var byteCount = encoder.GetBytes(buffer.Span, byteBuffer.AsSpan(), false);
innerStream.Write(byteBuffer.AsSpan()[..byteCount]);
remainingBytes -= byteCount;
}
ILuaIOStream wrapper = contentType == LuaFileContentType.Binary
? new BinaryLuaIOStream(luaMode, stream)
: new TextLuaIOStream(luaMode, stream);

if (nextFlushSize < (ulong)totalBytes)
if (luaMode == LuaFileOpenMode.ReadAppend)
{
innerStream.Flush();
nextFlushSize = flushSize;
wrapper.Seek(0, SeekOrigin.End);
}

reader?.Clear();
return new();
}

public ValueTask FlushAsync(CancellationToken cancellationToken)
{
innerStream.Flush();
nextFlushSize = flushSize;
return new();
return wrapper;
}

public void SetVBuf(LuaFileBufferingMode mode, int size)
public ILuaIOStream Open(string path, LuaFileMode mode)
{
// Ignore size parameter
if (mode is LuaFileBufferingMode.NoBuffering or LuaFileBufferingMode.LineBuffering)
if (mode is LuaFileMode.ReadBinaryOrText)
{
nextFlushSize = 0;
flushSize = 0;
}
else
{
nextFlushSize = (ulong)size;
flushSize = (ulong)size;
return new LuaChunkStream(File.OpenRead(path));
}

var openMode = mode.GetOpenMode();
var contentType = mode.GetContentType();
return Open(path, openMode, contentType);
}

public long Seek(long offset, SeekOrigin origin)
public ILuaIOStream Open(string path, string mode)
{
reader?.Clear();
return innerStream.Seek(offset, origin);
var flags = LuaFileModeExtensions.ParseModeString(mode);
return Open(path, flags);
}

public bool CanRead => innerStream.CanRead;
public bool CanSeek => innerStream.CanSeek;
public bool CanWrite => innerStream.CanWrite;
public void Rename(string oldName, string newName)
{
if (oldName == newName) return;
File.Move(oldName, newName);
File.Delete(oldName);
}

void ThrowIfNotReadable()
public void Remove(string path)
{
if (!innerStream.CanRead)
{
throw new IOException("Stream is not readable.");
}
File.Delete(path);
}

void ThrowIfNotWritable()
static readonly string directorySeparator = Path.DirectorySeparatorChar.ToString();
public string DirectorySeparator => directorySeparator;

public string GetTempFileName()
{
if (!innerStream.CanWrite)
{
throw new IOException("Stream is not writable.");
}
return Path.GetTempFileName();
}

public void Dispose()
public ILuaIOStream OpenTempFileStream()
{
if (innerStream.CanWrite) innerStream.Flush();
innerStream.Dispose();
reader?.Dispose();
return new TextLuaIOStream(LuaFileOpenMode.ReadAppend, File.Open(Path.GetTempFileName(), FileMode.Open, FileAccess.ReadWrite));
}
}
Loading