Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] More C# friendly API for Godot file access #40775

Closed
Closed
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
6 changes: 6 additions & 0 deletions core/bind/core_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,11 @@ void _File::close() {
f = nullptr;
}

void _File::flush() {
ERR_FAIL_COND_MSG(!f, "File must be opened before use.");
f->flush();
}

bool _File::is_open() const {
return f != nullptr;
}
Expand Down Expand Up @@ -1543,6 +1548,7 @@ void _File::_bind_methods() {

ClassDB::bind_method(D_METHOD("open", "path", "flags"), &_File::open);
ClassDB::bind_method(D_METHOD("close"), &_File::close);
ClassDB::bind_method(D_METHOD("flush"), &_File::flush);
ClassDB::bind_method(D_METHOD("get_path"), &_File::get_path);
ClassDB::bind_method(D_METHOD("get_path_absolute"), &_File::get_path_absolute);
ClassDB::bind_method(D_METHOD("is_open"), &_File::is_open);
Expand Down
1 change: 1 addition & 0 deletions core/bind/core_bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ class _File : public Reference {
Error open(const String &p_path, ModeFlags p_mode_flags); // open a file.
void close(); // Close a file.
bool is_open() const; // True when file is open.
void flush();

String get_path() const; // Returns the path for the current open file.
String get_path_absolute() const; // Returns the absolute path for the current open file.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;
using System.IO;

namespace Godot
{
public static class ErrorExtensions
{
public static void ThrowOnError(this Error error)
{
if (error.IsException(out var e))
throw e;
}

internal static void DisposeAndThrowOnError(this Error error, IDisposable disposable)
{
if (error.IsException(out var e))
{
disposable.Dispose();
throw e;
}
}

public static bool IsException(this Error error, out Exception e)
{
if (error == Error.Ok)
{
e = null;
return false;
}

var msg = $"Method returned with Godot error: '{Enum.GetName(typeof(Error), error)}'";

switch (error)
{
case Error.InvalidParameter:
e = new ArgumentException(message: msg, paramName: null);
return true;
case Error.ParameterRangeError:
e = new ArgumentOutOfRangeException(paramName: null, message: msg);
return true;
case Error.FileBadDrive:
case Error.FileBadPath:
case Error.FileNotFound:
e = new FileNotFoundException(msg);
return true;
case Error.AlreadyInUse:
case Error.CantAcquireResource:
case Error.CantOpen:
case Error.CantCreate:
case Error.FileAlreadyInUse:
case Error.FileCantOpen:
case Error.FileCantRead:
case Error.FileCantWrite:
case Error.FileEof:
case Error.Locked:
e = new IOException(msg);
return true;
case Error.InvalidData:
e = new InvalidDataException(msg);
return true;
case Error.OutOfMemory:
e = new OutOfMemoryException(msg);
return true;
case Error.Timeout:
e = new TimeoutException(msg);
return true;
case Error.FileNoPermission:
case Error.Unauthorized:
e = new UnauthorizedAccessException(msg);
return true;
default:
e = new Exception(msg);
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering all paths after the first if statement return true, maybe these should be break and then we can return true at the bottom to make this clear?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do!

}
}
}
}
213 changes: 213 additions & 0 deletions modules/mono/glue/GodotSharp/GodotSharp/Core/IO/Directory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

namespace Godot
{
public partial class Directory
{
private static void ThrowIfParamPathIsNullOrEmpty(string path, string paramName)
{
if (path == null)
throw new ArgumentNullException(paramName, "File name cannot be null.");
if (path.Length == 0)
throw new ArgumentException("Empty file name is not legal.", paramName);
}

// TODO: Need a replacement for DirectoryInfo, which is what this method would return.
public static string GetParent(string path)
{
ThrowIfParamPathIsNullOrEmpty(path, nameof(path));

string parent = path.GetBaseDir();

if (string.IsNullOrEmpty(parent))
return null;

return parent;
}

// TODO: Need a replacement for DirectoryInfo, which is what this method would return.
public static string CreateDirectory(string path)
{
ThrowIfParamPathIsNullOrEmpty(path, nameof(path));

using var dir = new Directory();

Error error;

// TODO: Replace this workaround (MakeDirRecursive fails if the directory or any of the sub-directories already exists)
while ((error = dir.MakeDirRecursive(path)) == Error.AlreadyExists && !Exists(path))
{
}

error.ThrowOnError();

return path;
}

public static bool Exists(string path)
{
try
{
if (string.IsNullOrEmpty(path))
return false;

using var dir = new Directory();
return dir.DirExists(path);
}
catch (ArgumentException)
{
}
catch (IOException)
{
}
catch (UnauthorizedAccessException)
{
}

return false;
}

private enum SearchKind
{
Files,
Directories,
All
}

public static string[] GetDirectories(string path)
=> GetDirectories(path, SearchOption.TopDirectoryOnly);

public static string[] GetDirectories(string path, SearchOption searchOption)
=> EnumerateDirectories(path, searchOption).ToArray();

public static string[] GetFiles(string path)
=> GetFiles(path, SearchOption.TopDirectoryOnly);

public static string[] GetFiles(string path, SearchOption searchOption)
=> EnumerateFiles(path, searchOption).ToArray();

public static string[] GetFileSystemEntries(string path)
=> GetFileSystemEntries(path, SearchOption.TopDirectoryOnly);

public static string[] GetFileSystemEntries(string path, SearchOption searchOption)
=> EnumerateFileSystemEntries(path, searchOption).ToArray();

public static IEnumerable<string> EnumerateDirectories(string path)
=> EnumerateDirectories(path, SearchOption.TopDirectoryOnly);

public static IEnumerable<string> EnumerateDirectories(string path, SearchOption searchOption)
=> _EnumeratePaths(path, SearchKind.Directories, searchOption).ToArray();

public static IEnumerable<string> EnumerateFiles(string path)
=> EnumerateFiles(path, SearchOption.TopDirectoryOnly);

public static IEnumerable<string> EnumerateFiles(string path, SearchOption searchOption)
=> _EnumeratePaths(path, SearchKind.Files, searchOption).ToArray();

public static IEnumerable<string> EnumerateFileSystemEntries(string path)
=> EnumerateFileSystemEntries(path, SearchOption.TopDirectoryOnly);

public static IEnumerable<string> EnumerateFileSystemEntries(string path, SearchOption searchOption)
=> _EnumeratePaths(path, SearchKind.All, searchOption).ToArray();

private static IEnumerable<string> _EnumeratePaths(
string path,
SearchKind searchKind,
SearchOption options)
{
if (path == null)
throw new ArgumentNullException(nameof(path));

using var dir = new Directory();

var dirQueue = new Queue<string>();
dirQueue.Enqueue(path);

while (dirQueue.TryDequeue(out string currentDir))
{
dir.Open(currentDir).ThrowOnError();

dir.ListDirBegin(skipNavigational: true, skipHidden: false).ThrowOnError();

string next;
while (!string.IsNullOrEmpty(next = dir.GetNext()))
{
next = currentDir.PlusFile(next);

bool isDir = dir.CurrentIsDir();

if (isDir)
{
if (options == SearchOption.AllDirectories)
dirQueue.Enqueue(next);

if (searchKind == SearchKind.Directories || searchKind == SearchKind.All)
yield return next;
}
else
{
if (searchKind == SearchKind.Files || searchKind == SearchKind.All)
yield return next;
}
}

dir.ListDirEnd();
}
}

public static string GetDirectoryRoot(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));

string fullPath = Path.GetFullPath(path);
string root = Path.GetPathRoot(fullPath)!;

return root;
}

public static string GetCurrentDirectory() => System.Environment.CurrentDirectory;

public static void Delete(string path)
{
ThrowIfParamPathIsNullOrEmpty(path, nameof(path));

if (File.Exists(path))
throw new IOException($"A file with the same name and location already exists: '{path}'.");
if (!Exists(path))
throw new DirectoryNotFoundException($"The directory could not found: '{path}'.");

using var dir = new Directory();
dir.Remove(path).ThrowOnError();
}

public static void Delete(string path, bool recursive)
=> throw new NotImplementedException();

public static string[] GetLogicalDrives()
{
using var dir = new Directory();
int driveCount = dir.GetDriveCount();
var logicalDrives = new string[driveCount];

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
for (int i = 0; i < driveCount; i++)
{
logicalDrives[i] = dir.GetDrive(i) + "\\";
}
}
else
{
for (int i = 0; i < driveCount; i++)
logicalDrives[i] = dir.GetDrive(i);
}

return logicalDrives;
}
}
}
Loading