From dfbe8595311d46c82de5921845c0871d92095fc8 Mon Sep 17 00:00:00 2001 From: Matchman Green Date: Sun, 20 May 2012 17:12:01 +0800 Subject: [PATCH] load and save from .grive_state and build the resource tree --- grive/src/main.cc | 1 + libgrive/src/drive/Drive.cc | 6 ++- libgrive/src/drive/Drive.hh | 1 + libgrive/src/drive/Entry.cc | 19 +++++++ libgrive/src/drive/Entry.hh | 9 +++- libgrive/src/drive/Resource.cc | 68 +++++++++++++++++++++---- libgrive/src/drive/Resource.hh | 17 +++++-- libgrive/src/drive/ResourceTree.cc | 36 +++++++++++++ libgrive/src/drive/ResourceTree.hh | 12 ++++- libgrive/src/drive/State.cc | 40 +++++++++++---- libgrive/src/protocol/Json.cc | 36 +++++++++---- libgrive/src/protocol/Json.hh | 1 + libgrive/src/util/DateTime.cc | 9 ++++ libgrive/src/util/DateTime.hh | 1 + libgrive/src/xml/Node.cc | 2 +- libgrive/test/UnitTest.cc | 2 + libgrive/test/drive/ResourceTreeTest.cc | 42 +++++++++++++++ libgrive/test/drive/ResourceTreeTest.hh | 41 +++++++++++++++ 18 files changed, 304 insertions(+), 39 deletions(-) create mode 100644 libgrive/test/drive/ResourceTreeTest.cc create mode 100644 libgrive/test/drive/ResourceTreeTest.hh diff --git a/grive/src/main.cc b/grive/src/main.cc index 1236420..46aa577 100644 --- a/grive/src/main.cc +++ b/grive/src/main.cc @@ -117,6 +117,7 @@ int main( int argc, char **argv ) Drive drive( token ) ; drive.Update() ; +// drive.SaveState() ; config.Save() ; } diff --git a/libgrive/src/drive/Drive.cc b/libgrive/src/drive/Drive.cc index f97bb23..bcf0af2 100644 --- a/libgrive/src/drive/Drive.cc +++ b/libgrive/src/drive/Drive.cc @@ -97,6 +97,7 @@ Drive::Drive( OAuth2& auth ) : if ( file.Kind() != "folder" ) { Resource *p = m_state.FindFolderByHref( file.ParentHref() ) ; +Trace( "finding parent of %1%: %2%", file.Title(), (void*)p ) ; if ( file.Filename().empty() ) Log( "file \"%1%\" is a google document, ignored", file.Title() ) ; @@ -122,7 +123,10 @@ Drive::Drive( OAuth2& auth ) : resp = xrsp.Response() ; } } while ( has_next ) ; - +} + +void Drive::SaveState() +{ m_state.Write( state_file ) ; } diff --git a/libgrive/src/drive/Drive.hh b/libgrive/src/drive/Drive.hh index d5c86ea..82cc6a6 100644 --- a/libgrive/src/drive/Drive.hh +++ b/libgrive/src/drive/Drive.hh @@ -42,6 +42,7 @@ public : void Update() ; void Sync() ; + void SaveState() ; struct Error : virtual Exception {} ; diff --git a/libgrive/src/drive/Entry.cc b/libgrive/src/drive/Entry.cc index 9bf9ff7..d5fe5f9 100644 --- a/libgrive/src/drive/Entry.cc +++ b/libgrive/src/drive/Entry.cc @@ -56,6 +56,25 @@ Entry::Entry( const fs::path& path ) : { } +Entry::Entry( + const std::string& name, + const std::string& resource_id, + const std::string& href, + const std::string& md5, + const std::string& kind, + const DateTime& mtime, + const std::string& parent_href ) : + m_title ( name ), + m_filename ( name ), + m_kind ( kind ), + m_md5 ( md5 ), + m_resource_id ( resource_id ), + m_parent_hrefs ( 1, parent_href ), + m_self_href ( href ), + m_mtime ( mtime ) +{ +} + void Entry::Update( const xml::Node& n ) { m_title = n["title"] ; diff --git a/libgrive/src/drive/Entry.hh b/libgrive/src/drive/Entry.hh index dde3405..d4f508e 100644 --- a/libgrive/src/drive/Entry.hh +++ b/libgrive/src/drive/Entry.hh @@ -45,6 +45,14 @@ public : Entry( ) ; explicit Entry( const fs::path& path ) ; explicit Entry( const xml::Node& n ) ; + explicit Entry( + const std::string& name, + const std::string& resource_id, + const std::string& href, + const std::string& md5, + const std::string& kind, + const DateTime& mtime, + const std::string& parent_href ) ; std::string Title() const ; std::string Filename() const ; @@ -78,7 +86,6 @@ private : std::string m_self_href ; std::string m_content_src ; - std::string m_parent_href ; std::string m_upload_link ; DateTime m_mtime ; diff --git a/libgrive/src/drive/Resource.cc b/libgrive/src/drive/Resource.cc index fb9c56d..d24e8d0 100644 --- a/libgrive/src/drive/Resource.cc +++ b/libgrive/src/drive/Resource.cc @@ -23,6 +23,7 @@ #include "http/Download.hh" #include "http/StringResponse.hh" #include "http/XmlResponse.hh" +#include "protocol/Json.hh" #include "util/Crypt.hh" #include "util/Log.hh" #include "util/OS.hh" @@ -44,6 +45,21 @@ Resource::Resource() : { } +/// construct from previously serialized JSON object +Resource::Resource( const Json& json, Resource *parent ) : + m_entry ( + json["name"].Str(), + json["id"].Str(), + json["href"].Str(), + json["md5"].Str(), + json["kind"].Str(), + DateTime( json["mtime"]["sec"].Int(), json["mtime"]["nsec"].Int() ), + parent != 0 ? parent->SelfHref() : "" ), + m_parent( parent ), + m_state( local_new ) +{ +} + Resource::Resource( const xml::Node& entry ) : m_entry ( entry ), m_parent( 0 ), @@ -58,16 +74,6 @@ Resource::Resource( const Entry& entry, Resource *parent ) : { } -// Resource::Resource( -// const std::string& name, -// const std::string& kind, -// const std::string& href ) : -// m_entry ( name, kind, href ), -// m_parent( 0 ), -// m_state ( local_new ) -// { -// } - Resource::Resource( const fs::path& path ) : m_entry ( path ), m_parent( 0 ), @@ -127,7 +133,7 @@ std::string Resource::ParentHref() const void Resource::AddChild( Resource *child ) { assert( child != 0 ) ; - assert( child->m_parent == 0 ) ; + assert( child->m_parent == 0 || child->m_parent == this ) ; assert( child != this ) ; child->m_parent = this ; @@ -285,6 +291,46 @@ bool Resource::Upload( http::Agent* http, std::streambuf *file, const http::Head return true ; } +Json Resource::Serialize() const +{ + Json result ; + result.Add( "name", Json(Name()) ) ; + result.Add( "id", Json(ResourceID()) ) ; + result.Add( "href", Json(SelfHref()) ) ; + result.Add( "md5", Json(m_entry.MD5()) ) ; + result.Add( "kind", Json(m_entry.Kind()) ) ; + + Json mtime ; + mtime.Add( "sec", Json(m_entry.MTime().Sec() ) ); + mtime.Add( "nsec", Json(m_entry.MTime().NanoSec() ) ); + + result.Add( "mtime",mtime ) ; + + std::vector array ; + for ( std::vector::const_iterator i = m_child.begin() ; i != m_child.end() ; ++i ) + array.push_back( (*i)->Serialize() ) ; + + result.Add( "child", Json(array) ) ; + + return result ; + +} + +Resource::iterator Resource::begin() const +{ + return m_child.begin() ; +} + +Resource::iterator Resource::end() const +{ + return m_child.end() ; +} + +std::size_t Resource::size() const +{ + return m_child.size() ; +} + } // end of namespace namespace std diff --git a/libgrive/src/drive/Resource.hh b/libgrive/src/drive/Resource.hh index f184fb8..b271018 100644 --- a/libgrive/src/drive/Resource.hh +++ b/libgrive/src/drive/Resource.hh @@ -30,6 +30,8 @@ namespace gr { +class Json ; + /*! \brief A resource can be a file or a folder in the google drive The google drive contains a number of resources, which is represented by this class. @@ -40,15 +42,15 @@ class Resource public : struct Error : virtual Exception {} ; + typedef std::vector Children ; + typedef Children::const_iterator iterator ; + public : Resource() ; explicit Resource( const xml::Node& entry ) ; explicit Resource( const Entry& entry, Resource *parent = 0 ) ; explicit Resource( const fs::path& path ) ; -// Resource( -// const std::string& name, -// const std::string& kind, -// const std::string& href ) ; + explicit Resource( const Json& json, Resource *parent = 0 ) ; void Swap( Resource& coll ) ; // default copy ctor & op= are fine @@ -72,6 +74,13 @@ public : void Sync( http::Agent *http, const http::Headers& auth ) ; void Delete( http::Agent* http, const http::Headers& auth ) ; + Json Serialize() const ; + + // children access + iterator begin() const ; + iterator end() const ; + std::size_t size() const ; + private : /// State of the resource. indicating what to do with the resource enum State diff --git a/libgrive/src/drive/ResourceTree.cc b/libgrive/src/drive/ResourceTree.cc index f4e3d09..0a94eaf 100644 --- a/libgrive/src/drive/ResourceTree.cc +++ b/libgrive/src/drive/ResourceTree.cc @@ -20,7 +20,9 @@ #include "ResourceTree.hh" #include "CommonUri.hh" +#include "protocol/Json.hh" #include "util/Destroy.hh" +#include "util/Log.hh" #include #include @@ -52,10 +54,18 @@ ResourceTree::ResourceTree( const ResourceTree& fs ) : } ResourceTree::~ResourceTree( ) +{ + Clear() ; +} + +void ResourceTree::Clear() { // delete all pointers const Set& s = m_set.get() ; std::for_each( s.begin(), s.end(), Destroy() ) ; + + m_set.clear() ; + m_root = 0 ; } Resource* ResourceTree::Root() @@ -152,4 +162,30 @@ ResourceTree::iterator ResourceTree::end() return m_set.get().end() ; } +void ResourceTree::Read( const Json& json ) +{ + Clear() ; + m_root = new Resource( json ) ; + AddTree( m_root, json ) ; +} + +void ResourceTree::AddTree( Resource *node, const Json& json ) +{ + assert( node != 0 ) ; + m_set.insert( node ) ; + + std::vector array = json["child"].AsArray() ; + for ( std::vector::iterator i = array.begin() ; i != array.end() ; ++i ) + { + Resource *c = new Resource( *i, node ) ; + node->AddChild( c ) ; + AddTree( c, *i ) ; + } +} + +Json ResourceTree::Serialize() const +{ + return m_root->Serialize() ; +} + } // end of namespace diff --git a/libgrive/src/drive/ResourceTree.hh b/libgrive/src/drive/ResourceTree.hh index eec869c..7ca5326 100644 --- a/libgrive/src/drive/ResourceTree.hh +++ b/libgrive/src/drive/ResourceTree.hh @@ -30,6 +30,8 @@ namespace gr { +class Json ; + namespace details { using namespace boost::multi_index ; @@ -73,6 +75,7 @@ public : const Resource* FindByHref( const std::string& href ) const ; Resource* FindByPath( const fs::path& path ) ; + Resource* FindByID( const std::string& id ) ; bool ReInsert( Resource *coll ) ; @@ -85,7 +88,14 @@ public : iterator begin() ; iterator end() ; - + + void Read( const Json& json ) ; + Json Serialize() const ; + +private : + void Clear() ; + void AddTree( Resource *node, const Json& json ) ; + private : details::Folders m_set ; Resource* m_root ; diff --git a/libgrive/src/drive/State.cc b/libgrive/src/drive/State.cc index 7b7e0f4..99582ae 100644 --- a/libgrive/src/drive/State.cc +++ b/libgrive/src/drive/State.cc @@ -40,8 +40,7 @@ namespace gr { State::State( const fs::path& filename ) { - if ( fs::exists( filename ) ) - Read( filename ); + Read( filename ) ; } std::string State::ChangeStamp() const @@ -68,15 +67,25 @@ void State::FromLocal( const fs::path& p, gr::Resource* folder ) for ( fs::directory_iterator i( p ) ; i != fs::directory_iterator() ; ++i ) { - if ( i->path().filename().string()[0] == '.' ) - Log( "file %1% is ignored by grive", i->path().filename().string(), log::info ) ; + std::string fname = i->path().filename().string() ; + + if ( fname[0] == '.' ) + Log( "file %1% is ignored by grive", fname, log::info ) ; else { - Resource *c = new Resource( i->path() ) ; - folder->AddChild( c ) ; - m_res.Insert( c ) ; - + Resource *c = folder->FindChild( fname ) ; + if ( c != 0 ) + { + Trace( "wow! file %1% is loaded from previous state", fname ) ; + } + else + { + c = new Resource( i->path() ) ; + folder->AddChild( c ) ; + m_res.Insert( c ) ; + } + if ( fs::is_directory( i->path() ) ) FromLocal( *i, c ) ; } @@ -124,7 +133,11 @@ bool State::Update( const Entry& e ) { assert( !e.ParentHref().empty() ) ; -// Resource *r = m_res.FindByID( e.ResourceID() ) ; + Resource *r = m_res.FindByHref( e.SelfHref() ) ; + if ( r != 0 ) + { +Trace( "wow! find %1% from state file?", r->Name() ) ; + } Resource *parent = m_res.FindByHref( e.ParentHref() ) ; if ( parent != 0 ) @@ -183,13 +196,20 @@ State::iterator State::end() void State::Read( const fs::path& filename ) { - Trace( "reading %1%", filename ) ; + if ( fs::exists( filename ) ) + { + Json json = Json::ParseFile( filename.string() ) ; + + m_change_stamp = json["change_stamp"].Str() ; + m_res.Read( json["rtree"] ) ; + } } void State::Write( const fs::path& filename ) const { Json result ; result.Add( "change_stamp", Json( m_change_stamp ) ) ; + result.Add( "rtree", m_res.Serialize() ) ; std::ofstream fs( filename.string().c_str() ) ; fs << result ; diff --git a/libgrive/src/protocol/Json.cc b/libgrive/src/protocol/Json.cc index e0c87d4..257a18a 100644 --- a/libgrive/src/protocol/Json.cc +++ b/libgrive/src/protocol/Json.cc @@ -34,7 +34,7 @@ Json::Json( ) : m_json( ::json_object_new_object() ) { if ( m_json == 0 ) - throw Error() << expt::ErrMsg( "cannot create json object" ) ; + BOOST_THROW_EXCEPTION( Error() << expt::ErrMsg( "cannot create json object" ) ) ; } template <> @@ -42,7 +42,8 @@ Json::Json( const std::string& str ) : m_json( ::json_object_new_string( str.c_str() ) ) { if ( m_json == 0 ) - throw Error() << expt::ErrMsg( "cannot create json string \"" + str + "\"" ) ; + BOOST_THROW_EXCEPTION( + Error() << expt::ErrMsg( "cannot create json string \"" + str + "\"" ) ) ; // paranoid check assert( ::json_object_get_string( m_json ) == str ) ; @@ -53,7 +54,7 @@ Json::Json( const int& l ) : m_json( ::json_object_new_int( l ) ) { if ( m_json == 0 ) - throw Error() << expt::ErrMsg( "cannot create json int" ) ; + BOOST_THROW_EXCEPTION( Error() << expt::ErrMsg( "cannot create json int" ) ) ; } template <> @@ -61,7 +62,15 @@ Json::Json( const long& l ) : m_json( ::json_object_new_int( static_cast(l) ) ) { if ( m_json == 0 ) - throw Error() << expt::ErrMsg( "cannot create json int" ) ; + BOOST_THROW_EXCEPTION( Error() << expt::ErrMsg( "cannot create json int" ) ) ; +} + +template <> +Json::Json( const unsigned long& l ) : + m_json( ::json_object_new_int( static_cast(l) ) ) +{ + if ( m_json == 0 ) + BOOST_THROW_EXCEPTION( Error() << expt::ErrMsg( "cannot create json int" ) ) ; } template <> @@ -69,7 +78,7 @@ Json::Json( const std::vector& arr ) : m_json( ::json_object_new_array( ) ) { if ( m_json == 0 ) - throw Error() << expt::ErrMsg( "cannot create json int" ) ; + BOOST_THROW_EXCEPTION( Error() << expt::ErrMsg( "cannot create json int" ) ) ; for ( std::vector::const_iterator i = arr.begin() ; i != arr.end() ; ++i ) Add( *i ) ; @@ -79,7 +88,7 @@ Json Json::Parse( const std::string& str ) { struct json_object *json = ::json_tokener_parse( str.c_str() ) ; if ( json == 0 ) - throw Error() << expt::ErrMsg( "json parse error" ) ; + BOOST_THROW_EXCEPTION( Error() << expt::ErrMsg( "json parse error" ) ) ; return Json( json, NotOwned() ) ; } @@ -98,7 +107,7 @@ Json Json::ParseFile( const std::string& filename ) json = ::json_tokener_parse_ex( tok, buf, count ) ; if ( json == 0 ) - throw Error() << expt::ErrMsg( ::json_tokener_errors[tok->err] ) ; + BOOST_THROW_EXCEPTION( Error() << expt::ErrMsg( ::json_tokener_errors[tok->err] ) ) ; ::json_tokener_free( tok ) ; @@ -152,7 +161,10 @@ Json Json::operator[]( const std::string& key ) const struct json_object *j = ::json_object_object_get( m_json, key.c_str() ) ; if ( j == 0 ) - throw Error() << expt::ErrMsg( "key: " + key + " is not found in object" ) ; + BOOST_THROW_EXCEPTION( + Error() + << expt::ErrMsg( "key: " + key + " is not found in object" ) + << JsonInfo( *this ) ) ; return Json( j ) ; } @@ -166,7 +178,10 @@ Json Json::operator[]( const std::size_t& idx ) const { std::ostringstream ss ; ss << "index " << idx << " is not found in array" ; - throw Error() << expt::ErrMsg( ss.str() ) ; + BOOST_THROW_EXCEPTION( + Error() + << expt::ErrMsg( ss.str() ) + << JsonInfo( *this ) ) ; } return Json( j ) ; @@ -294,7 +309,8 @@ Json Json::FindInArray( const std::string& key, const std::string& value ) const if ( item.Has(key) && item[key].Str() == value ) return item ; } - throw Error() << expt::ErrMsg( "cannot find " + key + " = " + value + " in array" ) ; + BOOST_THROW_EXCEPTION( + Error() << expt::ErrMsg( "cannot find " + key + " = " + value + " in array" ) ) ; } bool Json::FindInArray( const std::string& key, const std::string& value, Json& result ) const diff --git a/libgrive/src/protocol/Json.hh b/libgrive/src/protocol/Json.hh index 1dd1742..18cb88a 100644 --- a/libgrive/src/protocol/Json.hh +++ b/libgrive/src/protocol/Json.hh @@ -36,6 +36,7 @@ public : typedef std::vector Array ; struct Error : virtual Exception {} ; + typedef boost::error_info JsonInfo ; public : template diff --git a/libgrive/src/util/DateTime.cc b/libgrive/src/util/DateTime.cc index 424a8c0..68d2bad 100644 --- a/libgrive/src/util/DateTime.cc +++ b/libgrive/src/util/DateTime.cc @@ -19,6 +19,8 @@ #include "DateTime.hh" +#include + #include #include #include @@ -149,4 +151,11 @@ void DateTime::Swap( DateTime& dt ) std::swap( m_nsec, dt.m_nsec ) ; } +std::string DateTime::ToString() const +{ + std::ostringstream ss ; + ss << *this ; + return ss.str() ; +} + } // end of namespace diff --git a/libgrive/src/util/DateTime.hh b/libgrive/src/util/DateTime.hh index aebc86e..f4f0e10 100644 --- a/libgrive/src/util/DateTime.hh +++ b/libgrive/src/util/DateTime.hh @@ -51,6 +51,7 @@ public : bool operator<=( const DateTime& dt ) const ; void Swap( DateTime& dt ) ; + std::string ToString() const ; private : std::time_t m_sec ; diff --git a/libgrive/src/xml/Node.cc b/libgrive/src/xml/Node.cc index c8c71f3..8bd3b71 100644 --- a/libgrive/src/xml/Node.cc +++ b/libgrive/src/xml/Node.cc @@ -92,7 +92,7 @@ public : // cannot allow duplicate attribute nodes if ( child->m_type == attr && p.first != p.second ) - throw Error() << expt::ErrMsg( "duplicate attribute " + child->m_name ) ; + BOOST_THROW_EXCEPTION( Error() << expt::ErrMsg( "duplicate attribute " + child->m_name ) ) ; vec.insert( p.second, child ) ; } diff --git a/libgrive/test/UnitTest.cc b/libgrive/test/UnitTest.cc index 19f686f..8237af7 100644 --- a/libgrive/test/UnitTest.cc +++ b/libgrive/test/UnitTest.cc @@ -22,6 +22,7 @@ #include "util/DefaultLog.hh" #include "drive/EntryTest.hh" +#include "drive/ResourceTreeTest.hh" #include "drive/StateTest.hh" #include "util/DateTimeTest.hh" #include "util/FunctionTest.hh" @@ -40,6 +41,7 @@ int main( int argc, char **argv ) CppUnit::TextUi::TestRunner runner; runner.addTest( EntryTest::suite( ) ) ; runner.addTest( StateTest::suite( ) ) ; + runner.addTest( ResourceTreeTest::suite( ) ) ; runner.addTest( DateTimeTest::suite( ) ) ; runner.addTest( FunctionTest::suite( ) ) ; runner.addTest( SignalHandlerTest::suite( ) ) ; diff --git a/libgrive/test/drive/ResourceTreeTest.cc b/libgrive/test/drive/ResourceTreeTest.cc new file mode 100644 index 0000000..0e07d80 --- /dev/null +++ b/libgrive/test/drive/ResourceTreeTest.cc @@ -0,0 +1,42 @@ +/* + 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 "ResourceTreeTest.hh" + +#include "Assert.hh" + +#include "drive/ResourceTree.hh" +#include "drive/Resource.hh" + +#include + +namespace grut { + +using namespace gr ; + +ResourceTreeTest::ResourceTreeTest( ) +{ +} + +void ResourceTreeTest::TestSerialize( ) +{ + +} + +} // end of namespace grut diff --git a/libgrive/test/drive/ResourceTreeTest.hh b/libgrive/test/drive/ResourceTreeTest.hh new file mode 100644 index 0000000..b1a0abd --- /dev/null +++ b/libgrive/test/drive/ResourceTreeTest.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 ResourceTreeTest : public CppUnit::TestFixture +{ +public : + ResourceTreeTest( ) ; + + // declare suit function + CPPUNIT_TEST_SUITE( ResourceTreeTest ) ; + CPPUNIT_TEST( TestSerialize ) ; + CPPUNIT_TEST_SUITE_END(); + +private : + void TestSerialize( ) ; +} ; + +} // end of namespace