diff --git a/CMakeLists.txt b/CMakeLists.txt index e345418..e2f363a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ add_executable( grive src/protocol/HTTP.cc src/protocol/Json.cc src/protocol/OAuth2.cc + src/util/Crypt.cc src/util/DateTime.cc src/util/OS.cc ) @@ -30,6 +31,7 @@ add_executable( unittest test/UnitTest.cc src/util/DateTime.cc test/util/DateTimeTest.cc + test/util/FunctionTest.cc ) target_link_libraries( unittest diff --git a/src/drive/Collection.cc b/src/drive/Collection.cc index 8fc5ce5..ab8f8ea 100644 --- a/src/drive/Collection.cc +++ b/src/drive/Collection.cc @@ -82,6 +82,11 @@ void Collection::AddChild( Collection *child ) m_child.push_back( child ) ; } +void Collection::AddLeaf( const std::string& filename ) +{ + m_leaves.push_back( filename ) ; +} + bool Collection::IsCollection( const Json& entry ) { Json node ; @@ -96,12 +101,12 @@ void Collection::Swap( Collection& coll ) m_href.swap( coll.m_href ) ; std::swap( m_parent, coll.m_parent ) ; m_child.swap( coll.m_child ) ; + m_leaves.swap( coll.m_leaves ) ; } void Collection::CreateSubDir( const std::string& prefix ) { std::string dir = prefix + m_title ; -// mkdir( dir.c_str(), 0700 ) ; os::MakeDir( dir ) ; for ( std::vector::iterator i = m_child.begin() ; i != m_child.end() ; ++i ) @@ -111,6 +116,12 @@ void Collection::CreateSubDir( const std::string& prefix ) } } +void Collection::ForEachFile( + Function callback, + const std::string& prefix ) +{ +} + std::string Collection::Path() const { assert( m_parent != this ) ; diff --git a/src/drive/Collection.hh b/src/drive/Collection.hh index c84e512..9ff777a 100644 --- a/src/drive/Collection.hh +++ b/src/drive/Collection.hh @@ -19,6 +19,8 @@ #pragma once +#include "util/Function.hh" + #include #include @@ -44,10 +46,15 @@ public : std::string Path() const ; void AddChild( Collection *child ) ; + void AddLeaf( const std::string& filename ) ; void Swap( Collection& coll ) ; - void CreateSubDir( const std::string& prefix ) ; + // traversing the tree + void CreateSubDir( const std::string& prefix = "." ) ; + void ForEachFile( + Function callback, + const std::string& prefix = "." ) ; private : std::string m_title ; @@ -56,6 +63,8 @@ private : // not owned Collection *m_parent ; std::vector m_child ; + + std::vector m_leaves ; } ; } // end of namespace diff --git a/src/drive/Drive.cc b/src/drive/Drive.cc index 91641c1..53f7829 100644 --- a/src/drive/Drive.cc +++ b/src/drive/Drive.cc @@ -22,19 +22,15 @@ #include "protocol/HTTP.hh" #include "protocol/Json.hh" #include "protocol/OAuth2.hh" +#include "util/Crypt.hh" #include "util/DateTime.hh" #include "util/OS.hh" -// dependent libraries -#include - // standard C++ library #include #include #include -#include #include -#include // for debugging only #include @@ -43,30 +39,6 @@ namespace gr { const std::string root_url = "https://docs.google.com/feeds/default/private/full" ; -std::string MD5( std::streambuf *file ) -{ - char buf[64 * 1024] ; - EVP_MD_CTX md ; - EVP_MD_CTX_init( &md ); - EVP_DigestInit_ex( &md, EVP_md5(), 0 ) ; - - std::size_t count = 0 ; - while ( (count = file->sgetn( buf, sizeof(buf) )) > 0 ) - { - EVP_DigestUpdate( &md, buf, count ) ; - } - - unsigned int md5_size = EVP_MAX_MD_SIZE ; - unsigned char md5[EVP_MAX_MD_SIZE] ; - EVP_DigestFinal_ex( &md, md5, &md5_size ) ; - - std::ostringstream ss ; - for ( unsigned int i = 0 ; i < md5_size ; i++ ) - ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(md5[i]) ; - - return ss.str() ; -} - Drive::Drive( OAuth2& auth ) : m_auth( auth ), m_root( ".", "https://docs.google.com/feeds/default/private/full/folder%3Aroot" ) @@ -75,7 +47,7 @@ Drive::Drive( OAuth2& auth ) : m_http_hdr.push_back( "GData-Version: 3.0" ) ; - Json resp = Json::Parse(HttpGet( root_url + "?alt=json&showfolders=true", m_http_hdr )) ; + Json resp = Json::Parse( http::Get( root_url + "?alt=json&showfolders=true", m_http_hdr )) ; Json::Array entries = resp["feed"]["entry"].As() ; ConstructDirTree( entries ) ; @@ -182,11 +154,9 @@ void Drive::UpdateFile( const Json& entry ) if ( entry.Has( "docs$filename" ) ) { // use title as the filename - std::string filename = entry["docs$filename"]["$t"].As() ; - std::string url = entry["content"]["src"].As() ; + std::string filename = entry["docs$filename"]["$t"].Get() ; + std::string url = entry["content"]["src"].Get() ; std::string parent_href = Parent( entry ) ; - - DateTime remote( entry["updated"]["$t"].As() ) ; bool changed = true ; std::string path = "./" + filename ; @@ -198,31 +168,54 @@ void Drive::UpdateFile( const Json& entry ) if ( pit != m_coll.end() ) path = pit->Path() + "/" + filename ; } - DateTime local = os::FileMTime( path ) ; - std::cout << "file time: " << entry["updated"]["$t"].As() << " " << remote << " " << local << std::endl ; +// std::cout << "file time: " << entry["updated"]["$t"].As() << " " << remote << " " << local << std::endl ; // compare checksum first if file exists std::ifstream ifile( path.c_str(), std::ios::binary | std::ios::out ) ; if ( ifile && entry.Has("docs$md5Checksum") ) { - os::SetFileTime( path, remote ) ; - std::string remote_md5 = entry["docs$md5Checksum"]["$t"].As() ; - std::string local_md5 = MD5( ifile.rdbuf() ) ; + std::string local_md5 = crypt::MD5( ifile.rdbuf() ) ; std::transform( remote_md5.begin(), remote_md5.end(), remote_md5.begin(), tolower ) ; if ( local_md5 == remote_md5 ) changed = false ; } - // if the checksum is different, file is changed and we need to download + // if the checksum is different, file is changed and we need to update if ( changed ) { + DateTime remote( entry["updated"]["$t"].As() ) ; + DateTime local = ifile ? os::FileMTime( path ) : DateTime() ; + + // remote file is newer, download file + if ( remote > local ) + { std::cout << "downloading " << path << std::endl ; - HttpGetFile( url, path, m_http_hdr ) ; + http::GetFile( url, path, m_http_hdr ) ; + os::SetFileTime( path, remote ) ; + } + else + { +std::cout << "local " << filename << " is newer" << std::endl ; + UploadFile( entry ) ; + } } } } +void Drive::UploadFile( const Json& entry ) +{ +// std::cout << "entry:\n" << entry << std::endl ; + + Json resume_link = entry["link"].FindInArray( "rel", + "http://schemas.google.com/g/2005#resumable-edit-media" )["href"] ; + std::cout << resume_link.As() << std::endl ; + + std::string resp = http::Put( resume_link.Get(), "", m_http_hdr ) ; + + std::cout << "resp " << resp ; +} + } // end of namespace diff --git a/src/drive/Drive.hh b/src/drive/Drive.hh index 196ec24..768869c 100644 --- a/src/drive/Drive.hh +++ b/src/drive/Drive.hh @@ -41,6 +41,7 @@ public : private : void UpdateFile( const Json& entry ) ; + void UploadFile( const Json& entry ) ; std::string Parent( const Json& entry ) ; void ConstructDirTree( const std::vector& entries ) ; diff --git a/src/protocol/HTTP.cc b/src/protocol/HTTP.cc index 1965649..452fddc 100644 --- a/src/protocol/HTTP.cc +++ b/src/protocol/HTTP.cc @@ -24,24 +24,16 @@ // dependent libraries #include +#include #include +#include #include #include #include -namespace gr { +namespace { -HttpException::HttpException( int curl_code, int http_code, const char *err_buf ) - : runtime_error( Format( curl_code, http_code, err_buf ) ) -{ -} - -std::string HttpException::Format( int curl_code, int http_code, const char *err_buf ) -{ - std::ostringstream ss ; - ss << "CURL code = " << curl_code << " HTTP code = " << http_code << " (" << err_buf << ")" ; - return ss.str() ; -} +using namespace gr::http ; // libcurl callback to append to a string std::size_t WriteCallback( char *data, size_t size, size_t nmemb, std::string *resp ) @@ -54,6 +46,20 @@ std::size_t WriteCallback( char *data, size_t size, size_t nmemb, std::string *r return count ; } +size_t ReadCallback( void *ptr, std::size_t size, std::size_t nmemb, std::string *data ) +{ + assert( ptr != 0 ) ; + assert( data != 0 ) ; + + std::size_t count = std::min( size * nmemb, data->size() ) ; + if ( count > 0 ) + { + std::memcpy( &(*data)[0], ptr, count ) ; + data->erase( 0, count ) ; + } + return count ; +} + CURL* InitCurl( const std::string& url, std::string *resp, const Headers& hdr ) { CURL *curl = curl_easy_init(); @@ -93,16 +99,32 @@ void DoCurl( CURL *curl ) if ( curl_code != CURLE_OK ) { - throw HttpException( curl_code, http_code, error_buf ) ; + throw Exception( curl_code, http_code, error_buf ) ; } else if (http_code >= 400 ) { std::cout << "http error " << http_code << std::endl ; - throw HttpException( curl_code, http_code, error_buf ) ; + throw Exception( curl_code, http_code, error_buf ) ; } } -std::string HttpGet( const std::string& url, const Headers& hdr ) +} // end of local namespace + +namespace gr { namespace http { + +Exception::Exception( int curl_code, int http_code, const char *err_buf ) + : runtime_error( Format( curl_code, http_code, err_buf ) ) +{ +} + +std::string Exception::Format( int curl_code, int http_code, const char *err_buf ) +{ + std::ostringstream ss ; + ss << "CURL code = " << curl_code << " HTTP code = " << http_code << " (" << err_buf << ")" ; + return ss.str() ; +} + +std::string Get( const std::string& url, const Headers& hdr ) { std::string resp ; CURL *curl = InitCurl( url, &resp, hdr ) ; @@ -111,7 +133,7 @@ std::string HttpGet( const std::string& url, const Headers& hdr ) return resp; } -void HttpGetFile( +void GetFile( const std::string& url, const std::string& filename, const Headers& hdr ) @@ -125,7 +147,7 @@ void HttpGetFile( DoCurl( curl ) ; } -void HttpGetFile( +void GetFile( const std::string& url, const std::string& filename, std::string& md5sum, @@ -133,7 +155,7 @@ void HttpGetFile( { } -std::string HttpPostData( const std::string& url, const std::string& data, const Headers& hdr ) +std::string PostData( const std::string& url, const std::string& data, const Headers& hdr ) { std::string resp ; CURL *curl = InitCurl( url, &resp, hdr ) ; @@ -148,12 +170,31 @@ std::string HttpPostData( const std::string& url, const std::string& data, const return resp; } -std::string HttpPostFile( const std::string& url, const std::string& filename, const Headers& hdr ) +std::string PostFile( const std::string& url, const std::string& filename, const Headers& hdr ) { std::string resp ; return resp; } +std::string Put( + const std::string& url, + const std::string& data, + const Headers& hdr ) +{ + std::string resp ; + CURL *curl = InitCurl( url, &resp, hdr ) ; + + std::string put_data = data ; + + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, &ReadCallback ) ; + curl_easy_setopt(curl, CURLOPT_READDATA , &put_data ) ; + curl_easy_setopt(curl, CURLOPT_INFILESIZE, put_data.size() ) ; + + DoCurl( curl ) ; + return resp; +} + std::string Escape( const std::string& str ) { CURL *curl = curl_easy_init(); @@ -181,4 +222,4 @@ std::string Unescape( const std::string& str ) return result ; } -} +} } // end of namespace diff --git a/src/protocol/HTTP.hh b/src/protocol/HTTP.hh index f399a64..669d57c 100644 --- a/src/protocol/HTTP.hh +++ b/src/protocol/HTTP.hh @@ -23,40 +23,46 @@ #include #include -namespace gr +namespace gr { namespace http { typedef std::vector Headers ; - std::string HttpGet( const std::string& url, const Headers& hdr = Headers() ) ; - void HttpGetFile( + std::string Get( const std::string& url, const Headers& hdr = Headers() ) ; + void GetFile( const std::string& url, const std::string& filename, const Headers& hdr = Headers() ) ; - void HttpGetFile( + void GetFile( const std::string& url, const std::string& filename, std::string& md5sum, const Headers& hdr = Headers() ) ; - std::string HttpPostData( + std::string PostData( const std::string& url, const std::string& data, const Headers& hdr = Headers() ) ; - std::string HttpPostFile( + std::string PostFile( const std::string& url, const std::string& filename, const Headers& hdr = Headers() ) ; + std::string Put( + const std::string& url, + const std::string& data, + const Headers& hdr = Headers() ) ; + std::string Escape( const std::string& str ) ; std::string Unescape( const std::string& str ) ; - class HttpException : public std::runtime_error + class Exception : public std::runtime_error { public : - HttpException( int curl_code, int http_code, const char *err_buf ) ; + Exception( int curl_code, int http_code, const char *err_buf ) ; private : static std::string Format( int curl_code, int http_code, const char *err_buf ) ; } ; -} \ No newline at end of file + +} } // end of namespace diff --git a/src/protocol/Json.cc b/src/protocol/Json.cc index 0fcc12a..8c691f4 100644 --- a/src/protocol/Json.cc +++ b/src/protocol/Json.cc @@ -138,9 +138,18 @@ void Json::Add( const std::string& key, const Json& json ) ::json_object_object_add( m_json, key.c_str(), json.m_json ) ; } -Json::Proxy Json::As() const +template <> +bool Json::As() const { - return Proxy( *this ) ; + assert( m_json != 0 ) ; + return ::json_object_get_boolean( m_json ) ; +} + +template <> +bool Json::Is() const +{ + assert( m_json != 0 ) ; + return ::json_object_is_type( m_json, json_type_boolean ) ; } template <> @@ -248,4 +257,34 @@ bool Json::FindInArray( const std::string& key, const std::string& value, Json& } } +Json::Proxy Json::Get() const +{ + return Proxy( *this ) ; +} + +Json::Proxy::operator bool() const +{ + return referring.As() ; +} + +Json::Proxy::operator int() const +{ + return referring.As() ; +} + +Json::Proxy::operator Object() const +{ + return referring.As() ; +} + +Json::Proxy::operator Array() const +{ + return referring.As() ; +} + +Json::Proxy::operator std::string() const +{ + return referring.As() ; +} + } diff --git a/src/protocol/Json.hh b/src/protocol/Json.hh index 5417526..3a9df2f 100644 --- a/src/protocol/Json.hh +++ b/src/protocol/Json.hh @@ -56,9 +56,13 @@ public : { const Json& referring ; explicit Proxy( const Json& j ) : referring( j ) { } - template operator T() const { return referring.As() ; } + operator bool() const ; + operator int() const ; + operator Object() const ; + operator Array() const ; + operator std::string() const ; } ; - Proxy As() const ; + Proxy Get() const ; template bool Is() const ; diff --git a/src/protocol/OAuth2.cc b/src/protocol/OAuth2.cc index 7a77283..0e86db9 100644 --- a/src/protocol/OAuth2.cc +++ b/src/protocol/OAuth2.cc @@ -57,7 +57,7 @@ void OAuth2::Auth( const std::string& auth_code ) "&redirect_uri=" + "urn:ietf:wg:oauth:2.0:oob" + "&grant_type=authorization_code" ; - Json resp = Json::Parse( HttpPostData( token_url, post ) ) ; + Json resp = Json::Parse( http::PostData( token_url, post ) ) ; m_access = resp["access_token"].As() ; m_refresh = resp["refresh_token"].As() ; } @@ -66,6 +66,8 @@ std::string OAuth2::MakeAuthURL( const std::string& client_id, const std::string& state ) { + using gr::http::Escape ; + return "https://accounts.google.com/o/oauth2/auth" "?scope=" + Escape( "https://www.googleapis.com/auth/userinfo.email" ) + "+" + @@ -86,7 +88,7 @@ void OAuth2::Refresh( ) "&client_secret=" + m_client_secret + "&grant_type=refresh_token" ; - Json resp = Json::Parse( HttpPostData( token_url, post ) ) ; + Json resp = Json::Parse( http::PostData( token_url, post ) ) ; m_access = resp["access_token"].As() ; } diff --git a/src/util/Crypt.cc b/src/util/Crypt.cc new file mode 100644 index 0000000..35deeb0 --- /dev/null +++ b/src/util/Crypt.cc @@ -0,0 +1,55 @@ +/* + grive: an GPL program to sync a local directory with Google Drive + Copyright (C) 2012 Wan Wai Ho + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation version 2 + of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "Crypt.hh" + +#include +#include + +// dependent libraries +#include + +namespace gr { namespace crypt { + +std::string MD5( std::streambuf *file ) +{ + char buf[64 * 1024] ; + EVP_MD_CTX md ; + EVP_MD_CTX_init( &md ); + EVP_DigestInit_ex( &md, EVP_md5(), 0 ) ; + + std::size_t count = 0 ; + while ( (count = file->sgetn( buf, sizeof(buf) )) > 0 ) + { + EVP_DigestUpdate( &md, buf, count ) ; + } + + unsigned int md5_size = EVP_MAX_MD_SIZE ; + unsigned char md5[EVP_MAX_MD_SIZE] ; + EVP_DigestFinal_ex( &md, md5, &md5_size ) ; + + // format the MD5 string + std::ostringstream ss ; + for ( unsigned int i = 0 ; i < md5_size ; i++ ) + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(md5[i]) ; + + return ss.str() ; +} + +} } // end of namespaces diff --git a/src/util/Crypt.hh b/src/util/Crypt.hh new file mode 100644 index 0000000..e7f0293 --- /dev/null +++ b/src/util/Crypt.hh @@ -0,0 +1,32 @@ +/* + grive: an GPL program to sync a local directory with Google Drive + Copyright (C) 2012 Wan Wai Ho + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation version 2 + of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include + +namespace gr { + +namespace crypt +{ + std::string MD5( std::streambuf *file ) ; +} + +} // end of namespace gr diff --git a/src/util/DateTime.cc b/src/util/DateTime.cc index b32111b..76e553e 100644 --- a/src/util/DateTime.cc +++ b/src/util/DateTime.cc @@ -19,6 +19,7 @@ #include "DateTime.hh" +#include #include #include #include @@ -53,8 +54,8 @@ DateTime::DateTime( const std::string& iso ) : } DateTime::DateTime( std::time_t sec, unsigned long nsec ) : - m_sec ( sec ), - m_nsec ( nsec ) + m_sec ( sec + nsec / 1000000000 ), + m_nsec ( nsec % 1000000000 ) { } @@ -72,6 +73,7 @@ std::time_t DateTime::Sec( ) const unsigned long DateTime::NanoSec( ) const { + assert( m_nsec < 1000000000 ) ; return m_nsec ; } @@ -86,10 +88,45 @@ std::ostream& operator<<( std::ostream& os, const DateTime& dt ) struct timeval DateTime::Tv() const { + assert( m_nsec < 1000000000 ) ; + timeval result ; result.tv_sec = m_sec ; result.tv_usec = m_nsec / 1000 ; return result ; } +bool DateTime::operator==( const DateTime& dt ) const +{ + assert( m_nsec < 1000000000 ) ; + return m_sec == dt.m_sec && m_nsec == dt.m_nsec ; +} + +bool DateTime::operator!=( const DateTime& dt ) const +{ + return !( *this == dt ) ; +} + +bool DateTime::operator>( const DateTime& dt ) const +{ + assert( m_nsec < 1000000000 ) ; + assert( dt.m_nsec < 1000000000 ) ; + return m_sec == dt.m_sec ? m_nsec > dt.m_nsec : m_sec > dt.m_sec ; +} + +bool DateTime::operator>=( const DateTime& dt ) const +{ + return ( *this > dt ) || ( *this == dt ) ; +} + +bool DateTime::operator<( const DateTime& dt ) const +{ + return !( *this >= dt ) ; +} + +bool DateTime::operator<=( const DateTime& dt ) const +{ + return !( *this > dt ) ; +} + } // end of namespace diff --git a/src/util/DateTime.hh b/src/util/DateTime.hh index a2e1ae4..ff89f74 100644 --- a/src/util/DateTime.hh +++ b/src/util/DateTime.hh @@ -38,6 +38,13 @@ public : struct tm Tm() const ; struct timeval Tv() const ; + bool operator==( const DateTime& dt ) const ; + bool operator!=( const DateTime& dt ) const ; + bool operator>( const DateTime& dt ) const ; + bool operator>=( const DateTime& dt ) const ; + bool operator<( const DateTime& dt ) const ; + bool operator<=( const DateTime& dt ) const ; + private : std::time_t m_sec ; unsigned long m_nsec ; diff --git a/src/util/Function.hh b/src/util/Function.hh new file mode 100644 index 0000000..197418f --- /dev/null +++ b/src/util/Function.hh @@ -0,0 +1,184 @@ +/* + grive: an GPL program to sync a local directory with Google Drive + Copyright (C) 2012 Wan Wai Ho + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation version 2 + of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include + +namespace gr { + +namespace impl +{ + template class FuncImpl ; + + template class FuncImpl + { + public : + virtual R operator()( ) = 0 ; + virtual FuncImpl* Clone() const = 0 ; + virtual ~FuncImpl() {} + } ; + + template class FuncImpl + { + public : + virtual R operator()(P1) = 0 ; + virtual FuncImpl* Clone() const = 0 ; + virtual ~FuncImpl() {} + } ; + + template class FuncImpl + { + public : + virtual R operator()(P1,P2) = 0 ; + virtual FuncImpl* Clone() const = 0 ; + virtual ~FuncImpl() {} + } ; + + template + struct FuncTrait ; + + struct NullType {} ; + + template struct FuncTrait + { + typedef R ReturnType ; + typedef NullType Param1Type ; + typedef NullType Param2Type ; + } ; + + template struct FuncTrait + { + typedef R ReturnType ; + typedef P1 Param1Type ; + typedef NullType Param2Type ; + } ; + + template struct FuncTrait + { + typedef R ReturnType ; + typedef P1 Param1Type ; + typedef P2 Param2Type ; + } ; + + template class FuncHolder : public FuncImpl + { + public : + explicit FuncHolder( const F& f ) : m_func( f ) { } + FuncHolder* Clone() const { return new FuncHolder( *this ) ; } + + typedef typename FuncTrait::ReturnType ReturnType ; + + ReturnType operator()() { return m_func(); } + + ReturnType operator()( typename FuncTrait::Param1Type p1) + { + return m_func(p1); + } + + ReturnType operator()( + typename FuncTrait::Param1Type p1, + typename FuncTrait::Param2Type p2) + { + return m_func(p1,p2); + } + + private : + F m_func ; + } ; + + template + struct NullFunc + { + typedef typename FuncTrait::ReturnType ReturnType ; + ReturnType operator()() + { + return ReturnType() ; + } + ReturnType operator()( typename FuncTrait::Param1Type p1) + { + return ReturnType() ; + } + ReturnType operator()( + typename FuncTrait::Param1Type p1, + typename FuncTrait::Param2Type p2) + { + return ReturnType() ; + } + } ; + + template + FuncHolder* MakeFuncHolder( F func ) + { + return new FuncHolder( func ) ; + } +} + +template +class Function +{ +public : + Function( ) : + m_pimpl( impl::MakeFuncHolder( impl::NullFunc() ) ) + { + } + Function( const Function& f ) : + m_pimpl( f.m_pimpl->Clone() ) + { + } + template + Function( const F& f ) : + m_pimpl( impl::MakeFuncHolder( f ) ) + { + } + + Function& operator=( const Function& f ) + { + Function tmp( f ) ; + std::swap( m_pimpl, tmp.m_pimpl ) ; + return *this ; + } + ~Function( ) + { + } + + typedef typename impl::FuncTrait::ReturnType ReturnType ; + + ReturnType operator()( ) + { + return (*m_pimpl)() ; + } + + template ReturnType operator()( P1 p1 ) + { + return (*m_pimpl)( p1 ) ; + } + + template ReturnType operator()( P1 p1, P2 p2 ) + { + return (*m_pimpl)( p1, p2 ) ; + } ; + +private : + typedef impl::FuncImpl Impl ; + std::auto_ptr m_pimpl ; +} ; + +} // end of namespace diff --git a/test/util/DateTimeTest.cc b/test/util/DateTimeTest.cc index 6c803f4..341c3a8 100644 --- a/test/util/DateTimeTest.cc +++ b/test/util/DateTimeTest.cc @@ -60,4 +60,26 @@ void DateTimeTest::TestOffByOne( ) CPPUNIT_ASSERT_EQUAL( 21, tp.tm_mday ) ; } +void DateTimeTest::TestCompare( ) +{ + DateTime s1( 1000, 2000 ), s2( 1001, 2000 ), s3( 1000, 2001 ), s4( 1001, 2000 ) ; + + CPPUNIT_ASSERT( s1 < s3 ) ; + CPPUNIT_ASSERT( s1 <= s3 ) ; + CPPUNIT_ASSERT( s3 > s1 ) ; + CPPUNIT_ASSERT( s3 >= s1 ) ; + + CPPUNIT_ASSERT( s1 < s2 ) ; + CPPUNIT_ASSERT( s1 <= s2 ) ; + CPPUNIT_ASSERT( s2 > s1 ) ; + CPPUNIT_ASSERT( s2 >= s1 ) ; + + CPPUNIT_ASSERT( s2 == s4 ) ; + CPPUNIT_ASSERT( s2 >= s4 ) ; + CPPUNIT_ASSERT( s2 <= s4 ) ; + CPPUNIT_ASSERT( s4 == s2 ) ; + CPPUNIT_ASSERT( s4 >= s2 ) ; + CPPUNIT_ASSERT( s4 <= s2 ) ; +} + } // end of namespace grut diff --git a/test/util/DateTimeTest.hh b/test/util/DateTimeTest.hh index 1246a33..0c12a6d 100644 --- a/test/util/DateTimeTest.hh +++ b/test/util/DateTimeTest.hh @@ -35,6 +35,7 @@ public : CPPUNIT_TEST( TestParseNoMillisec ) ; CPPUNIT_TEST( TestOffByOne ) ; CPPUNIT_TEST( TestParseInvalid ) ; + CPPUNIT_TEST( TestCompare ) ; CPPUNIT_TEST_SUITE_END(); private : @@ -42,6 +43,7 @@ private : void TestParseNoMillisec( ) ; void TestOffByOne( ) ; void TestParseInvalid( ) ; + void TestCompare( ) ; } ; } // end of namespace diff --git a/test/util/FunctionTest.cc b/test/util/FunctionTest.cc new file mode 100644 index 0000000..2b24d2d --- /dev/null +++ b/test/util/FunctionTest.cc @@ -0,0 +1,46 @@ +/* + grive: an GPL program to sync a local directory with Google Drive + Copyright (C) 2012 Wan Wai Ho + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation version 2 + of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "FunctionTest.hh" + +#include "util/Function.hh" + +namespace grut { + +using namespace gr ; + +FunctionTest::FunctionTest( ) +{ +} + +int TestFunction( int v ) +{ + return v ; +} + +void FunctionTest::TestRun( ) +{ + Function f = &TestFunction ; + Function f2 ; + + CPPUNIT_ASSERT_EQUAL( 3, f(3) ) ; + CPPUNIT_ASSERT_EQUAL( std::string(), f2() ) ; +} + +} // end of namespace grut diff --git a/test/util/FunctionTest.hh b/test/util/FunctionTest.hh new file mode 100644 index 0000000..9e05979 --- /dev/null +++ b/test/util/FunctionTest.hh @@ -0,0 +1,41 @@ +/* + grive: an GPL program to sync a local directory with Google Drive + Copyright (C) 2012 Wan Wai Ho + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation version 2 + of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include + +namespace grut { + +class FunctionTest : public CppUnit::TestFixture +{ +public : + FunctionTest( ) ; + + // declare suit function + CPPUNIT_TEST_SUITE( FunctionTest ) ; + CPPUNIT_TEST( TestRun ) ; + CPPUNIT_TEST_SUITE_END(); + +private : + void TestRun( ) ; +} ; + +} // end of namespace