mirror of
https://github.com/Ale32bit/Capy64.git
synced 2025-12-14 10:05:44 +00:00
572 lines
14 KiB
C#
572 lines
14 KiB
C#
// This file is part of Capy64 - https://github.com/Ale32bit/Capy64
|
|
// Copyright 2023 Alessandro "AlexDevs" Proto
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License").
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
using Capy64.API;
|
|
using Capy64.Runtime.Extensions;
|
|
using Capy64.Runtime.Objects;
|
|
using KeraLua;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
|
|
namespace Capy64.Runtime.Libraries;
|
|
|
|
public class FileSystemLib : IComponent
|
|
{
|
|
public static string DataPath = Path.Combine(Capy64.AppDataPath, "data");
|
|
|
|
public FileSystemLib()
|
|
{
|
|
if (!Directory.Exists(DataPath))
|
|
{
|
|
Directory.CreateDirectory(DataPath);
|
|
}
|
|
}
|
|
|
|
// functions to add to the library, always end libraries with null
|
|
private readonly LuaRegister[] Library = new LuaRegister[] {
|
|
new()
|
|
{
|
|
name = "list",
|
|
function = L_List,
|
|
},
|
|
new()
|
|
{
|
|
name = "combine",
|
|
function = L_Combine,
|
|
},
|
|
new()
|
|
{
|
|
name = "getName",
|
|
function = L_GetPathName,
|
|
},
|
|
new()
|
|
{
|
|
name = "getDir",
|
|
function = L_GetDirectoryName,
|
|
},
|
|
new()
|
|
{
|
|
name = "getSize",
|
|
function = L_GetFileSize,
|
|
},
|
|
new()
|
|
{
|
|
name = "exists",
|
|
function = L_Exists,
|
|
},
|
|
new()
|
|
{
|
|
name = "isReadOnly",
|
|
function = L_IsReadOnly,
|
|
},
|
|
new()
|
|
{
|
|
name = "makeDir",
|
|
function = L_MakeDir,
|
|
},
|
|
new()
|
|
{
|
|
name = "move",
|
|
function = L_Move,
|
|
},
|
|
new()
|
|
{
|
|
name = "copy",
|
|
function = L_Copy,
|
|
},
|
|
new()
|
|
{
|
|
name = "delete",
|
|
function = L_Delete,
|
|
},
|
|
new()
|
|
{
|
|
name = "attributes",
|
|
function = L_GetAttributes,
|
|
},
|
|
new()
|
|
{
|
|
name = "isDir",
|
|
function = L_IsDirectory,
|
|
},
|
|
new()
|
|
{
|
|
name = "open",
|
|
function = L_Open,
|
|
},
|
|
new(), // NULL
|
|
};
|
|
|
|
public FileSystemLib(Capy64 _) { }
|
|
|
|
public void LuaInit(Lua state)
|
|
{
|
|
// Add "fs" library to lua, not global (uses require())
|
|
state.RequireF("fs", Open, false);
|
|
}
|
|
|
|
private int Open(IntPtr state)
|
|
{
|
|
var l = Lua.FromIntPtr(state);
|
|
l.NewLib(Library);
|
|
return 1;
|
|
}
|
|
|
|
public static string SanitizePath(string path)
|
|
{
|
|
// Replace \ to / for cross compatibility in case users prefer to use \
|
|
path = path.Replace("\\", "/");
|
|
|
|
// Get drive root (C:\ for Windows, / for *nix)
|
|
var rootPath = Path.GetFullPath(Path.GetPathRoot("/") ?? "/");
|
|
|
|
var invalidPathChars = Path.GetInvalidPathChars();
|
|
path = invalidPathChars.Aggregate(path, (current, invalidChar) => current.Replace(invalidChar, ' '));
|
|
|
|
// Join path to rootPath and resolves to absolute path
|
|
// Relative paths are resolved here (es. ../ and ./)
|
|
var absolutePath = Path.GetFullPath(path, rootPath);
|
|
|
|
// Trim root from path
|
|
string localPath;
|
|
try
|
|
{
|
|
localPath = absolutePath.Remove(0, rootPath.Length);
|
|
}
|
|
catch
|
|
{
|
|
localPath = absolutePath;
|
|
}
|
|
return Path.Join("/", localPath);
|
|
}
|
|
|
|
public static string Resolve(string path)
|
|
{
|
|
var isolatedPath = SanitizePath(path);
|
|
|
|
// Now join the isolatedPath to the Lua directory, always inside of it
|
|
var resolvedPath = Path.Join(DataPath, isolatedPath);
|
|
|
|
return resolvedPath;
|
|
}
|
|
|
|
public static string CleanOutputPath(string path)
|
|
{
|
|
if (string.IsNullOrEmpty(path))
|
|
return "";
|
|
|
|
var clean = path.Replace(Path.DirectorySeparatorChar, '/');
|
|
return clean;
|
|
}
|
|
|
|
public static string TrimBasePath(string path)
|
|
{
|
|
return path;
|
|
}
|
|
|
|
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive, bool overwrite = false)
|
|
{
|
|
var dir = new DirectoryInfo(sourceDir);
|
|
|
|
if (!dir.Exists)
|
|
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
|
|
|
DirectoryInfo[] dirs = dir.GetDirectories();
|
|
|
|
Directory.CreateDirectory(destinationDir);
|
|
|
|
foreach (FileInfo file in dir.GetFiles())
|
|
{
|
|
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
|
file.CopyTo(targetFilePath, overwrite);
|
|
}
|
|
|
|
if (recursive)
|
|
{
|
|
foreach (DirectoryInfo subDir in dirs)
|
|
{
|
|
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
|
CopyDirectory(subDir.FullName, newDestinationDir, true, overwrite);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static int L_List(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var path = L.CheckString(1);
|
|
path = Resolve(path);
|
|
|
|
if (!Directory.Exists(path))
|
|
{
|
|
L.Error("directory not found");
|
|
}
|
|
|
|
var fileList = Directory.EnumerateFileSystemEntries(path);
|
|
|
|
var list = new List<string>();
|
|
foreach (var file in fileList)
|
|
{
|
|
var sfile = Path.GetFileName(file);
|
|
list.Add(CleanOutputPath(sfile));
|
|
}
|
|
|
|
L.PushArray(list.Order().ToArray());
|
|
|
|
return 1;
|
|
}
|
|
|
|
private static int L_Combine(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var nargs = L.GetTop();
|
|
|
|
var parts = new List<string>();
|
|
for (int i = 1; i <= nargs; i++)
|
|
{
|
|
var pathPart = L.CheckString(i);
|
|
parts.Add(pathPart);
|
|
}
|
|
|
|
var result = Path.Combine(parts.ToArray());
|
|
if (string.IsNullOrEmpty(result))
|
|
{
|
|
L.PushString("");
|
|
return 1;
|
|
}
|
|
|
|
result = SanitizePath(result);
|
|
L.PushString(CleanOutputPath(result));
|
|
|
|
return 1;
|
|
}
|
|
|
|
private static int L_GetPathName(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var path = L.CheckString(1);
|
|
|
|
var result = Path.GetFileName(path);
|
|
|
|
L.PushString(CleanOutputPath(result));
|
|
|
|
return 1;
|
|
}
|
|
|
|
private static int L_GetDirectoryName(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var path = L.CheckString(1);
|
|
|
|
var result = Path.GetDirectoryName(path);
|
|
|
|
L.PushString(CleanOutputPath(result));
|
|
|
|
return 1;
|
|
}
|
|
|
|
private static int L_GetFileSize(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var path = L.CheckString(1);
|
|
|
|
path = Resolve(path);
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
L.Error("file not found");
|
|
}
|
|
|
|
var fileInfo = new FileInfo(path);
|
|
L.PushInteger(fileInfo.Length);
|
|
|
|
return 1;
|
|
}
|
|
|
|
private static int L_Exists(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var path = L.CheckString(1);
|
|
var exists = Path.Exists(Resolve(path));
|
|
|
|
L.PushBoolean(exists);
|
|
|
|
return 1;
|
|
}
|
|
|
|
private static int L_IsReadOnly(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
var path = Resolve(L.CheckString(1));
|
|
|
|
if (File.Exists(path))
|
|
{
|
|
var fileInfo = new FileInfo(path);
|
|
L.PushBoolean(fileInfo.IsReadOnly);
|
|
}
|
|
else if (Directory.Exists(path))
|
|
{
|
|
var dirInfo = new DirectoryInfo(path);
|
|
var isReadOnly = dirInfo.Attributes.HasFlag(FileAttributes.ReadOnly);
|
|
L.PushBoolean(isReadOnly);
|
|
}
|
|
else
|
|
{
|
|
L.Error("path not found");
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
private static int L_MakeDir(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var path = Resolve(L.CheckString(1));
|
|
|
|
if (Path.Exists(path))
|
|
{
|
|
L.Error("path already exists");
|
|
}
|
|
|
|
Directory.CreateDirectory(path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
private static int L_Move(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var sourcePath = Resolve(L.CheckString(1));
|
|
var destPath = Resolve(L.CheckString(2));
|
|
|
|
if (!Path.Exists(sourcePath))
|
|
{
|
|
L.Error("source path not found");
|
|
}
|
|
|
|
if (Path.Exists(destPath))
|
|
{
|
|
L.Error("destination path already exists");
|
|
}
|
|
|
|
var attr = File.GetAttributes(sourcePath);
|
|
if (attr.HasFlag(FileAttributes.Directory))
|
|
{
|
|
Directory.Move(sourcePath, destPath);
|
|
}
|
|
else
|
|
{
|
|
File.Move(sourcePath, destPath);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private static int L_Copy(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var sourcePath = Resolve(L.CheckString(1));
|
|
var destPath = Resolve(L.CheckString(2));
|
|
|
|
if (!Path.Exists(sourcePath))
|
|
{
|
|
L.Error("source path not found");
|
|
}
|
|
|
|
if (Path.Exists(destPath))
|
|
{
|
|
L.Error("destination path already exists");
|
|
}
|
|
|
|
var attr = File.GetAttributes(sourcePath);
|
|
if (attr.HasFlag(FileAttributes.Directory))
|
|
{
|
|
CopyDirectory(sourcePath, destPath, true);
|
|
}
|
|
else
|
|
{
|
|
File.Copy(sourcePath, destPath);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private static int L_Delete(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var path = Resolve(L.CheckString(1));
|
|
bool recursive = false;
|
|
if (!L.IsNoneOrNil(2))
|
|
{
|
|
L.CheckType(2, LuaType.Boolean);
|
|
recursive = L.ToBoolean(2);
|
|
}
|
|
|
|
if (!Path.Exists(path))
|
|
{
|
|
L.Error("path not found");
|
|
}
|
|
|
|
try
|
|
{
|
|
var attr = File.GetAttributes(path);
|
|
if (attr.HasFlag(FileAttributes.Directory))
|
|
{
|
|
if (!recursive && Directory.GetFileSystemEntries(path).Any())
|
|
{
|
|
L.Error("directory not empty");
|
|
return 0;
|
|
}
|
|
Directory.Delete(path, recursive);
|
|
}
|
|
else
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
return L.Error(e.Message);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private static int L_GetAttributes(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var path = Resolve(L.CheckString(1));
|
|
|
|
// { size = number, isDir = boolean, isReadOnly = boolean, created = number, modified = number }
|
|
if (!Path.Exists(path))
|
|
{
|
|
L.Error("path not found");
|
|
}
|
|
|
|
var attributes = new Dictionary<string, object>();
|
|
|
|
var pathAttributes = File.GetAttributes(path);
|
|
if (pathAttributes.HasFlag(FileAttributes.Directory))
|
|
{
|
|
var dattrs = new DirectoryInfo(path);
|
|
attributes["size"] = 0;
|
|
attributes["isDirectory"] = true;
|
|
attributes["isReadOnly"] = dattrs.Attributes.HasFlag(FileAttributes.ReadOnly);
|
|
attributes["created"] = new DateTimeOffset(dattrs.CreationTimeUtc).ToUnixTimeMilliseconds();
|
|
attributes["modified"] = new DateTimeOffset(dattrs.LastWriteTimeUtc).ToUnixTimeMilliseconds();
|
|
}
|
|
else
|
|
{
|
|
var fattrs = new FileInfo(path);
|
|
attributes["size"] = fattrs.Length;
|
|
attributes["isDirectory"] = false;
|
|
attributes["isReadOnly"] = fattrs.IsReadOnly;
|
|
attributes["created"] = new DateTimeOffset(fattrs.CreationTimeUtc).ToUnixTimeMilliseconds();
|
|
attributes["modified"] = new DateTimeOffset(fattrs.LastWriteTimeUtc).ToUnixTimeMilliseconds();
|
|
}
|
|
|
|
L.NewTable();
|
|
|
|
foreach (var attribute in attributes)
|
|
{
|
|
L.PushString(attribute.Key);
|
|
L.PushValue(attribute.Value);
|
|
|
|
L.SetTable(-3);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
private static int L_IsDirectory(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
|
|
var path = Resolve(L.CheckString(1));
|
|
|
|
L.PushBoolean(Directory.Exists(path));
|
|
|
|
return 1;
|
|
}
|
|
|
|
private static int L_Open(IntPtr state)
|
|
{
|
|
var L = Lua.FromIntPtr(state);
|
|
var path = Resolve(L.CheckString(1));
|
|
var mode = L.OptString(2, "r");
|
|
|
|
var errorMessage = "invalid file mode";
|
|
if (mode.Length < 1)
|
|
{
|
|
L.ArgumentError(2, errorMessage);
|
|
return 0;
|
|
}
|
|
|
|
FileMode fileMode;
|
|
FileAccess fileAccess;
|
|
switch (mode[0])
|
|
{
|
|
case 'r':
|
|
if (!File.Exists(path))
|
|
{
|
|
L.Error("file not found");
|
|
return 0;
|
|
}
|
|
fileMode = FileMode.Open;
|
|
fileAccess = FileAccess.Read;
|
|
break;
|
|
case 'w':
|
|
fileMode = FileMode.Create;
|
|
fileAccess = FileAccess.Write;
|
|
break;
|
|
case 'a':
|
|
fileMode = FileMode.Append;
|
|
fileAccess = FileAccess.Write;
|
|
break;
|
|
default:
|
|
L.ArgumentError(2, errorMessage);
|
|
return 0;
|
|
}
|
|
|
|
try
|
|
{
|
|
var fileStream = File.Open(path, fileMode, fileAccess, FileShare.ReadWrite | FileShare.Delete);
|
|
|
|
ObjectManager.PushObject(L, fileStream);
|
|
L.SetMetaTable(FileHandle.ObjectType);
|
|
|
|
return 1;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
L.Error(ex.Message);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
}
|