grive2/libgrive/src/drive/Resource.cc

418 lines
10 KiB
C++
Raw Normal View History

/*
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"
#include "CommonUri.hh"
2012-05-19 12:18:33 +04:00
#include "http/Download.hh"
#include "http/StringResponse.hh"
#include "http/XmlResponse.hh"
#include "protocol/Json.hh"
#include "util/CArray.hh"
2012-05-19 12:18:33 +04:00
#include "util/Crypt.hh"
#include "util/Log.hh"
#include "util/OS.hh"
2012-05-20 20:10:29 +04:00
#include "util/StdioFile.hh"
#include "xml/Node.hh"
#include "xml/NodeSet.hh"
2012-04-26 20:55:10 +04:00
#include <cassert>
// for debugging
#include <iostream>
namespace gr {
2012-05-20 10:57:25 +04:00
/// default constructor creates the root folder
Resource::Resource() :
m_parent( 0 ),
m_state ( sync )
{
}
2012-05-20 15:28:44 +04:00
/// Construct from previously serialized JSON object. The state of the
/// resource is treated as local_deleted by default. It is because the
/// state will be updated by scanning the local directory. If the state
/// is not updated during scanning, that means the resource is deleted.
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 ),
2012-05-20 15:28:44 +04:00
m_state( local_deleted )
{
2012-05-20 15:28:44 +04:00
// if the file exists in local directory, FromLocal() will mark the
// state as local_changed
FromLocal() ;
}
2012-05-19 11:41:21 +04:00
Resource::Resource( const xml::Node& entry ) :
2012-05-19 21:44:46 +04:00
m_entry ( entry ),
m_parent( 0 ),
2012-05-20 10:57:25 +04:00
m_state ( remote_new )
{
}
2012-05-19 12:18:33 +04:00
Resource::Resource( const Entry& entry, Resource *parent ) :
2012-05-19 21:44:46 +04:00
m_entry ( entry ),
m_parent( parent ),
2012-05-20 10:57:25 +04:00
m_state ( remote_new )
{
}
2012-05-20 10:57:25 +04:00
Resource::Resource( const fs::path& path ) :
m_entry ( path ),
2012-05-19 21:44:46 +04:00
m_parent( 0 ),
2012-05-20 10:57:25 +04:00
m_state ( local_new )
{
}
2012-05-20 10:57:25 +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.
void Resource::FromRemote( const Entry& remote )
2012-05-17 20:37:11 +04:00
{
2012-05-20 10:57:25 +04:00
// if checksum is equal, no need to compare the mtime
if ( remote.MD5() == m_entry.MD5() )
{
Log( "MD5 matches: %1% is already in sync", Name(), log::verbose ) ;
2012-05-20 10:57:25 +04:00
m_state = sync ;
}
2012-05-20 10:57:25 +04:00
// use mtime to check which one is more recent
else
{
2012-05-20 15:28:44 +04:00
assert( m_state == local_new || m_state == local_changed || m_state == local_deleted ) ;
2012-05-20 20:10:29 +04:00
2012-05-20 15:28:44 +04:00
m_state = ( remote.MTime() > m_entry.MTime() ? remote_changed : m_state ) ;
2012-05-20 20:10:29 +04:00
m_state = ( m_state == local_new ? local_changed : m_state ) ;
Log( "%1% state is %2%", Name(), m_state, log::verbose ) ;
2012-05-20 10:57:25 +04:00
}
2012-05-20 20:10:29 +04:00
m_entry.AssignID( remote ) ;
2012-05-17 20:37:11 +04:00
}
2012-05-20 15:28:44 +04:00
void Resource::FromLocal()
{
fs::path path = Path() ;
if ( !fs::exists( path ) )
{
2012-05-20 15:28:44 +04:00
m_state = local_deleted ;
Log( "%1% in state but not exist on disk: %2%", Name(), m_state ) ;
}
2012-05-20 20:10:29 +04:00
else
2012-05-20 15:28:44 +04:00
{
2012-05-20 20:10:29 +04:00
m_state = local_new ;
// to save time, compare mtime before checksum
DateTime mtime = os::FileMTime( path ) ;
if ( mtime > m_entry.MTime() )
{
2012-05-20 20:10:29 +04:00
Log( "%1% mtime newer on disk: %2%", Name(), m_state ) ;
m_entry.Update( crypt::MD5( path ), mtime ) ;
}
2012-05-20 15:28:44 +04:00
else
2012-05-20 20:10:29 +04:00
Log( "%1% unchanged on disk: %2%", Name(), m_state ) ;
}
2012-05-20 15:28:44 +04:00
}
2012-05-19 11:41:21 +04:00
std::string Resource::SelfHref() const
{
2012-05-09 19:52:06 +04:00
return m_entry.SelfHref() ;
}
2012-05-19 13:14:04 +04:00
std::string Resource::Name() const
{
2012-05-19 13:14:04 +04:00
return IsFolder() ? m_entry.Title() : m_entry.Filename() ;
}
2012-05-19 11:41:21 +04:00
std::string Resource::ResourceID() const
2012-05-17 20:37:11 +04:00
{
return m_entry.ResourceID() ;
}
2012-05-19 11:41:21 +04:00
const Resource* Resource::Parent() const
2012-04-26 20:55:10 +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()
{
assert( m_parent == 0 || m_parent->IsFolder() ) ;
return m_parent ;
}
2012-05-19 11:41:21 +04:00
std::string Resource::ParentHref() const
2012-05-09 19:52:06 +04:00
{
return m_entry.ParentHref() ;
}
2012-05-19 11:41:21 +04:00
void Resource::AddChild( Resource *child )
{
2012-04-26 20:55:10 +04:00
assert( child != 0 ) ;
assert( child->m_parent == 0 || child->m_parent == this ) ;
2012-04-26 20:55:10 +04:00
assert( child != this ) ;
child->m_parent = this ;
m_child.push_back( child ) ;
}
2012-05-19 11:41:21 +04:00
void Resource::Swap( Resource& coll )
{
2012-05-09 19:52:06 +04:00
m_entry.Swap( coll.m_entry ) ;
2012-04-26 20:55:10 +04:00
std::swap( m_parent, coll.m_parent ) ;
m_child.swap( coll.m_child ) ;
std::swap( m_state, coll.m_state ) ;
}
2012-05-19 12:18:33 +04:00
bool Resource::IsFolder() const
{
return m_entry.Kind() == "folder" ;
}
fs::path Resource::Path() const
2012-04-26 20:55:10 +04:00
{
assert( m_parent != this ) ;
assert( m_parent == 0 || m_parent->IsFolder() ) ;
2012-05-19 13:14:04 +04:00
return m_parent != 0 ? (m_parent->Path() / Name()) : "." ;
2012-04-26 20:55:10 +04:00
}
2012-05-19 11:41:21 +04:00
bool Resource::IsInRootTree() const
{
assert( m_parent == 0 || m_parent->IsFolder() ) ;
return m_parent == 0 ? (SelfHref() == root_href) : m_parent->IsInRootTree() ;
}
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-05-19 13:14:04 +04:00
if ( (*i)->Name() == name )
2012-05-17 20:37:11 +04:00
return *i ;
}
return 0 ;
}
2012-05-20 10:57:25 +04:00
// try to change the state to "sync"
void Resource::Sync( http::Agent *http, const http::Headers& auth )
2012-05-19 12:18:33 +04:00
{
// no need to update for folders
if ( IsFolder() )
return ;
2012-05-19 12:18:33 +04:00
assert( m_parent != 0 ) ;
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-05-20 21:18:16 +04:00
Log( "sync %1% %2% doesn't exist in server. upload \"%3%\"?",
m_entry.Title(), m_entry.Filename(), m_parent->m_entry.CreateLink(), log::verbose ) ;
if ( Create( http, auth ) )
m_state = sync ;
break ;
case local_deleted :
Log( "sync %1% deleted in local. delete?", m_entry.Filename(), log::verbose ) ;
2012-05-20 10:57:25 +04:00
break ;
case local_changed :
Log( "sync %1% changed in local", m_entry.Filename(), log::verbose ) ;
if ( EditContent( http, auth ) )
2012-05-20 10:57:25 +04:00
m_state = sync ;
break ;
case remote_new :
case remote_changed :
Log( "sync %1% changed in remote. download?", m_entry.Filename(), log::verbose ) ;
Download( http, Path(), auth ) ;
m_state = sync ;
2012-05-20 10:57:25 +04:00
break ;
case sync :
Log( "sync %1% already in sync", m_entry.Filename(), log::verbose ) ;
break ;
2012-05-20 10:57:25 +04:00
default :
break ;
2012-05-19 12:18:33 +04:00
}
}
void Resource::Delete( http::Agent *http, const http::Headers& auth )
{
http::Headers hdr( auth ) ;
hdr.push_back( "If-Match: " + m_entry.ETag() ) ;
http::StringResponse str ;
http->Custom( "DELETE", feed_base + "/" + m_entry.ResourceID() + "?delete=true", &str, hdr ) ;
}
void Resource::Download( http::Agent* http, const fs::path& file, const http::Headers& auth ) const
{
Log( "Downloading %1%", file ) ;
http::Download dl( file.string(), http::Download::NoChecksum() ) ;
long r = http->Get( m_entry.ContentSrc(), &dl, auth ) ;
if ( r <= 400 )
2012-05-20 10:57:25 +04:00
os::SetFileTime( file, m_entry.MTime() ) ;
}
2012-05-20 21:18:16 +04:00
bool Resource::EditContent( http::Agent* http, const http::Headers& auth )
2012-05-19 12:18:33 +04:00
{
// upload link missing means that file is read only
2012-05-20 21:18:16 +04:00
if ( m_entry.EditLink().empty() )
2012-05-19 12:18:33 +04:00
{
2012-05-19 21:44:46 +04:00
Log( "Cannot upload %1%: file read-only. %2%", m_entry.Title(), m_state, log::warning ) ;
2012-05-19 12:18:33 +04:00
return false ;
}
2012-05-20 21:18:16 +04:00
return Upload( http, m_entry.EditLink(), auth, false ) ;
}
bool Resource::Create( http::Agent* http, const http::Headers& auth )
{
assert( m_parent != 0 ) ;
return Upload( http, m_parent->m_entry.CreateLink() + "?convert=false", auth, true ) ;
}
2012-05-19 12:18:33 +04:00
2012-05-20 21:18:16 +04:00
bool Resource::Upload( http::Agent* http, const std::string& link, const http::Headers& auth, bool post )
{
Log( "Uploading %1%", m_entry.Title() ) ;
2012-05-19 12:18:33 +04:00
std::string meta =
"<?xml version='1.0' encoding='UTF-8'?>\n"
"<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:docs=\"http://schemas.google.com/docs/2007\">"
"<category scheme=\"http://schemas.google.com/g/2005#kind\" "
"term=\"http://schemas.google.com/docs/2007#file\"/>"
"<title>" + m_entry.Filename() + "</title>"
"</entry>" ;
2012-05-27 10:55:26 +04:00
StdioFile file( Path() ) ;
2012-05-19 12:18:33 +04:00
2012-05-24 20:55:28 +04:00
// TODO: upload in chunks
2012-05-20 20:10:29 +04:00
std::string data ;
char buf[4096] ;
std::size_t count = 0 ;
while ( (count = file.Read( buf, sizeof(buf) )) > 0 )
data.append( buf, count ) ;
2012-05-19 12:18:33 +04:00
std::ostringstream xcontent_len ;
xcontent_len << "X-Upload-Content-Length: " << data.size() ;
http::Headers hdr( auth ) ;
hdr.push_back( "Content-Type: application/atom+xml" ) ;
hdr.push_back( "X-Upload-Content-Type: application/octet-stream" ) ;
hdr.push_back( xcontent_len.str() ) ;
hdr.push_back( "If-Match: " + m_entry.ETag() ) ;
hdr.push_back( "Expect:" ) ;
http::StringResponse str ;
2012-05-20 21:18:16 +04:00
if ( post )
http->Post( link, meta, &str, hdr ) ;
else
http->Put( link, meta, &str, hdr ) ;
2012-05-19 12:18:33 +04:00
http::Headers uphdr ;
uphdr.push_back( "Expect:" ) ;
uphdr.push_back( "Accept:" ) ;
2012-05-20 20:10:29 +04:00
// the content upload URL is in the "Location" HTTP header
std::string uplink = http->RedirLocation() ;
http::XmlResponse xml ;
2012-05-19 12:18:33 +04:00
2012-05-20 20:10:29 +04:00
http->Put( uplink, data, &xml, uphdr ) ;
2012-05-19 12:18:33 +04:00
m_entry.Update( xml.Response() ) ;
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<Json> array ;
for ( std::vector<Resource*>::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() ;
}
std::ostream& operator<<( std::ostream& os, Resource::State s )
{
static const char *state[] =
{
"sync", "local_new", "local_changed", "local_deleted", "remote_new", "remote_changed"
} ;
assert( s >= 0 && s < Count(state) ) ;
return os << state[s] ;
}
std::string Resource::StateStr() const
{
std::ostringstream ss ;
ss << m_state ;
return ss.str() ;
}
} // end of namespace
namespace std
{
2012-05-19 11:41:21 +04:00
void swap( gr::Resource& c1, gr::Resource& c2 )
{
c1.Swap( c2 ) ;
}
}