diff --git a/libgrive/src/base/Drive.cc b/libgrive/src/base/Drive.cc index 9190111..bd306a1 100644 --- a/libgrive/src/base/Drive.cc +++ b/libgrive/src/base/Drive.cc @@ -58,19 +58,7 @@ Drive::Drive( Syncer *syncer, const Val& options ) : void Drive::FromRemote( const Entry& entry ) { - // entries from change feed does not have the parent HREF, - // so these checkings are done in normal entries only - Resource *parent = m_state.FindByHref( entry.ParentHref() ) ; - - if ( parent && !parent->IsFolder() ) - Log( "warning: entry %1% has parent %2% which is not a folder, ignored", - entry.Title(), parent->Name(), log::verbose ) ; - - else if ( !parent || !parent->IsInRootTree() ) - Log( "file \"%1%\" parent doesn't exist, ignored", entry.Title(), log::verbose ) ; - - else - m_state.FromRemote( entry ) ; + m_state.FromRemote( entry ) ; } void Drive::FromChange( const Entry& entry ) @@ -88,43 +76,10 @@ void Drive::SaveState() m_state.Write( m_root / state_file ) ; } -void Drive::SyncFolders( ) -{ - Log( "Synchronizing folders", log::info ) ; - - std::auto_ptr feed = m_syncer->GetFolders() ; - while ( feed->GetNext( m_syncer->Agent() ) ) - { - // first, get all collections from the query result - for ( Feed::iterator i = feed->begin() ; i != feed->end() ; ++i ) - { - const Entry &e = *i ; - if ( e.IsDir() ) - { - if ( e.ParentHrefs().size() != 1 ) - Log( "folder \"%1%\" has multiple parents, ignored", e.Title(), log::verbose ) ; - - else if ( e.Title().find('/') != std::string::npos ) - Log( "folder \"%1%\" contains a slash in its name, ignored", e.Title(), log::verbose ) ; - - else - m_state.FromRemote( e ) ; - } - } - } - - m_state.ResolveEntry() ; -} - void Drive::DetectChanges() { Log( "Reading local directories", log::info ) ; m_state.FromLocal( m_root ) ; - - long prev_stamp = m_state.ChangeStamp() ; - Trace( "previous change stamp is %1%", prev_stamp ) ; - - SyncFolders( ) ; Log( "Reading remote server file list", log::info ) ; std::auto_ptr feed = m_syncer->GetAll() ; @@ -135,12 +90,19 @@ void Drive::DetectChanges() feed->begin(), feed->end(), boost::bind( &Drive::FromRemote, this, _1 ) ) ; } - - // pull the changes feed + m_state.ResolveEntry() ; +} + +// pull the changes feed +// FIXME: unused until Grive will use the feed-based sync instead of reading full tree +void Drive::ReadChanges() +{ + long prev_stamp = m_state.ChangeStamp() ; if ( prev_stamp != -1 ) { + Trace( "previous change stamp is %1%", prev_stamp ) ; Log( "Detecting changes from last sync", log::info ) ; - feed = m_syncer->GetChanges( prev_stamp+1 ) ; + std::auto_ptr feed = m_syncer->GetChanges( prev_stamp+1 ) ; while ( feed->GetNext( m_syncer->Agent() ) ) { std::for_each( diff --git a/libgrive/src/base/Drive.hh b/libgrive/src/base/Drive.hh index db1a027..a930192 100644 --- a/libgrive/src/base/Drive.hh +++ b/libgrive/src/base/Drive.hh @@ -49,7 +49,7 @@ public : struct Error : virtual Exception {} ; private : - void SyncFolders( ) ; + void ReadChanges() ; void FromRemote( const Entry& entry ) ; void FromChange( const Entry& entry ) ; void UpdateChangeStamp( ) ; diff --git a/libgrive/src/base/Resource.cc b/libgrive/src/base/Resource.cc index d7d4ba8..d87c42a 100644 --- a/libgrive/src/base/Resource.cc +++ b/libgrive/src/base/Resource.cc @@ -90,23 +90,18 @@ void Resource::FromRemoteFolder( const Entry& remote ) Log( "folder %1% is in sync", path, log::verbose ) ; m_state = sync ; } - else if ( fs::exists( path ) ) { // TODO: handle type change Log( "%1% changed from folder to file", path, log::verbose ) ; m_state = sync ; } - - // remote file created after last sync, so remote is newer - // TODO: Check local index instead of last_sync time - else if ( remote.MTime() > last_sync ) + else if ( remote.MTime().Sec() > m_mtime.Sec() ) // FIXME only seconds are stored in local index { - // make all children as remote_new, if any + // remote folder created after last sync, so remote is newer Log( "folder %1% is created in remote", path, log::verbose ) ; SetState( remote_new ) ; } - else { Log( "folder %1% is deleted in local", path, log::verbose ) ; @@ -131,6 +126,7 @@ void Resource::FromRemote( const Entry& remote ) if ( m_state == remote_new || m_state == remote_changed ) m_md5 = remote.MD5() ; + m_mtime = remote.MTime() ; } @@ -170,8 +166,7 @@ void Resource::FromRemoteFile( const Entry& remote ) { Trace( "file %1% change stamp = %2%", Path(), remote.ChangeStamp() ) ; - // TODO: Check local index instead of last_sync time - if ( remote.MTime() > last_sync || remote.ChangeStamp() > 0 ) + if ( remote.MTime().Sec() > m_mtime.Sec() || remote.MD5() != m_md5 || remote.ChangeStamp() > 0 ) { Log( "file %1% is created in remote (change %2%)", path, remote.ChangeStamp(), log::verbose ) ; @@ -223,6 +218,19 @@ void Resource::FromRemoteFile( const Entry& remote ) } } +void Resource::FromDeleted( Val& state ) +{ + assert( !m_json ); + m_json = &state; + if ( state.Has( "ctime" ) ) + m_ctime.Assign( state["ctime"].U64(), 0 ); + if ( state.Has( "md5" ) ) + m_md5 = state["md5"]; + if ( state.Has( "srv_time" ) ) + m_mtime.Assign( state[ "srv_time" ].U64(), 0 ) ; + m_state = both_deleted; +} + /// Update the resource with the attributes of local file or directory. This /// function will propulate the fields in m_entry. void Resource::FromLocal( Val& state ) @@ -235,7 +243,7 @@ void Resource::FromLocal( Val& state ) { fs::path path = Path() ; bool is_dir; - os::Stat( path, &m_ctime, &m_size, &is_dir ) ; + os::Stat( path, &m_ctime, NULL, &is_dir ) ; m_name = path.filename().string() ; m_kind = is_dir ? "folder" : "file"; @@ -254,7 +262,7 @@ void Resource::FromLocal( Val& state ) { m_md5 = crypt::MD5::Get( path ); // File is changed locally. TODO: Detect conflicts - is_changed = state.Has( "md5" ) && m_md5 != state["md5"].Str(); + is_changed = !state.Has( "md5" ) || m_md5 != state["md5"].Str(); state.Set( "md5", Val( m_md5 ) ); } else @@ -265,9 +273,11 @@ void Resource::FromLocal( Val& state ) m_mtime.Assign( state[ "srv_time" ].U64(), 0 ) ; if ( is_dir ) state.Del( "md5" ); + else + state.Del( "tree" ); // follow parent recursively - if ( m_parent->m_state == local_new || m_parent->m_state == local_deleted ) + if ( m_parent->m_state == local_new || m_parent->m_state == remote_deleted ) m_state = m_parent->m_state ; else { @@ -413,22 +423,6 @@ void Resource::Sync( Syncer *syncer, const Val& options ) { std::for_each( m_child.begin(), m_child.end(), boost::bind( &Resource::Sync, _1, syncer, options ) ) ; - if ( IsFolder() ) - { - // delete state of removed files - Val& tree = (*m_json)["tree"]; - Val::Object leftover = tree.AsObject(); - for( iterator i = m_child.begin(); i != m_child.end(); i++ ) - { - Resource *r = *i; - if ( r->m_state != local_deleted && r->m_state != remote_deleted ) - leftover.erase( r->Name() ); - else - r->m_json = NULL; - } - for( Val::Object::iterator i = leftover.begin(); i != leftover.end(); i++ ) - tree.Del( i->first ); - } } } @@ -454,7 +448,10 @@ void Resource::SyncSelf( Syncer* syncer, const Val& options ) case local_deleted : Log( "sync %1% deleted in local. deleting remote", path, log::info ) ; if ( syncer ) + { syncer->DeleteRemote( this ) ; + DeleteIndex() ; + } break ; case local_changed : @@ -490,7 +487,15 @@ void Resource::SyncSelf( Syncer* syncer, const Val& options ) case remote_deleted : Log( "sync %1% deleted in remote. deleting local", path, log::info ) ; if ( syncer ) + { DeleteLocal() ; + DeleteIndex() ; + } + break ; + + case both_deleted : + if ( syncer ) + DeleteIndex() ; break ; case sync : @@ -506,7 +511,7 @@ void Resource::SyncSelf( Syncer* syncer, const Val& options ) break ; } - if ( m_state != local_deleted && m_state != remote_deleted ) + if ( syncer && m_json ) { // Update server time of this file m_json->Set( "srv_time", Val( m_mtime.Sec() ) ); @@ -548,18 +553,27 @@ void Resource::DeleteLocal() } } +void Resource::DeleteIndex() +{ + (*m_parent->m_json)["tree"].Del( Name() ); + m_json = NULL; +} + void Resource::SetIndex() { assert( m_parent->m_json != NULL ); if ( !m_json ) m_json = &((*m_parent->m_json)["tree"]).Item( Name() ); bool is_dir; - os::Stat( Path(), &m_ctime, &m_size, &is_dir ); + os::Stat( Path(), &m_ctime, NULL, &is_dir ); if ( !is_dir ) { m_json->Set( "ctime", Val( m_ctime.Sec() ) ); m_json->Set( "md5", Val( m_md5 ) ); + m_json->Del( "tree" ); } + else // check if tree item exists + m_json->Item( "tree" ); } Resource::iterator Resource::begin() const @@ -582,7 +596,7 @@ std::ostream& operator<<( std::ostream& os, Resource::State s ) static const char *state[] = { "sync", "local_new", "local_changed", "local_deleted", "remote_new", - "remote_changed", "remote_deleted" + "remote_changed", "remote_deleted", "both_deleted" } ; assert( s >= 0 && s < Count(state) ) ; return os << state[s] ; diff --git a/libgrive/src/base/Resource.hh b/libgrive/src/base/Resource.hh index a9b5ebf..97c55d4 100644 --- a/libgrive/src/base/Resource.hh +++ b/libgrive/src/base/Resource.hh @@ -67,12 +67,14 @@ public : /// We should download the file. remote_new, - /// Resource exists in both local & remote, but remote is newer. + /// Resource exists in both local & remote, but remote is newer. remote_changed, /// Resource delete in remote, need to delete in local remote_deleted, + /// Both deleted. State is used to remove leftover files from the index after sync. + both_deleted, /// invalid value unknown @@ -107,6 +109,7 @@ public : std::string MD5() const ; void FromRemote( const Entry& remote ) ; + void FromDeleted( Val& state ) ; void FromLocal( Val& state ) ; void Sync( Syncer* syncer, const Val& options ) ; @@ -133,6 +136,7 @@ private : void FromRemoteFile( const Entry& remote ) ; void DeleteLocal() ; + void DeleteIndex() ; void SetIndex() ; void SyncSelf( Syncer* syncer, const Val& options ) ; @@ -143,7 +147,6 @@ private : std::string m_md5 ; DateTime m_mtime ; DateTime m_ctime ; - off_t m_size ; std::string m_id ; std::string m_href ; diff --git a/libgrive/src/base/State.cc b/libgrive/src/base/State.cc index d677d4b..9d12232 100644 --- a/libgrive/src/base/State.cc +++ b/libgrive/src/base/State.cc @@ -65,7 +65,7 @@ State::State( const fs::path& filename, const Val& options ) : // the "-f" option will make grive always think remote is newer if ( force ) - m_last_sync = new DateTime() ; + m_last_sync = DateTime() ; m_ign_re = boost::regex( m_ign.empty() ? "^\\.(grive|grive_state|trash)" : ( m_ign+"|^\\.(grive|grive_state|trash)" ) ); @@ -80,12 +80,10 @@ State::~State() /// of local directory. void State::FromLocal( const fs::path& p ) { - if ( !m_st.Has( "tree" ) ) - m_st.Add( "tree", Val() ); // Remember m_update_sync just before reading local file tree m_update_sync = DateTime::Now() ; m_res.Root()->FromLocal( m_st ) ; - FromLocal( p, m_res.Root(), m_st["tree"] ) ; + FromLocal( p, m_res.Root(), m_st.Item( "tree" ) ) ; } bool State::IsIgnore( const std::string& filename ) @@ -98,6 +96,8 @@ void State::FromLocal( const fs::path& p, Resource* folder, Val& tree ) assert( folder != 0 ) ; assert( folder->IsFolder() ) ; + Val::Object leftover = tree.AsObject(); + for ( fs::directory_iterator i( p ) ; i != fs::directory_iterator() ; ++i ) { std::string fname = i->path().filename().string() ; @@ -116,18 +116,26 @@ void State::FromLocal( const fs::path& p, Resource* folder, Val& tree ) folder->AddChild( c ) ; m_res.Insert( c ) ; } - if ( !tree.Has( fname ) ) - tree.Add( fname, Val() ); - Val& rec = tree[fname]; + leftover.erase( fname ); + Val& rec = tree.Item( fname ); c->FromLocal( rec ) ; if ( c->IsFolder() ) - { - if ( !rec.Has("tree") ) - rec.Add( "tree", Val() ); - FromLocal( *i, c, rec["tree"] ) ; - } + FromLocal( *i, c, rec.Item( "tree" ) ) ; } } + + for( Val::Object::iterator i = leftover.begin(); i != leftover.end(); i++ ) + { + // Restore state of locally deleted files + Resource *c = folder->FindChild( i->first ) ; + if ( !c ) + { + c = new Resource( i->first, i->second.Has( "tree" ) ? "folder" : "file" ) ; + folder->AddChild( c ) ; + m_res.Insert( c ) ; + } + c->FromDeleted( tree.Item( i->first ) ); + } } void State::FromRemote( const Entry& e ) diff --git a/libgrive/test/base/ResourceTest.cc b/libgrive/test/base/ResourceTest.cc index f5cdc12..cbbe6c8 100644 --- a/libgrive/test/base/ResourceTest.cc +++ b/libgrive/test/base/ResourceTest.cc @@ -55,7 +55,7 @@ void ResourceTest::TestNormal( ) GRUT_ASSERT_EQUAL( subject.Path(), fs::path( TEST_DATA ) / "entry.xml" ) ; Val st; - subject.FromLocal( DateTime(), st ) ; + subject.FromLocal( st ) ; GRUT_ASSERT_EQUAL( subject.MD5(), "c0742c0a32b2c909b6f176d17a6992d0" ) ; GRUT_ASSERT_EQUAL( subject.StateStr(), "local_new" ) ;