start using state for synchronization

pull/40/head
Matchman Green 2012-05-20 14:57:25 +08:00
parent b1589bb181
commit 6453de2cf6
7 changed files with 144 additions and 100 deletions

View File

@ -168,7 +168,7 @@ void Drive::Update()
{
http::Agent http ;
std::for_each( m_state.begin(), m_state.end(),
boost::bind( &Resource::Update, _1, &http, m_http_hdr ) ) ;
boost::bind( &Resource::Sync, _1, &http, m_http_hdr ) ) ;
}
} // end of namespace

View File

@ -31,57 +31,52 @@
namespace gr {
/// construct an entry for the root folder
Entry::Entry( ) :
m_title ( "." ),
m_kind ( "folder" ),
m_resource_id ( "folder:root" ),
m_self_href ( root_href )
{
}
/// construct an entry for remote
Entry::Entry( const xml::Node& n )
{
Update( n ) ;
}
Entry::Entry( const std::string& title, const std::string& kind, const std::string& href ) :
m_title ( title ),
m_filename ( title ),
m_kind ( kind ),
m_self_href ( href )
{
}
/// construct an entry from a file or folder in local directory
Entry::Entry( const fs::path& path ) :
m_title ( path.filename().string() ),
m_filename ( path.filename().string() ),
m_kind ( fs::is_directory(path) ? "folder" : "file" ),
m_server_md5 ( crypt::MD5( path ) ),
m_server_modified( os::FileMTime( path ) )
m_title ( path.filename().string() ),
m_filename ( path.filename().string() ),
m_kind ( fs::is_directory(path) ? "folder" : "file" ),
m_md5 ( fs::is_directory(path) ? "" : crypt::MD5( path ) ),
m_mtime ( os::FileMTime( path ) )
{
}
void Entry::Update( const xml::Node& n )
{
m_title = n["title"] ;
m_etag = n["@gd:etag"] ;
m_filename = n["docs:suggestedFilename"] ;
m_content_src = n["content"]["@src"] ;
m_self_href = n["link"].Find( "@rel", "self" )["@href"] ;
m_server_modified = DateTime( n["updated"] ) ;
m_title = n["title"] ;
m_etag = n["@gd:etag"] ;
m_filename = n["docs:suggestedFilename"] ;
m_content_src = n["content"]["@src"] ;
m_self_href = n["link"].Find( "@rel", "self" )["@href"] ;
m_mtime = DateTime( n["updated"] ) ;
m_resource_id = n["gd:resourceId"] ;
m_server_md5 = n["docs:md5Checksum"] ;
m_kind = n["category"].Find( "@scheme", "http://schemas.google.com/g/2005#kind" )["@label"] ;
m_upload_link = n["link"].Find( "@rel", "http://schemas.google.com/g/2005#resumable-edit-media")["@href"] ;
m_resource_id = n["gd:resourceId"] ;
m_md5 = n["docs:md5Checksum"] ;
m_kind = n["category"].Find( "@scheme", "http://schemas.google.com/g/2005#kind" )["@label"] ;
m_upload_link = n["link"].Find( "@rel", "http://schemas.google.com/g/2005#resumable-edit-media")["@href"] ;
m_parent_hrefs.clear( ) ;
xml::NodeSet parents = n["link"].Find( "@rel", "http://schemas.google.com/docs/2007#parent" ) ;
for ( xml::NodeSet::iterator i = parents.begin() ; i != parents.end() ; ++i )
m_parent_hrefs.push_back( (*i)["@href"] ) ;
if ( !m_parent_hrefs.empty() )
m_parent_href = m_parent_hrefs.front() ;
// convert to lower case for easy comparison
std::transform(
m_server_md5.begin(),
m_server_md5.end(),
m_server_md5.begin(),
tolower ) ;
std::transform( m_md5.begin(), m_md5.end(), m_md5.begin(), tolower ) ;
}
const std::vector<std::string>& Entry::ParentHrefs() const
@ -104,14 +99,14 @@ std::string Entry::Kind() const
return m_kind ;
}
std::string Entry::ServerMD5() const
std::string Entry::MD5() const
{
return m_server_md5 ;
return m_md5 ;
}
DateTime Entry::ServerModified() const
DateTime Entry::MTime() const
{
return m_server_modified ;
return m_mtime ;
}
std::string Entry::SelfHref() const
@ -121,7 +116,7 @@ std::string Entry::SelfHref() const
std::string Entry::ParentHref() const
{
return m_parent_href ;
return m_parent_hrefs.empty() ? "" : m_parent_hrefs.front() ;
}
std::string Entry::ResourceID() const
@ -149,18 +144,17 @@ void Entry::Swap( Entry& e )
m_title.swap( e.m_title ) ;
m_filename.swap( e.m_filename ) ;
m_kind.swap( e.m_kind ) ;
m_server_md5.swap( e.m_server_md5 ) ;
m_md5.swap( e.m_md5 ) ;
m_etag.swap( e.m_etag ) ;
m_resource_id.swap( e.m_resource_id ) ;
m_parent_hrefs.swap( e.m_parent_hrefs ) ;
m_self_href.swap( e.m_self_href ) ;
m_parent_href.swap( e.m_parent_href ) ;
m_content_src.swap( e.m_content_src ) ;
m_upload_link.swap( e.m_upload_link ) ;
m_server_modified.Swap( e.m_server_modified ) ;
m_mtime.Swap( e.m_mtime ) ;
}
} // end of namespace

View File

@ -36,20 +36,21 @@ namespace xml
/*! \brief corresponds to an "entry" in the resource feed
This class is decodes an entry in the resource feed. It will stored the properties like
title, filename and ETag etc in member variables.
title, filename and ETag etc in member variables. Normally entries are created by
parsing the resource feed
*/
class Entry
{
public :
Entry( ) ;
explicit Entry( const fs::path& path ) ;
explicit Entry( const xml::Node& n ) ;
Entry( const std::string& title, const std::string& kind, const std::string& href ) ;
std::string Title() const ;
std::string Filename() const ;
std::string Kind() const ;
std::string ServerMD5() const ;
DateTime ServerModified() const ;
std::string MD5() const ;
DateTime MTime() const ;
std::string ResourceID() const ;
std::string ETag() const ;
@ -69,7 +70,7 @@ private :
std::string m_title ;
std::string m_filename ;
std::string m_kind ;
std::string m_server_md5 ;
std::string m_md5 ;
std::string m_etag ;
std::string m_resource_id ;
@ -80,7 +81,7 @@ private :
std::string m_parent_href ;
std::string m_upload_link ;
DateTime m_server_modified ;
DateTime m_mtime ;
} ;
} // end of namespace

View File

@ -37,35 +37,61 @@
namespace gr {
/// default constructor creates the root folder
Resource::Resource() :
m_parent( 0 ),
m_state ( sync )
{
}
Resource::Resource( const xml::Node& entry ) :
m_entry ( entry ),
m_parent( 0 ),
m_state ( new_remote )
m_state ( remote_new )
{
}
Resource::Resource( const Entry& entry, Resource *parent ) :
m_entry ( entry ),
m_parent( parent ),
m_state ( new_remote )
m_state ( remote_new )
{
}
Resource::Resource(
const std::string& name,
const std::string& kind,
const std::string& href ) :
m_entry ( name, kind, href ),
// 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 ),
m_state ( new_local )
m_state ( local_new )
{
}
void Resource::FromRemote( const Entry& e )
/// Update the state according to information (i.e. Entry) from remote. This function
/// compares the modification time and checksum of both copies and determine which
/// one is newer.
void Resource::FromRemote( const Entry& remote )
{
Trace( "%1% state is %2%", e.Title(), m_state ) ;
m_state = sync ;
m_entry = e ;
// if checksum is equal, no need to compare the mtime
if ( remote.MD5() == m_entry.MD5() )
m_state = sync ;
// use mtime to check which one is more recent
else
{
m_state = ( remote.MTime() > m_entry.MTime() ? remote_changed : local_changed ) ;
}
m_entry = remote ;
}
std::string Resource::SelfHref() const
@ -145,40 +171,39 @@ Resource* Resource::FindChild( const std::string& name )
return 0 ;
}
void Resource::Update( http::Agent *http, const http::Headers& auth )
// try to change the state to "sync"
void Resource::Sync( http::Agent *http, const http::Headers& auth )
{
// no need to update for folders
if ( IsFolder() )
return ;
assert( m_parent != 0 ) ;
bool changed = true ;
fs::path path = Path() ;
Trace( "updating %1%", path ) ;
// compare checksum first if file exists
std::ifstream ifile( path.string().c_str(), std::ios::binary | std::ios::in ) ;
if ( ifile && m_entry.ServerMD5() == crypt::MD5(ifile.rdbuf()) )
changed = false ;
// if the checksum is different, file is changed and we need to update
if ( changed )
switch ( m_state )
{
DateTime remote = m_entry.ServerModified() ;
DateTime local = ifile ? os::FileMTime( path ) : DateTime() ;
// remote file is newer, download file
if ( !ifile || remote > local )
Download( http, path, auth ) ;
else
{
// re-reading the file
ifile.seekg(0) ;
Upload( http, ifile.rdbuf(), auth ) ;
}
case local_new :
Trace( "file %1% doesn't exist in server. upload?", m_entry.Filename() ) ;
break ;
case local_changed :
Trace( "file %1% changed in local", m_entry.Filename() ) ;
if ( Upload( http, auth ) )
m_state = sync ;
break ;
case remote_new :
case remote_changed :
Trace( "file %1% changed in remote", m_entry.Filename() ) ;
Download( http, Path(), auth ) ;
m_state = sync ;
break ;
case sync :
Trace( "file %1% already in sync", m_entry.Filename() ) ;
default :
break ;
}
}
@ -198,7 +223,13 @@ void Resource::Download( http::Agent* http, const fs::path& file, const http::He
http::Download dl( file.string(), http::Download::NoChecksum() ) ;
long r = http->Get( m_entry.ContentSrc(), &dl, auth ) ;
if ( r <= 400 )
os::SetFileTime( file, m_entry.ServerModified() ) ;
os::SetFileTime( file, m_entry.MTime() ) ;
}
bool Resource::Upload( http::Agent* http, const http::Headers& auth )
{
std::ifstream ifile( Path().string().c_str(), std::ios::binary | std::ios::in ) ;
return Upload( http, ifile.rdbuf(), auth ) ;
}
bool Resource::Upload( http::Agent* http, std::streambuf *file, const http::Headers& auth )

View File

@ -41,12 +41,14 @@ public :
struct Error : virtual Exception {} ;
public :
Resource() ;
explicit Resource( const xml::Node& entry ) ;
explicit Resource( const Entry& entry, Resource *parent = 0 ) ;
Resource(
const std::string& name,
const std::string& kind,
const std::string& href ) ;
explicit Resource( const fs::path& path ) ;
// Resource(
// const std::string& name,
// const std::string& kind,
// const std::string& href ) ;
void Swap( Resource& coll ) ;
// default copy ctor & op= are fine
@ -67,27 +69,35 @@ public :
bool IsInRootTree() const ;
void FromRemote( const Entry& e ) ;
void Update( http::Agent *http, const http::Headers& auth ) ;
void Sync( http::Agent *http, const http::Headers& auth ) ;
void Delete( http::Agent* http, const http::Headers& auth ) ;
private :
/// State of the resource. indicating what to do with the resource
enum State
{
/// The best state: the file is the same in google drive and in local.
/// The best state: the file is the same in remote and in local.
sync,
/// Resource created in local, but google drive does not have it.
/// Resource created in local, but remote does not have it.
/// We should create the resource in google drive and upload new content
new_local,
local_new,
/// Resource exists in both local & remote, but changes in local is newer
/// than remote. We should upload local copy to overwrite remote.
local_changed,
/// Resource created in google drive, but not exist in local.
/// We should download the file.
new_remote
remote_new,
/// Resource exists in both local & remote, but remote is newer.
remote_changed
} ;
private :
void Download( http::Agent* http, const fs::path& file, const http::Headers& auth ) const ;
bool Upload( http::Agent* http, const http::Headers& auth ) ;
bool Upload( http::Agent* http, std::streambuf *file, const http::Headers& auth ) ;
private :

View File

@ -30,7 +30,7 @@ namespace gr {
using namespace details ;
ResourceTree::ResourceTree( ) :
m_root( new Resource( ".", "folder", root_href ) )
m_root( new Resource )
{
m_set.insert( m_root ) ;
}

View File

@ -75,7 +75,7 @@ void State::FromLocal( const fs::path& p, gr::Resource* folder )
{
if ( fs::is_directory( i->path() ) )
{
Resource *c = new Resource( i->path().filename().string(), "folder", "" ) ;
Resource *c = new Resource( i->path() ) ;
folder->AddChild( c ) ;
m_folders.Insert( c ) ;
@ -85,7 +85,7 @@ void State::FromLocal( const fs::path& p, gr::Resource* folder )
Log( "file %1% is ignored by grive", i->path().filename().string(), log::info ) ;
else
{
Resource *c = new Resource( i->path().filename().string(), "file", "" ) ;
Resource *c = new Resource( i->path() ) ;
folder->AddChild( c ) ;
m_folders.Insert( c ) ;
}
@ -150,12 +150,15 @@ bool State::Update( const Entry& e )
if ( parent != 0 )
{
assert( parent->IsFolder() ) ;
Trace( "remote entry title %1%, filename %2%", e.Title(), e.Filename() ) ;
// see if the entry already exist in local
Resource *child = parent->FindChild( e.Title() ) ;
std::string name = ( e.Kind() == "folder" ? e.Title() : e.Filename() ) ;
Resource *child = parent->FindChild( name ) ;
if ( child != 0 )
{
assert( child == m_folders.FindByHref( e.SelfHref() ) ) ;
// Trace( "remote entry %1%, local %2%", e.Title(), child->Name() ) ;
// assert( child == m_folders.FindByHref( e.SelfHref() ) ) ;
// since we are updating the ID and Href, we need to remove it and re-add it.
m_folders.Update( child, e ) ;
@ -176,6 +179,11 @@ bool State::Update( const Entry& e )
fs::create_directories( child_path ) ;
}
}
else
{
Trace( "what here? %1%", e.Title() ) ;
}
return true ;
}
else