2012-04-26 19:40:38 +04:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2012-05-19 11:41:21 +04:00
|
|
|
#include "Resource.hh"
|
2016-01-03 01:57:54 +03:00
|
|
|
#include "ResourceTree.hh"
|
2015-05-17 02:00:00 +03:00
|
|
|
#include "Entry.hh"
|
|
|
|
#include "Syncer.hh"
|
|
|
|
|
2015-05-16 00:27:59 +03:00
|
|
|
#include "json/Val.hh"
|
2012-05-20 19:16:59 +04:00
|
|
|
#include "util/CArray.hh"
|
2012-05-19 12:18:33 +04:00
|
|
|
#include "util/Crypt.hh"
|
2012-06-03 14:31:02 +04:00
|
|
|
#include "util/log/Log.hh"
|
2012-04-27 06:49:33 +04:00
|
|
|
#include "util/OS.hh"
|
2013-04-28 14:40:21 +04:00
|
|
|
#include "util/File.hh"
|
2012-04-26 19:40:38 +04:00
|
|
|
|
2015-12-13 01:53:35 +03:00
|
|
|
#include <boost/exception/all.hpp>
|
|
|
|
#include <boost/filesystem.hpp>
|
2012-06-10 19:38:27 +04:00
|
|
|
#include <boost/bind.hpp>
|
2012-06-03 12:42:51 +04:00
|
|
|
|
2015-12-13 01:53:35 +03:00
|
|
|
#include <errno.h>
|
|
|
|
|
2012-04-26 20:55:10 +04:00
|
|
|
#include <cassert>
|
|
|
|
|
2012-04-26 19:40:38 +04:00
|
|
|
// for debugging
|
|
|
|
#include <iostream>
|
|
|
|
|
2015-05-17 02:00:00 +03:00
|
|
|
namespace gr {
|
2012-05-27 20:24:37 +04:00
|
|
|
|
2012-05-20 10:57:25 +04:00
|
|
|
/// default constructor creates the root folder
|
2015-05-17 16:54:04 +03:00
|
|
|
Resource::Resource( const fs::path& root_folder ) :
|
2012-07-30 12:52:59 +04:00
|
|
|
m_name ( root_folder.string() ),
|
2012-06-10 12:18:21 +04:00
|
|
|
m_kind ( "folder" ),
|
|
|
|
m_id ( "folder:root" ),
|
2015-05-17 21:05:24 +03:00
|
|
|
m_href ( "root" ),
|
2016-01-01 18:15:34 +03:00
|
|
|
m_is_editable( true ),
|
2016-01-01 02:27:21 +03:00
|
|
|
m_parent ( 0 ),
|
|
|
|
m_state ( sync ),
|
|
|
|
m_json ( NULL )
|
2012-05-20 10:57:25 +04:00
|
|
|
{
|
|
|
|
}
|
2012-05-31 15:22:18 +04:00
|
|
|
|
2012-05-31 19:37:47 +04:00
|
|
|
Resource::Resource( const std::string& name, const std::string& kind ) :
|
2012-06-10 12:18:21 +04:00
|
|
|
m_name ( name ),
|
|
|
|
m_kind ( kind ),
|
2016-01-01 18:15:34 +03:00
|
|
|
m_is_editable( true ),
|
2016-01-01 02:27:21 +03:00
|
|
|
m_parent ( 0 ),
|
|
|
|
m_state ( unknown ),
|
|
|
|
m_json ( NULL )
|
2012-04-26 19:40:38 +04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2012-06-18 19:44:28 +04:00
|
|
|
void Resource::SetState( State new_state )
|
|
|
|
{
|
|
|
|
// only the new and delete states need to be set recursively
|
|
|
|
assert(
|
|
|
|
new_state == remote_new || new_state == remote_deleted ||
|
|
|
|
new_state == local_new || new_state == local_deleted
|
|
|
|
) ;
|
|
|
|
|
|
|
|
m_state = new_state ;
|
|
|
|
std::for_each( m_child.begin(), m_child.end(),
|
|
|
|
boost::bind( &Resource::SetState, _1, new_state ) ) ;
|
|
|
|
}
|
|
|
|
|
2016-01-01 22:04:39 +03:00
|
|
|
void Resource::FromRemoteFolder( const Entry& remote )
|
2012-05-17 20:37:11 +04:00
|
|
|
{
|
2012-05-30 21:17:22 +04:00
|
|
|
fs::path path = Path() ;
|
|
|
|
|
2015-05-16 12:35:41 +03:00
|
|
|
if ( !remote.IsEditable() )
|
2012-06-03 10:44:09 +04:00
|
|
|
Log( "folder %1% is read-only", path, log::verbose ) ;
|
2016-01-01 22:04:39 +03:00
|
|
|
|
2012-05-31 19:48:30 +04:00
|
|
|
// already sync
|
|
|
|
if ( fs::is_directory( path ) )
|
2012-05-27 18:49:49 +04:00
|
|
|
{
|
2012-05-31 19:48:30 +04:00
|
|
|
Log( "folder %1% is in sync", path, log::verbose ) ;
|
|
|
|
m_state = sync ;
|
|
|
|
}
|
2016-01-01 22:04:39 +03:00
|
|
|
else if ( fs::exists( path ) )
|
|
|
|
{
|
|
|
|
// TODO: handle type change
|
|
|
|
Log( "%1% changed from folder to file", path, log::verbose ) ;
|
|
|
|
m_state = sync ;
|
|
|
|
}
|
2016-01-02 15:04:28 +03:00
|
|
|
else if ( remote.MTime().Sec() > m_mtime.Sec() ) // FIXME only seconds are stored in local index
|
2012-05-31 19:48:30 +04:00
|
|
|
{
|
2016-01-02 15:04:28 +03:00
|
|
|
// remote folder created after last sync, so remote is newer
|
2016-01-01 22:04:39 +03:00
|
|
|
Log( "folder %1% is created in remote", path, log::verbose ) ;
|
|
|
|
SetState( remote_new ) ;
|
2012-05-31 19:48:30 +04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-01 22:04:39 +03:00
|
|
|
Log( "folder %1% is deleted in local", path, log::verbose ) ;
|
|
|
|
SetState( local_deleted ) ;
|
2012-05-27 18:49:49 +04:00
|
|
|
}
|
2012-05-31 19:48:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
2016-01-01 22:04:39 +03:00
|
|
|
void Resource::FromRemote( const Entry& remote )
|
2012-05-31 19:48:30 +04:00
|
|
|
{
|
|
|
|
// sync folder
|
2015-05-16 12:35:41 +03:00
|
|
|
if ( remote.IsDir() && IsFolder() )
|
2016-01-01 22:04:39 +03:00
|
|
|
FromRemoteFolder( remote ) ;
|
2012-06-03 11:55:32 +04:00
|
|
|
else
|
2016-01-01 22:04:39 +03:00
|
|
|
FromRemoteFile( remote ) ;
|
2012-05-27 18:49:49 +04:00
|
|
|
|
2012-06-10 12:18:21 +04:00
|
|
|
AssignIDs( remote ) ;
|
|
|
|
|
2012-06-03 11:55:32 +04:00
|
|
|
assert( m_state != unknown ) ;
|
2012-06-03 12:57:21 +04:00
|
|
|
|
|
|
|
if ( m_state == remote_new || m_state == remote_changed )
|
2016-01-01 02:27:21 +03:00
|
|
|
m_md5 = remote.MD5() ;
|
2016-01-02 15:04:28 +03:00
|
|
|
|
2016-01-01 02:27:21 +03:00
|
|
|
m_mtime = remote.MTime() ;
|
2012-06-10 12:18:21 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void Resource::AssignIDs( const Entry& remote )
|
|
|
|
{
|
2012-06-10 17:03:32 +04:00
|
|
|
// the IDs from change feed entries are different
|
|
|
|
if ( !remote.IsChange() )
|
|
|
|
{
|
|
|
|
m_id = remote.ResourceID() ;
|
|
|
|
m_href = remote.SelfHref() ;
|
|
|
|
m_content = remote.ContentSrc() ;
|
2015-05-17 02:00:00 +03:00
|
|
|
m_is_editable = remote.IsEditable() ;
|
2012-06-10 17:03:32 +04:00
|
|
|
m_etag = remote.ETag() ;
|
|
|
|
}
|
2012-06-03 11:55:32 +04:00
|
|
|
}
|
|
|
|
|
2016-01-01 22:04:39 +03:00
|
|
|
void Resource::FromRemoteFile( const Entry& remote )
|
2012-06-03 11:55:32 +04:00
|
|
|
{
|
|
|
|
assert( m_parent != 0 ) ;
|
|
|
|
|
|
|
|
fs::path path = Path() ;
|
|
|
|
|
|
|
|
// recursively create/delete folder
|
|
|
|
if ( m_parent->m_state == remote_new || m_parent->m_state == remote_deleted ||
|
|
|
|
m_parent->m_state == local_new || m_parent->m_state == local_deleted )
|
|
|
|
{
|
|
|
|
Log( "file %1% parent %2% recursively in %3% (%4%)", path,
|
|
|
|
( m_parent->m_state == remote_new || m_parent->m_state == local_new ) ? "created" : "deleted",
|
2012-06-05 19:08:03 +04:00
|
|
|
( m_parent->m_state == remote_new || m_parent->m_state == remote_deleted ) ? "remote" : "local",
|
2012-06-18 19:44:28 +04:00
|
|
|
m_parent->m_state, log::verbose ) ;
|
2012-06-03 11:55:32 +04:00
|
|
|
|
|
|
|
m_state = m_parent->m_state ;
|
|
|
|
}
|
|
|
|
|
2012-05-30 21:17:22 +04:00
|
|
|
// local not exists
|
|
|
|
else if ( !fs::exists( path ) )
|
|
|
|
{
|
2012-06-03 19:58:28 +04:00
|
|
|
Trace( "file %1% change stamp = %2%", Path(), remote.ChangeStamp() ) ;
|
|
|
|
|
2016-01-02 15:04:28 +03:00
|
|
|
if ( remote.MTime().Sec() > m_mtime.Sec() || remote.MD5() != m_md5 || remote.ChangeStamp() > 0 )
|
2012-05-30 21:17:22 +04:00
|
|
|
{
|
2012-06-05 19:08:03 +04:00
|
|
|
Log( "file %1% is created in remote (change %2%)", path,
|
|
|
|
remote.ChangeStamp(), log::verbose ) ;
|
|
|
|
|
2012-05-30 21:17:22 +04:00
|
|
|
m_state = remote_new ;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-05-31 20:40:07 +04:00
|
|
|
Log( "file %1% is deleted in local", path, log::verbose ) ;
|
2012-05-30 21:17:22 +04:00
|
|
|
m_state = local_deleted ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-16 19:37:50 +04:00
|
|
|
// remote checksum unknown, assume the file is not changed in remote
|
|
|
|
else if ( remote.MD5().empty() )
|
|
|
|
{
|
|
|
|
Log( "file %1% has unknown checksum in remote. assuned in sync",
|
|
|
|
Path(), log::verbose ) ;
|
|
|
|
m_state = sync ;
|
|
|
|
}
|
|
|
|
|
2012-05-31 15:22:18 +04:00
|
|
|
// if checksum is equal, no need to compare the mtime
|
2012-06-10 12:18:21 +04:00
|
|
|
else if ( remote.MD5() == m_md5 )
|
2012-05-31 15:22:18 +04:00
|
|
|
{
|
|
|
|
Log( "file %1% is already in sync", Path(), log::verbose ) ;
|
|
|
|
m_state = sync ;
|
|
|
|
}
|
|
|
|
|
2012-05-20 10:57:25 +04:00
|
|
|
// use mtime to check which one is more recent
|
|
|
|
else
|
|
|
|
{
|
2012-06-10 17:03:32 +04:00
|
|
|
assert( m_state != unknown ) ;
|
2012-05-29 21:30:06 +04:00
|
|
|
|
|
|
|
// if remote is modified
|
2016-01-01 22:04:39 +03:00
|
|
|
if ( remote.MTime().Sec() > m_mtime.Sec() )
|
2012-05-31 15:22:18 +04:00
|
|
|
{
|
|
|
|
Log( "file %1% is changed in remote", path, log::verbose ) ;
|
2012-05-29 21:30:06 +04:00
|
|
|
m_state = remote_changed ;
|
2012-05-31 15:22:18 +04:00
|
|
|
}
|
2012-05-29 21:30:06 +04:00
|
|
|
|
|
|
|
// remote also has the file, so it's not new in local
|
2012-05-30 21:17:22 +04:00
|
|
|
else if ( m_state == local_new || m_state == remote_deleted )
|
2012-05-31 15:22:18 +04:00
|
|
|
{
|
|
|
|
Log( "file %1% is changed in local", path, log::verbose ) ;
|
2012-05-29 21:30:06 +04:00
|
|
|
m_state = local_changed ;
|
2012-05-31 15:22:18 +04:00
|
|
|
}
|
2012-05-31 19:48:30 +04:00
|
|
|
else
|
2012-06-10 12:18:21 +04:00
|
|
|
Trace( "file %1% state is %2%", m_name, m_state ) ;
|
2012-05-20 10:57:25 +04:00
|
|
|
}
|
2012-05-17 20:37:11 +04:00
|
|
|
}
|
|
|
|
|
2016-01-02 15:04:28 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-05-29 21:30:06 +04:00
|
|
|
/// Update the resource with the attributes of local file or directory. This
|
|
|
|
/// function will propulate the fields in m_entry.
|
2016-01-01 22:04:39 +03:00
|
|
|
void Resource::FromLocal( Val& state )
|
2012-05-20 15:28:44 +04:00
|
|
|
{
|
2016-01-01 02:27:21 +03:00
|
|
|
assert( !m_json );
|
|
|
|
m_json = &state;
|
2012-05-27 20:24:37 +04:00
|
|
|
|
2012-05-31 19:37:47 +04:00
|
|
|
// root folder is always in sync
|
|
|
|
if ( !IsRoot() )
|
2012-05-20 15:28:44 +04:00
|
|
|
{
|
2016-01-01 02:27:21 +03:00
|
|
|
fs::path path = Path() ;
|
|
|
|
bool is_dir;
|
2016-01-02 15:04:28 +03:00
|
|
|
os::Stat( path, &m_ctime, NULL, &is_dir ) ;
|
2016-01-01 02:27:21 +03:00
|
|
|
|
|
|
|
m_name = path.filename().string() ;
|
2016-01-01 22:04:39 +03:00
|
|
|
m_kind = is_dir ? "folder" : "file";
|
|
|
|
|
|
|
|
bool is_changed;
|
|
|
|
if ( state.Has( "ctime" ) && (u64_t) m_ctime.Sec() <= state["ctime"].U64() &&
|
|
|
|
( is_dir || state.Has( "md5" ) ) )
|
2016-01-01 02:27:21 +03:00
|
|
|
{
|
2016-01-01 22:04:39 +03:00
|
|
|
if ( !is_dir )
|
2016-01-01 02:27:21 +03:00
|
|
|
m_md5 = state["md5"];
|
2016-01-01 22:04:39 +03:00
|
|
|
is_changed = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( !is_dir )
|
2016-01-01 02:27:21 +03:00
|
|
|
{
|
|
|
|
m_md5 = crypt::MD5::Get( path );
|
2016-01-01 22:04:39 +03:00
|
|
|
// File is changed locally. TODO: Detect conflicts
|
2016-01-02 15:04:28 +03:00
|
|
|
is_changed = !state.Has( "md5" ) || m_md5 != state["md5"].Str();
|
2016-01-01 02:27:21 +03:00
|
|
|
state.Set( "md5", Val( m_md5 ) );
|
|
|
|
}
|
2016-01-01 22:04:39 +03:00
|
|
|
else
|
|
|
|
is_changed = true;
|
|
|
|
state.Set( "ctime", Val( m_ctime.Sec() ) );
|
2016-01-01 02:27:21 +03:00
|
|
|
}
|
2016-01-03 01:57:54 +03:00
|
|
|
if ( state.Has( "srv_time" ) )
|
2016-01-01 22:04:39 +03:00
|
|
|
m_mtime.Assign( state[ "srv_time" ].U64(), 0 ) ;
|
|
|
|
if ( is_dir )
|
2016-01-01 02:27:21 +03:00
|
|
|
state.Del( "md5" );
|
2016-01-02 15:04:28 +03:00
|
|
|
else
|
|
|
|
state.Del( "tree" );
|
2012-06-13 21:42:41 +04:00
|
|
|
|
|
|
|
// follow parent recursively
|
2016-01-02 15:04:28 +03:00
|
|
|
if ( m_parent->m_state == local_new || m_parent->m_state == remote_deleted )
|
2016-01-01 22:04:39 +03:00
|
|
|
m_state = m_parent->m_state ;
|
2012-06-13 21:42:41 +04:00
|
|
|
else
|
2016-01-01 22:04:39 +03:00
|
|
|
{
|
|
|
|
// Upload file if it is changed and remove if not.
|
|
|
|
// State will be updated to sync/remote_changed in FromRemote()
|
|
|
|
m_state = is_changed ? local_new : remote_deleted;
|
|
|
|
}
|
2012-05-20 19:16:59 +04:00
|
|
|
}
|
2012-05-31 19:37:47 +04:00
|
|
|
|
|
|
|
assert( m_state != unknown ) ;
|
2012-05-20 15:28:44 +04:00
|
|
|
}
|
|
|
|
|
2012-05-19 11:41:21 +04:00
|
|
|
std::string Resource::SelfHref() const
|
2012-04-26 19:40:38 +04:00
|
|
|
{
|
2012-06-10 12:18:21 +04:00
|
|
|
return m_href ;
|
2012-04-26 19:40:38 +04:00
|
|
|
}
|
|
|
|
|
2015-05-17 02:00:00 +03:00
|
|
|
std::string Resource::ContentSrc() const
|
|
|
|
{
|
|
|
|
return m_content ;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Resource::ETag() const
|
|
|
|
{
|
|
|
|
return m_etag ;
|
|
|
|
}
|
|
|
|
|
2012-05-19 13:14:04 +04:00
|
|
|
std::string Resource::Name() const
|
2012-04-26 19:40:38 +04:00
|
|
|
{
|
2012-06-10 12:18:21 +04:00
|
|
|
return m_name ;
|
2012-04-26 19:40:38 +04:00
|
|
|
}
|
|
|
|
|
2015-05-17 02:00:00 +03:00
|
|
|
std::string Resource::Kind() const
|
|
|
|
{
|
|
|
|
return m_kind ;
|
|
|
|
}
|
|
|
|
|
2016-01-01 02:27:21 +03:00
|
|
|
DateTime Resource::ServerTime() const
|
2015-05-17 02:00:00 +03:00
|
|
|
{
|
|
|
|
return m_mtime ;
|
|
|
|
}
|
|
|
|
|
2012-05-19 11:41:21 +04:00
|
|
|
std::string Resource::ResourceID() const
|
2012-05-17 20:37:11 +04:00
|
|
|
{
|
2012-06-10 12:18:21 +04:00
|
|
|
return m_id ;
|
2012-05-17 20:37:11 +04:00
|
|
|
}
|
|
|
|
|
2015-05-17 02:00:00 +03:00
|
|
|
Resource::State Resource::GetState() const
|
|
|
|
{
|
|
|
|
return m_state ;
|
|
|
|
}
|
|
|
|
|
2012-05-19 11:41:21 +04:00
|
|
|
const Resource* Resource::Parent() const
|
2012-04-26 20:55:10 +04:00
|
|
|
{
|
2012-05-24 13:47:26 +04:00
|
|
|
assert( m_parent == 0 || m_parent->IsFolder() ) ;
|
2012-04-26 20:55:10 +04:00
|
|
|
return m_parent ;
|
|
|
|
}
|
|
|
|
|
2012-05-19 11:41:21 +04:00
|
|
|
Resource* Resource::Parent()
|
2012-04-26 19:40:38 +04:00
|
|
|
{
|
2012-05-24 13:47:26 +04:00
|
|
|
assert( m_parent == 0 || m_parent->IsFolder() ) ;
|
2012-04-26 19:40:38 +04:00
|
|
|
return m_parent ;
|
|
|
|
}
|
|
|
|
|
2012-05-19 11:41:21 +04:00
|
|
|
void Resource::AddChild( Resource *child )
|
2012-04-26 19:40:38 +04:00
|
|
|
{
|
2012-04-26 20:55:10 +04:00
|
|
|
assert( child != 0 ) ;
|
2012-05-20 13:12:01 +04:00
|
|
|
assert( child->m_parent == 0 || child->m_parent == this ) ;
|
2012-04-26 20:55:10 +04:00
|
|
|
assert( child != this ) ;
|
|
|
|
|
|
|
|
child->m_parent = this ;
|
2012-04-26 19:40:38 +04:00
|
|
|
m_child.push_back( child ) ;
|
|
|
|
}
|
|
|
|
|
2012-05-19 12:18:33 +04:00
|
|
|
bool Resource::IsFolder() const
|
|
|
|
{
|
2012-06-10 12:18:21 +04:00
|
|
|
return m_kind == "folder" ;
|
2012-05-19 12:18:33 +04:00
|
|
|
}
|
|
|
|
|
2015-05-17 02:00:00 +03:00
|
|
|
bool Resource::IsEditable() const
|
|
|
|
{
|
|
|
|
return m_is_editable ;
|
|
|
|
}
|
|
|
|
|
2012-05-19 12:18:33 +04:00
|
|
|
fs::path Resource::Path() const
|
2012-04-26 20:55:10 +04:00
|
|
|
{
|
|
|
|
assert( m_parent != this ) ;
|
2012-05-19 14:03:02 +04:00
|
|
|
assert( m_parent == 0 || m_parent->IsFolder() ) ;
|
|
|
|
|
2012-06-10 19:11:32 +04:00
|
|
|
return m_parent != 0 ? (m_parent->Path() / m_name) : m_name ;
|
2012-04-26 20:55:10 +04:00
|
|
|
}
|
|
|
|
|
2015-09-25 13:01:26 +03:00
|
|
|
// Path relative to the root directory
|
|
|
|
fs::path Resource::RelPath() const
|
|
|
|
{
|
|
|
|
assert( m_parent != this ) ;
|
|
|
|
assert( m_parent == 0 || m_parent->IsFolder() ) ;
|
|
|
|
|
2015-12-12 16:44:02 +03:00
|
|
|
return m_parent != 0 && !m_parent->IsRoot() ? (m_parent->RelPath() / m_name) : m_name ;
|
2015-09-25 13:01:26 +03:00
|
|
|
}
|
|
|
|
|
2012-05-19 11:41:21 +04:00
|
|
|
bool Resource::IsInRootTree() const
|
2012-05-12 21:57:15 +04:00
|
|
|
{
|
2012-05-19 14:03:02 +04:00
|
|
|
assert( m_parent == 0 || m_parent->IsFolder() ) ;
|
2015-05-17 16:54:04 +03:00
|
|
|
return m_parent == 0 ? IsRoot() : m_parent->IsInRootTree() ;
|
2012-05-12 21:57:15 +04:00
|
|
|
}
|
|
|
|
|
2012-05-19 13:14:04 +04:00
|
|
|
Resource* Resource::FindChild( const std::string& name )
|
2012-05-17 20:37:11 +04:00
|
|
|
{
|
2012-05-19 11:41:21 +04:00
|
|
|
for ( std::vector<Resource*>::iterator i = m_child.begin() ; i != m_child.end() ; ++i )
|
2012-05-17 20:37:11 +04:00
|
|
|
{
|
|
|
|
assert( (*i)->m_parent == this ) ;
|
2012-06-10 12:18:21 +04:00
|
|
|
if ( (*i)->m_name == name )
|
2012-05-17 20:37:11 +04:00
|
|
|
return *i ;
|
|
|
|
}
|
|
|
|
return 0 ;
|
|
|
|
}
|
2012-05-24 14:57:37 +04:00
|
|
|
|
2012-05-20 10:57:25 +04:00
|
|
|
// try to change the state to "sync"
|
2016-01-03 01:57:54 +03:00
|
|
|
void Resource::Sync( Syncer *syncer, ResourceTree *res_tree, const Val& options )
|
2012-05-19 12:18:33 +04:00
|
|
|
{
|
2012-05-31 19:37:47 +04:00
|
|
|
assert( m_state != unknown ) ;
|
2012-06-10 19:38:27 +04:00
|
|
|
assert( !IsRoot() || m_state == sync ) ; // root folder is already synced
|
2012-05-19 12:18:33 +04:00
|
|
|
|
2015-12-13 01:53:35 +03:00
|
|
|
try
|
|
|
|
{
|
2016-01-03 01:57:54 +03:00
|
|
|
SyncSelf( syncer, res_tree, options ) ;
|
2015-12-13 01:53:35 +03:00
|
|
|
}
|
|
|
|
catch ( File::Error &e )
|
|
|
|
{
|
|
|
|
int *en = boost::get_error_info< boost::errinfo_errno > ( e ) ;
|
|
|
|
Log( "Error syncing %1%: %2%", Path(), en ? strerror( *en ) : "", log::error );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
catch ( boost::filesystem::filesystem_error &e )
|
|
|
|
{
|
|
|
|
Log( "Error syncing %1%: %2%", Path(), e.what(), log::error );
|
|
|
|
return;
|
|
|
|
}
|
2012-07-26 20:45:53 +04:00
|
|
|
|
2012-06-10 20:40:56 +04:00
|
|
|
// if myself is deleted, no need to do the childrens
|
|
|
|
if ( m_state != local_deleted && m_state != remote_deleted )
|
2016-01-01 02:27:21 +03:00
|
|
|
{
|
2012-06-10 20:40:56 +04:00
|
|
|
std::for_each( m_child.begin(), m_child.end(),
|
2016-01-03 01:57:54 +03:00
|
|
|
boost::bind( &Resource::Sync, _1, syncer, res_tree, options ) ) ;
|
2016-01-01 02:27:21 +03:00
|
|
|
}
|
2012-06-10 19:38:27 +04:00
|
|
|
}
|
|
|
|
|
2016-01-03 01:57:54 +03:00
|
|
|
void Resource::SyncSelf( Syncer* syncer, ResourceTree *res_tree, const Val& options )
|
2012-06-10 19:38:27 +04:00
|
|
|
{
|
2012-06-18 19:17:28 +04:00
|
|
|
assert( !IsRoot() || m_state == sync ) ; // root is always sync
|
2015-05-17 02:00:00 +03:00
|
|
|
assert( IsRoot() || !syncer || m_parent->IsFolder() ) ;
|
2012-06-10 20:40:56 +04:00
|
|
|
assert( IsRoot() || m_parent->m_state != remote_deleted ) ;
|
|
|
|
assert( IsRoot() || m_parent->m_state != local_deleted ) ;
|
|
|
|
|
|
|
|
const fs::path path = Path() ;
|
|
|
|
|
2016-01-03 01:57:54 +03:00
|
|
|
// Detect renames
|
|
|
|
if ( !IsFolder() && ( m_state == local_new || m_state == local_deleted ||
|
|
|
|
m_state == remote_new || m_state == remote_deleted ) )
|
|
|
|
{
|
|
|
|
details::MD5Range moved = res_tree->FindByMD5( m_md5 );
|
|
|
|
bool is_local = m_state == local_new || m_state == local_deleted;
|
|
|
|
State other;
|
|
|
|
if ( m_state == local_new )
|
|
|
|
other = local_deleted;
|
|
|
|
else if ( m_state == local_deleted )
|
|
|
|
other = local_new;
|
|
|
|
else if ( m_state == remote_new )
|
|
|
|
other = remote_deleted;
|
|
|
|
else
|
|
|
|
other = remote_new;
|
|
|
|
for ( details::MD5Map::iterator i = moved.first ; i != moved.second; i++ )
|
|
|
|
{
|
|
|
|
Resource *m = *i;
|
|
|
|
if ( m->m_state == other )
|
|
|
|
{
|
|
|
|
Resource* from = m_state == local_new || m_state == remote_new ? m : this;
|
|
|
|
Resource* to = m_state == local_new || m_state == remote_new ? this : m;
|
|
|
|
Log( "sync %1% moved to %2%. moving %3%", from->Path(), to->Path(),
|
|
|
|
is_local ? "remote" : "local", log::info );
|
|
|
|
if ( syncer )
|
|
|
|
{
|
|
|
|
if ( is_local )
|
2016-01-03 01:57:54 +03:00
|
|
|
syncer->Move( from, to );
|
2016-01-03 01:57:54 +03:00
|
|
|
else
|
|
|
|
{
|
|
|
|
fs::rename( from->Path(), to->Path() );
|
|
|
|
to->SetIndex();
|
|
|
|
}
|
|
|
|
to->m_mtime = from->m_mtime;
|
|
|
|
to->m_json->Set( "srv_time", Val( from->m_mtime.Sec() ) );
|
|
|
|
from->DeleteIndex();
|
|
|
|
}
|
|
|
|
from->m_state = both_deleted;
|
|
|
|
to->m_state = sync;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-20 10:57:25 +04:00
|
|
|
switch ( m_state )
|
2012-05-19 12:18:33 +04:00
|
|
|
{
|
2012-05-20 10:57:25 +04:00
|
|
|
case local_new :
|
2012-06-10 20:40:56 +04:00
|
|
|
Log( "sync %1% doesn't exist in server, uploading", path, log::info ) ;
|
2012-05-27 18:49:49 +04:00
|
|
|
|
2015-05-21 19:20:58 +03:00
|
|
|
// FIXME: (?) do not write new timestamp on failed upload
|
2015-05-17 02:00:00 +03:00
|
|
|
if ( syncer && syncer->Create( this ) )
|
2012-05-20 21:18:16 +04:00
|
|
|
m_state = sync ;
|
2012-05-20 19:16:59 +04:00
|
|
|
break ;
|
|
|
|
|
|
|
|
case local_deleted :
|
2012-06-10 20:40:56 +04:00
|
|
|
Log( "sync %1% deleted in local. deleting remote", path, log::info ) ;
|
2015-05-17 02:00:00 +03:00
|
|
|
if ( syncer )
|
2016-01-02 15:04:28 +03:00
|
|
|
{
|
2015-05-17 02:00:00 +03:00
|
|
|
syncer->DeleteRemote( this ) ;
|
2016-01-02 15:04:28 +03:00
|
|
|
DeleteIndex() ;
|
|
|
|
}
|
2012-05-20 10:57:25 +04:00
|
|
|
break ;
|
|
|
|
|
|
|
|
case local_changed :
|
2012-06-10 20:40:56 +04:00
|
|
|
Log( "sync %1% changed in local. uploading", path, log::info ) ;
|
2015-05-17 02:00:00 +03:00
|
|
|
if ( syncer && syncer->EditContent( this, options["new-rev"].Bool() ) )
|
2012-05-20 10:57:25 +04:00
|
|
|
m_state = sync ;
|
|
|
|
break ;
|
|
|
|
|
|
|
|
case remote_new :
|
2012-06-10 20:40:56 +04:00
|
|
|
Log( "sync %1% created in remote. creating local", path, log::info ) ;
|
2015-05-17 02:00:00 +03:00
|
|
|
if ( syncer )
|
2012-06-17 12:33:38 +04:00
|
|
|
{
|
|
|
|
if ( IsFolder() )
|
|
|
|
fs::create_directories( path ) ;
|
|
|
|
else
|
2015-05-17 02:00:00 +03:00
|
|
|
syncer->Download( this, path ) ;
|
2016-01-01 02:27:21 +03:00
|
|
|
SetIndex() ;
|
2012-06-17 12:33:38 +04:00
|
|
|
m_state = sync ;
|
|
|
|
}
|
2012-06-10 20:40:56 +04:00
|
|
|
break ;
|
|
|
|
|
2012-05-20 10:57:25 +04:00
|
|
|
case remote_changed :
|
2012-06-10 20:40:56 +04:00
|
|
|
assert( !IsFolder() ) ;
|
|
|
|
Log( "sync %1% changed in remote. downloading", path, log::info ) ;
|
2015-05-17 02:00:00 +03:00
|
|
|
if ( syncer )
|
2012-06-17 12:33:38 +04:00
|
|
|
{
|
2015-05-17 02:00:00 +03:00
|
|
|
syncer->Download( this, path ) ;
|
2016-01-01 02:27:21 +03:00
|
|
|
SetIndex() ;
|
2012-06-17 12:33:38 +04:00
|
|
|
m_state = sync ;
|
|
|
|
}
|
2012-05-20 10:57:25 +04:00
|
|
|
break ;
|
|
|
|
|
2012-05-30 21:17:22 +04:00
|
|
|
case remote_deleted :
|
2012-06-10 20:40:56 +04:00
|
|
|
Log( "sync %1% deleted in remote. deleting local", path, log::info ) ;
|
2015-05-17 02:00:00 +03:00
|
|
|
if ( syncer )
|
2016-01-02 15:04:28 +03:00
|
|
|
{
|
2012-06-17 12:33:38 +04:00
|
|
|
DeleteLocal() ;
|
2016-01-02 15:04:28 +03:00
|
|
|
DeleteIndex() ;
|
|
|
|
}
|
|
|
|
break ;
|
|
|
|
|
|
|
|
case both_deleted :
|
|
|
|
if ( syncer )
|
|
|
|
DeleteIndex() ;
|
2012-05-30 21:17:22 +04:00
|
|
|
break ;
|
|
|
|
|
2012-05-20 10:57:25 +04:00
|
|
|
case sync :
|
2012-06-10 20:40:56 +04:00
|
|
|
Log( "sync %1% already in sync", path, log::verbose ) ;
|
2012-05-20 19:16:59 +04:00
|
|
|
break ;
|
2016-01-01 02:27:21 +03:00
|
|
|
|
2012-06-10 20:40:56 +04:00
|
|
|
// shouldn't go here
|
2012-06-10 19:38:27 +04:00
|
|
|
case unknown :
|
|
|
|
assert( false ) ;
|
|
|
|
break ;
|
2012-05-20 19:16:59 +04:00
|
|
|
|
2012-05-20 10:57:25 +04:00
|
|
|
default :
|
|
|
|
break ;
|
2012-05-19 12:18:33 +04:00
|
|
|
}
|
2016-01-01 02:27:21 +03:00
|
|
|
|
2016-01-02 15:04:28 +03:00
|
|
|
if ( syncer && m_json )
|
2016-01-01 02:27:21 +03:00
|
|
|
{
|
|
|
|
// Update server time of this file
|
|
|
|
m_json->Set( "srv_time", Val( m_mtime.Sec() ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Resource::SetServerTime( const DateTime& time )
|
|
|
|
{
|
|
|
|
m_mtime = time ;
|
2012-05-19 12:18:33 +04:00
|
|
|
}
|
|
|
|
|
2012-05-31 20:40:07 +04:00
|
|
|
/// this function doesn't really remove the local file. it renames it.
|
|
|
|
void Resource::DeleteLocal()
|
|
|
|
{
|
2012-06-03 17:53:51 +04:00
|
|
|
static const boost::format trash_file( "%1%-%2%" ) ;
|
|
|
|
|
2016-01-01 02:27:21 +03:00
|
|
|
assert( m_parent != NULL ) ;
|
2015-10-08 01:41:33 +03:00
|
|
|
Resource* p = m_parent;
|
|
|
|
fs::path destdir;
|
|
|
|
while ( !p->IsRoot() )
|
|
|
|
{
|
|
|
|
destdir = p->Name() / destdir;
|
|
|
|
p = p->Parent();
|
|
|
|
}
|
|
|
|
destdir = p->Path() / ".trash" / destdir;
|
|
|
|
|
|
|
|
fs::path dest = destdir / Name();
|
2012-05-31 20:40:07 +04:00
|
|
|
std::size_t idx = 1 ;
|
|
|
|
while ( fs::exists( dest ) && idx != 0 )
|
2015-10-08 01:41:33 +03:00
|
|
|
dest = destdir / (boost::format(trash_file) % Name() % idx++).str() ;
|
|
|
|
|
2012-05-31 20:40:07 +04:00
|
|
|
// wrap around! just remove the file
|
|
|
|
if ( idx == 0 )
|
|
|
|
fs::remove_all( Path() ) ;
|
|
|
|
else
|
2012-06-03 17:53:51 +04:00
|
|
|
{
|
|
|
|
fs::create_directories( dest.parent_path() ) ;
|
2012-05-31 20:40:07 +04:00
|
|
|
fs::rename( Path(), dest ) ;
|
2012-06-03 17:53:51 +04:00
|
|
|
}
|
2012-05-31 20:40:07 +04:00
|
|
|
}
|
|
|
|
|
2016-01-02 15:04:28 +03:00
|
|
|
void Resource::DeleteIndex()
|
|
|
|
{
|
|
|
|
(*m_parent->m_json)["tree"].Del( Name() );
|
|
|
|
m_json = NULL;
|
|
|
|
}
|
|
|
|
|
2016-01-01 02:27:21 +03:00
|
|
|
void Resource::SetIndex()
|
|
|
|
{
|
|
|
|
assert( m_parent->m_json != NULL );
|
|
|
|
if ( !m_json )
|
|
|
|
m_json = &((*m_parent->m_json)["tree"]).Item( Name() );
|
|
|
|
bool is_dir;
|
2016-01-02 15:04:28 +03:00
|
|
|
os::Stat( Path(), &m_ctime, NULL, &is_dir );
|
2016-01-01 02:27:21 +03:00
|
|
|
if ( !is_dir )
|
|
|
|
{
|
|
|
|
m_json->Set( "ctime", Val( m_ctime.Sec() ) );
|
|
|
|
m_json->Set( "md5", Val( m_md5 ) );
|
2016-01-02 15:04:28 +03:00
|
|
|
m_json->Del( "tree" );
|
2016-01-01 02:27:21 +03:00
|
|
|
}
|
2016-01-02 15:04:28 +03:00
|
|
|
else // check if tree item exists
|
|
|
|
m_json->Item( "tree" );
|
2016-01-01 02:27:21 +03:00
|
|
|
}
|
|
|
|
|
2012-05-20 13:12:01 +04:00
|
|
|
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() ;
|
|
|
|
}
|
|
|
|
|
2012-05-20 19:16:59 +04:00
|
|
|
std::ostream& operator<<( std::ostream& os, Resource::State s )
|
|
|
|
{
|
|
|
|
static const char *state[] =
|
|
|
|
{
|
2012-05-30 21:17:22 +04:00
|
|
|
"sync", "local_new", "local_changed", "local_deleted", "remote_new",
|
2016-01-02 15:04:28 +03:00
|
|
|
"remote_changed", "remote_deleted", "both_deleted"
|
2012-05-20 19:16:59 +04:00
|
|
|
} ;
|
|
|
|
assert( s >= 0 && s < Count(state) ) ;
|
|
|
|
return os << state[s] ;
|
|
|
|
}
|
|
|
|
|
2012-05-24 13:47:26 +04:00
|
|
|
std::string Resource::StateStr() const
|
|
|
|
{
|
|
|
|
std::ostringstream ss ;
|
|
|
|
ss << m_state ;
|
|
|
|
return ss.str() ;
|
|
|
|
}
|
|
|
|
|
2012-06-10 19:11:32 +04:00
|
|
|
std::string Resource::MD5() const
|
|
|
|
{
|
|
|
|
return m_md5 ;
|
|
|
|
}
|
|
|
|
|
2012-05-27 20:24:37 +04:00
|
|
|
bool Resource::IsRoot() const
|
|
|
|
{
|
2015-05-17 16:54:04 +03:00
|
|
|
// Root entry does not show up in file feeds, so we check for empty parent (and self-href)
|
2012-05-27 20:24:37 +04:00
|
|
|
return m_parent == 0 ;
|
|
|
|
}
|
|
|
|
|
2012-06-03 11:55:32 +04:00
|
|
|
bool Resource::HasID() const
|
|
|
|
{
|
2012-06-10 12:18:21 +04:00
|
|
|
return !m_href.empty() && !m_id.empty() ;
|
2012-06-03 11:55:32 +04:00
|
|
|
}
|
|
|
|
|
2015-05-17 02:00:00 +03:00
|
|
|
} // end of namespace
|