mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-20 17:08:00 +02:00
Moving corlib files out of "System" directory into root
This commit is contained in:
parent
4cd58262e4
commit
7dbfd15292
179 changed files with 3 additions and 0 deletions
284
BeefLibs/corlib/src/IO/Directory.bf
Normal file
284
BeefLibs/corlib/src/IO/Directory.bf
Normal file
|
@ -0,0 +1,284 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
public static class Directory
|
||||
{
|
||||
static extern bool Exists(char8* fileName);
|
||||
|
||||
public static bool Exists(StringView fileName)
|
||||
{
|
||||
return Exists(fileName.ToScopeCStr!());
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> CreateDirectory(StringView fullPath)
|
||||
{
|
||||
for (int32 pass = 0; pass < 2; pass++)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpDirectory_Create(fullPath.ToScopeCStr!(), &result);
|
||||
|
||||
if (result == .Ok)
|
||||
break;
|
||||
|
||||
if (result == .AlreadyExists)
|
||||
return .Ok;
|
||||
if ((pass == 0) && (result == .NotFound))
|
||||
{
|
||||
int32 prevSlash = Math.Max((int32)fullPath.LastIndexOf('/'), (int32)fullPath.LastIndexOf('\\'));
|
||||
if (prevSlash != -1)
|
||||
{
|
||||
StringView prevDir = StringView(fullPath, 0, prevSlash);
|
||||
Try!(CreateDirectory(prevDir));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return .Err(result);
|
||||
}
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> Delete(StringView path)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpDirectory_Delete(path.ToScopeCStr!(), &result);
|
||||
if (result != .Ok)
|
||||
return .Err(result);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void> DelTree(StringView path)
|
||||
{
|
||||
if (path.Length <= 2)
|
||||
return .Err;
|
||||
if ((path[0] != '/') && (path[0] != '\\'))
|
||||
{
|
||||
if (path[1] == ':')
|
||||
{
|
||||
if (path.Length < 3)
|
||||
return .Err;
|
||||
}
|
||||
else
|
||||
return .Err;
|
||||
}
|
||||
|
||||
for (var fileEntry in Directory.EnumerateDirectories(path))
|
||||
{
|
||||
let fileName = scope String();
|
||||
fileEntry.GetFilePath(fileName);
|
||||
Try!(DelTree(fileName));
|
||||
}
|
||||
|
||||
for (var fileEntry in Directory.EnumerateFiles(path))
|
||||
{
|
||||
let fileName = scope String();
|
||||
fileEntry.GetFilePath(fileName);
|
||||
|
||||
Try!(File.SetAttributes(fileName, FileAttributes.Archive));
|
||||
Try!(File.Delete(fileName));
|
||||
}
|
||||
|
||||
// Allow failure for the directory, this can often be locked for various reasons
|
||||
// but we only consider a file failure to be an "actual" failure
|
||||
Directory.Delete(path).IgnoreError();
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> Move(StringView oldName, StringView newName)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpDirectory_Rename(oldName.ToScopeCStr!(), newName.ToScopeCStr!(), &result);
|
||||
if (result != .Ok)
|
||||
return .Err(result);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static void GetCurrentDirectory(String outPath)
|
||||
{
|
||||
Platform.GetStrHelper(outPath, scope (outPtr, outSize, outResult) =>
|
||||
{
|
||||
Platform.BfpDirectory_GetCurrent(outPtr, outSize, (Platform.BfpFileResult*)outResult);
|
||||
});
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> SetCurrentDirectory(StringView path)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpDirectory_SetCurrent(path.ToScopeCStr!(), &result);
|
||||
if (result != .Ok)
|
||||
return .Err(result);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
enum EnumerateFlags
|
||||
{
|
||||
Files = 1,
|
||||
Directories = 2
|
||||
}
|
||||
|
||||
public static FileEnumerator Enumerate(StringView searchStr, EnumerateFlags flags)
|
||||
{
|
||||
String useStr = new String(searchStr.Length + 1);
|
||||
useStr.Append(searchStr);
|
||||
useStr.EnsureNullTerminator();
|
||||
|
||||
Platform.BfpFindFileFlags bfpFlags = .None;
|
||||
if (flags.HasFlag(.Directories))
|
||||
bfpFlags |= .Directories;
|
||||
if (flags.HasFlag(.Files))
|
||||
bfpFlags |= .Files;
|
||||
let findFileData = Platform.BfpFindFileData_FindFirstFile(useStr, bfpFlags, null);
|
||||
return FileEnumerator(useStr, findFileData);
|
||||
}
|
||||
|
||||
public static FileEnumerator EnumerateDirectories(StringView dirPath)
|
||||
{
|
||||
let searchStr = scope String();
|
||||
searchStr.Append(dirPath);
|
||||
searchStr.Append("/*");
|
||||
return Enumerate(searchStr, .Directories);
|
||||
}
|
||||
|
||||
public static FileEnumerator EnumerateDirectories(StringView dirPath, StringView wildcard)
|
||||
{
|
||||
let searchStr = scope String();
|
||||
searchStr.Append(dirPath);
|
||||
searchStr.Append("/");
|
||||
searchStr.Append(wildcard);
|
||||
return Enumerate(searchStr, .Directories);
|
||||
}
|
||||
|
||||
public static FileEnumerator EnumerateFiles(StringView dirPath)
|
||||
{
|
||||
let searchStr = scope String();
|
||||
searchStr.Append(dirPath);
|
||||
searchStr.Append("/*");
|
||||
return Enumerate(searchStr, .Files);
|
||||
}
|
||||
|
||||
public static FileEnumerator EnumerateFiles(StringView dirPath, StringView wildcard)
|
||||
{
|
||||
let searchStr = scope String();
|
||||
searchStr.Append(dirPath);
|
||||
searchStr.Append("/");
|
||||
searchStr.Append(wildcard);
|
||||
return Enumerate(searchStr, .Files);
|
||||
}
|
||||
}
|
||||
|
||||
struct FileFindEntry
|
||||
{
|
||||
String mSearchStr;
|
||||
Platform.BfpFindFileData* mFindFileData;
|
||||
|
||||
public this(String searchStr, Platform.BfpFindFileData* findFileData)
|
||||
{
|
||||
mSearchStr = searchStr;
|
||||
mFindFileData = findFileData;
|
||||
}
|
||||
|
||||
public bool IsDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return Platform.BfpFindFileData_GetFileAttributes(mFindFileData).HasFlag(.Directory);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetFileName(String outFileName)
|
||||
{
|
||||
Platform.GetStrHelper(outFileName, scope (outPtr, outSize, outResult) =>
|
||||
{
|
||||
Platform.BfpFindFileData_GetFileName(mFindFileData, outPtr, outSize, (Platform.BfpFileResult*)outResult);
|
||||
});
|
||||
}
|
||||
|
||||
public void GetFilePath(String outPath)
|
||||
{
|
||||
Path.GetDirectoryPath(mSearchStr, outPath);
|
||||
outPath.Append(Path.DirectorySeparatorChar);
|
||||
GetFileName(outPath);
|
||||
}
|
||||
|
||||
public DateTime GetLastWriteTime()
|
||||
{
|
||||
return DateTime.FromFileTime((int64)Platform.BfpFindFileData_GetTime_LastWrite(mFindFileData));
|
||||
}
|
||||
|
||||
public DateTime GetLastWriteTimeUtc()
|
||||
{
|
||||
return DateTime.FromFileTimeUtc((int64)Platform.BfpFindFileData_GetTime_LastWrite(mFindFileData));
|
||||
}
|
||||
|
||||
public DateTime GetCreatedTime()
|
||||
{
|
||||
return DateTime.FromFileTime((int64)Platform.BfpFindFileData_GetTime_Created(mFindFileData));
|
||||
}
|
||||
|
||||
public DateTime GetCreatedTimeUtc()
|
||||
{
|
||||
return DateTime.FromFileTimeUtc((int64)Platform.BfpFindFileData_GetTime_Created(mFindFileData));
|
||||
}
|
||||
|
||||
public DateTime GetAccessedTime()
|
||||
{
|
||||
return DateTime.FromFileTime((int64)Platform.BfpFindFileData_GetTime_Access(mFindFileData));
|
||||
}
|
||||
|
||||
public DateTime GetAccessedTimeUtc()
|
||||
{
|
||||
return DateTime.FromFileTimeUtc((int64)Platform.BfpFindFileData_GetTime_Access(mFindFileData));
|
||||
}
|
||||
|
||||
public Platform.BfpFileAttributes GetFileAttributes()
|
||||
{
|
||||
return Platform.BfpFindFileData_GetFileAttributes(mFindFileData);
|
||||
}
|
||||
}
|
||||
|
||||
struct FileEnumerator : IEnumerator<FileFindEntry>
|
||||
{
|
||||
String mSearchStr;
|
||||
Platform.BfpFindFileData* mFindFileData;
|
||||
int mIdx;
|
||||
|
||||
public this(String searchStr, Platform.BfpFindFileData* findFileData)
|
||||
{
|
||||
mSearchStr = searchStr;
|
||||
mFindFileData = findFileData;
|
||||
mIdx = -1;
|
||||
}
|
||||
|
||||
public FileFindEntry Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return FileFindEntry(mSearchStr, mFindFileData);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
delete mSearchStr;
|
||||
if (mFindFileData != null)
|
||||
Platform.BfpFindFileData_Release(mFindFileData);
|
||||
}
|
||||
|
||||
public bool MoveNext() mut
|
||||
{
|
||||
mIdx++;
|
||||
if (mIdx == 0)
|
||||
return mFindFileData != null;
|
||||
|
||||
return Platform.BfpFindFileData_FindNextFile(mFindFileData);
|
||||
}
|
||||
|
||||
public Result<FileFindEntry> GetNext() mut
|
||||
{
|
||||
if (!MoveNext())
|
||||
return .Err;
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
}
|
115
BeefLibs/corlib/src/IO/DynMemStream.bf
Normal file
115
BeefLibs/corlib/src/IO/DynMemStream.bf
Normal file
|
@ -0,0 +1,115 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
class DynMemStream : Stream
|
||||
{
|
||||
List<uint8> mData ~ { if (mOwnsData) delete _; };
|
||||
int mPosition = 0;
|
||||
bool mOwnsData;
|
||||
|
||||
public this()
|
||||
{
|
||||
mData = new .();
|
||||
mOwnsData = true;
|
||||
}
|
||||
|
||||
public uint8* Ptr
|
||||
{
|
||||
get
|
||||
{
|
||||
return mData.Ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public Span<uint8> Content
|
||||
{
|
||||
get
|
||||
{
|
||||
return mData;
|
||||
}
|
||||
}
|
||||
|
||||
public override int64 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
mPosition = (.)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override int64 Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return mData.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public List<uint8> TakeOwnership()
|
||||
{
|
||||
Debug.Assert(mOwnsData);
|
||||
mOwnsData = false;
|
||||
return mData;
|
||||
}
|
||||
|
||||
public override Result<int> TryRead(Span<uint8> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
return .Ok(0);
|
||||
int readBytes = Math.Min(data.Length, mData.Count - mPosition);
|
||||
if (readBytes <= 0)
|
||||
return .Ok(readBytes);
|
||||
|
||||
Internal.MemCpy(data.Ptr, &mData[mPosition], readBytes);
|
||||
mPosition += readBytes;
|
||||
return .Ok(readBytes);
|
||||
}
|
||||
|
||||
public override Result<int> TryWrite(Span<uint8> data)
|
||||
{
|
||||
let count = data.Length;
|
||||
if (count == 0)
|
||||
return .Ok(0);
|
||||
int growSize = mPosition + count - mData.Count;
|
||||
if (growSize > 0)
|
||||
mData.GrowUnitialized(growSize);
|
||||
Internal.MemCpy(&mData[mPosition], data.Ptr, count);
|
||||
mPosition += count;
|
||||
return .Ok(count);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void RemoveFromStart(int count)
|
||||
{
|
||||
mPosition = Math.Max(mPosition - count, 0);
|
||||
mData.RemoveRange(0, count);
|
||||
}
|
||||
}
|
||||
}
|
173
BeefLibs/corlib/src/IO/File.bf
Normal file
173
BeefLibs/corlib/src/IO/File.bf
Normal file
|
@ -0,0 +1,173 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
public enum FileOpenError
|
||||
{
|
||||
NotFound,
|
||||
NotFile,
|
||||
Unknown,
|
||||
SharingViolation
|
||||
}
|
||||
|
||||
public enum FileReadError
|
||||
{
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum FileError
|
||||
{
|
||||
case FileOpenError(FileOpenError);
|
||||
case FileReadError(FileReadError);
|
||||
}
|
||||
|
||||
class File
|
||||
{
|
||||
public static Result<void, FileError> ReadAllText(StringView path, String outText, bool preserveLineEnding = false)
|
||||
{
|
||||
StreamReader sr = scope StreamReader();
|
||||
if (sr.Open(path) case .Err(let err))
|
||||
return .Err(.FileOpenError(err));
|
||||
if (sr.ReadToEnd(outText) case .Err)
|
||||
return .Err(.FileReadError(.Unknown));
|
||||
|
||||
if (!preserveLineEnding)
|
||||
{
|
||||
if (Environment.NewLine.Length > 1)
|
||||
outText.Replace("\r", "");
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void> WriteAllText(StringView path, StringView text, bool doAppend = false)
|
||||
{
|
||||
FileStream fs = scope FileStream();
|
||||
var result = fs.Open(path, doAppend ? .Append : .Create, .Write);
|
||||
if (result case .Err)
|
||||
return .Err;
|
||||
fs.TryWrite(.((uint8*)text.Ptr, text.Length));
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void> WriteAllText(StringView path, StringView text, Encoding encoding)
|
||||
{
|
||||
FileStream fs = scope FileStream();
|
||||
|
||||
int len = encoding.GetEncodedSize(text);
|
||||
uint8* data = new uint8[len]*;
|
||||
defer delete data;
|
||||
int actualLen = encoding.Encode(text, .(data, len));
|
||||
Debug.Assert(len == actualLen);
|
||||
if (len != actualLen)
|
||||
return .Err;
|
||||
|
||||
var result = fs.Open(path, .Create, .Write);
|
||||
if (result case .Err)
|
||||
return .Err;
|
||||
fs.TryWrite(.(data, len));
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void> WriteAllLines(StringView path, IEnumerator<StringView> enumerator)
|
||||
{
|
||||
String strBuf = scope String();
|
||||
for (var str in enumerator)
|
||||
{
|
||||
strBuf.Append(str);
|
||||
strBuf.Append(Environment.NewLine);
|
||||
}
|
||||
return WriteAllText(path, strBuf);
|
||||
}
|
||||
|
||||
public static Result<void> WriteAllLines(StringView path, IEnumerator<String> enumerator)
|
||||
{
|
||||
String strBuf = scope String();
|
||||
for (var str in enumerator)
|
||||
{
|
||||
strBuf.Append(str);
|
||||
strBuf.Append(Environment.NewLine);
|
||||
}
|
||||
return WriteAllText(path, strBuf);
|
||||
}
|
||||
|
||||
/*public static Result<IEnumerator<Result<String>>> ReadLines(String fileName)
|
||||
{
|
||||
ThrowUnimplemented();
|
||||
return (IEnumerator<Result<String>>)null;
|
||||
}*/
|
||||
|
||||
static extern bool Exists(char8* fileName);
|
||||
|
||||
public static bool Exists(StringView fileName)
|
||||
{
|
||||
return Exists(fileName.ToScopeCStr!());
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> Delete(StringView fileName)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpFile_Delete(fileName.ToScopeCStr!(), &result);
|
||||
if ((result != .Ok) && (result != .NotFound))
|
||||
return .Err(result);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> Move(StringView fromPath, StringView toPath)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpFile_Rename(fromPath.ToScopeCStr!(), toPath.ToScopeCStr!(), &result);
|
||||
if (result != .Ok)
|
||||
return .Err(result);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> Copy(StringView fromPath, StringView toPath)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpFile_Copy(fromPath.ToScopeCStr!(), toPath.ToScopeCStr!(), .Always, &result);
|
||||
if (result != .Ok)
|
||||
return .Err(result);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> CopyIfNewer(StringView fromPath, StringView toPath)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpFile_Copy(fromPath.ToScopeCStr!(), toPath.ToScopeCStr!(), .IfNewer, &result);
|
||||
if (result != .Ok)
|
||||
return .Err(result);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> Copy(StringView fromPath, StringView toPath, bool overwrite)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpFile_Copy(fromPath.ToScopeCStr!(), toPath.ToScopeCStr!(), overwrite ? .Always : .IfNotExists, &result);
|
||||
if (result != .Ok)
|
||||
return .Err(result);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void, Platform.BfpFileResult> SetAttributes(StringView path, FileAttributes attr)
|
||||
{
|
||||
Platform.BfpFileResult result = default;
|
||||
Platform.BfpFile_SetAttributes(path.ToScopeCStr!(), (Platform.BfpFileAttributes)attr, &result);
|
||||
if (result != .Ok)
|
||||
return .Err(result);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<DateTime> GetLastWriteTime(StringView fullPath)
|
||||
{
|
||||
return DateTime.FromFileTime((int64)Platform.BfpFile_GetTime_LastWrite(fullPath.ToScopeCStr!()));
|
||||
}
|
||||
|
||||
public static Result<DateTime> GetLastWriteTimeUtc(StringView fullPath)
|
||||
{
|
||||
return DateTime.FromFileTimeUtc((int64)Platform.BfpFile_GetTime_LastWrite(fullPath.ToScopeCStr!()));
|
||||
}
|
||||
}
|
||||
}
|
11
BeefLibs/corlib/src/IO/FileAccess.bf
Normal file
11
BeefLibs/corlib/src/IO/FileAccess.bf
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
public enum FileAccess
|
||||
{
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
ReadWrite = 3,
|
||||
}
|
||||
}
|
21
BeefLibs/corlib/src/IO/FileAttributes.bf
Normal file
21
BeefLibs/corlib/src/IO/FileAttributes.bf
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
// These correspond to Platform.BfpFileFlags
|
||||
public enum FileAttributes
|
||||
{
|
||||
None = 0,
|
||||
Normal = 1,
|
||||
Directory = 2,
|
||||
SymLink = 4,
|
||||
Device = 8,
|
||||
ReadOnly = 0x10,
|
||||
Hidden = 0x20,
|
||||
System = 0x40,
|
||||
Temporary = 0x80,
|
||||
Offline = 0x100,
|
||||
Encrypted = 0x200,
|
||||
Archive = 0x400,
|
||||
}
|
||||
}
|
32
BeefLibs/corlib/src/IO/FileMode.bf
Normal file
32
BeefLibs/corlib/src/IO/FileMode.bf
Normal file
|
@ -0,0 +1,32 @@
|
|||
// This file contains portions of code released by Microsoft under the MIT license as part
|
||||
// of an open-sourcing initiative in 2014 of the C# core libraries.
|
||||
// The original source was submitted to https://github.com/Microsoft/referencesource
|
||||
|
||||
using System;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
public enum FileMode
|
||||
{
|
||||
/// Creates a new file. Fails if the file already exists.
|
||||
CreateNew = 1,
|
||||
|
||||
/// Creates a new file. If the file already exists, it is overwritten.
|
||||
Create = 2,
|
||||
|
||||
/// Opens an existing file. Fails if the file does not exist.
|
||||
Open = 3,
|
||||
|
||||
// Opens the file if it exists. Otherwise, creates a new file.
|
||||
OpenOrCreate = 4,
|
||||
|
||||
// Opens an existing file. Once opened, the file is truncated so that its
|
||||
// size is zero bytes. The calling process must open the file with at least
|
||||
// WRITE access. Fails if the file does not exist.
|
||||
Truncate = 5,
|
||||
|
||||
// Opens the file if it exists and seeks to the end. Otherwise,
|
||||
// creates a new file.
|
||||
Append = 6,
|
||||
}
|
||||
}
|
31
BeefLibs/corlib/src/IO/FileOptions.bf
Normal file
31
BeefLibs/corlib/src/IO/FileOptions.bf
Normal file
|
@ -0,0 +1,31 @@
|
|||
// This file contains portions of code released by Microsoft under the MIT license as part
|
||||
// of an open-sourcing initiative in 2014 of the C# core libraries.
|
||||
// The original source was submitted to https://github.com/Microsoft/referencesource
|
||||
|
||||
using System;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
// Maps to FILE_FLAG_DELETE_ON_CLOSE and similar values from winbase.h.
|
||||
// We didn't expose a number of these values because we didn't believe
|
||||
// a number of them made sense in managed code, at least not yet.
|
||||
public enum FileOptions
|
||||
{
|
||||
// NOTE: any change to FileOptions enum needs to be
|
||||
// matched in the FileStream ctor for error validation
|
||||
None = 0,
|
||||
WriteThrough = ((int32)0x80000000),
|
||||
Asynchronous = ((int32)0x40000000), // FILE_FLAG_OVERLAPPED
|
||||
// NoBuffering = 0x20000000,
|
||||
RandomAccess = 0x10000000,
|
||||
DeleteOnClose = 0x04000000,
|
||||
SequentialScan = 0x08000000,
|
||||
// AllowPosix = 0x01000000, // FILE_FLAG_POSIX_SEMANTICS
|
||||
// BackupOrRestore,
|
||||
// DisallowReparsePoint = 0x00200000, // FILE_FLAG_OPEN_REPARSE_POINT
|
||||
// NoRemoteRecall = 0x00100000, // FILE_FLAG_OPEN_NO_RECALL
|
||||
// FirstPipeInstance = 0x00080000, // FILE_FLAG_FIRST_PIPE_INSTANCE
|
||||
Encrypted = 0x00004000, // FILE_ATTRIBUTE_ENCRYPTED
|
||||
}
|
||||
}
|
||||
|
44
BeefLibs/corlib/src/IO/FileShare.bf
Normal file
44
BeefLibs/corlib/src/IO/FileShare.bf
Normal file
|
@ -0,0 +1,44 @@
|
|||
// This file contains portions of code released by Microsoft under the MIT license as part
|
||||
// of an open-sourcing initiative in 2014 of the C# core libraries.
|
||||
// The original source was submitted to https://github.com/Microsoft/referencesource
|
||||
|
||||
using System;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
// Contains constants for controlling file sharing options while
|
||||
// opening files. You can specify what access other processes trying
|
||||
// to open the same file concurrently can have.
|
||||
//
|
||||
// Note these values currently match the values for FILE_SHARE_READ,
|
||||
// FILE_SHARE_WRITE, and FILE_SHARE_DELETE in winnt.h
|
||||
//
|
||||
public enum FileShare
|
||||
{
|
||||
/// No sharing. Any request to open the file (by this process or another
|
||||
/// process) will fail until the file is closed.
|
||||
None = 0,
|
||||
|
||||
/// Allows subsequent opening of the file for reading. If this flag is not
|
||||
/// specified, any request to open the file for reading (by this process or
|
||||
/// another process) will fail until the file is closed.
|
||||
Read = 1,
|
||||
|
||||
/// Allows subsequent opening of the file for writing. If this flag is not
|
||||
/// specified, any request to open the file for writing (by this process or
|
||||
/// another process) will fail until the file is closed.
|
||||
Write = 2,
|
||||
|
||||
/// Allows subsequent opening of the file for writing or reading. If this flag
|
||||
/// is not specified, any request to open the file for writing or reading (by
|
||||
/// this process or another process) will fail until the file is closed.
|
||||
ReadWrite = 3,
|
||||
|
||||
/// Open the file, but allow someone else to delete the file.
|
||||
Delete = 4,
|
||||
|
||||
/// Whether the file handle should be inheritable by child processes.
|
||||
/// Note this is not directly supported like this by Win32.
|
||||
Inheritable = 0x10,
|
||||
}
|
||||
}
|
212
BeefLibs/corlib/src/IO/FileStream.bf
Normal file
212
BeefLibs/corlib/src/IO/FileStream.bf
Normal file
|
@ -0,0 +1,212 @@
|
|||
using System.Threading;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
abstract class FileStreamBase : Stream
|
||||
{
|
||||
protected Platform.BfpFile* mBfpFile;
|
||||
|
||||
public override int64 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return Platform.BfpFile_Seek(mBfpFile, 0, .Relative);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
Platform.BfpFile_Seek(mBfpFile, value, .Absolute);
|
||||
}
|
||||
}
|
||||
|
||||
public override int64 Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return Platform.BfpFile_GetFileSize(mBfpFile);
|
||||
}
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
public override Result<void> Seek(int64 pos, SeekKind seekKind = .Absolute)
|
||||
{
|
||||
int64 newPos = Platform.BfpFile_Seek(mBfpFile, pos, (Platform.BfpFileSeekKind)seekKind);
|
||||
// Ensure position is what was requested
|
||||
if ((seekKind == .Absolute) && (newPos != pos))
|
||||
return .Err;
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public override Result<int> TryRead(Span<uint8> data)
|
||||
{
|
||||
Platform.BfpFileResult result = .Ok;
|
||||
int numBytesRead = Platform.BfpFile_Read(mBfpFile, data.Ptr, data.Length, -1, &result);
|
||||
if ((result != .Ok) && (result != .PartialData))
|
||||
return .Err;
|
||||
return numBytesRead;
|
||||
}
|
||||
|
||||
public Result<int> TryRead(Span<uint8> data, int timeoutMS)
|
||||
{
|
||||
Platform.BfpFileResult result = .Ok;
|
||||
int numBytesRead = Platform.BfpFile_Read(mBfpFile, data.Ptr, data.Length, timeoutMS, &result);
|
||||
if ((result != .Ok) && (result != .PartialData))
|
||||
return .Err;
|
||||
return numBytesRead;
|
||||
}
|
||||
|
||||
public override Result<int> TryWrite(Span<uint8> data)
|
||||
{
|
||||
Platform.BfpFileResult result = .Ok;
|
||||
int numBytesWritten = Platform.BfpFile_Write(mBfpFile, data.Ptr, data.Length, -1, &result);
|
||||
if ((result != .Ok) && (result != .PartialData))
|
||||
return .Err;
|
||||
return numBytesWritten;
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
if (mBfpFile != null)
|
||||
Platform.BfpFile_Release(mBfpFile);
|
||||
mBfpFile = null;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if (mBfpFile != null)
|
||||
Platform.BfpFile_Flush(mBfpFile);
|
||||
}
|
||||
}
|
||||
|
||||
class FileStream : FileStreamBase
|
||||
{
|
||||
FileAccess mFileAccess;
|
||||
|
||||
public this()
|
||||
{
|
||||
}
|
||||
|
||||
public this(Platform.BfpFile* handle, FileAccess access, int32 bufferSize, bool isAsync)
|
||||
{
|
||||
mBfpFile = handle;
|
||||
mFileAccess = access;
|
||||
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return mFileAccess.HasFlag(FileAccess.Read);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return mFileAccess.HasFlag(FileAccess.Write);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<void, FileOpenError> Create(StringView path, FileAccess access = .ReadWrite, FileShare share = .None, int bufferSize = 4096, FileOptions options= .None, SecurityAttributes* secAttrs = null)
|
||||
{
|
||||
return Open(path, FileMode.Create, access, share, bufferSize, options, secAttrs);
|
||||
}
|
||||
|
||||
public Result<void, FileOpenError> Open(StringView path, FileAccess access = .ReadWrite, FileShare share = .None, int bufferSize = 4096, FileOptions options= .None, SecurityAttributes* secAttrs = null)
|
||||
{
|
||||
return Open(path, FileMode.Open, access, share, bufferSize, options, secAttrs);
|
||||
}
|
||||
|
||||
public Result<void, FileOpenError> OpenStd(Platform.BfpFileStdKind stdKind)
|
||||
{
|
||||
Platform.BfpFileResult fileResult = .Ok;
|
||||
mBfpFile = Platform.BfpFile_GetStd(stdKind, &fileResult);
|
||||
|
||||
if ((mBfpFile == null) || (fileResult != .Ok))
|
||||
{
|
||||
switch (fileResult)
|
||||
{
|
||||
case .ShareError:
|
||||
return .Err(.SharingViolation);
|
||||
case .NotFound:
|
||||
return .Err(.NotFound);
|
||||
default:
|
||||
return .Err(.Unknown);
|
||||
}
|
||||
}
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void, FileOpenError> Open(StringView path, FileMode mode, FileAccess access, FileShare share = .None, int bufferSize = 4096, FileOptions options= .None, SecurityAttributes* secAttrs = null)
|
||||
{
|
||||
Runtime.Assert(mBfpFile == null);
|
||||
|
||||
Platform.BfpFileCreateKind createKind = .CreateAlways;
|
||||
Platform.BfpFileCreateFlags createFlags = .None;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case .CreateNew:
|
||||
createKind = .CreateIfNotExists;
|
||||
case .Create:
|
||||
createKind = .CreateAlways;
|
||||
case .Open:
|
||||
createKind = .OpenExisting;
|
||||
case .OpenOrCreate:
|
||||
createKind = .CreateAlways;
|
||||
case .Truncate:
|
||||
createKind = .CreateAlways;
|
||||
createFlags |= .Truncate;
|
||||
case .Append:
|
||||
createKind = .CreateAlways;
|
||||
createFlags |= .Append;
|
||||
}
|
||||
|
||||
if (access.HasFlag(.Read))
|
||||
createFlags |= .Read;
|
||||
if (access.HasFlag(.Write))
|
||||
createFlags |= .Write;
|
||||
|
||||
if (share.HasFlag(.Read))
|
||||
createFlags |= .ShareRead;
|
||||
if (share.HasFlag(.Write))
|
||||
createFlags |= .ShareWrite;
|
||||
if (share.HasFlag(.Delete))
|
||||
createFlags |= .ShareDelete;
|
||||
|
||||
Platform.BfpFileAttributes fileFlags = .Normal;
|
||||
|
||||
Platform.BfpFileResult fileResult = .Ok;
|
||||
mBfpFile = Platform.BfpFile_Create(path.ToScopeCStr!(128), createKind, createFlags, fileFlags, &fileResult);
|
||||
|
||||
if ((mBfpFile == null) || (fileResult != .Ok))
|
||||
{
|
||||
switch (fileResult)
|
||||
{
|
||||
case .ShareError:
|
||||
return .Err(.SharingViolation);
|
||||
case .NotFound:
|
||||
return .Err(.NotFound);
|
||||
default:
|
||||
return .Err(.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public void Attach(Platform.BfpFile* bfpFile)
|
||||
{
|
||||
Close();
|
||||
mBfpFile = bfpFile;
|
||||
}
|
||||
}
|
||||
}
|
92
BeefLibs/corlib/src/IO/FileSystemWatcher.bf
Normal file
92
BeefLibs/corlib/src/IO/FileSystemWatcher.bf
Normal file
|
@ -0,0 +1,92 @@
|
|||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
public class FileSystemWatcher
|
||||
{
|
||||
String mDirectory ~ delete _;
|
||||
String mFilter ~ delete _;
|
||||
Platform.BfpFileWatcher* mFileWatcher;
|
||||
|
||||
public delegate void CreatedFunc(String fileName);
|
||||
public delegate void DeletedFunc(String fileName);
|
||||
public delegate void ChangedFunc(String fileName);
|
||||
public delegate void RenameFunc(String newName, String oldName);
|
||||
public delegate void ErrorFunc();
|
||||
|
||||
public Event<ChangedFunc> OnChanged ~ _.Dispose();
|
||||
public Event<CreatedFunc> OnCreated ~ _.Dispose();
|
||||
public Event<DeletedFunc> OnDeleted ~ _.Dispose();
|
||||
public Event<RenameFunc> OnRenamed ~ _.Dispose();
|
||||
public Event<ErrorFunc> OnError ~ _.Dispose();
|
||||
public bool IncludeSubdirectories;
|
||||
|
||||
public this()
|
||||
{
|
||||
mDirectory = String.Empty;
|
||||
mFilter = "*.*";
|
||||
}
|
||||
|
||||
public this(StringView path) : this(path, "*.*")
|
||||
{
|
||||
}
|
||||
|
||||
public this(StringView path, StringView filter)
|
||||
{
|
||||
this.mDirectory = new String(path);
|
||||
this.mFilter = new String(filter);
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
StopRaisingEvents().IgnoreError();
|
||||
}
|
||||
|
||||
public String Directory
|
||||
{
|
||||
get
|
||||
{
|
||||
return mDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
static void BfpDirectoryChangeFunc(Platform.BfpFileWatcher* watcher, void* userData, Platform.BfpFileChangeKind changeKind, char8* directory, char8* fileName, char8* newName)
|
||||
{
|
||||
let fileSysWatcher = (FileSystemWatcher)Internal.UnsafeCastToObject(userData);
|
||||
|
||||
switch (changeKind)
|
||||
{
|
||||
case .BfpFileChangeKind_Added:
|
||||
fileSysWatcher.OnCreated(scope String(fileName));
|
||||
case .BfpFileChangeKind_Modified:
|
||||
fileSysWatcher.OnChanged(scope String(fileName));
|
||||
case .BfpFileChangeKind_Removed:
|
||||
fileSysWatcher.OnDeleted(scope String(fileName));
|
||||
case .BfpFileChangeKind_Renamed:
|
||||
fileSysWatcher.OnRenamed(scope String(fileName), scope String(newName));
|
||||
case .BfpFileChangeKind_Failed:
|
||||
fileSysWatcher.OnError();
|
||||
}
|
||||
}
|
||||
|
||||
public Result<void> StartRaisingEvents()
|
||||
{
|
||||
Platform.BfpFileWatcherFlags flags = IncludeSubdirectories ? .IncludeSubdirectories : .None;
|
||||
mFileWatcher = Platform.BfpFileWatcher_WatchDirectory(mDirectory, => BfpDirectoryChangeFunc, flags, Internal.UnsafeCastToPtr(this), null);
|
||||
if (mFileWatcher == null)
|
||||
return .Err;
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void> StopRaisingEvents()
|
||||
{
|
||||
if (mFileWatcher == null)
|
||||
return .Ok;
|
||||
|
||||
Platform.BfpFileWatcher_Release(mFileWatcher);
|
||||
mFileWatcher = null;
|
||||
return .Ok;
|
||||
}
|
||||
}
|
||||
}
|
285
BeefLibs/corlib/src/IO/FolderBrowserDialog.bf
Normal file
285
BeefLibs/corlib/src/IO/FolderBrowserDialog.bf
Normal file
|
@ -0,0 +1,285 @@
|
|||
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
namespace System.IO
|
||||
{
|
||||
class FolderBrowserDialog : CommonDialog
|
||||
{
|
||||
String mSelectedPath = new String() ~ delete _;
|
||||
public bool ShowNewFolderButton;
|
||||
String mDescriptionText = new String() ~ delete _;
|
||||
bool mSelectedPathNeedsCheck;
|
||||
static FolderBrowserDialog sCurrentThis;
|
||||
|
||||
public this()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public StringView SelectedPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return mSelectedPath;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
mSelectedPath.Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
public StringView Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return mDescriptionText;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
mDescriptionText.Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
mSelectedPath.Clear();
|
||||
mDescriptionText.Clear();
|
||||
mSelectedPathNeedsCheck = false;
|
||||
ShowNewFolderButton = true;
|
||||
}
|
||||
|
||||
protected Result<DialogResult> RunDialog_New(Windows.HWnd hWndOwner, FolderBrowserDialog.COM_IFileDialog* fileDialog)
|
||||
{
|
||||
if (!mSelectedPath.IsEmpty)
|
||||
{
|
||||
COM_IShellItem* folderShellItem = null;
|
||||
Windows.SHCreateItemFromParsingName(mSelectedPath.ToScopedNativeWChar!(), null, COM_IShellItem.sIID, (void**)&folderShellItem);
|
||||
if (folderShellItem != null)
|
||||
{
|
||||
fileDialog.VT.SetDefaultFolder(fileDialog, folderShellItem);
|
||||
folderShellItem.VT.Release(folderShellItem);
|
||||
}
|
||||
}
|
||||
|
||||
fileDialog.VT.SetOptions(fileDialog, .PICKFOLDERS);
|
||||
fileDialog.VT.Show(fileDialog, hWndOwner);
|
||||
|
||||
DialogResult result = .Cancel;
|
||||
|
||||
mSelectedPath.Clear();
|
||||
COM_IShellItem* shellItem = null;
|
||||
fileDialog.VT.GetResult(fileDialog, out shellItem);
|
||||
if (shellItem != null)
|
||||
{
|
||||
char16* cStr = null;
|
||||
if (shellItem.VT.GetDisplayName(shellItem, .FILESYSPATH, out cStr) == .OK)
|
||||
{
|
||||
let str = scope String..Append(cStr);
|
||||
mSelectedPath.Append(str);
|
||||
Windows.COM_IUnknown.CoTaskMemFree(cStr);
|
||||
result = .OK;
|
||||
}
|
||||
shellItem.VT.Release(shellItem);
|
||||
}
|
||||
|
||||
fileDialog.VT.Release(fileDialog);
|
||||
return .Ok(result);
|
||||
}
|
||||
|
||||
protected override Result<DialogResult> RunDialog(Windows.HWnd hWndOwner)
|
||||
{
|
||||
FolderBrowserDialog.COM_IFileDialog* fileDialog = null;
|
||||
let hr = Windows.COM_IUnknown.CoCreateInstance(ref FolderBrowserDialog.COM_IFileDialog.sCLSID, null, .INPROC_SERVER, ref FolderBrowserDialog.COM_IFileDialog.sIID, (void**)&fileDialog);
|
||||
if (hr == 0)
|
||||
return RunDialog_New(hWndOwner, fileDialog);
|
||||
|
||||
int pidlRoot = 0;
|
||||
//Windows.SHGetSpecialFolderLocation(hWndOwner, (int32)mRootFolder, ref pidlRoot);
|
||||
if (pidlRoot == (int)0)
|
||||
{
|
||||
Windows.SHGetSpecialFolderLocation(hWndOwner, Windows.CSIDL_DESKTOP, ref pidlRoot);
|
||||
if (pidlRoot == (int)0)
|
||||
return .Err;
|
||||
}
|
||||
|
||||
int32 mergedOptions = (int32)Windows.BrowseInfos.NewDialogStyle;
|
||||
if (!ShowNewFolderButton)
|
||||
mergedOptions |= (int32)Windows.BrowseInfos.HideNewFolderButton;
|
||||
|
||||
String displayName = scope String(Windows.MAX_PATH);
|
||||
|
||||
Windows.WndProc callback = => FolderBrowserDialog_BrowseCallbackProc;
|
||||
|
||||
Windows.BrowseInfo bi;
|
||||
bi.mHWndOwner = hWndOwner;
|
||||
bi.mIdlRoot = pidlRoot;
|
||||
bi.mDisplayName = displayName;
|
||||
bi.mTitle = mDescriptionText;
|
||||
bi.mFlags = mergedOptions;
|
||||
bi.mCallback = callback;
|
||||
bi.mLParam = 0;
|
||||
bi.mImage = 0;
|
||||
|
||||
sCurrentThis = this;
|
||||
int pidlRet = Windows.SHBrowseForFolder(ref bi);
|
||||
sCurrentThis = null;
|
||||
|
||||
if (pidlRet == (int)0)
|
||||
return DialogResult.Cancel;
|
||||
|
||||
char8* selectedPathCStr = scope char8[Windows.MAX_PATH]*;
|
||||
Windows.SHGetPathFromIDList(pidlRet, selectedPathCStr);
|
||||
mSelectedPath.Clear();
|
||||
mSelectedPath.Append(selectedPathCStr);
|
||||
|
||||
return DialogResult.OK;
|
||||
}
|
||||
|
||||
public static int FolderBrowserDialog_BrowseCallbackProc(Windows.HWnd hWnd, int32 msg, int wParam, int lParam)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case Windows.BFFM_INITIALIZED:
|
||||
// Indicates the browse dialog box has finished initializing. The lpData value is zero.
|
||||
if (sCurrentThis.mSelectedPath.Length != 0)
|
||||
{
|
||||
// Try to select the folder specified by selectedPath
|
||||
Windows.SendMessageW(hWnd, Windows.BFFM_SETSELECTIONA, 1, (int)sCurrentThis.mSelectedPath.ToScopedNativeWChar!());
|
||||
}
|
||||
break;
|
||||
case Windows.BFFM_SELCHANGED:
|
||||
// Indicates the selection has changed. The lpData parameter points to the item identifier list for the newly selected item.
|
||||
int selectedPidl = lParam;
|
||||
if (selectedPidl != (int)0)
|
||||
{
|
||||
char8* pszSelectedPath = scope char8[Windows.MAX_PATH]*;
|
||||
// Try to retrieve the path from the IDList
|
||||
bool isFileSystemFolder = Windows.SHGetPathFromIDList(selectedPidl, pszSelectedPath);
|
||||
Windows.SendMessageW(hWnd, Windows.BFFM_ENABLEOK, 0, (int)(isFileSystemFolder ? 1 : 0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct COM_IFileDialogEvents
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
struct COM_IShellItem : Windows.COM_IUnknown
|
||||
{
|
||||
public static Guid sIID = .(0x43826d1e, 0xe718, 0x42ee, 0xbc, 0x55, 0xa1, 0xe2, 0x61, 0xc3, 0x7b, 0xfe);
|
||||
|
||||
public enum SIGDN : uint32
|
||||
{
|
||||
NORMALDISPLAY = 0x00000000, // SHGDN_NORMAL
|
||||
PARENTRELATIVEPARSING = 0x80018001, // SHGDN_INFOLDER | SHGDN_FORPARSING
|
||||
DESKTOPABSOLUTEPARSING = 0x80028000, // SHGDN_FORPARSING
|
||||
PARENTRELATIVEEDITING = 0x80031001, // SHGDN_INFOLDER | SHGDN_FOREDITING
|
||||
DESKTOPABSOLUTEEDITING = 0x8004c000, // SHGDN_FORPARSING | SHGDN_FORADDRESSBAR
|
||||
FILESYSPATH = 0x80058000, // SHGDN_FORPARSING
|
||||
URL = 0x80068000, // SHGDN_FORPARSING
|
||||
PARENTRELATIVEFORADDRESSBAR = 0x8007c001, // SHGDN_INFOLDER | SHGDN_FORPARSING | SHGDN_FORADDRESSBAR
|
||||
PARENTRELATIVE = 0x80080001 // SHGDN_INFOLDER
|
||||
}
|
||||
|
||||
public struct VTable : Windows.COM_IUnknown.VTable
|
||||
{
|
||||
public function HResult(COM_IShellItem* self, void* pbc, ref Guid bhid, ref Guid riid, void** ppv) BindToHandler;
|
||||
public function HResult(COM_IShellItem* self, out COM_IShellItem* ppsi) GetParent;
|
||||
public function HResult(COM_IShellItem* self, SIGDN sigdnName, out char16* ppszName) GetDisplayName;
|
||||
public function HResult(COM_IShellItem* self, uint sfgaoMask, out uint psfgaoAttribs) GetAttributes;
|
||||
public function HResult(COM_IShellItem* self, COM_IShellItem* psi, uint32 hint, out int32 piOrder) Compare;
|
||||
|
||||
}
|
||||
public new VTable* VT
|
||||
{
|
||||
get
|
||||
{
|
||||
return (.)mVT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct COMDLG_FILTERSPEC
|
||||
{
|
||||
internal char16* pszName;
|
||||
internal char16* pszSpec;
|
||||
}
|
||||
|
||||
internal enum FDAP : uint32
|
||||
{
|
||||
FDAP_BOTTOM = 0x00000000,
|
||||
FDAP_TOP = 0x00000001,
|
||||
}
|
||||
|
||||
public struct COM_IFileDialog : Windows.COM_IUnknown
|
||||
{
|
||||
public static Guid sIID = .(0x42f85136, 0xdb7e, 0x439c, 0x85, 0xf1, 0xe4, 0x07, 0x5d, 0x13, 0x5f, 0xc8);
|
||||
public static Guid sCLSID = .(0xdc1c5a9c, 0xe88a, 0x4dde, 0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7);
|
||||
|
||||
///s
|
||||
public enum FOS : uint32
|
||||
{
|
||||
OVERWRITEPROMPT = 0x00000002,
|
||||
STRICTFILETYPES = 0x00000004,
|
||||
NOCHANGEDIR = 0x00000008,
|
||||
PICKFOLDERS = 0x00000020,
|
||||
FORCEFILESYSTEM = 0x00000040,
|
||||
ALLNONSTORAGEITEMS = 0x00000080,
|
||||
NOVALIDATE = 0x00000100,
|
||||
ALLOWMULTISELECT = 0x00000200,
|
||||
PATHMUSTEXIST = 0x00000800,
|
||||
FILEMUSTEXIST = 0x00001000,
|
||||
CREATEPROMPT = 0x00002000,
|
||||
SHAREAWARE = 0x00004000,
|
||||
NOREADONLYRETURN = 0x00008000,
|
||||
NOTESTFILECREATE = 0x00010000,
|
||||
HIDEMRUPLACES = 0x00020000,
|
||||
HIDEPINNEDPLACES = 0x00040000,
|
||||
NODEREFERENCELINKS = 0x00100000,
|
||||
DONTADDTORECENT = 0x02000000,
|
||||
FORCESHOWHIDDEN = 0x10000000,
|
||||
DEFAULTNOMINIMODE = 0x20000000
|
||||
}
|
||||
|
||||
public struct VTable : Windows.COM_IUnknown.VTable
|
||||
{
|
||||
public function HResult(COM_IFileDialog* self, Windows.HWnd parent) Show;
|
||||
public function HResult(COM_IFileDialog* self, uint cFileTypes, COMDLG_FILTERSPEC* rgFilterSpec) SetFileTypes;
|
||||
public function HResult(COM_IFileDialog* self, uint iFileType) SetFileTypeIndex;
|
||||
public function HResult(COM_IFileDialog* self, out uint piFileType) GetFileTypeIndex;
|
||||
public function HResult(COM_IFileDialog* self, COM_IFileDialogEvents* pfde, out uint pdwCookie) Advise;
|
||||
public function HResult(COM_IFileDialog* self, uint dwCookie) Unadvise;
|
||||
public function HResult(COM_IFileDialog* self, FOS fos) SetOptions;
|
||||
public function HResult(COM_IFileDialog* self, out FOS pfos) GetOptions;
|
||||
public function HResult(COM_IFileDialog* self, COM_IShellItem* psi) SetDefaultFolder;
|
||||
public function HResult(COM_IFileDialog* self, COM_IShellItem* psi) SetFolder;
|
||||
public function HResult(COM_IFileDialog* self, out COM_IShellItem* ppsi) GetFolder;
|
||||
public function HResult(COM_IFileDialog* self, out COM_IShellItem* ppsi) GetCurrentSelection;
|
||||
public function HResult(COM_IFileDialog* self, char16* pszName) SetFileName;
|
||||
public function HResult(COM_IFileDialog* self, out char16* pszName) GetFileName;
|
||||
public function HResult(COM_IFileDialog* self, char16* pszTitle) SetTitle;
|
||||
public function HResult(COM_IFileDialog* self, char16* pszText) SetOkButtonLabel;
|
||||
public function HResult(COM_IFileDialog* self, char16* pszLabel) SetFileNameLabel;
|
||||
public function HResult(COM_IFileDialog* self, out COM_IShellItem* ppsi) GetResult;
|
||||
public function HResult(COM_IFileDialog* self, COM_IShellItem* psi, FDAP fdap) AddPlace;
|
||||
public function HResult(COM_IFileDialog* self, char16* pszDefaultExtension) SetDefaultExtension;
|
||||
public function HResult(COM_IFileDialog* self, int hr) Close;
|
||||
public function HResult(COM_IFileDialog* self, ref Guid guid) SetClientGuid;
|
||||
public function HResult(COM_IFileDialog* self) ClearClientData;
|
||||
public function HResult(COM_IFileDialog* self, void* pFilter) SetFilter;
|
||||
}
|
||||
public new VTable* VT
|
||||
{
|
||||
get
|
||||
{
|
||||
return (.)mVT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
79
BeefLibs/corlib/src/IO/MemoryStream.bf
Normal file
79
BeefLibs/corlib/src/IO/MemoryStream.bf
Normal file
|
@ -0,0 +1,79 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
class MemoryStream : Stream
|
||||
{
|
||||
List<uint8> mMemory = new List<uint8>() ~ delete _;
|
||||
int mPosition = 0;
|
||||
|
||||
public override int64 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
mPosition = (.)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override int64 Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return mMemory.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override Result<int> TryRead(Span<uint8> data)
|
||||
{
|
||||
let count = data.Length;
|
||||
if (count == 0)
|
||||
return .Ok(0);
|
||||
int readBytes = Math.Min(count, mMemory.Count - mPosition);
|
||||
if (readBytes <= 0)
|
||||
return .Ok(readBytes);
|
||||
|
||||
Internal.MemCpy(data.Ptr, &mMemory[mPosition], readBytes);
|
||||
mPosition += readBytes;
|
||||
return .Ok(readBytes);
|
||||
}
|
||||
|
||||
public override Result<int> TryWrite(Span<uint8> data)
|
||||
{
|
||||
let count = data.Length;
|
||||
if (count == 0)
|
||||
return .Ok(0);
|
||||
int growSize = mPosition + count - mMemory.Count;
|
||||
if (growSize > 0)
|
||||
mMemory.GrowUnitialized(growSize);
|
||||
Internal.MemCpy(&mMemory[mPosition], data.Ptr, count);
|
||||
mPosition += count;
|
||||
return .Ok(count);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
16
BeefLibs/corlib/src/IO/NotifyFilters.bf
Normal file
16
BeefLibs/corlib/src/IO/NotifyFilters.bf
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace System.IO
|
||||
{
|
||||
public enum NotifyFilters
|
||||
{
|
||||
FileName = 0x00000001,
|
||||
DirectoryName= 0x00000002,
|
||||
Attributes = 0x00000004,
|
||||
Size = 0x00000008,
|
||||
LastWrite = 0x00000010,
|
||||
LastAccess = 0x00000020,
|
||||
CreationTime = 0x00000040,
|
||||
Security = 0x00000100,
|
||||
|
||||
All = FileName | DirectoryName | Attributes | Size | LastWrite | LastAccess | CreationTime | Security
|
||||
}
|
||||
}
|
64
BeefLibs/corlib/src/IO/NullStream.bf
Normal file
64
BeefLibs/corlib/src/IO/NullStream.bf
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
/// The NullStream will allow ignore all writes (returning success) and return 'no data' on all reads.
|
||||
class NullStream : Stream
|
||||
{
|
||||
public this()
|
||||
{
|
||||
}
|
||||
|
||||
public override int64 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override int64 Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override Result<int> TryRead(Span<uint8> data)
|
||||
{
|
||||
return .Ok(0);
|
||||
}
|
||||
|
||||
public override Result<int> TryWrite(Span<uint8> data)
|
||||
{
|
||||
return .Ok(data.Length);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
562
BeefLibs/corlib/src/IO/OpenFileDialog.bf
Normal file
562
BeefLibs/corlib/src/IO/OpenFileDialog.bf
Normal file
|
@ -0,0 +1,562 @@
|
|||
// This file contains portions of code released by Microsoft under the MIT license as part
|
||||
// of an open-sourcing initiative in 2014 of the C# core libraries.
|
||||
// The original source was submitted to https://github.com/Microsoft/referencesource
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Text;
|
||||
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
enum DialogResult
|
||||
{
|
||||
None = 0,
|
||||
OK = 1,
|
||||
Cancel = 2
|
||||
}
|
||||
|
||||
abstract class CommonDialog
|
||||
{
|
||||
public Windows.HWnd mHWnd;
|
||||
public Windows.HWnd mDefaultControlHwnd;
|
||||
public int mDefWndProc;
|
||||
|
||||
private const int32 CDM_SETDEFAULTFOCUS = Windows.WM_USER + 0x51;
|
||||
|
||||
public static Dictionary<int, CommonDialog> sHookMap = new Dictionary<int, CommonDialog>() ~
|
||||
{
|
||||
Debug.Assert(sHookMap.Count == 0);
|
||||
delete _;
|
||||
};
|
||||
public static Monitor sMonitor = new Monitor() ~ delete _;
|
||||
|
||||
public Result<DialogResult> ShowDialog(INativeWindow owner = null)
|
||||
{
|
||||
Windows.HWnd hwndOwner = 0;
|
||||
if (owner != null)
|
||||
hwndOwner = (.)owner.Handle;
|
||||
//Native.WndProc wndProc = scope => OwnerWndProc;
|
||||
|
||||
//mDefWndProc = Native.SetWindowLong(mHWnd, Native.GWL_WNDPROC, (intptr)wndProc.GetFuncPtr().Value);
|
||||
|
||||
var result = RunDialog(hwndOwner);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual int OwnerWndProc(Windows.HWnd hWnd, int32 msg, int wParam, int lParam)
|
||||
{
|
||||
return Windows.CallWindowProcW(mDefWndProc, hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
protected virtual int HookProc(Windows.HWnd hWnd, int32 msg, int wParam, int lparam)
|
||||
{
|
||||
if (msg == Windows.WM_INITDIALOG)
|
||||
{
|
||||
//TODO: MoveToScreenCenter(hWnd);
|
||||
// Under some circumstances, the dialog
|
||||
// does not initially focus on any control. We fix that by explicitly
|
||||
// setting focus ourselves. See ASURT 39435.
|
||||
//
|
||||
mDefaultControlHwnd = (Windows.HWnd)wParam;
|
||||
if (mDefaultControlHwnd != 0)
|
||||
Windows.SetFocus(mDefaultControlHwnd);
|
||||
}
|
||||
else if (msg == Windows.WM_SETFOCUS)
|
||||
{
|
||||
Windows.PostMessageW(hWnd, CDM_SETDEFAULTFOCUS, 0, 0);
|
||||
}
|
||||
else if (msg == CDM_SETDEFAULTFOCUS)
|
||||
{
|
||||
// If the dialog box gets focus, bounce it to the default control.
|
||||
// so we post a message back to ourselves to wait for the focus change then push it to the default
|
||||
// control. See ASURT 84016.
|
||||
//
|
||||
if (mDefaultControlHwnd != 0)
|
||||
Windows.SetFocus(mDefaultControlHwnd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected abstract Result<DialogResult> RunDialog(Windows.HWnd hWndOwner);
|
||||
}
|
||||
|
||||
abstract class FileDialog : CommonDialog
|
||||
{
|
||||
internal abstract Result<DialogResult> RunFileDialog(ref Windows.OpenFileName ofn);
|
||||
|
||||
protected override Result<DialogResult> RunDialog(Windows.HWnd hWndOwner)
|
||||
{
|
||||
return RunDialogOld(hWndOwner);
|
||||
}
|
||||
|
||||
private const int32 FILEBUFSIZE = 8192;
|
||||
internal const int32 OPTION_ADDEXTENSION = (int32)0x80000000;
|
||||
|
||||
internal int32 mOptions;
|
||||
private String mTitle ~ delete _;
|
||||
private String mInitialDir ~ delete _;
|
||||
private String mDefaultExt ~ delete _;
|
||||
protected String[] mFileNames ~ DeleteContainerAndItems!(_);
|
||||
private bool mSecurityCheckFileNames;
|
||||
private String mFilter ~ delete _;
|
||||
private String mFilterBuffer = new String() ~ delete _;
|
||||
private int32 mFilterIndex;
|
||||
private bool mSupportMultiDottedExtensions;
|
||||
private bool mIgnoreSecondFileOkNotification; // Used for VS Whidbey 95342
|
||||
private int32 mOKNotificationCount; // Same
|
||||
//private String char8Buffer = new String(FILEBUFSIZE) ~ delete _;
|
||||
|
||||
public this()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
DeleteAndNullify!(mTitle);
|
||||
DeleteAndNullify!(mInitialDir);
|
||||
DeleteAndNullify!(mDefaultExt);
|
||||
DeleteAndNullify!(mFileNames);
|
||||
DeleteAndNullify!(mFilter);
|
||||
mFilterIndex = 1;
|
||||
mSupportMultiDottedExtensions = false;
|
||||
mOptions = Windows.OFN_HIDEREADONLY | Windows.OFN_PATHMUSTEXIST |
|
||||
OPTION_ADDEXTENSION;
|
||||
}
|
||||
|
||||
protected int32 Options
|
||||
{
|
||||
get
|
||||
{
|
||||
return mOptions & (
|
||||
Windows.OFN_READONLY |
|
||||
Windows.OFN_HIDEREADONLY |
|
||||
Windows.OFN_NOCHANGEDIR |
|
||||
Windows.OFN_SHOWHELP |
|
||||
Windows.OFN_NOVALIDATE |
|
||||
Windows.OFN_ALLOWMULTISELECT |
|
||||
Windows.OFN_PATHMUSTEXIST |
|
||||
Windows.OFN_FILEMUSTEXIST |
|
||||
Windows.OFN_NODEREFERENCELINKS |
|
||||
Windows.OFN_OVERWRITEPROMPT);
|
||||
//return mOptions;
|
||||
}
|
||||
}
|
||||
|
||||
public StringView Title
|
||||
{
|
||||
set
|
||||
{
|
||||
String.NewOrSet!(mTitle, value);
|
||||
}
|
||||
|
||||
get
|
||||
{
|
||||
return mTitle;
|
||||
}
|
||||
}
|
||||
|
||||
public StringView InitialDirectory
|
||||
{
|
||||
set
|
||||
{
|
||||
String.NewOrSet!(mInitialDir, value);
|
||||
}
|
||||
|
||||
get
|
||||
{
|
||||
return mInitialDir;
|
||||
}
|
||||
}
|
||||
|
||||
public String[] FileNames
|
||||
{
|
||||
get
|
||||
{
|
||||
return mFileNames;
|
||||
}
|
||||
}
|
||||
|
||||
public StringView FileName
|
||||
{
|
||||
set
|
||||
{
|
||||
if (mFileNames == null)
|
||||
{
|
||||
mFileNames = new String[](new String(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddExtension
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetOption(OPTION_ADDEXTENSION);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetOption(OPTION_ADDEXTENSION, value);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool CheckFileExists
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetOption(Windows.OFN_FILEMUSTEXIST);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetOption(Windows.OFN_FILEMUSTEXIST, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DereferenceLinks
|
||||
{
|
||||
get
|
||||
{
|
||||
return !GetOption(Windows.OFN_NODEREFERENCELINKS);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetOption(Windows.OFN_NODEREFERENCELINKS, !value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckPathExists
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetOption(Windows.OFN_PATHMUSTEXIST);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetOption(Windows.OFN_PATHMUSTEXIST, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Multiselect
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetOption(Windows.OFN_ALLOWMULTISELECT);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetOption(Windows.OFN_ALLOWMULTISELECT, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ValidateNames
|
||||
{
|
||||
get
|
||||
{
|
||||
return !GetOption(Windows.OFN_NOVALIDATE);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetOption(Windows.OFN_NOVALIDATE, !value);
|
||||
}
|
||||
}
|
||||
|
||||
public StringView DefaultExt
|
||||
{
|
||||
get
|
||||
{
|
||||
return mDefaultExt == null ? "" : mDefaultExt;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
delete mDefaultExt;
|
||||
mDefaultExt = null;
|
||||
|
||||
//if (!String.IsNullOrEmpty(value))
|
||||
if (value.Length > 0)
|
||||
{
|
||||
mDefaultExt = new String(value);
|
||||
if (mDefaultExt.StartsWith("."))
|
||||
mDefaultExt.Remove(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GetFilter(String outFilter)
|
||||
{
|
||||
if (mFilter != null)
|
||||
outFilter.Append(mFilter);
|
||||
}
|
||||
|
||||
public Result<void> SetFilter(StringView value)
|
||||
{
|
||||
String useValue = scope String(value);
|
||||
if (useValue != null && useValue.Length > 0)
|
||||
{
|
||||
var formats = String.StackSplit!(useValue, '|');
|
||||
if (formats == null || formats.Count % 2 != 0)
|
||||
{
|
||||
return .Err;
|
||||
}
|
||||
///
|
||||
/*String[] formats = value.Split('|');
|
||||
if (formats == null || formats.Length % 2 != 0)
|
||||
{
|
||||
throw new ArgumentException(SR.GetString(SR.FileDialogInvalidFilter));
|
||||
}*/
|
||||
String.NewOrSet!(mFilter, useValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
useValue = null;
|
||||
DeleteAndNullify!(mFilter);
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
internal bool GetOption(int32 option)
|
||||
{
|
||||
return (mOptions & option) != 0;
|
||||
}
|
||||
|
||||
internal void SetOption(int32 option, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
mOptions |= option;
|
||||
}
|
||||
else
|
||||
{
|
||||
mOptions &= ~option;
|
||||
}
|
||||
}
|
||||
|
||||
private static Result<void> MakeFilterString(String s, bool dereferenceLinks, String filterBuffer)
|
||||
{
|
||||
String useStr = s;
|
||||
if (useStr == null || useStr.Length == 0)
|
||||
{
|
||||
// Workaround for Whidbey bug #5165
|
||||
// Apply the workaround only when DereferenceLinks is true and OS is at least WinXP.
|
||||
if (dereferenceLinks && System.Environment.OSVersion.Version.Major >= 5)
|
||||
{
|
||||
useStr = " |*.*";
|
||||
}
|
||||
else if (useStr == null)
|
||||
{
|
||||
return .Err;
|
||||
}
|
||||
}
|
||||
|
||||
filterBuffer.Set(s);
|
||||
for (int32 i = 0; i < filterBuffer.Length; i++)
|
||||
if (filterBuffer[i] == '|')
|
||||
filterBuffer[i] = (char8)0;
|
||||
filterBuffer.Append((char8)0);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static mixin Testie()
|
||||
{
|
||||
int a = 123;
|
||||
char16* buf;
|
||||
if (a == 0)
|
||||
{
|
||||
buf = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
buf = new char16[123]* { ? };
|
||||
defer:mixin delete buf;
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
private Result<DialogResult> RunDialogOld(Windows.HWnd hWndOwner)
|
||||
{
|
||||
//RunDialogTest(hWndOwner);
|
||||
|
||||
Windows.WndProc hookProcPtr = => StaticHookProc;
|
||||
Windows.OpenFileName ofn = Windows.OpenFileName();
|
||||
|
||||
char16[FILEBUFSIZE] char16Buffer = .(0, ?);
|
||||
|
||||
if (mFileNames != null)
|
||||
{
|
||||
//int len = UTF16.GetEncodedLen(fileNames[0]);
|
||||
//char16Buffer = scope:: char16[len + 1]*;
|
||||
UTF16.Encode(mFileNames[0], (char16*)&char16Buffer, FILEBUFSIZE);
|
||||
}
|
||||
// Degrade to the older style dialog if we're not on Win2K.
|
||||
// We do this by setting the struct size to a different value
|
||||
//
|
||||
|
||||
if (Environment.OSVersion.Platform != System.PlatformID.Win32NT ||
|
||||
Environment.OSVersion.Version.Major < 5) {
|
||||
ofn.mStructSize = 0x4C;
|
||||
}
|
||||
ofn.mHwndOwner = hWndOwner;
|
||||
ofn.mHInstance = (Windows.HInstance)Windows.GetModuleHandleW(null);
|
||||
|
||||
if (mFilter != null)
|
||||
{
|
||||
Try!(MakeFilterString(mFilter, this.DereferenceLinks, mFilterBuffer));
|
||||
ofn.mFilter = mFilterBuffer.ToScopedNativeWChar!::();
|
||||
}
|
||||
ofn.nFilterIndex = mFilterIndex;
|
||||
ofn.mFile = (char16*)&char16Buffer;
|
||||
ofn.nMaxFile = FILEBUFSIZE;
|
||||
if (mInitialDir != null)
|
||||
ofn.mInitialDir = mInitialDir.ToScopedNativeWChar!::();
|
||||
if (mTitle != null)
|
||||
ofn.mTitle = mTitle.ToScopedNativeWChar!();
|
||||
ofn.mFlags = Options | (Windows.OFN_EXPLORER | Windows.OFN_ENABLEHOOK | Windows.OFN_ENABLESIZING);
|
||||
ofn.mHook = hookProcPtr;
|
||||
ofn.mCustData = (int)(void*)this;
|
||||
ofn.mFlagsEx = Windows.OFN_USESHELLITEM;
|
||||
if (mDefaultExt != null && AddExtension)
|
||||
ofn.mDefExt = mDefaultExt;
|
||||
|
||||
DeleteContainerAndItems!(mFileNames);
|
||||
mFileNames = null;
|
||||
//Security checks happen here
|
||||
return RunFileDialog(ref ofn);
|
||||
}
|
||||
|
||||
static int StaticHookProc(Windows.HWnd hWnd, int32 msg, int wParam, int lparam)
|
||||
{
|
||||
if (msg == Windows.WM_INITDIALOG)
|
||||
{
|
||||
using (sMonitor.Enter())
|
||||
{
|
||||
var ofn = (Windows.OpenFileName*)lparam;
|
||||
sHookMap[(int)hWnd] = (CommonDialog)Internal.UnsafeCastToObject((void*)ofn.mCustData);
|
||||
}
|
||||
}
|
||||
|
||||
CommonDialog dlg;
|
||||
using (sMonitor.Enter())
|
||||
{
|
||||
sHookMap.TryGetValue((int)hWnd, out dlg);
|
||||
}
|
||||
if (dlg == null)
|
||||
return 0;
|
||||
|
||||
dlg.[Friend]HookProc(hWnd, msg, wParam, lparam);
|
||||
if (msg == Windows.WM_DESTROY)
|
||||
{
|
||||
using (sMonitor.Enter())
|
||||
{
|
||||
sHookMap.Remove((int)hWnd);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
//TODO: Add ProcessFileNames for validation
|
||||
}
|
||||
|
||||
class OpenFileDialog : FileDialog
|
||||
{
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
SetOption(Windows.OFN_FILEMUSTEXIST, true);
|
||||
}
|
||||
|
||||
public bool ReadOnlyChecked
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetOption(Windows.OFN_READONLY);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetOption(Windows.OFN_READONLY, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return !GetOption(Windows.OFN_HIDEREADONLY);
|
||||
}
|
||||
set
|
||||
{
|
||||
SetOption(Windows.OFN_HIDEREADONLY, !value);
|
||||
}
|
||||
}
|
||||
|
||||
internal override Result<DialogResult> RunFileDialog(ref Windows.OpenFileName ofn)
|
||||
{
|
||||
bool result = Windows.GetOpenFileNameW(ref ofn);
|
||||
if (!result)
|
||||
return .Err;
|
||||
|
||||
if (!Multiselect)
|
||||
{
|
||||
let pathName = new String();
|
||||
UTF16.Decode(ofn.mFile, pathName);
|
||||
mFileNames = new String[](pathName);
|
||||
return DialogResult.OK;
|
||||
}
|
||||
|
||||
int32 entryCount = 0;
|
||||
int32 prevNull = -1;
|
||||
for (int32 i = 0; true; i++)
|
||||
{
|
||||
if (ofn.mFile[i] == (char8)0)
|
||||
{
|
||||
if (prevNull == i - 1)
|
||||
break;
|
||||
prevNull = i;
|
||||
entryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
String pathName = null;
|
||||
prevNull = -1;
|
||||
mFileNames = new String[Math.Max(1, entryCount - 1)];
|
||||
entryCount = 0;
|
||||
for (int32 i = 0; true; i++)
|
||||
{
|
||||
if (ofn.mFile[i] == (char8)0)
|
||||
{
|
||||
if (prevNull == i - 1)
|
||||
break;
|
||||
if (prevNull == -1)
|
||||
{
|
||||
pathName = scope:: String();
|
||||
UTF16.Decode(ofn.mFile, pathName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var str = new String(pathName.Length + 1 + i - prevNull - 1);
|
||||
str.Append(pathName);
|
||||
str.Append(Path.DirectorySeparatorChar);
|
||||
UTF16.Decode(ofn.mFile + prevNull + 1, str);
|
||||
|
||||
mFileNames[entryCount++] = str;
|
||||
}
|
||||
prevNull = i;
|
||||
}
|
||||
}
|
||||
|
||||
if ((entryCount == 0) && (pathName != null))
|
||||
mFileNames[0] = new String(pathName);
|
||||
|
||||
return DialogResult.OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
600
BeefLibs/corlib/src/IO/Path.bf
Normal file
600
BeefLibs/corlib/src/IO/Path.bf
Normal file
|
@ -0,0 +1,600 @@
|
|||
// This file contains portions of code released by Microsoft under the MIT license as part
|
||||
// of an open-sourcing initiative in 2014 of the C# core libraries.
|
||||
// The original source was submitted to https://github.com/Microsoft/referencesource
|
||||
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
public static class Path
|
||||
{
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
public const char8 DirectorySeparatorChar = '\\';
|
||||
#else
|
||||
public const char8 DirectorySeparatorChar = '/';
|
||||
#endif //BF_PLATFORM_WINDOWS
|
||||
|
||||
// Platform specific alternate directory separator char8acter.
|
||||
// This is backslash ('\') on Unix, and slash ('/') on Windows
|
||||
// and MacOS.
|
||||
//
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
public const char8 AltDirectorySeparatorChar = '/';
|
||||
#else
|
||||
public const char8 AltDirectorySeparatorChar = '\\';
|
||||
#endif //BF_PLATFORM_WINDOWS
|
||||
|
||||
// Platform specific volume separator char8acter. This is colon (':')
|
||||
// on Windows and MacOS, and slash ('/') on Unix. This is mostly
|
||||
// useful for parsing paths like "c:\windows" or "MacVolume:System Folder".
|
||||
//
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
public const char8 VolumeSeparatorChar = ':';
|
||||
#else
|
||||
public const char8 VolumeSeparatorChar = '/';
|
||||
#endif //BF_PLATFORM_WINDOWS
|
||||
|
||||
// Make this public sometime.
|
||||
// The max total path is 260, and the max individual component length is 255.
|
||||
// For example, D:\<256 char8 file name> isn't legal, even though it's under 260 char8s.
|
||||
internal const int32 MaxPath = 260;
|
||||
private const int32 MaxDirectoryLength = 255;
|
||||
|
||||
public static void GetFullPath(String inPartialPath, String outFullPath)
|
||||
{
|
||||
Platform.GetStrHelper(outFullPath, scope (outPtr, outSize, outResult) =>
|
||||
{
|
||||
Platform.BfpFile_GetFullPath(inPartialPath, outPtr, outSize, (Platform.BfpFileResult*)outResult);
|
||||
});
|
||||
}
|
||||
|
||||
internal static void CheckInvalidPathChars(StringView path, bool checkAdditional = false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static void GetFileName(String inPath, String outFileName)
|
||||
{
|
||||
if (inPath == null)
|
||||
return;
|
||||
|
||||
CheckInvalidPathChars(inPath);
|
||||
|
||||
int length = inPath.Length;
|
||||
for (int i = length; --i >= 0; )
|
||||
{
|
||||
char8 ch = inPath[i];
|
||||
if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
|
||||
{
|
||||
outFileName.Append(inPath, i + 1, length - i - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
outFileName.Append(inPath);
|
||||
}
|
||||
|
||||
internal static String NormalizePath(String path, bool fullCheck)
|
||||
{
|
||||
return NormalizePath(path, fullCheck, MaxPath);
|
||||
}
|
||||
|
||||
|
||||
internal static String NormalizePath(String path, bool fullCheck, bool expandShortPaths)
|
||||
{
|
||||
return NormalizePath(path, fullCheck, MaxPath, expandShortPaths);
|
||||
}
|
||||
|
||||
|
||||
internal static String NormalizePath(String path, bool fullCheck, int32 maxPathLength)
|
||||
{
|
||||
return NormalizePath(path, fullCheck, maxPathLength, true);
|
||||
}
|
||||
|
||||
internal static String NormalizePath(String path, bool fullCheck, int32 maxPathLength, bool expandShortPaths)
|
||||
{
|
||||
//TODO: Implement
|
||||
return path;
|
||||
}
|
||||
|
||||
internal static String RemoveLongPathPrefix(String path)
|
||||
{
|
||||
//TODO: Implement
|
||||
return path;
|
||||
}
|
||||
|
||||
public static Result<void> GetDirectoryPath(StringView path, String outDir)
|
||||
{
|
||||
String usePath = scope String(Math.Min(MaxPath, path.Length));
|
||||
usePath.Append(path);
|
||||
|
||||
//String origPath = path;
|
||||
//if (usePath != null)
|
||||
{
|
||||
CheckInvalidPathChars(usePath);
|
||||
String normalizedPath = NormalizePath(usePath, false);
|
||||
|
||||
// If there are no permissions for PathDiscovery to this path, we should NOT expand the short paths
|
||||
// as this would leak information about paths to which the user would not have access to.
|
||||
if (usePath.Length > 0)
|
||||
{
|
||||
// If we were passed in a path with \\?\ we need to remove it as FileIOPermission does not like it.
|
||||
String tempPath = Path.RemoveLongPathPrefix(usePath);
|
||||
|
||||
// FileIOPermission cannot handle paths that contain ? or *
|
||||
// So we only pass to FileIOPermission the text up to them.
|
||||
int32 pos = 0;
|
||||
while (pos < tempPath.Length && (tempPath[pos] != '?' && tempPath[pos] != '*'))
|
||||
pos++;
|
||||
// GetFullPath will Demand that we have the PathDiscovery FileIOPermission and thus throw
|
||||
// SecurityException if we don't.
|
||||
// While we don't use the result of this call we are using it as a consistent way of
|
||||
// doing the security checks.
|
||||
if (pos > 0)
|
||||
{
|
||||
//Path.GetFullPath(tempPath.Substring(0, pos));
|
||||
String newPath = scope String(usePath.Length - pos);
|
||||
newPath.Append(usePath, pos);
|
||||
usePath = newPath;
|
||||
}
|
||||
/*}
|
||||
catch (SecurityException) {
|
||||
// If the user did not have permissions to the path, make sure that we don't leak expanded short paths
|
||||
// Only re-normalize if the original path had a ~ in it.
|
||||
if (path.IndexOf("~", StringComparison.Ordinal) != -1)
|
||||
{
|
||||
normalizedPath = NormalizePath(path, /*fullCheck*/ false, /*expandShortPaths*/ false);
|
||||
}
|
||||
}
|
||||
catch (PathTooLongException) { }
|
||||
catch (NotSupportedException) { } // Security can throw this on "c:\foo:"
|
||||
catch (IOException) { }
|
||||
catch (ArgumentException) { } // The normalizePath with fullCheck will throw this for file: and http:*/
|
||||
}
|
||||
|
||||
usePath = normalizedPath;
|
||||
|
||||
int root = GetRootLength(usePath);
|
||||
int i = usePath.Length;
|
||||
if (i > root)
|
||||
{
|
||||
i = usePath.Length;
|
||||
if (i == root) return .Err;
|
||||
while (i > root && usePath[--i] != DirectorySeparatorChar && usePath[i] != AltDirectorySeparatorChar) {}
|
||||
outDir.Append(usePath, 0, i);
|
||||
return .Ok;
|
||||
}
|
||||
}
|
||||
return .Err;
|
||||
}
|
||||
|
||||
// Gets the length of the root DirectoryInfo or whatever DirectoryInfo markers
|
||||
// are specified for the first part of the DirectoryInfo name.
|
||||
//
|
||||
internal static int GetRootLength(String path)
|
||||
{
|
||||
CheckInvalidPathChars(path);
|
||||
|
||||
int i = 0;
|
||||
int length = path.Length;
|
||||
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
if (length >= 1 && (IsDirectorySeparator(path[0])))
|
||||
{
|
||||
// handles UNC names and directories off current drive's root.
|
||||
i = 1;
|
||||
if (length >= 2 && (IsDirectorySeparator(path[1])))
|
||||
{
|
||||
i = 2;
|
||||
int32 n = 2;
|
||||
while (i < length && ((path[i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) || --n > 0)) i++;
|
||||
}
|
||||
}
|
||||
else if (length >= 2 && path[1] == VolumeSeparatorChar)
|
||||
{
|
||||
// handles A:\foo.
|
||||
i = 2;
|
||||
if (length >= 3 && (IsDirectorySeparator(path[2]))) i++;
|
||||
}
|
||||
return i;
|
||||
#else
|
||||
if (length >= 1 && (IsDirectorySeparator(path[0]))) {
|
||||
i = 1;
|
||||
}
|
||||
return i;
|
||||
#endif //BF_PLATFORM_WINDOWS
|
||||
}
|
||||
|
||||
internal static bool IsDirectorySeparator(char8 c)
|
||||
{
|
||||
return (c==DirectorySeparatorChar || c == AltDirectorySeparatorChar);
|
||||
}
|
||||
|
||||
/*
|
||||
public static char8[] GetInvalidPathChars()
|
||||
{
|
||||
return RealInvalidPathChars;
|
||||
}
|
||||
|
||||
public static char8[] GetInvalidFileNameChars()
|
||||
{
|
||||
return (char8[]) InvalidFileNameChars.Clone();
|
||||
} */
|
||||
|
||||
public static void GetFileNameWithoutExtension(String inPath, String outFileName)
|
||||
{
|
||||
int lastSlash = Math.Max(inPath.LastIndexOf('\\'), inPath.LastIndexOf('/'));
|
||||
|
||||
int i;
|
||||
if ((i = inPath.LastIndexOf('.')) != -1)
|
||||
{
|
||||
int len = i - lastSlash - 1;
|
||||
if (len > 0)
|
||||
outFileName.Append(inPath, lastSlash + 1, i - lastSlash - 1);
|
||||
}
|
||||
else
|
||||
outFileName.Append(inPath, lastSlash + 1);
|
||||
}
|
||||
|
||||
public static Result<void> GetExtension(String inPath, String outExt)
|
||||
{
|
||||
int i;
|
||||
if ((i = inPath.LastIndexOf('.')) != -1)
|
||||
outExt.Append(inPath, i);
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void> GetTempPath(String outPath)
|
||||
{
|
||||
Platform.GetStrHelper(outPath, scope (outPtr, outSize, outResult) =>
|
||||
{
|
||||
Platform.BfpFile_GetTempPath(outPtr, outSize, (Platform.BfpFileResult*)outResult);
|
||||
});
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static Result<void> GetTempFileName(String outPath)
|
||||
{
|
||||
Platform.GetStrHelper(outPath, scope (outPtr, outSize, outResult) =>
|
||||
{
|
||||
Platform.BfpFile_GetTempFileName(outPtr, outSize, (Platform.BfpFileResult*)outResult);
|
||||
});
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public static void InternalCombine(String target, params String[] components)
|
||||
{
|
||||
for (var component in components)
|
||||
{
|
||||
if ((target.Length > 0) && (!target.EndsWith("\\")) && (!target.EndsWith("/")))
|
||||
target.Append(Path.DirectorySeparatorChar);
|
||||
target.Append(component);
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetActualPathName(StringView inPath, String outPath)
|
||||
{
|
||||
Platform.GetStrHelper(outPath, scope (outPtr, outSize, outResult) =>
|
||||
{
|
||||
Platform.BfpFile_GetActualPath(scope String()..Append(inPath), outPtr, outSize, (Platform.BfpFileResult*)outResult);
|
||||
});
|
||||
}
|
||||
|
||||
public static bool Equals(StringView filePathA, StringView filePathB)
|
||||
{
|
||||
Debug.Assert(!filePathA.Contains(Path.AltDirectorySeparatorChar));
|
||||
Debug.Assert(!filePathB.Contains(Path.AltDirectorySeparatorChar));
|
||||
return filePathA.Equals(filePathB, !Environment.IsFileSystemCaseSensitive);
|
||||
}
|
||||
|
||||
static void GetDriveStringTo(String outDrive, String path)
|
||||
{
|
||||
if ((path.Length >= 2) && (path[1] == ':'))
|
||||
outDrive.Append(path, 0, 2);
|
||||
}
|
||||
|
||||
/// Tests if the given path contains a root. A path is considered rooted
|
||||
/// if it starts with a backslash ("\") or a drive letter and a colon (":").
|
||||
public static bool IsPathRooted(StringView path)
|
||||
{
|
||||
CheckInvalidPathChars(path);
|
||||
int length = path.Length;
|
||||
if ((length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) || (length >= 2 && path[1] == VolumeSeparatorChar))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void GetRelativePath(StringView fullPath, StringView curDir, String outRelPath)
|
||||
{
|
||||
String curPath1 = scope String(curDir);
|
||||
String curPath2 = scope String(fullPath);
|
||||
|
||||
if (curPath1.Contains(Path.AltDirectorySeparatorChar))
|
||||
curPath1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
if (curPath2.Contains(Path.AltDirectorySeparatorChar))
|
||||
curPath1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
|
||||
String driveString1 = scope String();
|
||||
GetDriveStringTo(driveString1, curPath1);
|
||||
String driveString2 = scope String();
|
||||
GetDriveStringTo(driveString2, curPath2);
|
||||
|
||||
StringComparison compareType = Environment.IsFileSystemCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
|
||||
|
||||
// On seperate drives?
|
||||
if (!String.Equals(driveString1, driveString2, compareType))
|
||||
{
|
||||
outRelPath.Set(fullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (driveString1.Length > 0)
|
||||
curPath1.Remove(0, Math.Min(driveString1.Length + 1, curPath1.Length));
|
||||
if (driveString2.Length > 0)
|
||||
curPath2.Remove(0, Math.Min(driveString2.Length + 1, curPath2.Length));
|
||||
|
||||
while ((curPath1.Length > 0) && (curPath2.Length > 0))
|
||||
{
|
||||
int slashPos1 = curPath1.IndexOf(Path.DirectorySeparatorChar);
|
||||
if (slashPos1 == -1)
|
||||
slashPos1 = curPath1.Length;
|
||||
int slashPos2 = curPath2.IndexOf(Path.DirectorySeparatorChar);
|
||||
if (slashPos2 == -1)
|
||||
slashPos2 = curPath2.Length;
|
||||
|
||||
String section1 = scope String();
|
||||
section1.Append(curPath1, 0, slashPos1);
|
||||
String section2 = scope String();
|
||||
section2.Append(curPath2, 0, slashPos2);
|
||||
|
||||
if (!String.Equals(section1, section2, compareType))
|
||||
{
|
||||
// a/b/c
|
||||
// d/e/f
|
||||
|
||||
while (curPath1.Length > 0)
|
||||
{
|
||||
slashPos1 = curPath1.IndexOf(Path.DirectorySeparatorChar);
|
||||
if (slashPos1 == -1)
|
||||
slashPos1 = curPath1.Length;
|
||||
|
||||
if (slashPos1 + 1 >= curPath1.Length)
|
||||
curPath1.Clear();
|
||||
else
|
||||
curPath1.Remove(0, slashPos1 + 1);
|
||||
if (Path.DirectorySeparatorChar == '\\')
|
||||
curPath2.Insert(0, "..\\");
|
||||
else
|
||||
curPath2.Insert(0, "../");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (slashPos1 + 1 >= curPath1.Length)
|
||||
curPath1.Clear();
|
||||
else
|
||||
curPath1.Remove(0, slashPos1 + 1);
|
||||
|
||||
if (slashPos2 + 2 >= curPath2.Length)
|
||||
curPath1 = "";
|
||||
else
|
||||
curPath2.Remove(0, slashPos2 + 1);
|
||||
}
|
||||
}
|
||||
|
||||
outRelPath.Set(curPath2);
|
||||
}
|
||||
|
||||
public static void GetAbsolutePath(StringView relPath, StringView relToAbsPath, String outAbsPath)
|
||||
{
|
||||
String driveString = null;
|
||||
|
||||
var relPath;
|
||||
if (relPath == outAbsPath)
|
||||
relPath = scope:: String(relPath);
|
||||
|
||||
if ((relPath.Length >= 2) && (relPath[1] == ':'))
|
||||
{
|
||||
outAbsPath.Append(relPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((relPath.Length > 1) &&
|
||||
(relPath[0] == '/') || (relPath[0] == '\\'))
|
||||
{
|
||||
outAbsPath.Append(relPath);
|
||||
return;
|
||||
}
|
||||
|
||||
int startLen = outAbsPath.Length;
|
||||
outAbsPath.Append(relToAbsPath);
|
||||
//outAbsPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
|
||||
char8 slashChar = Path.DirectorySeparatorChar;
|
||||
|
||||
if ((outAbsPath.Length >= 2) && (outAbsPath[1] == ':'))
|
||||
{
|
||||
driveString = scope:: String();
|
||||
driveString.Append(outAbsPath, 0, 2);
|
||||
outAbsPath.Remove(0, 2);
|
||||
}
|
||||
|
||||
// Append a trailing slash if necessary
|
||||
if ((outAbsPath.Length > 0) && (outAbsPath[outAbsPath.Length - 1] != '\\') && (outAbsPath[outAbsPath.Length - 1] != '/'))
|
||||
outAbsPath.Append(slashChar);
|
||||
|
||||
int32 relIdx = 0;
|
||||
for (; ;)
|
||||
{
|
||||
if (outAbsPath.Length == 0)
|
||||
break;
|
||||
|
||||
int32 firstSlash = -1;
|
||||
for (int32 i = relIdx; i < relPath.Length; i++)
|
||||
if ((relPath[i] == '\\') || (relPath[i] == '/'))
|
||||
{
|
||||
firstSlash = i;
|
||||
break;
|
||||
}
|
||||
if (firstSlash == -1)
|
||||
break;
|
||||
|
||||
String chDir = scope String();
|
||||
chDir.Append(relPath, relIdx, firstSlash - relIdx);
|
||||
|
||||
//relPath.Substring(relIdx, firstSlash - relIdx, chDir);
|
||||
//chDir.Append(relPath.Ptr + relIdx, firstSlash - relIdx);
|
||||
|
||||
relIdx = firstSlash + 1;
|
||||
|
||||
if (chDir == "..")
|
||||
{
|
||||
int32 lastDirStart = (int32)outAbsPath.Length - 1;
|
||||
while ((lastDirStart > 0) && (outAbsPath[lastDirStart - 1] != '\\') && (outAbsPath[lastDirStart - 1] != '/'))
|
||||
lastDirStart--;
|
||||
|
||||
String lastDir = scope String();
|
||||
lastDir.Append(outAbsPath, lastDirStart, outAbsPath.Length - lastDirStart - 1);
|
||||
if (lastDir == "..")
|
||||
{
|
||||
outAbsPath.Append("..");
|
||||
outAbsPath.Append(Path.DirectorySeparatorChar);
|
||||
}
|
||||
else
|
||||
{
|
||||
//newPath.erase(newPath.begin() + lastDirStart, newPath.end());
|
||||
outAbsPath.Remove(lastDirStart, outAbsPath.Length - lastDirStart);
|
||||
}
|
||||
}
|
||||
else if (chDir == "")
|
||||
{
|
||||
outAbsPath.Append(Path.DirectorySeparatorChar);
|
||||
break;
|
||||
}
|
||||
else if (chDir != ".")
|
||||
{
|
||||
//newPath += chDir + slashChar;
|
||||
outAbsPath.Append(chDir);
|
||||
outAbsPath.Append(Path.DirectorySeparatorChar);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (driveString != null)
|
||||
outAbsPath.Insert(0, driveString);
|
||||
//relPath.Substring(relIdx, outAbsPath);
|
||||
outAbsPath.Append(relPath, relIdx);
|
||||
//outAbsPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
for (int i = startLen; i < outAbsPath.Length; i++)
|
||||
{
|
||||
if (outAbsPath[i] == Path.AltDirectorySeparatorChar)
|
||||
outAbsPath[i] = Path.DirectorySeparatorChar;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool WildcareCompare(StringView path, StringView wildcard)
|
||||
{
|
||||
bool matches = true;
|
||||
char8* afterLastWild = null; // The location after the last '*', if we’ve encountered one
|
||||
char8* afterLastPath = null; // The location in the path string, from which we started after last wildcard
|
||||
char8 t, w;
|
||||
|
||||
char8* pathPtr = path.Ptr;
|
||||
char8* pathEnd = path.EndPtr;
|
||||
char8* wildPtr = wildcard.Ptr;
|
||||
char8* wildEnd = wildcard.EndPtr;
|
||||
|
||||
// Walk the text strings one character at a time.
|
||||
while (true)
|
||||
{
|
||||
// How do you match a unique text string?
|
||||
if (pathPtr == pathEnd)
|
||||
{
|
||||
// Easy: unique up on it!
|
||||
if (wildPtr == wildEnd)
|
||||
{
|
||||
break; // "x" matches "x"
|
||||
}
|
||||
w = *wildPtr;
|
||||
if (w == '*')
|
||||
{
|
||||
wildPtr++;
|
||||
continue;// "x*" matches "x" or "xy"
|
||||
}
|
||||
else if (afterLastPath != null)
|
||||
{
|
||||
if (afterLastPath == pathEnd)
|
||||
{
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
pathPtr = afterLastPath++;
|
||||
wildPtr = afterLastWild;
|
||||
continue;
|
||||
}
|
||||
|
||||
matches = false;
|
||||
break; // "x" doesn't match "xy"
|
||||
}
|
||||
else
|
||||
{
|
||||
t = *pathPtr;
|
||||
w = *wildPtr;
|
||||
|
||||
if (!Environment.IsFileSystemCaseSensitive)
|
||||
{
|
||||
t = t.ToUpper;
|
||||
w = w.ToUpper;
|
||||
}
|
||||
|
||||
// How do you match a tame text string?
|
||||
if (t != w)
|
||||
{
|
||||
// The tame way: unique up on it!
|
||||
if (w == '*')
|
||||
{
|
||||
afterLastWild = ++wildPtr;
|
||||
afterLastPath = pathPtr;
|
||||
if (wildPtr == wildEnd)
|
||||
{
|
||||
break; // "*" matches "x"
|
||||
}
|
||||
w = *wildPtr;
|
||||
continue; // "*y" matches "xy"
|
||||
}
|
||||
else if (afterLastWild != null)
|
||||
{
|
||||
if (afterLastWild != wildPtr)
|
||||
{
|
||||
wildPtr = afterLastWild;
|
||||
w = *wildPtr;
|
||||
|
||||
if (!Environment.IsFileSystemCaseSensitive)
|
||||
w = w.ToUpper;
|
||||
|
||||
if (t == w)
|
||||
{
|
||||
wildPtr++;
|
||||
}
|
||||
}
|
||||
pathPtr++;
|
||||
continue; // "*sip*" matches "mississippi"
|
||||
}
|
||||
else
|
||||
{
|
||||
matches = false;
|
||||
break; // "x" doesn't match "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathPtr++;
|
||||
wildPtr++;
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
}
|
100
BeefLibs/corlib/src/IO/Pipe.bf
Normal file
100
BeefLibs/corlib/src/IO/Pipe.bf
Normal file
|
@ -0,0 +1,100 @@
|
|||
namespace System.IO
|
||||
{
|
||||
enum PipeOptions
|
||||
{
|
||||
None = 0,
|
||||
AllowTimeouts = 1
|
||||
}
|
||||
|
||||
class NamedPipe : FileStreamBase
|
||||
{
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<void, FileOpenError> Create(StringView machineName, StringView pipeName, PipeOptions options)
|
||||
{
|
||||
Runtime.Assert(mBfpFile == null);
|
||||
|
||||
String path = scope String();
|
||||
path.Append(pipeName);
|
||||
|
||||
Platform.BfpFileCreateKind createKind = .CreateAlways;
|
||||
Platform.BfpFileCreateFlags createFlags = .Pipe;
|
||||
|
||||
if (options.HasFlag(.AllowTimeouts))
|
||||
createFlags |= .AllowTimeouts;
|
||||
|
||||
createKind = .CreateIfNotExists;
|
||||
createFlags |= .Read;
|
||||
createFlags |= .Write;
|
||||
|
||||
Platform.BfpFileAttributes fileFlags = .Normal;
|
||||
|
||||
Platform.BfpFileResult fileResult = .Ok;
|
||||
mBfpFile = Platform.BfpFile_Create(path, createKind, createFlags, fileFlags, &fileResult);
|
||||
|
||||
if ((mBfpFile == null) || (fileResult != .Ok))
|
||||
{
|
||||
switch (fileResult)
|
||||
{
|
||||
case .ShareError:
|
||||
return .Err(.SharingViolation);
|
||||
case .NotFound:
|
||||
return .Err(.NotFound);
|
||||
default:
|
||||
return .Err(.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void, FileOpenError> Open(StringView machineName, StringView pipeName, PipeOptions options)
|
||||
{
|
||||
Runtime.Assert(mBfpFile == null);
|
||||
|
||||
String path = scope String();
|
||||
path.Append(pipeName);
|
||||
|
||||
Platform.BfpFileCreateKind createKind = .CreateAlways;
|
||||
Platform.BfpFileCreateFlags createFlags = .Pipe;
|
||||
|
||||
createKind = .OpenExisting;
|
||||
createFlags |= .Read;
|
||||
createFlags |= .Write;
|
||||
|
||||
Platform.BfpFileAttributes fileFlags = .Normal;
|
||||
|
||||
Platform.BfpFileResult fileResult = .Ok;
|
||||
mBfpFile = Platform.BfpFile_Create(path, createKind, createFlags, fileFlags, &fileResult);
|
||||
|
||||
if ((mBfpFile == null) || (fileResult != .Ok))
|
||||
{
|
||||
switch (fileResult)
|
||||
{
|
||||
case .ShareError:
|
||||
return .Err(.SharingViolation);
|
||||
case .NotFound:
|
||||
return .Err(.NotFound);
|
||||
default:
|
||||
return .Err(.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
}
|
||||
}
|
100
BeefLibs/corlib/src/IO/SaveFileDialog.bf
Normal file
100
BeefLibs/corlib/src/IO/SaveFileDialog.bf
Normal file
|
@ -0,0 +1,100 @@
|
|||
// This file contains portions of code released by Microsoft under the MIT license as part
|
||||
// of an open-sourcing initiative in 2014 of the C# core libraries.
|
||||
// The original source was submitted to https://github.com/Microsoft/referencesource
|
||||
|
||||
using System.Text;
|
||||
|
||||
#if BF_PLATFORM_WINDOWS
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
class SaveFileDialog : FileDialog
|
||||
{
|
||||
public this()
|
||||
{
|
||||
//mOptions &= ~Windows.OFN_PATHMUSTEXIST;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
mOptions = 0;
|
||||
}
|
||||
|
||||
public virtual bool OverwritePrompt
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetOption(Windows.OFN_OVERWRITEPROMPT);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetOption(Windows.OFN_OVERWRITEPROMPT, value);
|
||||
}
|
||||
}
|
||||
|
||||
internal override Result<DialogResult> RunFileDialog(ref Windows.OpenFileName ofn)
|
||||
{
|
||||
bool result = Windows.GetSaveFileNameW(ref ofn);
|
||||
if (!result)
|
||||
return .Err;
|
||||
|
||||
if (!Multiselect)
|
||||
{
|
||||
let pathName = new String();
|
||||
UTF16.Decode(ofn.mFile, pathName);
|
||||
mFileNames = new String[](pathName);
|
||||
return DialogResult.OK;
|
||||
}
|
||||
|
||||
int32 entryCount = 0;
|
||||
int32 prevNull = -1;
|
||||
for (int32 i = 0; true; i++)
|
||||
{
|
||||
if (ofn.mFile[i] == (char8)0)
|
||||
{
|
||||
if (prevNull == i - 1)
|
||||
break;
|
||||
prevNull = i;
|
||||
entryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
String pathName = null;
|
||||
prevNull = -1;
|
||||
mFileNames = new String[Math.Max(1, entryCount - 1)];
|
||||
entryCount = 0;
|
||||
for (int32 i = 0; true; i++)
|
||||
{
|
||||
if (ofn.mFile[i] == (char8)0)
|
||||
{
|
||||
if (prevNull == i - 1)
|
||||
break;
|
||||
if (prevNull == -1)
|
||||
{
|
||||
pathName = scope:: String();
|
||||
UTF16.Decode(ofn.mFile, pathName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var str = new String(pathName.Length + 1 + i - prevNull - 1);
|
||||
str.Append(pathName);
|
||||
str.Append(Path.DirectorySeparatorChar);
|
||||
UTF16.Decode(ofn.mFile + prevNull + 1, str);
|
||||
|
||||
mFileNames[entryCount++] = str;
|
||||
}
|
||||
prevNull = i;
|
||||
}
|
||||
}
|
||||
|
||||
if ((entryCount == 0) && (pathName != null))
|
||||
mFileNames[0] = new String(pathName);
|
||||
|
||||
return DialogResult.OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
4
BeefLibs/corlib/src/IO/SearchOption.bf
Normal file
4
BeefLibs/corlib/src/IO/SearchOption.bf
Normal file
|
@ -0,0 +1,4 @@
|
|||
namespace System.IO
|
||||
{
|
||||
|
||||
}
|
10
BeefLibs/corlib/src/IO/SecurityAttributes.bf
Normal file
10
BeefLibs/corlib/src/IO/SecurityAttributes.bf
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace System.IO
|
||||
{
|
||||
[CRepr]
|
||||
struct SecurityAttributes
|
||||
{
|
||||
internal int32 mLength = (int32)sizeof(SecurityAttributes);
|
||||
internal uint8* mSecurityDescriptor = null;
|
||||
internal int32 mInheritHandle = 0;
|
||||
}
|
||||
}
|
127
BeefLibs/corlib/src/IO/Shell.bf
Normal file
127
BeefLibs/corlib/src/IO/Shell.bf
Normal file
|
@ -0,0 +1,127 @@
|
|||
#if BF_PLATFORM_WINDOWS
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
static class Shell
|
||||
{
|
||||
|
||||
public struct COM_IPersist : Windows.COM_IUnknown
|
||||
{
|
||||
public struct VTable : COM_IUnknown.VTable
|
||||
{
|
||||
public function HResult(COM_IPersistFile* self, Guid* pClassID) GetClassID;
|
||||
}
|
||||
}
|
||||
|
||||
public struct COM_IPersistFile : COM_IPersist
|
||||
{
|
||||
public static Guid sIID = .(0x0000010b, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
|
||||
|
||||
public struct VTable : COM_IPersist.VTable
|
||||
{
|
||||
public function HResult(COM_IPersistFile* self) IsDirty;
|
||||
public function HResult(COM_IPersistFile* self, char16* pszFileName) Load;
|
||||
public function HResult(COM_IPersistFile* self, char16* pszFileName, Windows.IntBool remember) Save;
|
||||
public function HResult(COM_IPersistFile* self, char16* pszFileName) SaveCompleted;
|
||||
public function HResult(COM_IPersistFile* self, char16* pszName) GetCurFile;
|
||||
}
|
||||
public new VTable* VT
|
||||
{
|
||||
get
|
||||
{
|
||||
return (.)mVT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct COM_IShellLink : Windows.COM_IUnknown
|
||||
{
|
||||
public static Guid sCLSID = .(0x00021401, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
|
||||
public static Guid sIID = .(0x000214F9, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
|
||||
|
||||
struct IDLIST;
|
||||
|
||||
public struct VTable : Windows.COM_IUnknown.VTable
|
||||
{
|
||||
public function HResult(COM_IShellLink* self, char16* pszFile, int32 cch, Windows.NativeFindData* pfd, uint32 fFlags) GetPath;
|
||||
public function HResult(COM_IShellLink* self, IDLIST** ppidl) GetIDList;
|
||||
public function HResult(COM_IShellLink* self, IDLIST* pidl) SetIDList;
|
||||
public function HResult(COM_IShellLink* self, char16* pszName, int32 cch) GetDescription;
|
||||
public function HResult(COM_IShellLink* self, char16* pszName) SetDescription;
|
||||
public function HResult(COM_IShellLink* self, char16* pszDir, int32 cch) GetWorkingDirectory;
|
||||
public function HResult(COM_IShellLink* self, char16* pszDir) SetWorkingDirectory;
|
||||
public function HResult(COM_IShellLink* self, char16* pszArgs, int32 cch) GetArguments;
|
||||
public function HResult(COM_IShellLink* self, char16* pszArgs) SetArguments;
|
||||
public function HResult(COM_IShellLink* self, uint16 *pwHotkey) GetHotkey;
|
||||
public function HResult(COM_IShellLink* self, uint16 wHotkey) SetHotkey;
|
||||
public function HResult(COM_IShellLink* self, int32 *piShowCmd) GetShowCmd;
|
||||
public function HResult(COM_IShellLink* self, int32 iShowCmd) SetShowCmd;
|
||||
public function HResult(COM_IShellLink* self, char16* pszIconPath, int32 cch, int32 *piIcon) GetIconLocation;
|
||||
public function HResult(COM_IShellLink* self, char16* pszIconPath, int32 iIcon) SetIconLocation;
|
||||
public function HResult(COM_IShellLink* self, char16* pszPathRel, uint32 dwReserved) SetRelativePath;
|
||||
public function HResult(COM_IShellLink* self, Windows.HWnd hwnd, uint32 fFlags) Resolve;
|
||||
public function HResult(COM_IShellLink* self, char16* pszFile) SetPath;
|
||||
|
||||
}
|
||||
public new VTable* VT
|
||||
{
|
||||
get
|
||||
{
|
||||
return (.)mVT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ShellError
|
||||
{
|
||||
case AccessDenied;
|
||||
case UnknownError;
|
||||
|
||||
public this(Windows.COM_IUnknown.HResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case .E_ACCESSDENIED: this = .AccessDenied;
|
||||
default: this = .UnknownError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Result<void, ShellError> CreateShortcut(StringView linkPath, StringView targetPath, StringView arguments, StringView workingDirectory, StringView description)
|
||||
{
|
||||
COM_IShellLink* shellLink = null;
|
||||
COM_IPersistFile* persistFile = null;
|
||||
|
||||
defer
|
||||
{
|
||||
if (persistFile != null)
|
||||
persistFile.VT.Release(persistFile);
|
||||
if (shellLink != null)
|
||||
shellLink.VT.Release(shellLink);
|
||||
}
|
||||
|
||||
mixin TryHR(Windows.COM_IUnknown.HResult result)
|
||||
{
|
||||
if (result != .OK)
|
||||
{
|
||||
return .Err(ShellError(result));
|
||||
}
|
||||
}
|
||||
|
||||
TryHR!(Windows.COM_IUnknown.CoCreateInstance(ref COM_IShellLink.sCLSID, null, .INPROC_SERVER, ref COM_IShellLink.sIID, (void**)&shellLink));
|
||||
TryHR!(shellLink.VT.SetPath(shellLink, targetPath.ToScopedNativeWChar!()));
|
||||
if (!arguments.IsEmpty)
|
||||
TryHR!(shellLink.VT.SetArguments(shellLink, arguments.ToScopedNativeWChar!()));
|
||||
if (!workingDirectory.IsEmpty)
|
||||
TryHR!(shellLink.VT.SetWorkingDirectory(shellLink, workingDirectory.ToScopedNativeWChar!()));
|
||||
if (!description.IsEmpty)
|
||||
TryHR!(shellLink.VT.SetDescription(shellLink, description.ToScopedNativeWChar!()));
|
||||
TryHR!(shellLink.VT.QueryInterface(shellLink, ref COM_IPersistFile.sIID, (void**)&persistFile));
|
||||
TryHR!(persistFile.VT.Save(persistFile, linkPath.ToScopedNativeWChar!(), true));
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
325
BeefLibs/corlib/src/IO/Stream.bf
Normal file
325
BeefLibs/corlib/src/IO/Stream.bf
Normal file
|
@ -0,0 +1,325 @@
|
|||
// This file contains portions of code released by Microsoft under the MIT license as part
|
||||
// of an open-sourcing initiative in 2014 of the C# core libraries.
|
||||
// The original source was submitted to https://github.com/Microsoft/referencesource
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
abstract class Stream
|
||||
{
|
||||
private ReadWriteTask _activeReadWriteTask;
|
||||
private SemaphoreSlim _asyncActiveSemaphore;
|
||||
|
||||
public enum SeekKind
|
||||
{
|
||||
Absolute,
|
||||
Relative,
|
||||
FromEnd
|
||||
}
|
||||
|
||||
public abstract int64 Position
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public abstract int64 Length
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract bool CanRead
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract bool CanWrite
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return Length == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Result<void> Seek(int64 pos, SeekKind seekKind = .Absolute)
|
||||
{
|
||||
if (seekKind == .Absolute)
|
||||
Position = pos;
|
||||
else
|
||||
Runtime.FatalError();
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public abstract Result<int> TryRead(Span<uint8> data);
|
||||
public abstract Result<int> TryWrite(Span<uint8> data);
|
||||
public abstract void Close();
|
||||
|
||||
public Result<T> Read<T>() where T : struct
|
||||
{
|
||||
T val = ?;
|
||||
int size = Try!(TryRead(.((uint8*)&val, sizeof(T))));
|
||||
if (size != sizeof(T))
|
||||
return .Err;
|
||||
return .Ok(val);
|
||||
}
|
||||
|
||||
public Result<void> Write<T>(T val) where T : struct
|
||||
{
|
||||
var val;
|
||||
int size = Try!(TryWrite(.((uint8*)&val, sizeof(T))));
|
||||
if (size != sizeof(T))
|
||||
return .Err;
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void> Write<T, T2>(T val) where T : Span<T2>
|
||||
{
|
||||
int trySize = val.Length * sizeof(T2);
|
||||
int size = Try!(TryWrite(.((uint8*)val.Ptr, trySize)));
|
||||
if (size != trySize)
|
||||
return .Err;
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void> WriteStrSized32(StringView val)
|
||||
{
|
||||
int trySize = val.Length;
|
||||
Try!(Write((int32)trySize));
|
||||
int size = Try!(TryWrite(.((uint8*)val.Ptr, trySize)));
|
||||
if (size != trySize)
|
||||
return .Err;
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void> WriteStrUnsized(StringView val)
|
||||
{
|
||||
int trySize = val.Length;
|
||||
int size = Try!(TryWrite(.((uint8*)val.Ptr, trySize)));
|
||||
if (size != trySize)
|
||||
return .Err;
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void> Write(String val)
|
||||
{
|
||||
int trySize = val.Length;
|
||||
int size = Try!(TryWrite(Span<uint8>((uint8*)val.Ptr, trySize)));
|
||||
if (size != trySize)
|
||||
return .Err;
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public virtual void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public void Align(int alignSize)
|
||||
{
|
||||
int64 pos = Length;
|
||||
int64 alignAdd = alignSize - (pos % alignSize);
|
||||
if (alignAdd == alignSize)
|
||||
return;
|
||||
|
||||
int64 emptyData = 0;
|
||||
while (alignAdd > 0)
|
||||
{
|
||||
int64 writeSize = Math.Min(alignAdd, sizeof(decltype(emptyData)));
|
||||
TryWrite(.((uint8*)&emptyData, (int)writeSize));
|
||||
alignAdd -= writeSize;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Result<int> CopyTo(Stream destStream)
|
||||
{
|
||||
uint8[4096] buffer;
|
||||
int totalBytes = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readBytes = Try!(TryRead(.(&buffer, sizeof(decltype(buffer)))));
|
||||
Try!(destStream.TryWrite(.(&buffer, readBytes)));
|
||||
if (readBytes <= 0)
|
||||
break;
|
||||
totalBytes += readBytes;
|
||||
}
|
||||
|
||||
return .Ok(totalBytes);
|
||||
}
|
||||
|
||||
public virtual IAsyncResult BeginRead(uint8[] buffer, int offset, int count, AsyncCallback callback, Object state)
|
||||
{
|
||||
//Contract.Ensures(Contract.Result<IAsyncResult>() != null);
|
||||
return BeginReadInternal(buffer, offset, count, callback, state, false);
|
||||
}
|
||||
|
||||
// Task used by BeginRead / BeginWrite to do Read / Write asynchronously.
|
||||
// A single instance of this task serves four purposes:
|
||||
// 1. The work item scheduled to run the Read / Write operation
|
||||
// 2. The state holding the arguments to be passed to Read / Write
|
||||
// 3. The IAsyncResult returned from BeginRead / BeginWrite
|
||||
// 4. The completion action that runs to invoke the user-provided callback.
|
||||
// This last item is a bit tricky. Before the AsyncCallback is invoked, the
|
||||
// IAsyncResult must have completed, so we can't just invoke the handler
|
||||
// from within the task, since it is the IAsyncResult, and thus it's not
|
||||
// yet completed. Instead, we use AddCompletionAction to install this
|
||||
// task as its own completion handler. That saves the need to allocate
|
||||
// a separate completion handler, it guarantees that the task will
|
||||
// have completed by the time the handler is invoked, and it allows
|
||||
// the handler to be invoked synchronously upon the completion of the
|
||||
// task. This all enables BeginRead / BeginWrite to be implemented
|
||||
// with a single allocation.
|
||||
private sealed class ReadWriteTask : Task<int>, ITaskCompletionAction
|
||||
{
|
||||
internal readonly bool _isRead;
|
||||
internal Stream _stream;
|
||||
internal uint8 [] _buffer;
|
||||
internal int _offset;
|
||||
internal int _count;
|
||||
private AsyncCallback _callback;
|
||||
//private ExecutionContext _context;
|
||||
|
||||
internal void ClearBeginState() // Used to allow the args to Read/Write to be made available for GC
|
||||
{
|
||||
_stream = null;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public this(
|
||||
bool isRead,
|
||||
Func<Object, int> func, Object state,
|
||||
Stream stream, uint8[] buffer, int offset, int count, AsyncCallback callback) :
|
||||
base(func, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach)
|
||||
{
|
||||
Contract.Requires(func != null);
|
||||
Contract.Requires(stream != null);
|
||||
Contract.Requires(buffer != null);
|
||||
Contract.EndContractBlock();
|
||||
|
||||
//StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
|
||||
|
||||
// Store the arguments
|
||||
_isRead = isRead;
|
||||
_stream = stream;
|
||||
_buffer = buffer;
|
||||
_offset = offset;
|
||||
_count = count;
|
||||
|
||||
// If a callback was provided, we need to:
|
||||
// - Store the user-provided handler
|
||||
// - Capture an ExecutionContext under which to invoke the handler
|
||||
// - Add this task as its own completion handler so that the Invoke method
|
||||
// will run the callback when this task completes.
|
||||
if (callback != null)
|
||||
{
|
||||
_callback = callback;
|
||||
/*_context = ExecutionContext.Capture(ref stackMark,
|
||||
ExecutionContext.CaptureOptions.OptimizeDefaultCase | ExecutionContext.CaptureOptions.IgnoreSyncCtx);*/
|
||||
base.AddCompletionAction(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static void InvokeAsyncCallback(Object completedTask)
|
||||
{
|
||||
var rwc = (ReadWriteTask)completedTask;
|
||||
var callback = rwc._callback;
|
||||
rwc._callback = null;
|
||||
callback(rwc);
|
||||
}
|
||||
|
||||
void ITaskCompletionAction.Invoke(Task completingTask)
|
||||
{
|
||||
var callback = _callback;
|
||||
_callback = null;
|
||||
callback(completingTask);
|
||||
}
|
||||
}
|
||||
|
||||
internal IAsyncResult BeginReadInternal(uint8[] buffer, int offset, int count, AsyncCallback callback, Object state, bool serializeAsynchronously)
|
||||
{
|
||||
// To avoid a race with a stream's position pointer & generating ----
|
||||
// conditions with internal buffer indexes in our own streams that
|
||||
// don't natively support async IO operations when there are multiple
|
||||
// async requests outstanding, we will block the application's main
|
||||
// thread if it does a second IO request until the first one completes.
|
||||
|
||||
//TODO: Implement
|
||||
/*var semaphore = EnsureAsyncActiveSemaphoreInitialized();
|
||||
Task semaphoreTask = null;
|
||||
if (serializeAsynchronously)
|
||||
{
|
||||
semaphoreTask = semaphore.WaitAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
semaphore.Wait();
|
||||
}*/
|
||||
|
||||
// Create the task to asynchronously do a Read. This task serves both
|
||||
// as the asynchronous work item and as the IAsyncResult returned to the user.
|
||||
var asyncResult = new ReadWriteTask(true /*isRead*/, new (obj) =>
|
||||
{
|
||||
// The ReadWriteTask stores all of the parameters to pass to Read.
|
||||
// As we're currently inside of it, we can get the current task
|
||||
// and grab the parameters from it.
|
||||
var thisTask = Task.InternalCurrent as ReadWriteTask;
|
||||
Contract.Assert(thisTask != null, "Inside ReadWriteTask, InternalCurrent should be the ReadWriteTask");
|
||||
|
||||
// Do the Read and return the number of bytes read
|
||||
int bytesRead = thisTask._stream.TryRead(.(thisTask._buffer, thisTask._offset, thisTask._count));
|
||||
thisTask.ClearBeginState(); // just to help alleviate some memory pressure
|
||||
return bytesRead;
|
||||
}, state, this, buffer, offset, count, callback);
|
||||
|
||||
// Schedule it
|
||||
|
||||
//TODO:
|
||||
/*if (semaphoreTask != null)
|
||||
RunReadWriteTaskWhenReady(semaphoreTask, asyncResult);
|
||||
else
|
||||
RunReadWriteTask(asyncResult);*/
|
||||
|
||||
|
||||
return asyncResult; // return it
|
||||
}
|
||||
|
||||
public virtual Result<int> EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
if (asyncResult == null)
|
||||
Runtime.FatalError();
|
||||
//Contract.Ensures(Contract.Result<int>() >= 0);
|
||||
Contract.EndContractBlock();
|
||||
|
||||
var readTask = _activeReadWriteTask;
|
||||
|
||||
if (readTask == null)
|
||||
{
|
||||
Runtime.FatalError();
|
||||
}
|
||||
else if (readTask != asyncResult)
|
||||
{
|
||||
Runtime.FatalError();
|
||||
}
|
||||
else if (!readTask._isRead)
|
||||
{
|
||||
Runtime.FatalError();
|
||||
}
|
||||
|
||||
|
||||
int result = readTask.GetAwaiter().GetResult(); // block until completion, then get result / propagate any exception
|
||||
|
||||
_activeReadWriteTask = null;
|
||||
Contract.Assert(_asyncActiveSemaphore != null, "Must have been initialized in order to get here.");
|
||||
_asyncActiveSemaphore.Release();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
612
BeefLibs/corlib/src/IO/StreamReader.bf
Normal file
612
BeefLibs/corlib/src/IO/StreamReader.bf
Normal file
|
@ -0,0 +1,612 @@
|
|||
// This file contains portions of code released by Microsoft under the MIT license as part
|
||||
// of an open-sourcing initiative in 2014 of the C# core libraries.
|
||||
// The original source was submitted to https://github.com/Microsoft/referencesource
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
class StreamReader
|
||||
{
|
||||
private const int32 DefaultFileStreamBufferSize = 4096;
|
||||
private const int32 MinBufferSize = 128;
|
||||
|
||||
Stream mStream ~ if (mOwnsStream) delete _;
|
||||
public bool mOwnsStream;
|
||||
int mCharLen;
|
||||
int mCharPos;
|
||||
|
||||
private int mBytePos;
|
||||
|
||||
// Record the number of valid bytes in the byteBuffer, for a few checks.
|
||||
private int mByteLen;
|
||||
// We will support looking for byte order marks in the stream and trying
|
||||
// to decide what the encoding might be from the byte order marks, IF they
|
||||
// exist. But that's all we'll do.
|
||||
private bool mDetectEncoding;
|
||||
|
||||
// Whether we must still check for the encoding's given preamble at the
|
||||
// beginning of this file.
|
||||
private bool mCheckPreamble;
|
||||
|
||||
// Whether the stream is most likely not going to give us back as much
|
||||
// data as we want the next time we call it. We must do the computation
|
||||
// before we do any byte order mark handling and save the result. Note
|
||||
// that we need this to allow users to handle streams used for an
|
||||
// interactive protocol, where they block waiting for the remote end
|
||||
// to send a response, like logging in on a Unix machine.
|
||||
private bool mIsBlocked;
|
||||
|
||||
private uint8[] mPreamble ~ delete _; // Encoding's preamble, which identifies this encoding.
|
||||
|
||||
private uint8[] mByteBuffer;
|
||||
private char8[] mCharBuffer;
|
||||
private bool mOwnsBuffers;
|
||||
|
||||
private int32 mMaxCharsPerBuffer;
|
||||
private Encoding mEncoding;
|
||||
|
||||
public Stream BaseStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return mStream;
|
||||
}
|
||||
}
|
||||
|
||||
public this()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
if (mOwnsBuffers)
|
||||
{
|
||||
delete mByteBuffer;
|
||||
delete mCharBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
public Encoding CurrentEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
return mEncoding;
|
||||
}
|
||||
}
|
||||
|
||||
public LineReader Lines
|
||||
{
|
||||
get
|
||||
{
|
||||
return LineReader(this);
|
||||
}
|
||||
}
|
||||
|
||||
[AllowAppend]
|
||||
public this(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int32 bufferSize, bool ownsSteam = false)
|
||||
{
|
||||
int32 useBufferSize = bufferSize;
|
||||
if (useBufferSize < MinBufferSize) useBufferSize = MinBufferSize;
|
||||
let maxCharsPerBuffer = (encoding != null) ? encoding.GetMaxCharCount(useBufferSize) : bufferSize;
|
||||
|
||||
let byteBuffer = append uint8[useBufferSize];
|
||||
let charBuffer = append char8[maxCharsPerBuffer];
|
||||
/*let byteBuffer = new uint8[useBufferSize];
|
||||
let charBuffer = new char8[maxCharsPerBuffer];
|
||||
mOwnsBuffers = true;*/
|
||||
|
||||
mMaxCharsPerBuffer = (.)maxCharsPerBuffer;
|
||||
mByteBuffer = byteBuffer;
|
||||
mCharBuffer = charBuffer;
|
||||
mOwnsStream = ownsSteam;
|
||||
|
||||
Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize);
|
||||
}
|
||||
|
||||
[AllowAppend]
|
||||
public this(Stream stream) : this(stream, .UTF8, false, 4096)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int GetMaxCharCount(int32 byteCount)
|
||||
{
|
||||
if (mEncoding == null)
|
||||
return byteCount;
|
||||
// UTF-8 to UTF-8
|
||||
return mEncoding.GetMaxCharCount(byteCount);
|
||||
}
|
||||
|
||||
void Init(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int32 bufferSize)
|
||||
{
|
||||
mStream = stream;
|
||||
this.mEncoding = encoding;
|
||||
//decoder = encoding.GetDecoder();
|
||||
|
||||
if (mByteBuffer == null)
|
||||
{
|
||||
int32 useBufferSize = bufferSize;
|
||||
if (useBufferSize < MinBufferSize) useBufferSize = MinBufferSize;
|
||||
mMaxCharsPerBuffer = (.)GetMaxCharCount(useBufferSize);
|
||||
mByteBuffer = new uint8[useBufferSize];
|
||||
mCharBuffer = new char8[mMaxCharsPerBuffer];
|
||||
mOwnsBuffers = true;
|
||||
}
|
||||
|
||||
mByteLen = 0;
|
||||
mBytePos = 0;
|
||||
mDetectEncoding = detectEncodingFromByteOrderMarks;
|
||||
//mPreamble = encoding.GetPreamble();
|
||||
mCheckPreamble = false;//(mPreamble.Length > 0);
|
||||
mIsBlocked = false;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
delete mStream;
|
||||
mStream = null;
|
||||
}
|
||||
|
||||
public bool EndOfStream
|
||||
{
|
||||
get
|
||||
{
|
||||
if (mCharPos < mCharLen)
|
||||
return false;
|
||||
|
||||
// This may block on pipes!
|
||||
int numRead = ReadBuffer();
|
||||
return numRead == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Result<void, FileOpenError> Open(StringView fileName)
|
||||
{
|
||||
Contract.Assert(mStream == null);
|
||||
|
||||
var fileStream = new FileStream();
|
||||
Encoding encoding = null;
|
||||
Init(fileStream, encoding, true, DefaultFileStreamBufferSize);
|
||||
mOwnsStream = true;
|
||||
|
||||
if (fileStream.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) case .Err(let err))
|
||||
return .Err(err);
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Task<String> ReadLineAsync()
|
||||
{
|
||||
// If we have been inherited into a subclass, the following implementation could be incorrect
|
||||
// since it does not call through to Read() which a subclass might have overriden.
|
||||
// To be safe we will only use this implementation in cases where we know it is safe to do so,
|
||||
// and delegate to our base class (which will call into Read) when we are not sure.
|
||||
//TODO:
|
||||
//if (this.GetType() != typeof(StreamReader))
|
||||
//return base.ReadLineAsync();
|
||||
|
||||
//if (mStream == null)
|
||||
//__Error.ReaderClosed();
|
||||
|
||||
//CheckAsyncTaskInProgress();
|
||||
|
||||
Task<String> task = ReadLineAsyncInternal();
|
||||
//_asyncReadTask = task;
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
class ReadLineTask : Task<String>
|
||||
{
|
||||
StreamReader mStreamReader;
|
||||
WaitEvent mDoneEvent = new WaitEvent() ~ delete _;
|
||||
|
||||
internal this(StreamReader streamReader)
|
||||
{
|
||||
Debug.WriteLine("ReadLineTask this {0}", this);
|
||||
|
||||
mStreamReader = streamReader;
|
||||
ThreadPool.QueueUserWorkItem(new => Proc);
|
||||
}
|
||||
|
||||
internal ~this()
|
||||
{
|
||||
//Debug.WriteLine("ReadLineTask ~this waiting {0}", this);
|
||||
mDoneEvent.WaitFor();
|
||||
//Debug.WriteLine("ReadLineTask ~this done {0}", this);
|
||||
|
||||
delete m_result;
|
||||
}
|
||||
|
||||
void Proc()
|
||||
{
|
||||
//Debug.WriteLine("ReadLineTask Proc start {0}", this);
|
||||
m_result = new String();
|
||||
var result = mStreamReader.ReadLine(m_result);
|
||||
//Debug.WriteLine("ReadLineTask Proc finishing {0}", this);
|
||||
Finish(false);
|
||||
Ref();
|
||||
if (result case .Ok)
|
||||
Notify(false);
|
||||
mDoneEvent.Set();
|
||||
Deref();
|
||||
}
|
||||
}
|
||||
|
||||
private Task<String> ReadLineAsyncInternal()
|
||||
{
|
||||
/*if (CharPos_Prop == CharLen_Prop && (await ReadBufferAsync().ConfigureAwait(false)) == 0)
|
||||
return null;
|
||||
|
||||
String sb = null;
|
||||
|
||||
do
|
||||
{
|
||||
char8[] tmpCharBuffer = CharBuffer_Prop;
|
||||
int tmpCharLen = CharLen_Prop;
|
||||
int tmpCharPos = CharPos_Prop;
|
||||
int i = tmpCharPos;
|
||||
|
||||
do
|
||||
{
|
||||
char8 ch = tmpCharBuffer[i];
|
||||
|
||||
// Note the following common line feed char8s:
|
||||
// \n - UNIX \r\n - DOS \r - Mac
|
||||
if (ch == '\r' || ch == '\n')
|
||||
{
|
||||
String s;
|
||||
|
||||
if (sb != null)
|
||||
{
|
||||
sb.Append(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
|
||||
s = sb;//.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
s = new String(tmpCharBuffer, tmpCharPos, i - tmpCharPos);
|
||||
}
|
||||
|
||||
CharPos_Prop = tmpCharPos = i + 1;
|
||||
|
||||
if (ch == '\r' && (tmpCharPos < tmpCharLen || (await ReadBufferAsync().ConfigureAwait(false)) > 0))
|
||||
{
|
||||
tmpCharPos = CharPos_Prop;
|
||||
if (CharBuffer_Prop[tmpCharPos] == '\n')
|
||||
CharPos_Prop = ++tmpCharPos;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
} while (i < tmpCharLen);
|
||||
|
||||
i = tmpCharLen - tmpCharPos;
|
||||
if (sb == null) sb = new String(i + 80);
|
||||
sb.Append(tmpCharBuffer, tmpCharPos, i);
|
||||
|
||||
} while (await ReadBufferAsync().ConfigureAwait(false) > 0);
|
||||
|
||||
return sb.ToString();*/
|
||||
|
||||
|
||||
ReadLineTask task = new ReadLineTask(this);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
public Result<void> ReadToEnd(String outText)
|
||||
{
|
||||
outText.Reserve((.)mStream.Length + 1);
|
||||
|
||||
while (true)
|
||||
{
|
||||
Try!(ReadBuffer());
|
||||
if (mCharLen == 0)
|
||||
break;
|
||||
|
||||
outText.Append(StringView(mCharBuffer, 0, mCharLen));
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
// Trims the preamble bytes from the byteBuffer. This routine can be called multiple times
|
||||
// and we will buffer the bytes read until the preamble is matched or we determine that
|
||||
// there is no match. If there is no match, every byte read previously will be available
|
||||
// for further consumption. If there is a match, we will compress the buffer for the
|
||||
// leading preamble bytes
|
||||
private bool IsPreamble()
|
||||
{
|
||||
if (!mCheckPreamble)
|
||||
return mCheckPreamble;
|
||||
|
||||
//Contract.Assert(bytePos <= _preamble.Length, "_compressPreamble was called with the current bytePos greater than the preamble buffer length. Are two threads using this StreamReader at the same time?");
|
||||
int len = (mByteLen >= (mPreamble.Count)) ? (mPreamble.Count - mBytePos) : (mByteLen - mBytePos);
|
||||
|
||||
for (int32 i = 0; i < len; i++,mBytePos++)
|
||||
{
|
||||
if (mByteBuffer[mBytePos] != mPreamble[mBytePos])
|
||||
{
|
||||
mBytePos = 0;
|
||||
mCheckPreamble = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Contract.Assert(mBytePos <= mPreamble.Count, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
|
||||
|
||||
if (mCheckPreamble)
|
||||
{
|
||||
if (mBytePos == mPreamble.Count)
|
||||
{
|
||||
// We have a match
|
||||
CompressBuffer(mPreamble.Count);
|
||||
mBytePos = 0;
|
||||
mCheckPreamble = false;
|
||||
mDetectEncoding = false;
|
||||
}
|
||||
}
|
||||
|
||||
return mCheckPreamble;
|
||||
}
|
||||
|
||||
// Trims n bytes from the front of the buffer.
|
||||
private void CompressBuffer(int n)
|
||||
{
|
||||
//Contract.Assert(byteLen >= n, "CompressBuffer was called with a number of bytes greater than the current buffer length. Are two threads using this StreamReader at the same time?");
|
||||
//Buffer.InternalBlockCopy(byteBuffer, n, byteBuffer, 0, byteLen - n);
|
||||
mByteBuffer.CopyTo(mByteBuffer, n, 0, mByteLen - n);
|
||||
mByteLen -= (int32)n;
|
||||
}
|
||||
|
||||
void DetectEncoding()
|
||||
{
|
||||
mEncoding = Encoding.DetectEncoding(mByteBuffer, var bomSize);
|
||||
if (mEncoding != null)
|
||||
{
|
||||
if (bomSize > 0)
|
||||
CompressBuffer(bomSize);
|
||||
mDetectEncoding = false;
|
||||
}
|
||||
else if (mByteLen > 4)
|
||||
{
|
||||
mDetectEncoding = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual Result<int> ReadBuffer()
|
||||
{
|
||||
mCharLen = 0;
|
||||
mCharPos = 0;
|
||||
|
||||
if (!mCheckPreamble)
|
||||
mByteLen = 0;
|
||||
repeat
|
||||
{
|
||||
if (mCheckPreamble)
|
||||
{
|
||||
//Contract.Assert(bytePos <= _preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
|
||||
int len = Try!(mStream.TryRead(.(mByteBuffer, mBytePos, mByteBuffer.Count - mBytePos)));
|
||||
/*switch (mStream.Read(mByteBuffer, mBytePos, mByteBuffer.Length - mBytePos))
|
||||
{
|
||||
case .Ok(var gotLen):
|
||||
len = gotLen;
|
||||
break;
|
||||
case .Err: return .Err;
|
||||
}*/
|
||||
//Contract.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
|
||||
|
||||
if (len == 0)
|
||||
{
|
||||
// EOF but we might have buffered bytes from previous
|
||||
// attempt to detect preamble that needs to be decoded now
|
||||
if (mByteLen > 0)
|
||||
{
|
||||
//TODO:
|
||||
/*char8Len += decoder.GetChars(byteBuffer, 0, byteLen, char8Buffer, char8Len);
|
||||
|
||||
// Need to zero out the byteLen after we consume these bytes so that we don't keep infinitely hitting this code path
|
||||
bytePos = byteLen = 0;*/
|
||||
}
|
||||
|
||||
return mCharLen;
|
||||
}
|
||||
|
||||
mByteLen += len;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Contract.Assert(bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
|
||||
|
||||
mByteLen = Try!(mStream.TryRead(.(mByteBuffer, 0, mByteBuffer.Count)));
|
||||
/*switch (mStream.Read(mByteBuffer, 0, mByteBuffer.Length))
|
||||
{
|
||||
case .Ok(var byteLen):
|
||||
mByteLen = byteLen;
|
||||
break;
|
||||
case .Err: return .Err;
|
||||
}*/
|
||||
//Contract.Assert(byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
|
||||
|
||||
if (mByteLen == 0) // We're at EOF
|
||||
return mCharLen;
|
||||
}
|
||||
|
||||
// _isBlocked == whether we read fewer bytes than we asked for.
|
||||
// Note we must check it here because CompressBuffer or
|
||||
// DetectEncoding will change byteLen.
|
||||
mIsBlocked = (mByteLen < mByteBuffer.Count);
|
||||
|
||||
// Check for preamble before detect encoding. This is not to override the
|
||||
// user suppplied Encoding for the one we implicitly detect. The user could
|
||||
// customize the encoding which we will loose, such as ThrowOnError on UTF8
|
||||
if (IsPreamble())
|
||||
continue;
|
||||
|
||||
// If we're supposed to detect the encoding and haven't done so yet,
|
||||
// do it. Note this may need to be called more than once.
|
||||
|
||||
if (mDetectEncoding && mByteLen >= 2)
|
||||
DetectEncoding();
|
||||
else if (mEncoding == null)
|
||||
mEncoding = Encoding.ASCII;
|
||||
|
||||
switch (mEncoding.DecodeToUTF8(.(mByteBuffer, 0, mByteLen), .(&mCharBuffer[mCharLen], mCharBuffer.Count - mCharLen)))
|
||||
{
|
||||
case .Ok(let outChars):
|
||||
mCharLen += outChars;
|
||||
Debug.Assert(outChars <= mCharBuffer.Count);
|
||||
break;
|
||||
case .Err(let err):
|
||||
switch (err)
|
||||
{
|
||||
case .PartialDecode(let decodedBytes, let outChars):
|
||||
//TODO: Handle this partial read...
|
||||
Debug.Assert(outChars <= mCharBuffer.Count);
|
||||
mCharLen += outChars;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
while (mCharLen == 0);
|
||||
|
||||
//Console.WriteLine("ReadBuffer called. char8s: "+char8Len);
|
||||
return mCharLen;
|
||||
}
|
||||
|
||||
int GetChars(uint8[] byteBuffer, int byteOffset, int byteLength, char8[] char8Buffer, int char8Offset)
|
||||
{
|
||||
//TODO: This only handles UTF-8 to UTF-8
|
||||
for (int32 i = 0; i < byteLength; i++)
|
||||
char8Buffer[char8Offset + i] = (char8)byteBuffer[i + byteOffset];
|
||||
return byteLength;
|
||||
}
|
||||
|
||||
// Reads a line. A line is defined as a sequence of characters followed by
|
||||
// a carriage return ('\r'), a line feed ('\n'), or a carriage return
|
||||
// immediately followed by a line feed. The resulting string does not
|
||||
// contain the terminating carriage return and/or line feed. The returned
|
||||
// value is null if the end of the input stream has been reached.
|
||||
//
|
||||
public Result<void> ReadLine(String strBuffer)
|
||||
{
|
||||
if (mStream == null)
|
||||
{
|
||||
return .Err;
|
||||
//__Error.ReaderClosed();
|
||||
}
|
||||
|
||||
#if FEATURE_ASYNC_IO
|
||||
CheckAsyncTaskInProgress();
|
||||
#endif
|
||||
|
||||
if (mCharPos == mCharLen)
|
||||
{
|
||||
if (Try!(ReadBuffer()) == 0) return .Err;
|
||||
}
|
||||
|
||||
repeat
|
||||
{
|
||||
int i = mCharPos;
|
||||
repeat
|
||||
{
|
||||
char8 ch = mCharBuffer[i];
|
||||
// Note the following common line feed char8s:
|
||||
// \n - UNIX \r\n - DOS \r - Mac
|
||||
if (ch == '\r' || ch == '\n')
|
||||
{
|
||||
/*String s;
|
||||
if (sb != null)
|
||||
{
|
||||
sb.Append(char8Buffer, char8Pos, i - char8Pos);
|
||||
s = sb.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
s = new String(char8Buffer, char8Pos, i - char8Pos);
|
||||
}*/
|
||||
|
||||
strBuffer.Append(mCharBuffer.CArray() + mCharPos, i - mCharPos);
|
||||
|
||||
mCharPos = i + 1;
|
||||
if (ch == '\r' && (mCharPos < mCharLen || Try!(ReadBuffer()) > 0))
|
||||
{
|
||||
if (mCharBuffer[mCharPos] == '\n') mCharPos++;
|
||||
}
|
||||
return .Ok;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
while (i < mCharLen);
|
||||
i = mCharLen - mCharPos;
|
||||
//if (sb == null) sb = new StringBuilder(i + 80);
|
||||
//sb.Append(char8Buffer, char8Pos, i);
|
||||
strBuffer.Append(mCharBuffer.CArray() + mCharPos, i);
|
||||
}
|
||||
while (Try!(ReadBuffer()) > 0);
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public struct LineReader : IEnumerator<Result<StringView>>
|
||||
{
|
||||
StreamReader mStreamReader;
|
||||
String mCurrentLine;
|
||||
Result<void> mLastReadResult;
|
||||
|
||||
internal this(StreamReader streamReader)
|
||||
{
|
||||
mStreamReader = streamReader;
|
||||
mCurrentLine = new String();
|
||||
mLastReadResult = default(Result<void>);
|
||||
}
|
||||
|
||||
public Result<StringView> Current
|
||||
{
|
||||
get
|
||||
{
|
||||
Try!(mLastReadResult);
|
||||
return (StringView)mCurrentLine;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool MoveNext() mut
|
||||
{
|
||||
mCurrentLine.Clear();
|
||||
mLastReadResult = mStreamReader.ReadLine(mCurrentLine);
|
||||
return !(mLastReadResult case .Err);
|
||||
}
|
||||
|
||||
public void Dispose() mut
|
||||
{
|
||||
delete mCurrentLine;
|
||||
mCurrentLine = null;
|
||||
}
|
||||
|
||||
public Result<Result<StringView>> GetNext() mut
|
||||
{
|
||||
if (!MoveNext())
|
||||
return .Err;
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
102
BeefLibs/corlib/src/IO/StreamWriter.bf
Normal file
102
BeefLibs/corlib/src/IO/StreamWriter.bf
Normal file
|
@ -0,0 +1,102 @@
|
|||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
public class StreamWriter
|
||||
{
|
||||
Stream mStream ~ if (mOwnsStream) delete _;
|
||||
public bool mOwnsStream;
|
||||
public Encoding mEncoding;
|
||||
|
||||
public bool AutoFlush;
|
||||
|
||||
public this()
|
||||
{
|
||||
}
|
||||
|
||||
public this(Stream stream, Encoding encoding, int32 bufferSize, bool ownsStream = false)
|
||||
{
|
||||
Debug.Assert(encoding != null);
|
||||
mStream = stream;
|
||||
mEncoding = encoding;
|
||||
mOwnsStream = ownsStream;
|
||||
}
|
||||
|
||||
public Result<void, FileOpenError> Create(StringView fileName)
|
||||
{
|
||||
Debug.Assert(mStream == null);
|
||||
|
||||
var fileStream = new FileStream();
|
||||
mStream = fileStream;
|
||||
mEncoding = .UTF8;
|
||||
mOwnsStream = true;
|
||||
|
||||
if (fileStream.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.ReadWrite) case .Err(let err))
|
||||
return .Err(err);
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void> Write(Span<uint8> data)
|
||||
{
|
||||
var spanLeft = data;
|
||||
while (!spanLeft.IsEmpty)
|
||||
{
|
||||
int bytesWritten = Try!(mStream.TryWrite(spanLeft));
|
||||
spanLeft.Adjust(bytesWritten);
|
||||
}
|
||||
if (AutoFlush)
|
||||
Flush();
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void> Write(StringView str)
|
||||
{
|
||||
var curSpan = str;
|
||||
|
||||
Span<uint8> outSpan = scope uint8[4096];
|
||||
|
||||
while (!curSpan.IsEmpty)
|
||||
{
|
||||
switch (mEncoding.Encode(curSpan, outSpan))
|
||||
{
|
||||
case .Ok(let encodedBytes):
|
||||
Try!(Write(Span<uint8>(outSpan.Ptr, encodedBytes)));
|
||||
return .Ok;
|
||||
case .Err(let err):
|
||||
switch (err)
|
||||
{
|
||||
case .PartialEncode(int inChars, int encodedBytes):
|
||||
Try!(Write(Span<uint8>(outSpan.Ptr, encodedBytes)));
|
||||
curSpan.Adjust(inChars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
public Result<void> WriteLine(StringView str)
|
||||
{
|
||||
Try!(Write(str));
|
||||
return Write("\n");
|
||||
}
|
||||
|
||||
public Result<void> Write(StringView fmt, params Object[] args)
|
||||
{
|
||||
return Write(scope String()..AppendF(fmt, params args));
|
||||
}
|
||||
|
||||
public Result<void> WriteLine(StringView fmt, params Object[] args)
|
||||
{
|
||||
Try!(Write(scope String()..AppendF(fmt, params args)));
|
||||
return Write("\n");
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
mStream.Flush();
|
||||
}
|
||||
}
|
||||
}
|
165
BeefLibs/corlib/src/IO/StringStream.bf
Normal file
165
BeefLibs/corlib/src/IO/StringStream.bf
Normal file
|
@ -0,0 +1,165 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
class StringStream : Stream
|
||||
{
|
||||
enum StringKind
|
||||
{
|
||||
Append,
|
||||
Reference,
|
||||
Owned
|
||||
}
|
||||
|
||||
StringKind mStringKind;
|
||||
String mString;
|
||||
int mPosition;
|
||||
|
||||
[AllowAppend]
|
||||
public this()
|
||||
{
|
||||
let appendStr = append String();
|
||||
mString = appendStr;
|
||||
}
|
||||
|
||||
public ~this()
|
||||
{
|
||||
if (mStringKind == .Append)
|
||||
delete:append mString;
|
||||
else if (mStringKind == .Owned)
|
||||
DeleteOwned();
|
||||
}
|
||||
|
||||
protected virtual void DeleteOwned()
|
||||
{
|
||||
delete mString;
|
||||
}
|
||||
|
||||
public enum StringViewInitKind
|
||||
{
|
||||
Copy,
|
||||
Reference,
|
||||
}
|
||||
|
||||
[AllowAppend]
|
||||
public this(StringView str, StringViewInitKind initKind)
|
||||
{
|
||||
let appendStr = append String();
|
||||
mString = appendStr;
|
||||
mStringKind = .Append;
|
||||
|
||||
switch (initKind)
|
||||
{
|
||||
case .Copy:
|
||||
mString.Set(str);
|
||||
case .Reference:
|
||||
mString = new String();
|
||||
mString.Reference(str.Ptr, str.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public enum StringInitKind
|
||||
{
|
||||
Copy,
|
||||
Reference,
|
||||
TakeOwnership
|
||||
}
|
||||
|
||||
[AllowAppend]
|
||||
public this(String str, StringInitKind initKind)
|
||||
{
|
||||
let appendStr = append String();
|
||||
mString = appendStr;
|
||||
|
||||
switch (initKind)
|
||||
{
|
||||
case .Copy:
|
||||
mString.Set(str);
|
||||
mStringKind = .Append;
|
||||
case .Reference:
|
||||
mString = str;
|
||||
mStringKind = .Reference;
|
||||
case .TakeOwnership:
|
||||
mString = str;
|
||||
mStringKind = .Owned;
|
||||
}
|
||||
}
|
||||
|
||||
public StringView Content
|
||||
{
|
||||
get
|
||||
{
|
||||
return mString;
|
||||
}
|
||||
}
|
||||
|
||||
public override int64 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
mPosition = (.)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override int64 Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return mString.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override Result<int> TryRead(Span<uint8> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
return .Ok(0);
|
||||
int readBytes = Math.Min(data.Length, mString.Length - mPosition);
|
||||
if (readBytes <= 0)
|
||||
return .Ok(readBytes);
|
||||
|
||||
Internal.MemCpy(data.Ptr, &mString[mPosition], readBytes);
|
||||
mPosition += readBytes;
|
||||
return .Ok(readBytes);
|
||||
}
|
||||
|
||||
public override Result<int> TryWrite(Span<uint8> data)
|
||||
{
|
||||
let count = data.Length;
|
||||
if (count == 0)
|
||||
return .Ok(0);
|
||||
int growSize = mPosition + count - mString.Length;
|
||||
if (growSize > 0)
|
||||
mString.PrepareBuffer(growSize);
|
||||
Internal.MemCpy(&mString[mPosition], data.Ptr, count);
|
||||
mPosition += count;
|
||||
return .Ok(count);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
78
BeefLibs/corlib/src/IO/Substream.bf
Normal file
78
BeefLibs/corlib/src/IO/Substream.bf
Normal file
|
@ -0,0 +1,78 @@
|
|||
using System.IO;
|
||||
|
||||
namespace System.IO
|
||||
{
|
||||
class Substream : Stream
|
||||
{
|
||||
public bool mOwnsStream;
|
||||
Stream mChildStream ~ { if (mOwnsStream) delete _; };
|
||||
int64 mOffset;
|
||||
int64 mLength;
|
||||
|
||||
public override int64 Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return mChildStream.Position + mOffset;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
mChildStream.Position = value + mOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public override int64 Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return mLength;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return mChildStream.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return mChildStream.CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public this(Stream childStream, int64 offset, int64 length, bool ownsStream = false)
|
||||
{
|
||||
mChildStream = childStream;
|
||||
mOffset = offset;
|
||||
mLength = length;
|
||||
mOwnsStream = ownsStream;
|
||||
}
|
||||
|
||||
public override Result<int> TryRead(Span<uint8> data)
|
||||
{
|
||||
return mChildStream.TryRead(data);
|
||||
}
|
||||
|
||||
public override Result<int> TryWrite(Span<uint8> data)
|
||||
{
|
||||
return mChildStream.TryWrite(data);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
mChildStream.Close();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
mChildStream.Flush();
|
||||
}
|
||||
}
|
||||
}
|
13
BeefLibs/corlib/src/IO/WatcherChangeTypes.bf
Normal file
13
BeefLibs/corlib/src/IO/WatcherChangeTypes.bf
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace System.IO
|
||||
{
|
||||
enum WatcherChangeTypes
|
||||
{
|
||||
FileCreated = 1,
|
||||
DirectoryCreated = 2,
|
||||
Deleted = 4,
|
||||
Changed = 8,
|
||||
Renamed = 0x10,
|
||||
Failed = 0x20,
|
||||
All = FileCreated | DirectoryCreated | Deleted | Changed | Renamed | Failed
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue