Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

C-file #107

Merged
merged 6 commits into from
Aug 13, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions include/fc/io/cfile.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#pragma once
#include <fc/filesystem.hpp>
#include <cstdio>

namespace fc {

namespace detail {
using unique_file = std::unique_ptr<FILE, decltype( &fclose )>;
}

class cfile_datastream;

/**
* Wrapper for c-file access that provides a similar interface as fstream without all the overhead of std streams.
* std::runtime_error exception thrown for errors.
*/
class cfile {
public:
cfile()
: _file(nullptr, &fclose)
{}

void set_file_path( fc::path file_path ) {
_file_path = std::move( file_path );
}

fc::path get_file_path() const {
return _file_path;
}

bool is_open() const { return _open; }

size_t file_size() const {
jgiszczak marked this conversation as resolved.
Show resolved Hide resolved
return fc::file_size( _file_path );
}

/// @param mode is any mode supported by fopen
/// Tested with:
/// "ab+" - open for binary update - create if does not exist
/// "rb+" - open for binary update - file must exist
void open( const char* mode ) {
_file.reset( fopen( _file_path.generic_string().c_str(), mode ) );
jgiszczak marked this conversation as resolved.
Show resolved Hide resolved
if( !_file ) {
throw std::runtime_error( "cfile unable to open: " + _file_path.generic_string() + " in mode: " + std::string( mode ) );
}
_open = true;
}

long tellp() const {
return ftell( _file.get() );
}

void seek( long loc ) {
if( 0 != fseek( _file.get(), loc, SEEK_SET ) ) {
throw std::runtime_error( "cfile: " + _file_path.generic_string() +
" unable to SEEK_SET to: " + std::to_string(loc) );
}
}

void seek_end( long loc ) {
if( 0 != fseek( _file.get(), loc, SEEK_END ) ) {
throw std::runtime_error( "cfile: " + _file_path.generic_string() +
" unable to SEEK_END to: " + std::to_string(loc) );
}
}

void read( char* d, size_t n ) {
size_t result = fread( d, 1, n, _file.get() );
if( result != n ) {
throw std::runtime_error( "cfile: " + _file_path.generic_string() +
" unable to read " + std::to_string( n ) + " only read " + std::to_string( result ) );
jgiszczak marked this conversation as resolved.
Show resolved Hide resolved
}
}

void write( const char* d, size_t n ) {
size_t result = fwrite( d, 1, n, _file.get() );
if( result != n ) {
throw std::runtime_error( "cfile: " + _file_path.generic_string() +
" unable to write " + std::to_string( n ) + " only wrote " + std::to_string( result ) );
jgiszczak marked this conversation as resolved.
Show resolved Hide resolved
}
}

void flush() {
if( 0 != fflush( _file.get() ) ) {
throw std::runtime_error( "cfile: " + _file_path.generic_string() + " unable to flush file." );
jgiszczak marked this conversation as resolved.
Show resolved Hide resolved
}
}

void close() {
_file.reset();
_open = false;
}

void remove() {
if( _open ) {
throw std::runtime_error( "cfile: " + _file_path.generic_string() + " Unable to remove as file is open" );
}
fc::remove_all( _file_path );
jgiszczak marked this conversation as resolved.
Show resolved Hide resolved
}

cfile_datastream create_datastream();

private:
bool _open = false;
fc::path _file_path;
detail::unique_file _file;
};

/*
* @brief datastream adapter that adapts cfile for use with fc unpack
*
* This class supports unpack functionality but not pack.
*/
class cfile_datastream {
public:
explicit cfile_datastream( cfile& cf ) : cf(cf) {}

void skip( size_t s ) {
std::vector<char> d( s );
read( &d[0], s );
}

bool read( char* d, size_t s ) {
cf.read( d, s );
return true;
}

bool get( unsigned char& c ) { return get( *(char*)&c ); }

bool get( char& c ) { return read(&c, 1); }

private:
cfile& cf;
};

inline cfile_datastream cfile::create_datastream() {
return cfile_datastream(*this);
}


} // namespace fc
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
add_subdirectory( crypto )
add_subdirectory( io )
add_subdirectory( static_variant )
add_subdirectory( variant )

Expand Down
4 changes: 4 additions & 0 deletions test/io/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
add_executable( test_io test_cfile.cpp)
target_link_libraries( test_io fc )

add_test(NAME test_io COMMAND libraries/fc/test/io/test_io WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
49 changes: 49 additions & 0 deletions test/io/test_cfile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#define BOOST_TEST_MODULE io
#include <boost/test/included/unit_test.hpp>

#include <fc/io/cfile.hpp>

using namespace fc;

BOOST_AUTO_TEST_SUITE(cfile_test_suite)
BOOST_AUTO_TEST_CASE(test_simple)
{
fc::temp_directory tempdir;

cfile t;
t.set_file_path( tempdir.path() / "test" );
t.open( "ab+" );
BOOST_CHECK( t.is_open() );
BOOST_CHECK( fc::exists( tempdir.path() / "test") );

t.open( "rb+" );
t.write( "abc", 3 );
BOOST_CHECK_EQUAL( t.tellp(), 3 );
std::vector<char> v(3);
t.seek( 0 );
BOOST_CHECK_EQUAL( t.tellp(), 0 );
t.read( &v[0], 3 );

BOOST_CHECK_EQUAL( v[0], 'a' );
BOOST_CHECK_EQUAL( v[1], 'b' );
BOOST_CHECK_EQUAL( v[2], 'c' );

t.seek_end( -2 );
BOOST_CHECK_EQUAL( t.tellp(), 1 );
t.read( &v[0], 1 );
BOOST_CHECK_EQUAL( v[0], 'b' );

int x = 42, y = 0;
t.seek( 1 );
t.write( reinterpret_cast<char*>( &x ), sizeof( x ) );
t.seek( 1 );
t.read( reinterpret_cast<char*>( &y ), sizeof( y ) );
BOOST_CHECK_EQUAL( x, y );

t.close();
BOOST_CHECK( !t.is_open() );
jgiszczak marked this conversation as resolved.
Show resolved Hide resolved
t.remove();
BOOST_CHECK( !fc::exists( tempdir.path() / "test") );
}

BOOST_AUTO_TEST_SUITE_END()