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 all 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
148 changes: 148 additions & 0 deletions include/fc/io/cfile.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#pragma once
#include <fc/filesystem.hpp>
#include <cstdio>

#ifndef _WIN32
#define FC_FOPEN(p, m) fopen(p, m)
#else
#define FC_CAT(s1, s2) s1 ## s2
#define FC_PREL(s) FC_CAT(L, s)
#define FC_FOPEN(p, m) _wfopen(p, FC_PREL(m))
#endif

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::ios_base::failure 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; }

/// @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( FC_FOPEN( _file_path.generic_string().c_str(), mode ) );
if( !_file ) {
throw std::ios_base::failure( "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::ios_base::failure( "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::ios_base::failure( "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::ios_base::failure( "cfile: " + _file_path.generic_string() +
" unable to read " + std::to_string( n ) + " bytes; only read " + std::to_string( result ) );
}
}

void write( const char* d, size_t n ) {
size_t result = fwrite( d, 1, n, _file.get() );
if( result != n ) {
throw std::ios_base::failure( "cfile: " + _file_path.generic_string() +
" unable to write " + std::to_string( n ) + " bytes; only wrote " + std::to_string( result ) );
}
}

void flush() {
if( 0 != fflush( _file.get() ) ) {
int ec = ferror( _file.get() );
throw std::ios_base::failure( "cfile: " + _file_path.generic_string() +
" unable to flush file, ferror: " + std::to_string( ec ) );
}
}

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

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

#ifndef _WIN32
#undef FC_FOPEN
#else
#undef FC_CAT
#undef FC_PREL
#undef FC_FOPEN
#endif
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})
61 changes: 61 additions & 0 deletions test/io/test_cfile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#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+" );
BOOST_CHECK( t.is_open() );
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

// re-open and read again
t.open( "rb+" );
BOOST_CHECK( t.is_open() );

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

t.close();
fc::remove_all( t.get_file_path() );
BOOST_CHECK( !fc::exists( tempdir.path() / "test") );
}

BOOST_AUTO_TEST_SUITE_END()