mirror of https://github.com/vitalif/grive2
saved the metadata of the files
parent
3c8c6bab54
commit
30f9834968
|
@ -163,6 +163,8 @@ int main( int argc, char **argv )
|
|||
{
|
||||
OAuth2 token( refresh_token, client_id, client_secret ) ;
|
||||
Drive drive( token ) ;
|
||||
|
||||
drive.Update() ;
|
||||
}
|
||||
catch ( gr::Exception& e )
|
||||
{
|
||||
|
|
|
@ -87,9 +87,9 @@ void Collection::AddChild( Collection *child )
|
|||
m_child.push_back( child ) ;
|
||||
}
|
||||
|
||||
void Collection::AddLeaf( const std::string& filename )
|
||||
void Collection::AddLeaf( File *file )
|
||||
{
|
||||
m_leaves.push_back( filename ) ;
|
||||
m_leaf.push_back( file ) ;
|
||||
}
|
||||
|
||||
void Collection::Swap( Collection& coll )
|
||||
|
@ -97,7 +97,7 @@ void Collection::Swap( Collection& coll )
|
|||
m_entry.Swap( coll.m_entry ) ;
|
||||
std::swap( m_parent, coll.m_parent ) ;
|
||||
m_child.swap( coll.m_child ) ;
|
||||
m_leaves.swap( coll.m_leaves ) ;
|
||||
m_leaf.swap( coll.m_leaf ) ;
|
||||
}
|
||||
|
||||
void Collection::CreateSubDir( const Path& prefix )
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
namespace gr {
|
||||
|
||||
class Path ;
|
||||
class File ;
|
||||
|
||||
class Collection
|
||||
{
|
||||
|
@ -48,7 +49,7 @@ public :
|
|||
bool IsInRootTree() const ;
|
||||
|
||||
void AddChild( Collection *child ) ;
|
||||
void AddLeaf( const std::string& filename ) ;
|
||||
void AddLeaf( File *file ) ;
|
||||
|
||||
void Swap( Collection& coll ) ;
|
||||
|
||||
|
@ -66,8 +67,7 @@ private :
|
|||
// not owned
|
||||
Collection *m_parent ;
|
||||
std::vector<Collection*> m_child ;
|
||||
|
||||
std::vector<std::string> m_leaves ;
|
||||
std::vector<File*> m_leaf ;
|
||||
} ;
|
||||
|
||||
} // end of namespace
|
||||
|
|
|
@ -21,19 +21,20 @@
|
|||
|
||||
#include "CommonUri.hh"
|
||||
#include "Entry.hh"
|
||||
#include "File.hh"
|
||||
|
||||
#include "http/Agent.hh"
|
||||
#include "http/ResponseLog.hh"
|
||||
#include "http/XmlResponse.hh"
|
||||
#include "protocol/OAuth2.hh"
|
||||
#include "util/Crypt.hh"
|
||||
#include "util/DateTime.hh"
|
||||
#include "util/Destroy.hh"
|
||||
#include "util/Log.hh"
|
||||
#include "util/OS.hh"
|
||||
#include "util/Path.hh"
|
||||
#include "xml/Node.hh"
|
||||
#include "xml/NodeSet.hh"
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
// standard C++ library
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
@ -95,6 +96,7 @@ Drive::Drive( OAuth2& auth ) :
|
|||
|
||||
Drive::~Drive( )
|
||||
{
|
||||
std::for_each( m_files.begin(), m_files.end(), Destroy() ) ;
|
||||
}
|
||||
|
||||
struct SortCollectionByHref
|
||||
|
@ -184,41 +186,28 @@ void Drive::ConstructDirTree( http::Agent *http )
|
|||
Root()->CreateSubDir( Path() ) ;
|
||||
}
|
||||
|
||||
void Drive::UpdateFile( Entry& file, const Collection& parent, http::Agent *http )
|
||||
void Drive::UpdateFile( Entry& entry, Collection& parent, http::Agent *http )
|
||||
{
|
||||
// only handle uploaded files
|
||||
if ( !file.Filename().empty() )
|
||||
if ( !entry.Filename().empty() )
|
||||
{
|
||||
bool changed = true ;
|
||||
Path path = parent.Dir() / file.Filename() ;
|
||||
File *file = new File( entry, &parent ) ;
|
||||
m_files.push_back( file ) ;
|
||||
parent.AddLeaf( file ) ;
|
||||
|
||||
// compare checksum first if file exists
|
||||
std::ifstream ifile( path.Str().c_str(), std::ios::binary | std::ios::in ) ;
|
||||
if ( ifile && file.ServerMD5() == crypt::MD5(ifile.rdbuf()) )
|
||||
changed = false ;
|
||||
|
||||
// if the checksum is different, file is changed and we need to update
|
||||
if ( changed )
|
||||
{
|
||||
DateTime remote = file.ServerModified() ;
|
||||
DateTime local = ifile ? os::FileMTime( path ) : DateTime() ;
|
||||
|
||||
// remote file is newer, download file
|
||||
if ( !ifile || remote > local )
|
||||
file.Download( http, path, m_http_hdr ) ;
|
||||
|
||||
else
|
||||
{
|
||||
// re-reading the file
|
||||
ifile.seekg(0) ;
|
||||
file.Upload( http, ifile.rdbuf(), m_http_hdr ) ;
|
||||
}
|
||||
}
|
||||
// file->Update( http, m_http_hdr ) ;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log( "file \"%1%\" is a google document, ignored", file.Title() ) ;
|
||||
Log( "file \"%1%\" is a google document, ignored", entry.Title() ) ;
|
||||
}
|
||||
}
|
||||
|
||||
void Drive::Update()
|
||||
{
|
||||
http::Agent http ;
|
||||
std::for_each( m_files.begin(), m_files.end(),
|
||||
boost::bind( &File::Update, _1, &http, m_http_hdr ) ) ;
|
||||
}
|
||||
|
||||
} // end of namespace
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace xml
|
|||
}
|
||||
|
||||
class OAuth2 ;
|
||||
class Json ;
|
||||
class File ;
|
||||
|
||||
class Drive
|
||||
{
|
||||
|
@ -49,10 +49,12 @@ public :
|
|||
Drive( OAuth2& auth ) ;
|
||||
~Drive( ) ;
|
||||
|
||||
void Update() ;
|
||||
|
||||
struct Error : virtual Exception {} ;
|
||||
|
||||
private :
|
||||
void UpdateFile( Entry& file, const Collection& parent, http::Agent *http ) ;
|
||||
void UpdateFile( Entry& file, Collection& parent, http::Agent *http ) ;
|
||||
|
||||
void ConstructDirTree( http::Agent *http ) ;
|
||||
|
||||
|
@ -66,6 +68,7 @@ private :
|
|||
std::string m_resume_link ;
|
||||
|
||||
FolderList m_coll ;
|
||||
std::vector<File*> m_files ;
|
||||
} ;
|
||||
|
||||
} // end of namespace
|
||||
|
|
|
@ -19,12 +19,6 @@
|
|||
|
||||
#include "Entry.hh"
|
||||
|
||||
#include "CommonUri.hh"
|
||||
|
||||
#include "http/Download.hh"
|
||||
#include "http/StringResponse.hh"
|
||||
#include "http/XmlResponse.hh"
|
||||
#include "protocol/OAuth2.hh"
|
||||
#include "util/Log.hh"
|
||||
#include "util/OS.hh"
|
||||
#include "util/Path.hh"
|
||||
|
@ -33,7 +27,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
|
||||
namespace gr {
|
||||
|
||||
|
@ -122,66 +115,6 @@ std::string Entry::ParentHref() const
|
|||
return m_parent_href ;
|
||||
}
|
||||
|
||||
void Entry::Download( http::Agent* http, const Path& file, const http::Headers& auth ) const
|
||||
{
|
||||
Log( "Downloading %1%", file ) ;
|
||||
http::Download dl( file.Str(), http::Download::NoChecksum() ) ;
|
||||
long r = http->Get( m_content_src, &dl, auth ) ;
|
||||
if ( r <= 400 )
|
||||
os::SetFileTime( file, m_server_modified ) ;
|
||||
}
|
||||
|
||||
bool Entry::Upload( http::Agent* http, std::streambuf *file, const http::Headers& auth )
|
||||
{
|
||||
// upload link missing means that file is read only
|
||||
if ( m_upload_link.empty() )
|
||||
{
|
||||
Log( "Cannot upload %1%: file read-only.", m_title, log::warning ) ;
|
||||
return false ;
|
||||
}
|
||||
|
||||
Log( "Uploading %1%", m_title ) ;
|
||||
|
||||
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_filename + "</title>"
|
||||
"</entry>" ;
|
||||
|
||||
std::string data(
|
||||
(std::istreambuf_iterator<char>(file)),
|
||||
(std::istreambuf_iterator<char>()) ) ;
|
||||
|
||||
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_etag ) ;
|
||||
hdr.push_back( "Expect:" ) ;
|
||||
|
||||
http::StringResponse str ;
|
||||
http->Put( m_upload_link, meta, &str, hdr ) ;
|
||||
|
||||
std::string uplink = http->RedirLocation() ;
|
||||
|
||||
// parse the header and find "Location"
|
||||
http::Headers uphdr ;
|
||||
uphdr.push_back( "Expect:" ) ;
|
||||
uphdr.push_back( "Accept:" ) ;
|
||||
|
||||
http::XmlResponse xml ;
|
||||
http->Put( uplink, data, &xml, uphdr ) ;
|
||||
|
||||
Trace( "Receipted response = %1%", xml.Response() ) ;
|
||||
|
||||
return true ;
|
||||
}
|
||||
|
||||
std::string Entry::ResourceID() const
|
||||
{
|
||||
return m_resource_id ;
|
||||
|
@ -192,13 +125,14 @@ std::string Entry::ETag() const
|
|||
return m_etag ;
|
||||
}
|
||||
|
||||
void Entry::Delete( http::Agent *http, const http::Headers& auth )
|
||||
std::string Entry::ContentSrc() const
|
||||
{
|
||||
http::Headers hdr( auth ) ;
|
||||
hdr.push_back( "If-Match: " + m_etag ) ;
|
||||
|
||||
http::StringResponse str ;
|
||||
http->Custom( "DELETE", feed_base + "/" + m_resource_id + "?delete=true", &str, hdr ) ;
|
||||
return m_content_src ;
|
||||
}
|
||||
|
||||
std::string Entry::UploadLink() const
|
||||
{
|
||||
return m_upload_link ;
|
||||
}
|
||||
|
||||
void Entry::Swap( Entry& e )
|
||||
|
|
|
@ -19,12 +19,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "http/Agent.hh"
|
||||
#include "util/DateTime.hh"
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gr {
|
||||
|
||||
|
@ -33,8 +32,6 @@ namespace xml
|
|||
class Node ;
|
||||
}
|
||||
|
||||
class Json ;
|
||||
class OAuth2 ;
|
||||
class Path ;
|
||||
|
||||
/*! \brief corresponds to an "entry" in the resource feed
|
||||
|
@ -60,13 +57,11 @@ public :
|
|||
|
||||
std::string SelfHref() const ;
|
||||
std::string ParentHref() const ;
|
||||
std::string ContentSrc() const ;
|
||||
std::string UploadLink() const ;
|
||||
|
||||
const std::vector<std::string>& ParentHrefs() const ;
|
||||
|
||||
void Download( http::Agent* http, const Path& file, const http::Headers& auth ) const ;
|
||||
bool Upload( http::Agent* http, std::streambuf *file, const http::Headers& auth ) ;
|
||||
void Delete( http::Agent* http, const gr::http::Headers& auth ) ;
|
||||
|
||||
void Swap( Entry& e ) ;
|
||||
|
||||
private :
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
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 "File.hh"
|
||||
|
||||
#include "Collection.hh"
|
||||
#include "CommonUri.hh"
|
||||
|
||||
#include "http/Download.hh"
|
||||
#include "http/StringResponse.hh"
|
||||
#include "http/XmlResponse.hh"
|
||||
#include "protocol/OAuth2.hh"
|
||||
#include "util/Crypt.hh"
|
||||
#include "util/DateTime.hh"
|
||||
#include "util/Path.hh"
|
||||
#include "util/OS.hh"
|
||||
#include "util/Log.hh"
|
||||
#include "xml/Node.hh"
|
||||
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
|
||||
namespace gr {
|
||||
|
||||
File::File( const Entry& entry, const Collection *parent ) :
|
||||
m_entry ( entry ),
|
||||
m_parent ( parent )
|
||||
{
|
||||
}
|
||||
|
||||
void File::Update( http::Agent *http, const http::Headers& auth )
|
||||
{
|
||||
assert( m_parent != 0 ) ;
|
||||
|
||||
bool changed = true ;
|
||||
Path path = m_parent->Dir() / m_entry.Filename() ;
|
||||
|
||||
// compare checksum first if file exists
|
||||
std::ifstream ifile( path.Str().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 )
|
||||
{
|
||||
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 ) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void File::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 File::Download( http::Agent* http, const Path& file, const http::Headers& auth ) const
|
||||
{
|
||||
Log( "Downloading %1%", file ) ;
|
||||
http::Download dl( file.Str(), http::Download::NoChecksum() ) ;
|
||||
long r = http->Get( m_entry.ContentSrc(), &dl, auth ) ;
|
||||
if ( r <= 400 )
|
||||
os::SetFileTime( file, m_entry.ServerModified() ) ;
|
||||
}
|
||||
|
||||
bool File::Upload( http::Agent* http, std::streambuf *file, const http::Headers& auth )
|
||||
{
|
||||
// upload link missing means that file is read only
|
||||
if ( m_entry.UploadLink().empty() )
|
||||
{
|
||||
Log( "Cannot upload %1%: file read-only.", m_entry.Title(), log::warning ) ;
|
||||
return false ;
|
||||
}
|
||||
|
||||
Log( "Uploading %1%", m_entry.Title() ) ;
|
||||
|
||||
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>" ;
|
||||
|
||||
std::string data(
|
||||
(std::istreambuf_iterator<char>(file)),
|
||||
(std::istreambuf_iterator<char>()) ) ;
|
||||
|
||||
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 ;
|
||||
http->Put( m_entry.UploadLink(), meta, &str, hdr ) ;
|
||||
|
||||
std::string uplink = http->RedirLocation() ;
|
||||
|
||||
// parse the header and find "Location"
|
||||
http::Headers uphdr ;
|
||||
uphdr.push_back( "Expect:" ) ;
|
||||
uphdr.push_back( "Accept:" ) ;
|
||||
|
||||
http::XmlResponse xml ;
|
||||
http->Put( uplink, data, &xml, uphdr ) ;
|
||||
|
||||
Trace( "Receipted response = %1%", xml.Response() ) ;
|
||||
|
||||
return true ;
|
||||
}
|
||||
|
||||
} // end of namespace
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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 "Entry.hh"
|
||||
#include "http/Agent.hh"
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
namespace gr {
|
||||
|
||||
namespace http
|
||||
{
|
||||
class Agent ;
|
||||
}
|
||||
|
||||
class Collection ;
|
||||
|
||||
class File
|
||||
{
|
||||
public :
|
||||
explicit File( const Entry& e, const Collection *parent ) ;
|
||||
|
||||
void Update( http::Agent *http, const http::Headers& auth ) ;
|
||||
|
||||
void Download( http::Agent* http, const Path& file, const http::Headers& auth ) const ;
|
||||
bool Upload( http::Agent* http, std::streambuf *file, const http::Headers& auth ) ;
|
||||
void Delete( http::Agent* http, const http::Headers& auth ) ;
|
||||
|
||||
private :
|
||||
Entry m_entry ;
|
||||
const Collection *m_parent ;
|
||||
} ;
|
||||
|
||||
} // end of namespace
|
|
@ -135,7 +135,7 @@ long Agent::ExecCurl(
|
|||
long http_code = 0;
|
||||
::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
Log( "HTTP response %1%", http_code ) ;
|
||||
Trace( "HTTP response %1%", http_code ) ;
|
||||
|
||||
if ( curl_code != CURLE_OK || http_code >= 400 )
|
||||
{
|
||||
|
@ -154,7 +154,7 @@ long Agent::Put(
|
|||
Receivable *dest,
|
||||
const http::Headers& hdr )
|
||||
{
|
||||
Log("HTTP PUT \"%1%\"", url ) ;
|
||||
Trace("HTTP PUT \"%1%\"", url ) ;
|
||||
|
||||
CURL *curl = m_pimpl->curl ;
|
||||
|
||||
|
@ -177,7 +177,7 @@ long Agent::Get(
|
|||
Receivable *dest,
|
||||
const http::Headers& hdr )
|
||||
{
|
||||
Log("HTTP GET \"%1%\"", url ) ;
|
||||
Trace("HTTP GET \"%1%\"", url ) ;
|
||||
|
||||
CURL *curl = m_pimpl->curl ;
|
||||
|
||||
|
@ -196,7 +196,7 @@ long Agent::Post(
|
|||
Receivable *dest,
|
||||
const http::Headers& hdr )
|
||||
{
|
||||
Log("HTTP POST \"%1%\" with \"%2%\"", url, data ) ;
|
||||
Trace("HTTP POST \"%1%\" with \"%2%\"", url, data ) ;
|
||||
|
||||
CURL *curl = m_pimpl->curl ;
|
||||
|
||||
|
@ -218,7 +218,7 @@ long Agent::Custom(
|
|||
Receivable *dest,
|
||||
const http::Headers& hdr )
|
||||
{
|
||||
Log("HTTP %2% \"%1%\"", url, method ) ;
|
||||
Trace("HTTP %2% \"%1%\"", url, method ) ;
|
||||
|
||||
CURL *curl = m_pimpl->curl ;
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
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
|
||||
|
||||
namespace gr {
|
||||
|
||||
struct Destroy
|
||||
{
|
||||
template <typename T>
|
||||
void operator()( T *t ) const
|
||||
{
|
||||
delete t ;
|
||||
}
|
||||
} ;
|
||||
|
||||
} // end of namespace
|
Loading…
Reference in New Issue