IOStream can now be open on a file and write into it.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <Core/Common/NonNullPtr.h>
|
||||
#include <Core/Common/String.h>
|
||||
#include <Juliet.h>
|
||||
|
||||
@@ -8,5 +9,52 @@ namespace Juliet
|
||||
// Opaque type
|
||||
struct IOStream;
|
||||
|
||||
struct IOStreamDataPayload
|
||||
{
|
||||
};
|
||||
|
||||
enum class IOStreamStatus : uint8
|
||||
{
|
||||
Ready,
|
||||
Error,
|
||||
EndOfFile,
|
||||
NotReady,
|
||||
ReadOnly,
|
||||
WriteOnly
|
||||
};
|
||||
|
||||
enum class IOStreamSeekPivot : uint8
|
||||
{
|
||||
Begin,
|
||||
Current,
|
||||
End,
|
||||
Count
|
||||
};
|
||||
|
||||
// IOStream can be opened on a file or memory, or anything else.
|
||||
// Use the interface to make it transparent to the user.
|
||||
struct IOStreamInterface
|
||||
{
|
||||
uint32 Version;
|
||||
|
||||
int64 (*Size)(NonNullPtr<IOStreamDataPayload> data);
|
||||
|
||||
int64 (*Seek)(NonNullPtr<IOStreamDataPayload> data, int64 offset, IOStreamSeekPivot pivot);
|
||||
size_t (*Read)(NonNullPtr<IOStreamDataPayload> data, void* outBuffer, size_t size, NonNullPtr<IOStreamStatus> status);
|
||||
size_t (*Write)(NonNullPtr<IOStreamDataPayload> data, const void* inBuffer, size_t size,
|
||||
NonNullPtr<IOStreamStatus> status);
|
||||
bool (*Flush)(NonNullPtr<IOStreamDataPayload> data, NonNullPtr<IOStreamStatus> status);
|
||||
|
||||
bool (*Close)(NonNullPtr<IOStreamDataPayload> data);
|
||||
};
|
||||
|
||||
extern JULIET_API IOStream* IOFromFile(String filename, String mode);
|
||||
|
||||
// Let you use an interface to open any io. Is used internally by IOFromFile
|
||||
extern JULIET_API IOStream* IOFromInterface(NonNullPtr<const IOStreamInterface> interface, NonNullPtr<IOStreamDataPayload> payload);
|
||||
|
||||
// Write formatted string into the stream.
|
||||
extern JULIET_API size_t IOPrintf(NonNullPtr<IOStream> stream, _Printf_format_string_ const char* format, ...);
|
||||
extern JULIET_API size_t IOWrite(NonNullPtr<IOStream> stream, const void* ptr, size_t size);
|
||||
|
||||
} // namespace Juliet
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <Core/Common/String.h>
|
||||
#include <Core/HAL/IO/IOStream.h>
|
||||
#include <Core/HAL/IO/IOStream_Private.h>
|
||||
#include <Core/Memory/Allocator.h>
|
||||
#include <cstdarg>
|
||||
|
||||
namespace Juliet
|
||||
{
|
||||
@@ -21,4 +23,60 @@ namespace Juliet
|
||||
|
||||
return Internal::IOFromFile(filename, mode);
|
||||
}
|
||||
|
||||
IOStream* IOFromInterface(NonNullPtr<const IOStreamInterface> interface, NonNullPtr<IOStreamDataPayload> payload)
|
||||
{
|
||||
Assert(interface->Version >= sizeof(*interface.Get()));
|
||||
|
||||
auto stream = static_cast<IOStream*>(Calloc(1, sizeof(IOStream)));
|
||||
if (stream)
|
||||
{
|
||||
IOStreamInterface* dstInterface = &stream->Interface;
|
||||
const IOStreamInterface* srcInterface = interface.Get();
|
||||
static_assert(sizeof(*(dstInterface)) == sizeof(*(srcInterface)), "Source and Destination type mismatch");
|
||||
MemCopy(dstInterface, srcInterface, sizeof(*srcInterface));
|
||||
stream->Data = payload.Get();
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
size_t IOPrintf(NonNullPtr<IOStream> stream, _Printf_format_string_ const char* format, ...)
|
||||
{
|
||||
// TODO: Juliet format function should be able to allocate on scratch arena here.
|
||||
// This buffer should be big enough until then
|
||||
char formattedBuffer[4096];
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
int writtenSize = vsprintf_s(formattedBuffer, format, args); // Cast to void to ignore the return type. TODO : Juliet format function
|
||||
va_end(args);
|
||||
|
||||
Assert(writtenSize >= 0);
|
||||
|
||||
return IOWrite(stream, formattedBuffer, static_cast<size_t>(writtenSize));
|
||||
}
|
||||
|
||||
size_t IOWrite(NonNullPtr<IOStream> stream, const void* ptr, size_t size)
|
||||
{
|
||||
if (!stream->Interface.Write)
|
||||
{
|
||||
stream->Status = IOStreamStatus::ReadOnly;
|
||||
Log(LogLevel::Error, LogCategory::Core, "Trying to write to a readonly IOStream");
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream->Status = IOStreamStatus::Ready;
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t writtenBytes = stream->Interface.Write(stream->Data, ptr, size, &stream->Status);
|
||||
if ((writtenBytes == 0) && (stream->Status == IOStreamStatus::Ready))
|
||||
{
|
||||
stream->Status = IOStreamStatus::Error;
|
||||
}
|
||||
return writtenBytes;
|
||||
}
|
||||
} // namespace Juliet
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <Core/Common/String.h>
|
||||
#include <Core/HAL/IO/IOStream.h>
|
||||
|
||||
namespace Juliet
|
||||
{
|
||||
struct IOStream;
|
||||
}
|
||||
struct IOStream
|
||||
{
|
||||
IOStreamInterface Interface;
|
||||
IOStreamDataPayload* Data;
|
||||
IOStreamStatus Status;
|
||||
};
|
||||
} // namespace Juliet
|
||||
|
||||
namespace Juliet::Internal
|
||||
{
|
||||
extern JULIET_API IOStream* IOFromFile(String filename, String mode);
|
||||
|
||||
@@ -1,11 +1,129 @@
|
||||
#include <pch.h>
|
||||
|
||||
#include <Core/Common/EnumUtils.h>
|
||||
#include <Core/Common/String.h>
|
||||
#include <Core/HAL/IO/IOStream.h>
|
||||
#include <Core/HAL/Win32.h>
|
||||
#include <Core/Memory/Allocator.h>
|
||||
|
||||
namespace Juliet::Internal
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct Win32IOStreamDataPayload : IOStreamDataPayload
|
||||
{
|
||||
HANDLE Handle;
|
||||
void* Data;
|
||||
size_t SizeLeft;
|
||||
bool IsAppending;
|
||||
bool ShouldAutoClose;
|
||||
};
|
||||
|
||||
constexpr size_t kFileReadBufferSize = 1024;
|
||||
|
||||
int64 FileSize(NonNullPtr<IOStreamDataPayload> payload)
|
||||
{
|
||||
auto win32Payload = static_cast<Win32IOStreamDataPayload*>(payload.Get());
|
||||
LARGE_INTEGER size;
|
||||
|
||||
if (!GetFileSizeEx(win32Payload->Handle, &size))
|
||||
{
|
||||
Log(LogLevel::Error, LogCategory::Core, "IoStream:Size error: %d", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return size.QuadPart;
|
||||
}
|
||||
|
||||
int64 FileSeek(NonNullPtr<IOStreamDataPayload> payload, int64 offset, IOStreamSeekPivot pivot)
|
||||
{
|
||||
auto win32Payload = static_cast<Win32IOStreamDataPayload*>(payload.Get());
|
||||
if ((pivot == IOStreamSeekPivot::Current) && (win32Payload->SizeLeft > 0))
|
||||
{
|
||||
offset -= static_cast<int64>(win32Payload->SizeLeft);
|
||||
}
|
||||
win32Payload->SizeLeft = 0;
|
||||
|
||||
DWORD windowsPivot = 0;
|
||||
switch (pivot)
|
||||
{
|
||||
case IOStreamSeekPivot::Begin: windowsPivot = FILE_BEGIN; break;
|
||||
case IOStreamSeekPivot::Current: windowsPivot = FILE_CURRENT; break;
|
||||
case IOStreamSeekPivot::End: windowsPivot = FILE_END; break;
|
||||
case IOStreamSeekPivot::Count:
|
||||
Log(LogLevel::Error, LogCategory::Core, "IoStream:Seek: Invalid Pivot value");
|
||||
return -1;
|
||||
}
|
||||
static_assert(ToUnderlying(IOStreamSeekPivot::Count) == 3, "IOStreamSeekPivot::Count must be 3");
|
||||
|
||||
LARGE_INTEGER windowsOffset;
|
||||
windowsOffset.QuadPart = offset;
|
||||
if (!SetFilePointerEx(win32Payload->Handle, windowsOffset, &windowsOffset, windowsPivot))
|
||||
{
|
||||
Log(LogLevel::Error, LogCategory::Core, "IoStream:Seek:Error: %d", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
return windowsOffset.QuadPart;
|
||||
}
|
||||
|
||||
size_t FileWrite(NonNullPtr<IOStreamDataPayload> payload, const void* inBuffer, size_t size,
|
||||
NonNullPtr<IOStreamStatus> status)
|
||||
{
|
||||
auto win32Payload = static_cast<Win32IOStreamDataPayload*>(payload.Get());
|
||||
DWORD bytes;
|
||||
|
||||
if (win32Payload->SizeLeft)
|
||||
{
|
||||
if (!SetFilePointer(win32Payload->Handle, -static_cast<LONG>(win32Payload->SizeLeft), nullptr, FILE_CURRENT))
|
||||
{
|
||||
Log(LogLevel::Error, LogCategory::Core, "IoStream:FileWrite:Error seeking in datastream: %d", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
win32Payload->SizeLeft = 0;
|
||||
}
|
||||
|
||||
// if in append mode, we must go to the EOF before write
|
||||
if (win32Payload->IsAppending)
|
||||
{
|
||||
LARGE_INTEGER windowsOffset;
|
||||
windowsOffset.QuadPart = 0;
|
||||
if (!SetFilePointerEx(win32Payload->Handle, windowsOffset, &windowsOffset, FILE_END))
|
||||
{
|
||||
Log(LogLevel::Error, LogCategory::Core, "IoStream:FileWrite:Error seeking in datastream: %d", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!WriteFile(win32Payload->Handle, inBuffer, static_cast<DWORD>(size), &bytes, nullptr))
|
||||
{
|
||||
Log(LogLevel::Error, LogCategory::Core, "IoStream:FileWrite:Error writing to datastream: %d", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
if (bytes == 0 && size > 0)
|
||||
{
|
||||
*status = IOStreamStatus::NotReady;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bool FileClose(NonNullPtr<IOStreamDataPayload> payload)
|
||||
{
|
||||
auto win32Payload = static_cast<Win32IOStreamDataPayload*>(payload.Get());
|
||||
if (win32Payload->Handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (win32Payload->ShouldAutoClose)
|
||||
{
|
||||
CloseHandle(win32Payload->Handle);
|
||||
}
|
||||
win32Payload->Handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
SafeFree(win32Payload->Data);
|
||||
SafeFree(win32Payload);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
IOStream* IOFromFile(String filename, String mode)
|
||||
{
|
||||
// "r" = reading, file must exist
|
||||
@@ -33,9 +151,10 @@ namespace Juliet::Internal
|
||||
}
|
||||
#endif
|
||||
|
||||
DWORD openExisting = ContainsChar(mode, 'r') ? OPEN_EXISTING : 0;
|
||||
DWORD createAlways = ContainsChar(mode, 'w') ? CREATE_ALWAYS : 0;
|
||||
DWORD openAlways = ContainsChar(mode, 'a') ? OPEN_ALWAYS : 0;
|
||||
DWORD openExisting = ContainsChar(mode, 'r') ? OPEN_EXISTING : 0;
|
||||
DWORD createAlways = ContainsChar(mode, 'w') ? CREATE_ALWAYS : 0;
|
||||
const bool isAppending = ContainsChar(mode, 'a');
|
||||
DWORD openAlways = isAppending ? OPEN_ALWAYS : 0;
|
||||
|
||||
bool hasPlus = ContainsChar(mode, '+');
|
||||
DWORD canRead = openExisting || hasPlus ? GENERIC_READ : 0;
|
||||
@@ -49,11 +168,49 @@ namespace Juliet::Internal
|
||||
|
||||
HANDLE hFile = CreateFileA(CStr(filename), (canWrite | canRead), (canWrite) ? 0 : FILE_SHARE_READ, nullptr,
|
||||
(openExisting | createAlways | openAlways), FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (FAILED(hFile))
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Log(LogLevel::Error, LogCategory::Core, "IOFromFile: CreateFileW failed");
|
||||
Log(LogLevel::Error, LogCategory::Core, "IOFromFile: couldn't open %s", CStr(filename));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
constexpr bool autoClose = true;
|
||||
auto payload = static_cast<Win32IOStreamDataPayload*>(Calloc(1, sizeof(Win32IOStreamDataPayload)));
|
||||
if (!payload)
|
||||
{
|
||||
if (autoClose)
|
||||
{
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IOStreamInterface interface = {};
|
||||
interface.Version = sizeof(interface);
|
||||
if (GetFileType(hFile) == FILE_TYPE_DISK)
|
||||
{
|
||||
interface.Size = FileSize;
|
||||
interface.Seek = FileSeek;
|
||||
}
|
||||
interface.Write = FileWrite;
|
||||
interface.Close = FileClose;
|
||||
|
||||
payload->Handle = hFile;
|
||||
payload->IsAppending = isAppending;
|
||||
payload->ShouldAutoClose = autoClose;
|
||||
|
||||
payload->Data = static_cast<char*>(Malloc(kFileReadBufferSize));
|
||||
if (!payload->Data)
|
||||
{
|
||||
interface.Close(payload);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IOStream* stream = IOFromInterface(&interface, payload);
|
||||
if (!stream)
|
||||
{
|
||||
interface.Close(payload);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
} // namespace Juliet::Internal
|
||||
|
||||
@@ -45,5 +45,9 @@ void Compile()
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
auto* stream = IOFromFile(ConstString("XF"), ConstString("w"));
|
||||
|
||||
IOPrintf(stream, ":)");
|
||||
IOPrintf(stream, "%d%s", 1234, "Hello World!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user