/******************************************************************************
* Copyright (C) 2012 Matthew Goff *
* *
* This software is provided 'as-is', without any express or implied *
* warranty. In no event will the authors be held liable for any damages *
* arising from the use of this software. *
* *
* Permission is granted to anyone to use this software for any purpose, *
* including commercial applications, and to alter it and redistribute it *
* freely, subject to the following restrictions: *
* *
* 1. The origin of this software must not be misrepresented; you must not *
* claim that you wrote the original software. If you use this software *
* in a product, an acknowledgment in the product documentation would be *
* appreciated but is not required. *
* *
* 2. Altered source versions must be plainly marked as such, and must not *
* be misrepresented as being the original software. *
* *
* 3. This notice may not be removed or altered from any source distribution. *
* *
* Matthew Goff (matt@goff.cc) <http://www.ackmud.net/> *
******************************************************************************/
/**
* @file utils.cpp
* @brief All non-template member functions of the Utils namespace.
*
* The Utils namespace contains all general purpose, multi-use, and non-class
* functions. Classes that are implemented to extend default operators or
* designed to be used in a utility sense, rather than actual objects, are also
* contained within this namespace.
*/
#include "utils.h"
/**
* @brief Returns the current system time.
* @retval timeval A timeval struct filled with the current system time.
*/
const timeval Utils::CurrentTime()
{
UFLAGS_DE( flags );
timeval now;
if ( ::gettimeofday( &now, NULL ) < 0 )
{
LOGERRNO( flags, "Utils::CurrentTime()->" );
return timeval();
}
return now;
}
/**
* @brief Calculates the different between two timeval variables.
* @param[in] prev A timeval of the beginning time.
* @param[in] current A timeval of the current, or end time.
* @param[in] granularity A #long unsigned int variable specified as a UTILS_TIME value from #UTILS_OPTS.
* @retval long unsigned int The difference between prev and current in units granularity.
*/
const long unsigned int Utils::DiffTime( const timeval& prev, const timeval& current, const long unsigned int& granularity )
{
switch ( granularity )
{
case UTILS_TIME_S: return ( current.tv_sec - prev.tv_sec );
case UTILS_TIME_MS: return ( current.tv_usec - prev.tv_usec ) / 1000;
case UTILS_TIME_US:
default: return ( current.tv_usec - prev.tv_usec );
}
}
/**
* @brief Returns a string consisting of directory/file.ext.
* @param[in] directory The top level directory build the path from.
* @param[in] file The file to build the path from.
* @param[in] ext Optionally replaces the file extension with this.
* @retval string A string consisting of directory/file.ext.
*/
const string Utils::DirPath( const string& directory, const string& file, const string& ext )
{
string path( directory );
path.append( "/" );
if ( !ext.empty() )
{
path.append( file.substr( 0, file.find_last_of( "." ) + 1 ) );
path.append( ext );
}
else
path.append( file );
return path;
}
/**
* @brief This is a nested wrapper for Utils::__FormatString and should not be called directly.
* @param[in] narg A #long unsigned int variable of the total number of arguments passed. Handled automatically.
* @param[in] flags Any number of flags from #UTILS_OPTS to control output formatting and options.
* @param[in] caller A string value containing the calling function. Handled automatically.
* @param[in] fmt A string value containing a printf-style format string.
* @param[in] ... A variable arguments list to populate fmt with.
* @retval string A printf-style formatted string.
*/
const string Utils::_FormatString( const long unsigned int& narg, const bitset<CFG_MEM_MAX_BITSET>& flags, const string& caller, const string& fmt, ... )
{
UFLAGS_DE( uflags );
va_list args;
string output;
if ( fmt.empty() )
{
LOGSTR( uflags, "Utils::_FormatString()-> called with empty fmt" );
return output;
}
va_start( args, fmt );
output = __FormatString( narg, flags, caller, fmt, args );
va_end( args );
return output;
}
/**
* @brief This is the printf-style string formatter. It should not be invoked directly, but rather by using Utils::FormatString() to ensure proper argument count and caller passing.
* @param[in] narg A #long unsigned int variable of the total number of arguments passed. Handled automatically.
* @param[in] flags Any number of flags from #UTILS_OPTS to control output formatting and options.
* @param[in] caller A string value containing the calling function. Handled automatically.
* @param[in] fmt A string value containing a printf-style format string.
* @param[in] val A variable arguments list to populate fmt with.
* @retval string A printf-style formatted string.
*/
const string Utils::__FormatString( const long unsigned int& narg, const bitset<CFG_MEM_MAX_BITSET>& flags, const string& caller, const string& fmt, va_list& val ) // Thanks go to Darien @ MudBytes.net for the start of this
{
UFLAGS_DE( uflags );
va_list args;
vector<string> arguments;
vector<string>::iterator si;
vector<char> buf;
string output, token;
long signed int size = 0;
if ( fmt.empty() )
{
LOGSTR( uflags, "Utils::__FormatString()-> called with empty fmt" );
return output;
}
arguments = StrTokens( fmt );
for ( si = arguments.begin(); si != arguments.end(); si++ )
{
token = *si;
if ( token.find( "%" ) != string::npos ) // not foolproof, but it should catch some worst cases by attempting
size++; // to ensure a matching narg : format specifier count
}
if ( narg != 1 && narg != static_cast<long unsigned int>( size ) && narg != NumChar( fmt, "%" ) ) // if narg == 1 invocation was func( flags, string )
{
bitset<CFG_MEM_MAX_BITSET> eflags;
eflags.set( UTILS_TYPE_ERROR );
Logger( eflags, "Number of arguments (%lu) did not match number of format specifiers (%lu) at: %s", narg, size, CSTR( caller ) );
return output = "";
}
va_copy( args, val );
size = vsnprintf( NULL, 0, CSTR( fmt ), args );
va_end( args );
va_copy( args, val );
buf.resize( size + 1 );
vsnprintf( &buf[0], ( size + 1 ), CSTR( fmt ), args );
va_end( args );
return output = &buf[0];
}
/**
* @brief This is the logging output engine. It should not be invoked directly, but rather by calling Utils::Logger() to ensure proper argument count and caller passing.
* @param[in] narg A #long unsigned int variable of the total number of arguments passed. Handled automatically.
* @param[in] flags Any number of flags from #UTILS_OPTS to control output formatting and options.
* @param[in] caller A string value containing the calling function. Handled automatically.
* @param[in] fmt A string value containing a printf-style format string.
* @param[in] ... A variable arguments list to populate fmt with.
* @retval string A printf-style formatted string.
*/
const void Utils::_Logger( const long unsigned int& narg, const bitset<CFG_MEM_MAX_BITSET>& flags, const string& caller, const string& fmt, ... )
{
UFLAGS_DE( uflags );
va_list args;
string pre, post, output;
long unsigned int i = 0;
if ( fmt.empty() )
{
LOGSTR( uflags, "Utils::_Logger()-> called with empty fmt" );
return;
}
va_start( args, fmt );
output = __FormatString( narg, flags, caller, fmt, args );
va_end( args );
if ( output.empty() )
return;
// prepend timestamp
pre = StrTime( CurrentTime() );
pre.append( " :: " );
for ( i = 0; i < MAX_UTILS; i++ )
{
if ( flags.test( i ) )
{
switch( i )
{
case UTILS_DEBUG: //output caller
post.append( " [" );
post.append( caller );
post.append( "]" );
break;
case UTILS_RAW: //no extraneous data applied
pre.clear();
post.clear();
i = MAX_UTILS;
break;
case UTILS_TYPE_ERROR: //so fancy!
pre.append( CFG_STR_UTILS_ERROR );
break;
case UTILS_TYPE_INFO: //so fancy!
pre.append( CFG_STR_UTILS_INFO );
break;
case UTILS_TYPE_SOCKET: //so fancy!
pre.append( CFG_STR_UTILS_SOCKET );
break;
default: break;
}
}
}
clog << pre << output << post << endl;
/** @todo Add monitor channel support */
return;
}
/**
* @brief Returns the number of a specific character in a given string.
* @param[in] input A string value to search.
* @param[in] item The character to search for within input.
* @retval long unsigned int The total count of item within input.
*/
const long unsigned int Utils::NumChar( const string& input, const string& item )
{
UFLAGS_DE( flags );
long unsigned int amount = 0, i = 0;
if ( input.empty() )
{
LOGSTR( flags, "Utils::NumChar()-> called with empty input" );
return amount;
}
for ( i = 0; i < input.length(); i++ )
if ( input[i] == item[0] )
amount++;
return amount;
}
/**
* @brief Returns a vector of strings split at linebreaks based on input.
* @param[in] input A string to split on newline characters.
* @retval vector<string> A vector of strings that were split on the linebreaks detected from input.
*/
const vector<string> Utils::StrNewlines( const string& input )
{
UFLAGS_DE( flags );
if ( input.empty() )
{
LOGSTR( flags, "Utils::StrNewlines()-> called with empty input" );
return vector<string>();
}
stringstream ss( input );
string line;
vector<string> output;
while ( getline( ss, line ) )
{
// Strip the newline off the end
line.resize( line.length() - 1 );
if ( !line.empty() )
output.push_back( line );
}
return output;
}
/**
* @brief Returns a given time as a string.
* @param[in] now A timeval to be formatted into a string.
* @retval string A string value containing the human readable form of the contents of now.
*/
const string Utils::StrTime( const timeval& now )
{
UFLAGS_DE( flags );
string output;
if ( ( output = ::ctime( &now.tv_sec ) ).empty() )
{
LOGSTR( flags, "Utils::CurrentTime()->ctime()-> returned NULL" );
return output;
}
// Strip the newline off the end
output.resize( output.length() - 1 );
return output;
}
/**
* @brief Returns a vector of strings split at spaces based on input.
* @param[in] input A string to split on space characters.
* @retval vector<string> A vector of strings that were split on the spaces detected from input.
*/
const vector<string> Utils::StrTokens( const string& input )
{
UFLAGS_DE( flags );
if ( input.empty() )
{
LOGSTR( flags, "Utils::StrTokens()-> called with empty input" );
return vector<string>();
}
stringstream ss( input );
istream_iterator<string> si( ss );
istream_iterator<string> end;
vector<string> output( si, end );
return output;
}
/**
* @brief Determines if a file path is a directory or file on disk.
* @param[in] dir A string containing the file path to be checked.
* @retval false Returned if the file path received in dir is not of type directory or an error occurs during stat.
* @retval true Returned if the file path received in dir is of type directory.
*/
const bool Utils::iDirectory( const string& dir )
{
UFLAGS_DE( flags );
struct stat dir_info;
if ( ::stat( CSTR( dir ), &dir_info ) < 0 )
{
LOGERRNO( flags, "Utils::iDirectory()->stat()->" );
return false;
}
if ( !S_ISDIR( dir_info.st_mode ) )
return false;
return true;
}
/**
* @brief Determines if a file path is a file or directory on disk.
* @param[in] file A string containing the file path to be checked.
* @retval false Returned if the file path received in file is not of type file or an error occurs during stat.
* @retval true Returned if the file path receievd in file is of type file.
*/
const bool Utils::iFile( const string& file )
{
UFLAGS_DE( flags );
struct stat dir_info;
if ( ::stat( CSTR( file ), &dir_info ) < 0 )
{
LOGERRNO( flags, "Utils::iFile()->stat()->" );
return false;
}
if ( !S_ISREG( dir_info.st_mode ) )
return false;
return true;
}
/**
* @brief Determines if a string is only a string of numerical values.
* @param[in] input A string to check for numerical values.
* @retval false Returned if input is empty or the input contains non-numerical values.
* @retval true Returned if input contains only numerical values.
*/
const bool Utils::iNumber( const string& input )
{
UFLAGS_DE( flags );
long unsigned int i = 0;
if ( input.empty() )
{
LOGSTR( flags, "Utils::iNumber()-> called with empty input" );
return false;
}
for ( i = 0; i < input.length(); i++ )
if ( !isdigit( input[i] ) )
return false;
return true;
}
/**
* @brief Determines if a file is readable.
* @param[in] file A string containing the file path to be checked.
* @retval false Returned if file does not exist or is not readable.
* @retval true Returned if file exists and is readable.
*/
const bool Utils::iReadable( const string& file )
{
UFLAGS_DE( flags );
ifstream ifile;
bool ret = false;
ifile.open( CSTR( file ), ifstream::in );
if ( ifile.fail() )
ret = false;
else
ret = true;
ifile.close();
return ret;
}
/**
* @brief Return a multimap of a specified directory tree on disk.
* @param[in] dir The filesystem path to search.
* @param[in] recursive If tue, the function will continue to recursively list folders multi-layers deep rather than top level only.
* @param[in,out] output A multimap<bool,string> consisting of a boolean value denoting either #UTILS_IS_DIRECTORY or #UTILS_IS_FILE. If recursive, this will be updated on each pass.
* @param[in,out] dir_close A #long unsigned int pointing to the total directory opened count on a Server object.
* @param[in,out] dir_open A #long unsigned int pointing to the total directory closed count on a Server object.
* @retval multimap<bool,string> A multimap<bool,string> consisting of a boolean value denoting either #UTILS_IS_DIRECTORY or #UTILS_IS_FILE. If recursive, this will be updated on each pass.
*/
const multimap<bool,string> Utils::ListDirectory( const string& dir, const bool& recursive, multimap<bool,string>& output, long unsigned int& dir_close, long unsigned int& dir_open )
{
UFLAGS_DE( flags );
DIR* directory = NULL;
dirent* entry = NULL;
string ifile, idir;
if ( ( directory = ::opendir( CSTR( dir ) ) ) == NULL )
{
LOGFMT( flags, "Utils::OpenDirectory()->opendir()-> returned NULL for dir: %s", CSTR( dir ) );
return output;
}
dir_open++;
idir = dir;
// Ensure a trailing slash is present to properly recurse
if ( idir.compare( dir.length() - 1, 1, "/" ) != 0 )
idir.append( "/" );
while ( ( entry = ::readdir( directory ) ) != NULL )
{
ifile = entry->d_name;
// Skip over the unwanteds
if ( ifile.compare( "." ) == 0 || ifile.compare( ".." ) == 0 )
continue;
if ( iDirectory( idir + ifile ) )
output.insert( pair<bool,string>( UTILS_IS_DIRECTORY, ifile ) );
else
output.insert( pair<bool,string>( UTILS_IS_FILE, ifile ) );
// Only recurse if another directory is found, otherwise a file was found, so skip it
if ( iDirectory( idir + ifile ) && recursive )
ListDirectory( idir + ifile, recursive, output, dir_close, dir_open );
}
if ( ::closedir( directory ) < 0 )
LOGERRNO( flags, "Utils::OpenDir()->closedir()->" );
else
dir_close++;
return output;
}