Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
35c654d
feat: enhance module loading and package management
Akeit0 May 18, 2025
39be24e
change: abstract async file and stream system
Akeit0 May 18, 2025
1c778d2
rename: rename LoadModuleAsync to ReadFileAsync
Akeit0 May 18, 2025
bf1e2d8
rename: rename LuaFile to LuaFileContent and update related methods
Akeit0 May 18, 2025
c784577
rename: rename ILuaFileManager to ILuaFileSystem and update references
Akeit0 May 19, 2025
e75c81b
rename: update FileManager references to FileSystem in OperatingSyste…
Akeit0 May 19, 2025
2403e5b
change: move file systems to Lua.IO
Akeit0 May 19, 2025
5278982
refactor: rename SystemStream references to StreamWrapper
Akeit0 May 19, 2025
95e1c80
refactor: update ReadByteAsync to ReadStringAsync and simplify Seek m…
Akeit0 May 20, 2025
f25d1aa
refactor: simplify FileHandle by removing reader/writer and using ISt…
Akeit0 May 20, 2025
3ef61e3
refactor: update file opening methods to use LuaFileMode and simplify…
Akeit0 May 20, 2025
fa2c90d
refactor: rename LuaFileMode to LuaFileOpenMode
Akeit0 May 20, 2025
12413f3
change: update Open method to include throwError parameter for error …
Akeit0 May 20, 2025
0cf2eca
feat: add GetTempFileName method to ILuaFileSystem and update TmpName…
Akeit0 May 20, 2025
89d6ce7
reformat
Akeit0 May 20, 2025
060ee0f
feat: add DirectorySeparator property to ILuaFileSystem
Akeit0 May 20, 2025
5199f2f
fix: FileHandle.Read
Akeit0 May 20, 2025
299ab4d
rename: rename IStream to ILuaIOStream
Akeit0 May 20, 2025
0d65bce
refactor: remove unused members from ILuaFileSystem interface
Akeit0 May 20, 2025
f499aef
feat: implement enum LuaFileBufferingMode for SetVBuf method
Akeit0 May 20, 2025
e9c4b27
test: add abstract file read tests
Akeit0 May 20, 2025
2ef8081
fix: io exception was not thrown well
Akeit0 May 21, 2025
ca04ade
add: io.tmpfile
Akeit0 May 21, 2025
3b655f3
fix: compile error
Akeit0 May 21, 2025
fd686da
fix : ILuaIOStream open mode behavior
Akeit0 May 21, 2025
c72b8b5
fix: FileSystem.OpenTempFileStream is not used
Akeit0 May 21, 2025
ecdf1a8
fix: update io module to include stdin, stdout, and stderr
Akeit0 May 21, 2025
678a2fe
change to throw open error always
Akeit0 May 21, 2025
705856e
fix: throw open error
Akeit0 May 21, 2025
8a6a083
fix: test compile error
Akeit0 May 21, 2025
5c19191
fix: LuaFileBufferingMode order for c compatibility
Akeit0 May 21, 2025
d6ceabf
Merge branch 'v0.5-dev' into file-system
Akeit0 May 21, 2025
772d71e
fix: io registry field naming std -> _IO_
Akeit0 May 21, 2025
46df7d6
Merge remote-tracking branch 'origin/file-system' into file-system
Akeit0 May 21, 2025
8104c0b
fix: flush was not called on close
Akeit0 May 22, 2025
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
224 changes: 224 additions & 0 deletions src/Lua/IO/ILuaFileSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
using Lua.Internal;
using System.Text;

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 void Rename(string oldName, string newName);
public void Remove(string path);
public string DirectorySeparator { get; }
public string GetTempFileName();
public ILuaIOStream OpenTempFileStream();
}

public interface ILuaIOStream : IDisposable
{
public LuaFileOpenMode Mode { get; }
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 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)
{
return new LuaIOStreamWrapper(mode, stream);
}
}

public sealed class FileSystem : ILuaFileSystem
{
public static readonly FileSystem Instance = new();

public static (FileMode, FileAccess access) GetFileMode(LuaFileOpenMode luaFileOpenMode)
{
return luaFileOpenMode switch
{
LuaFileOpenMode.Read => (FileMode.Open, FileAccess.Read),
LuaFileOpenMode.Write => (FileMode.Create, FileAccess.Write),
LuaFileOpenMode.Append => (FileMode.Append, FileAccess.Write),
LuaFileOpenMode.ReadWriteOpen => (FileMode.Open, FileAccess.ReadWrite),
LuaFileOpenMode.ReadWriteCreate => (FileMode.Truncate, FileAccess.ReadWrite),
LuaFileOpenMode.ReadAppend => (FileMode.Append, FileAccess.ReadWrite),
_ => throw new ArgumentOutOfRangeException(nameof(luaFileOpenMode), luaFileOpenMode, null)
};
}

public bool IsReadable(string path)
{
if (!File.Exists(path)) return false;
try
{
File.Open(path, FileMode.Open, FileAccess.Read).Dispose();
return true;
}
catch (Exception)
{
return false;
}
}

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)
{
var (mode, access) = GetFileMode(luaMode);

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;
}

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)
{
innerStream.Seek(0, SeekOrigin.End);
}

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;
}

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

reader?.Clear();
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)
{
reader?.Clear();
return innerStream.Seek(offset, origin);
}

public bool CanRead => innerStream.CanRead;
public bool CanSeek => innerStream.CanSeek;
public bool CanWrite => innerStream.CanWrite;

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();
reader?.Dispose();
}
}
18 changes: 18 additions & 0 deletions src/Lua/IO/LuaFileBufferingMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Lua.IO;

public enum LuaFileBufferingMode
{
/// <summary>
/// Full buffering `full` in Lua
/// </summary>
FullBuffering,

/// <summary>
/// Line buffering `line` in Lua
/// </summary>
LineBuffering,
/// <summary>
/// No buffering. `no` in Lua
/// </summary>
NoBuffering,
}
35 changes: 35 additions & 0 deletions src/Lua/IO/LuaFileOpenMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace Lua.IO
{
public enum LuaFileOpenMode
{
/// <summary>
/// r
/// </summary>
Read,

/// <summary>
/// w
/// </summary>
Write,

/// <summary>
/// a
/// </summary>
Append,

/// <summary>
/// r+
/// </summary>
ReadWriteOpen,

/// <summary>
/// w+
/// </summary>
ReadWriteCreate,

/// <summary>
/// a+
/// </summary>
ReadAppend,
}
}
Loading