Add RandomAccessFile

This commit is contained in:
Vinnie Falco
2013-07-17 07:02:33 -07:00
parent ad933bae9c
commit 74510a95be
11 changed files with 801 additions and 3 deletions

View File

@@ -140,6 +140,7 @@
<ClInclude Include="..\..\modules\beast_core\files\beast_FileOutputStream.h" />
<ClInclude Include="..\..\modules\beast_core\files\beast_FileSearchPath.h" />
<ClInclude Include="..\..\modules\beast_core\files\beast_MemoryMappedFile.h" />
<ClInclude Include="..\..\modules\beast_core\files\beast_RandomAccessFile.h" />
<ClInclude Include="..\..\modules\beast_core\files\beast_TemporaryFile.h" />
<ClInclude Include="..\..\modules\beast_core\json\beast_JSON.h" />
<ClInclude Include="..\..\modules\beast_core\logging\beast_FileLogger.h" />
@@ -437,6 +438,12 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\modules\beast_core\files\beast_RandomAccessFile.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\modules\beast_core\files\beast_TemporaryFile.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>

View File

@@ -623,6 +623,9 @@
<ClInclude Include="..\..\modules\beast_core\containers\beast_SharedObjectArray.h">
<Filter>beast_core\containers</Filter>
</ClInclude>
<ClInclude Include="..\..\modules\beast_core\files\beast_RandomAccessFile.h">
<Filter>beast_core\files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\modules\beast_core\beast_core.cpp">
@@ -967,6 +970,9 @@
<ClCompile Include="..\..\modules\beast_crypto\math\beast_UnsignedInteger.cpp">
<Filter>beast_crypto\math</Filter>
</ClCompile>
<ClCompile Include="..\..\modules\beast_core\files\beast_RandomAccessFile.cpp">
<Filter>beast_core\files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Text Include="..\..\TODO.txt" />

View File

@@ -155,6 +155,7 @@ namespace beast
#include "files/beast_FileInputStream.cpp"
#include "files/beast_FileOutputStream.cpp"
#include "files/beast_FileSearchPath.cpp"
#include "files/beast_RandomAccessFile.cpp"
#include "files/beast_TemporaryFile.cpp"
#include "json/beast_JSON.cpp"

View File

@@ -252,6 +252,7 @@ namespace beast
#include "files/beast_FileOutputStream.h"
#include "files/beast_FileSearchPath.h"
#include "files/beast_MemoryMappedFile.h"
#include "files/beast_RandomAccessFile.h"
#include "files/beast_TemporaryFile.h"
#include "json/beast_JSON.h"
#include "logging/beast_FileLogger.h"

View File

@@ -0,0 +1,214 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
RandomAccessFile::RandomAccessFile (int bufferSizeToUse) noexcept
: fileHandle (nullptr)
, currentPosition (0)
, bufferSize (bufferSizeToUse)
, bytesInBuffer (0)
, writeBuffer (bmax (bufferSizeToUse, 16)) // enforce minimum size of 16
{
}
RandomAccessFile::~RandomAccessFile ()
{
close ();
}
Result RandomAccessFile::open (File const& path, Mode mode)
{
close ();
return nativeOpen (path, mode);
}
void RandomAccessFile::close ()
{
if (isOpen ())
{
flushBuffer ();
nativeFlush ();
nativeClose ();
}
}
Result RandomAccessFile::setPosition (FileOffset newPosition)
{
Result result (Result::ok ());
if (newPosition != currentPosition)
{
flushBuffer ();
result = nativeSetPosition (newPosition);
}
return result;
}
Result RandomAccessFile::read (void* buffer, ByteCount numBytes, ByteCount* pActualAmount)
{
return nativeRead (buffer, numBytes, pActualAmount);
}
Result RandomAccessFile::write (const void* data, ByteCount numBytes, ByteCount* pActualAmount)
{
bassert (data != nullptr && ((ssize_t) numBytes) >= 0);
Result result (Result::ok ());
ByteCount amountWritten = 0;
if (bytesInBuffer + numBytes < bufferSize)
{
memcpy (writeBuffer + bytesInBuffer, data, numBytes);
bytesInBuffer += numBytes;
currentPosition += numBytes;
}
else
{
result = flushBuffer ();
if (result.wasOk ())
{
if (numBytes < bufferSize)
{
bassert (bytesInBuffer == 0);
memcpy (writeBuffer + bytesInBuffer, data, numBytes);
bytesInBuffer += numBytes;
currentPosition += numBytes;
}
else
{
ByteCount bytesWritten;
result = nativeWrite (data, numBytes, &bytesWritten);
if (result.wasOk ())
currentPosition += bytesWritten;
}
}
}
if (pActualAmount != nullptr)
*pActualAmount = amountWritten;
return result;
}
Result RandomAccessFile::truncate ()
{
Result result = flush ();
if (result.wasOk ())
result = nativeTruncate ();
return result;
}
Result RandomAccessFile::flush ()
{
Result result = flushBuffer ();
if (result.wasOk ())
result = nativeFlush ();
return result;
}
Result RandomAccessFile::flushBuffer ()
{
bassert (isOpen ());
Result result (Result::ok ());
if (bytesInBuffer > 0)
{
result = nativeWrite (writeBuffer, bytesInBuffer);
bytesInBuffer = 0;
}
return result;
}
//------------------------------------------------------------------------------
class RandomAccessFileTests : public UnitTest
{
public:
RandomAccessFileTests () : UnitTest ("RandomAccessFile")
{
}
struct Payload
{
Payload (int maxBytes)
: bufferSize (maxBytes)
, data (maxBytes)
{
}
// Create a pseudo-random payload
void generate (int64 seedValue) noexcept
{
Random r (seedValue);
bytes = 1 + r.nextInt (bufferSize);
bassert (bytes >= 1 && bytes <= bufferSize);
for (int i = 0; i < bytes; ++i)
data [i] = static_cast <unsigned char> (r.nextInt ());
}
bool operator== (Payload const& other) const noexcept
{
if (bytes == other.bytes)
{
return memcmp (data.getData (), other.data.getData (), bytes) == 0;
}
else
{
return false;
}
}
int const bufferSize;
int bytes;
HeapBlock <char> data;
};
void runTest ()
{
Result result = file.open (File::createTempFile ("tests"), RandomAccessFile::readWrite);
expect (result.wasOk (), "Should be ok");
}
private:
RandomAccessFile file;
};
static RandomAccessFileTests randomAccessFileTests;

View File

@@ -0,0 +1,242 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Portions of this file are from JUCE.
Copyright (c) 2013 - Raw Material Software Ltd.
Please visit http://www.juce.com
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_RANDOMACCESSFILE_H_INCLUDED
#define BEAST_RANDOMACCESSFILE_H_INCLUDED
#include "../misc/beast_Result.h"
/** Provides random access reading and writing to an operating system file.
This class wraps the underlying native operating system routines for
opening and closing a file for reading and/or writing, seeking within
the file, and performing read and write operations. There are also methods
provided for obtaining an input or output stream which will work with
the file.
Writes are batched using an internal buffer. The buffer is flushed when
it fills, the current position is manually changed, or the file
is closed. It is also possible to explicitly flush the buffer.
@note All files are opened in binary mode. No text newline conversions
are performed.
@see FileInputStream, FileOutputStream
*/
class BEAST_API RandomAccessFile : Uncopyable, LeakChecked <RandomAccessFile>
{
public:
/** The type of an FileOffset.
This can be useful when writing templates.
*/
typedef int64 FileOffset;
/** The type of a byte count.
This can be useful when writing templates.
*/
typedef size_t ByteCount;
/** The access mode.
@see open
*/
enum Mode
{
readOnly,
readWrite
};
//==============================================================================
/** Creates an unopened file object.
@see open, isOpen
*/
explicit RandomAccessFile (int bufferSizeToUse = 16384) noexcept;
/** Destroy the file object.
If the operating system file is open it will be closed.
*/
~RandomAccessFile ();
/** Determine if a file is open.
@return `true` if the operating system file is open.
*/
bool isOpen () const noexcept { return fileHandle != nullptr; }
/** Opens a file object.
The file is opened with the specified permissions. The initial
position is set to the beginning of the file.
@note If a file is already open, it will be closed first.
@param path The path to the file
@param mode The access permissions
@return An indication of the success of the operation.
@see Mode
*/
Result open (File const& path, Mode mode);
/** Closes the file object.
Any data that needs to be flushed will be written before the file is closed.
@note If no file is opened, this call does nothing.
*/
void close ();
/** Retrieve the @ref File associated with this object.
@return The associated @ref File.
*/
File const& getFile () const noexcept { return file; }
/** Get the current position.
The next read or write will take place from here.
@return The current position, as an absolute byte FileOffset from the begining.
*/
FileOffset getPosition () const noexcept { return currentPosition; }
/** Set the current position.
The next read or write will take place at this location.
@param newPosition The byte FileOffset from the beginning of the file to move to.
@return `true` if the operation was successful.
*/
Result setPosition (FileOffset newPosition);
/** Read data at the current position.
The caller is responsible for making sure that the memory pointed to
by `buffer` is at least as large as `bytesToRead`.
@note The file must have been opened with read permission.
@param buffer The memory to store the incoming data
@param numBytes The number of bytes to read.
@param pActualAmount Pointer to store the actual amount read, or `nullptr`.
@return `true` if all the bytes were read.
*/
Result read (void* buffer, ByteCount numBytes, ByteCount* pActualAmount = 0);
/** Write data at the current position.
The current position is advanced past the data written. If data is
written past the end of the file, the file size is increased on disk.
The caller is responsible for making sure that the memory pointed to
by `buffer` is at least as large as `bytesToWrite`.
@note The file must have been opened with write permission.
@param data A pointer to the data buffer to write to the file.
@param numBytes The number of bytes to write.
@param pActualAmount Pointer to store the actual amount written, or `nullptr`.
@return `true` if all the data was written.
*/
Result write (const void* data, ByteCount numBytes, ByteCount* pActualAmount = 0);
/** Truncate the file at the current position.
*/
Result truncate ();
/** Flush the output buffers.
This calls the operating system to make sure all data has been written.
*/
Result flush();
//==============================================================================
private:
Result flushBuffer ();
// Some of these these methods are implemented natively on
// the corresponding platform.
//
// See beast_posix_SharedCode.h and beast_win32_Files.cpp
Result nativeOpen (File const& path, Mode mode);
void nativeClose ();
Result nativeSetPosition (FileOffset newPosition);
Result nativeRead (void* buffer, ByteCount numBytes, ByteCount* pActualAmount = 0);
Result nativeWrite (const void* data, ByteCount numBytes, ByteCount* pActualAmount = 0);
Result nativeTruncate ();
Result nativeFlush ();
private:
File file;
void* fileHandle;
FileOffset currentPosition;
ByteCount const bufferSize;
ByteCount bytesInBuffer;
HeapBlock <char> writeBuffer;
};
class BEAST_API RandomAccessFileInputStream : public InputStream
{
public:
explicit RandomAccessFileInputStream (RandomAccessFile& file) : m_file (file) { }
int64 getTotalLength() { return m_file.getFile ().getSize (); }
bool isExhausted() { return getPosition () == getTotalLength (); }
int read (void* destBuffer, int maxBytesToRead)
{
size_t actualBytes = 0;
m_file.read (destBuffer, maxBytesToRead, &actualBytes);
return actualBytes;
}
int64 getPosition() { return m_file.getPosition (); }
bool setPosition (int64 newPosition) { return m_file.setPosition (newPosition); }
void skipNextBytes (int64 numBytesToSkip) { m_file.setPosition (getPosition () + numBytesToSkip); }
private:
RandomAccessFile& m_file;
};
class BEAST_API RandomAccessFileOutputStream : public OutputStream
{
public:
explicit RandomAccessFileOutputStream (RandomAccessFile& file) : m_file (file) { }
void flush() { m_file.flush (); }
int64 getPosition() { return m_file.getPosition (); }
bool setPosition (int64 newPosition) { return m_file.setPosition (newPosition); }
bool write (const void* dataToWrite, size_t numberOfBytes) { return m_file.write (dataToWrite, numberOfBytes); }
private:
RandomAccessFile& m_file;
};
#endif

View File

@@ -45,13 +45,16 @@
@code
class MyClass : Uncopyable
class MyClass : public Uncopyable
{
public:
//...
};
@endcode
@note The derivation should be public or else child classes which
also derive from Uncopyable may not compile.
*/
class Uncopyable
{

View File

@@ -504,6 +504,176 @@ Result FileOutputStream::truncate()
return getResultForReturnValue (ftruncate (getFD (fileHandle), (off_t) currentPosition));
}
//==============================================================================
RandomAccessFile::RandomAccessFile (int bufferSizeToUse) noexcept
: fileHandle (nullptr)
, currentPosition (0)
, writeBuffer (bufferSizeToUse)
{
}
RandomAccessFile::~RandomAccessFile ()
{
close ();
}
Result RandomAccessFile::open (File const& path, Mode mode)
{
close ();
Result result (Result::ok ());
if (path.exists())
{
int oflag;
switch (mode)
{
case readOnly:
oflag = O_RDONLY;
break;
default:
case readWRite:
oflag = O_RDWR;
break;
};
const int f = ::open (path.getFullPathName().toUTF8(), oflag, 00644);
if (f != -1)
{
currentPosition = lseek (f, 0, SEEK_SET);
if (currentPosition >= 0)
{
file = path;
fileHandle = fdToVoidPointer (f);
}
else
{
result = getResultForErrno();
::close (f);
}
}
else
{
result = getResultForErrno();
}
}
else if (mode == readWrite)
{
const int f = open (file.getFullPathName().toUTF8(), O_RDWR + O_CREAT, 00644);
if (f != -1)
{
file = path;
fileHandle = fdToVoidPointer (f);
}
else
{
result = getResultForErrno();
}
}
else
{
// file doesn't exist and we're opening read-only
Result::fail (String (strerror (ENOENT)));
}
return result;
}
void RandomAccessFile::close ()
{
if (fileHandle != nullptr)
{
file = File::nonexistent ();
::close (getFD (fileHandle));
fileHandle = nullptr;
}
}
Result RandomAccessFile::setPosition (Offset newPosition)
{
bassert (isOpen ());
Result result (Result::ok ());
off_t const actual = lseek (getFD (fileHandle), newPosition, SEEK_SET);
if (actual != newPosition)
result = getResultForErrno();
return result;
}
Result RandomAccessFile::read (void* buffer, ByteCount numBytes, ByteCount* pActualAmount )
{
bassert (isOpen ());
Result result (Result::ok ());
ssize_t amount = ::read (getFD (fileHandle), buffer, numBytes);
if (amount < 0)
{
result = getResultForErrno();
amount = 0;
}
if (pActualAmount != nullptr)
*pActualAmount = amount;
return (size_t) result;
}
Result RandomAccessFile::write (void const* data, ByteCount numBytes, size_t* pActualAmount)
{
bassert (isOpen ());
Result result (Result::ok ());
ssize_t const actual = ::write (getFD (fileHandle), data, numBytes);
if (actual == -1)
{
status = getResultForErrno();
actual = 0;
}
if (pActualAmount != nullptr)
*pActualAmount = actual;
return result;
}
Result RandomAccessFile::truncate ()
{
flush();
return getResultForReturnValue (ftruncate (getFD (fileHandle), (off_t) currentPosition));
}
void RandomAccessFile::flush ()
{
bassert (isOpen ());
if (fileHandle != nullptr)
{
if (fsync (getFD (fileHandle)) == -1)
status = getResultForErrno();
#if BEAST_ANDROID
// This stuff tells the OS to asynchronously update the metadata
// that the OS has cached aboud the file - this metadata is used
// when the device is acting as a USB drive, and unless it's explicitly
// refreshed, it'll get out of step with the real file.
const LocalRef<jstring> t (javaString (file.getFullPathName()));
android.activity.callVoidMethod (BeastAppActivity.scanFile, t.get());
#endif
}
}
//==============================================================================
String SystemStats::getEnvironmentVariable (const String& name, const String& defaultValue)
{

View File

@@ -307,6 +307,161 @@ Result FileOutputStream::truncate()
: WindowsFileHelpers::getResultForLastError();
}
//==============================================================================
Result RandomAccessFile::nativeOpen (File const& path, Mode mode)
{
bassert (! isOpen ());
Result result (Result::ok ());
DWORD dwDesiredAccess;
switch (mode)
{
case readOnly:
dwDesiredAccess = GENERIC_READ;
break;
default:
case readWrite:
dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
break;
};
DWORD dwCreationDisposition;
switch (mode)
{
case readOnly:
dwCreationDisposition = OPEN_EXISTING;
break;
default:
case readWrite:
dwCreationDisposition = OPEN_ALWAYS;
break;
};
HANDLE h = CreateFile (path.getFullPathName().toWideCharPointer(),
dwDesiredAccess,
FILE_SHARE_READ,
0,
dwCreationDisposition,
FILE_ATTRIBUTE_NORMAL,
0);
if (h != INVALID_HANDLE_VALUE)
{
file = path;
fileHandle = h;
result = setPosition (0);
if (result.failed ())
nativeClose ();
}
else
{
result = WindowsFileHelpers::getResultForLastError();
}
return result;
}
void RandomAccessFile::nativeClose ()
{
bassert (isOpen ());
CloseHandle ((HANDLE) fileHandle);
file = File::nonexistent ();
fileHandle = nullptr;
currentPosition = 0;
}
Result RandomAccessFile::nativeSetPosition (FileOffset newPosition)
{
bassert (isOpen ());
Result result (Result::ok ());
LARGE_INTEGER li;
li.QuadPart = newPosition;
li.LowPart = SetFilePointer ((HANDLE) fileHandle,
(LONG) li.LowPart,
&li.HighPart,
FILE_BEGIN);
if (li.LowPart != INVALID_SET_FILE_POINTER)
{
currentPosition = li.QuadPart;
}
else
{
result = WindowsFileHelpers::getResultForLastError();
}
return result;
}
Result RandomAccessFile::nativeRead (void* buffer, ByteCount numBytes, ByteCount* pActualAmount )
{
bassert (isOpen ());
Result result (Result::ok ());
DWORD actualNum = 0;
if (! ReadFile ((HANDLE) fileHandle, buffer, (DWORD) numBytes, &actualNum, 0))
result = WindowsFileHelpers::getResultForLastError();
if (pActualAmount != nullptr)
*pActualAmount = actualNum;
return result;
}
Result RandomAccessFile::nativeWrite (void const* data, ByteCount numBytes, size_t* pActualAmount)
{
bassert (isOpen ());
Result result (Result::ok ());
DWORD actualNum = 0;
if (! WriteFile ((HANDLE) fileHandle, data, (DWORD) numBytes, &actualNum, 0))
result = WindowsFileHelpers::getResultForLastError();
if (pActualAmount != nullptr)
*pActualAmount = actualNum;
return result;
}
Result RandomAccessFile::nativeTruncate ()
{
bassert (isOpen ());
Result result (Result::ok ());
if (! SetEndOfFile ((HANDLE) fileHandle))
result = WindowsFileHelpers::getResultForLastError();
return result;
}
Result RandomAccessFile::nativeFlush ()
{
bassert (isOpen ());
Result result (Result::ok ());
if (! FlushFileBuffers ((HANDLE) fileHandle))
result = WindowsFileHelpers::getResultForLastError();
return result;
}
//==============================================================================
void MemoryMappedFile::openInternal (const File& file, AccessMode mode)
{

View File

@@ -38,7 +38,6 @@
class BEAST_API MemoryOutputStream
: public OutputStream
, LeakChecked <MemoryOutputStream>
, Uncopyable
{
public:
//==============================================================================

View File

@@ -40,7 +40,7 @@ class File;
@see InputStream, MemoryOutputStream, FileOutputStream
*/
class BEAST_API OutputStream
class BEAST_API OutputStream : public Uncopyable
{
protected:
//==============================================================================