Remove SVN sync code.
This commit is contained in:
@@ -19,10 +19,6 @@ set(HEADERS
|
||||
HTTPMemoryRequest.hxx
|
||||
HTTPRequest.hxx
|
||||
AbstractRepository.hxx
|
||||
DAVMultiStatus.hxx
|
||||
SVNRepository.hxx
|
||||
SVNDirectory.hxx
|
||||
SVNReportParser.hxx
|
||||
HTTPRepository.hxx
|
||||
)
|
||||
|
||||
@@ -43,10 +39,6 @@ set(SOURCES
|
||||
HTTPMemoryRequest.cxx
|
||||
HTTPRequest.cxx
|
||||
AbstractRepository.cxx
|
||||
DAVMultiStatus.cxx
|
||||
SVNRepository.cxx
|
||||
SVNDirectory.cxx
|
||||
SVNReportParser.cxx
|
||||
HTTPRepository.cxx
|
||||
)
|
||||
|
||||
@@ -59,9 +51,6 @@ simgear_component(io io "${SOURCES}" "${HEADERS}")
|
||||
|
||||
if(ENABLE_TESTS)
|
||||
|
||||
add_executable(http_svn http_svn.cxx)
|
||||
target_link_libraries(http_svn ${TEST_LIBS})
|
||||
|
||||
add_executable(test_sock socktest.cxx)
|
||||
target_link_libraries(test_sock ${TEST_LIBS})
|
||||
|
||||
|
||||
@@ -1,402 +0,0 @@
|
||||
// DAVMultiStatus.cxx -- parser for WebDAV MultiStatus XML data
|
||||
//
|
||||
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// 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; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear_config.h>
|
||||
#endif
|
||||
|
||||
#include "DAVMultiStatus.hxx"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "simgear/debug/logstream.hxx"
|
||||
#include "simgear/misc/strutils.hxx"
|
||||
#include "simgear/structure/exception.hxx"
|
||||
|
||||
#ifdef SYSTEM_EXPAT
|
||||
# include <expat.h>
|
||||
#else
|
||||
# include "sg_expat.h"
|
||||
#endif
|
||||
|
||||
using std::string;
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
#define DAV_NS "DAV::"
|
||||
#define SUBVERSION_DAV_NS "http://subversion.tigris.org/xmlns/dav/"
|
||||
|
||||
const char* DAV_MULTISTATUS_TAG = DAV_NS "multistatus";
|
||||
const char* DAV_RESPONSE_TAG = DAV_NS "response";
|
||||
const char* DAV_PROPSTAT_TAG = DAV_NS "propstat";
|
||||
const char* DAV_PROP_TAG = DAV_NS "prop";
|
||||
|
||||
const char* DAV_HREF_TAG = DAV_NS "href";
|
||||
const char* DAV_RESOURCE_TYPE_TAG = DAV_NS "resourcetype";
|
||||
const char* DAV_CONTENT_TYPE_TAG = DAV_NS "getcontenttype";
|
||||
const char* DAV_CONTENT_LENGTH_TAG = DAV_NS "getcontentlength";
|
||||
const char* DAV_VERSIONNAME_TAG = DAV_NS "version-name";
|
||||
const char* DAV_COLLECTION_TAG = DAV_NS "collection";
|
||||
const char* DAV_VCC_TAG = DAV_NS "version-controlled-configuration";
|
||||
|
||||
const char* SUBVERSION_MD5_CHECKSUM_TAG = SUBVERSION_DAV_NS ":md5-checksum";
|
||||
|
||||
DAVResource::DAVResource(const string& href) :
|
||||
_type(Unknown),
|
||||
_url(href),
|
||||
_container(NULL)
|
||||
{
|
||||
assert(!href.empty());
|
||||
if (strutils::ends_with(href, "/")) {
|
||||
_url = href.substr(0, _url.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void DAVResource::setVersionName(const std::string& aVersion)
|
||||
{
|
||||
_versionName = aVersion;
|
||||
}
|
||||
|
||||
void DAVResource::setVersionControlledConfiguration(const std::string& vcc)
|
||||
{
|
||||
_vcc = vcc;
|
||||
}
|
||||
|
||||
void DAVResource::setMD5(const std::string& md5Hex)
|
||||
{
|
||||
_md5 = md5Hex;
|
||||
}
|
||||
|
||||
std::string DAVResource::name() const
|
||||
{
|
||||
string::size_type index = _url.rfind('/');
|
||||
if (index != string::npos) {
|
||||
return _url.substr(index + 1);
|
||||
}
|
||||
|
||||
throw sg_exception("bad DAV resource HREF:" + _url);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
DAVCollection::DAVCollection(const string& href) :
|
||||
DAVResource(href)
|
||||
{
|
||||
_type = DAVResource::Collection;
|
||||
}
|
||||
|
||||
DAVCollection::~DAVCollection()
|
||||
{
|
||||
BOOST_FOREACH(DAVResource* c, _contents) {
|
||||
delete c;
|
||||
}
|
||||
}
|
||||
|
||||
void DAVCollection::addChild(DAVResource *res)
|
||||
{
|
||||
assert(res);
|
||||
if (res->container() == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(res->container() == NULL);
|
||||
assert(std::find(_contents.begin(), _contents.end(), res) == _contents.end());
|
||||
assert(strutils::starts_with(res->url(), _url));
|
||||
assert(childWithUrl(res->url()) == NULL);
|
||||
|
||||
res->_container = this;
|
||||
_contents.push_back(res);
|
||||
}
|
||||
|
||||
void DAVCollection::removeChild(DAVResource* res)
|
||||
{
|
||||
assert(res);
|
||||
assert(res->container() == this);
|
||||
|
||||
res->_container = NULL;
|
||||
DAVResourceList::iterator it = std::find(_contents.begin(), _contents.end(), res);
|
||||
assert(it != _contents.end());
|
||||
_contents.erase(it);
|
||||
}
|
||||
|
||||
DAVCollection*
|
||||
DAVCollection::createChildCollection(const std::string& name)
|
||||
{
|
||||
DAVCollection* child = new DAVCollection(urlForChildWithName(name));
|
||||
addChild(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
DAVResourceList DAVCollection::contents() const
|
||||
{
|
||||
return _contents;
|
||||
}
|
||||
|
||||
DAVResource* DAVCollection::childWithUrl(const string& url) const
|
||||
{
|
||||
if (url.empty())
|
||||
return NULL;
|
||||
|
||||
BOOST_FOREACH(DAVResource* c, _contents) {
|
||||
if (c->url() == url) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DAVResource* DAVCollection::childWithName(const string& name) const
|
||||
{
|
||||
return childWithUrl(urlForChildWithName(name));
|
||||
}
|
||||
|
||||
std::string DAVCollection::urlForChildWithName(const std::string& name) const
|
||||
{
|
||||
return url() + "/" + name;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class DAVMultiStatus::DAVMultiStatusPrivate
|
||||
{
|
||||
public:
|
||||
DAVMultiStatusPrivate() :
|
||||
parserInited(false),
|
||||
valid(false)
|
||||
{
|
||||
rootResource = NULL;
|
||||
}
|
||||
|
||||
void startElement (const char * name)
|
||||
{
|
||||
if (tagStack.empty()) {
|
||||
if (strcmp(name, DAV_MULTISTATUS_TAG)) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "root element is not " <<
|
||||
DAV_MULTISTATUS_TAG << ", got:" << name);
|
||||
} else {
|
||||
|
||||
}
|
||||
} else {
|
||||
// not at the root element
|
||||
if (tagStack.back() == DAV_MULTISTATUS_TAG) {
|
||||
if (strcmp(name, DAV_RESPONSE_TAG)) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "multistatus child is not response: saw:"
|
||||
<< name);
|
||||
}
|
||||
}
|
||||
|
||||
if (tagStack.back() == DAV_RESOURCE_TYPE_TAG) {
|
||||
if (!strcmp(name, DAV_COLLECTION_TAG)) {
|
||||
currentElementType = DAVResource::Collection;
|
||||
} else {
|
||||
currentElementType = DAVResource::Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tagStack.push_back(name);
|
||||
if (!strcmp(name, DAV_RESPONSE_TAG)) {
|
||||
currentElementType = DAVResource::Unknown;
|
||||
currentElementUrl.clear();
|
||||
currentElementMD5.clear();
|
||||
currentVersionName.clear();
|
||||
currentVCC.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void endElement (const char * name)
|
||||
{
|
||||
assert(tagStack.back() == name);
|
||||
tagStack.pop_back();
|
||||
|
||||
if (!strcmp(name, DAV_RESPONSE_TAG)) {
|
||||
// finish complete response
|
||||
currentElementUrl = strutils::strip(currentElementUrl);
|
||||
|
||||
DAVResource* res = NULL;
|
||||
if (currentElementType == DAVResource::Collection) {
|
||||
DAVCollection* col = new DAVCollection(currentElementUrl);
|
||||
res = col;
|
||||
} else {
|
||||
res = new DAVResource(currentElementUrl);
|
||||
}
|
||||
|
||||
res->setVersionName(strutils::strip(currentVersionName));
|
||||
res->setVersionControlledConfiguration(currentVCC);
|
||||
if (rootResource &&
|
||||
strutils::starts_with(currentElementUrl, rootResource->url()))
|
||||
{
|
||||
static_cast<DAVCollection*>(rootResource)->addChild(res);
|
||||
}
|
||||
|
||||
if (!rootResource) {
|
||||
rootResource = res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void data (const char * s, int length)
|
||||
{
|
||||
if (tagStack.back() == DAV_HREF_TAG) {
|
||||
if (tagN(1) == DAV_RESPONSE_TAG) {
|
||||
currentElementUrl += string(s, length);
|
||||
} else if (tagN(1) == DAV_VCC_TAG) {
|
||||
currentVCC += string(s, length);
|
||||
}
|
||||
} else if (tagStack.back() == SUBVERSION_MD5_CHECKSUM_TAG) {
|
||||
currentElementMD5 = string(s, length);
|
||||
} else if (tagStack.back() == DAV_VERSIONNAME_TAG) {
|
||||
currentVersionName = string(s, length);
|
||||
} else if (tagStack.back() == DAV_CONTENT_LENGTH_TAG) {
|
||||
std::istringstream is(string(s, length));
|
||||
is >> currentElementLength;
|
||||
}
|
||||
}
|
||||
|
||||
void pi (const char * target, const char * data) {}
|
||||
|
||||
string tagN(const unsigned int n) const
|
||||
{
|
||||
size_t sz = tagStack.size();
|
||||
if (n >= sz) {
|
||||
return string();
|
||||
}
|
||||
|
||||
return tagStack[sz - (1 + n)];
|
||||
}
|
||||
|
||||
bool parserInited;
|
||||
bool valid;
|
||||
XML_Parser xmlParser;
|
||||
DAVResource* rootResource;
|
||||
|
||||
// in-flight data
|
||||
string_list tagStack;
|
||||
DAVResource::Type currentElementType;
|
||||
string currentElementUrl,
|
||||
currentVersionName,
|
||||
currentVCC;
|
||||
int currentElementLength;
|
||||
string currentElementMD5;
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Static callback functions for Expat.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define VISITOR static_cast<DAVMultiStatus::DAVMultiStatusPrivate *>(userData)
|
||||
|
||||
static void
|
||||
start_element (void * userData, const char * name, const char ** atts)
|
||||
{
|
||||
VISITOR->startElement(name);
|
||||
}
|
||||
|
||||
static void
|
||||
end_element (void * userData, const char * name)
|
||||
{
|
||||
VISITOR->endElement(name);
|
||||
}
|
||||
|
||||
static void
|
||||
character_data (void * userData, const char * s, int len)
|
||||
{
|
||||
VISITOR->data(s, len);
|
||||
}
|
||||
|
||||
static void
|
||||
processing_instruction (void * userData,
|
||||
const char * target,
|
||||
const char * data)
|
||||
{
|
||||
VISITOR->pi(target, data);
|
||||
}
|
||||
|
||||
#undef VISITOR
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
DAVMultiStatus::DAVMultiStatus() :
|
||||
_d(new DAVMultiStatusPrivate)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DAVMultiStatus::~DAVMultiStatus()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void DAVMultiStatus::parseXML(const char* data, int size)
|
||||
{
|
||||
if (!_d->parserInited) {
|
||||
_d->xmlParser = XML_ParserCreateNS(0, ':');
|
||||
XML_SetUserData(_d->xmlParser, _d.get());
|
||||
XML_SetElementHandler(_d->xmlParser, start_element, end_element);
|
||||
XML_SetCharacterDataHandler(_d->xmlParser, character_data);
|
||||
XML_SetProcessingInstructionHandler(_d->xmlParser, processing_instruction);
|
||||
_d->parserInited = true;
|
||||
}
|
||||
|
||||
if (!XML_Parse(_d->xmlParser, data, size, false)) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "DAV parse error:" << XML_ErrorString(XML_GetErrorCode(_d->xmlParser))
|
||||
<< " at line:" << XML_GetCurrentLineNumber(_d->xmlParser)
|
||||
<< " column " << XML_GetCurrentColumnNumber(_d->xmlParser));
|
||||
|
||||
XML_ParserFree(_d->xmlParser);
|
||||
_d->parserInited = false;
|
||||
_d->valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DAVMultiStatus::finishParse()
|
||||
{
|
||||
if (_d->parserInited) {
|
||||
if (!XML_Parse(_d->xmlParser, NULL, 0, true)) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "DAV parse error:" << XML_ErrorString(XML_GetErrorCode(_d->xmlParser))
|
||||
<< " at line:" << XML_GetCurrentLineNumber(_d->xmlParser)
|
||||
<< " column " << XML_GetCurrentColumnNumber(_d->xmlParser));
|
||||
_d->valid = false;
|
||||
} else {
|
||||
_d->valid = true;
|
||||
}
|
||||
XML_ParserFree(_d->xmlParser);
|
||||
}
|
||||
|
||||
_d->parserInited = false;
|
||||
}
|
||||
|
||||
DAVResource* DAVMultiStatus::resource()
|
||||
{
|
||||
return _d->rootResource;
|
||||
}
|
||||
|
||||
bool DAVMultiStatus::isValid() const
|
||||
{
|
||||
return _d->valid;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
// DAVMultiStatus.hxx -- parser for WebDAV MultiStatus XML data
|
||||
//
|
||||
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// 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; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
|
||||
|
||||
#ifndef SG_IO_DAVMULTISTATUS_HXX
|
||||
#define SG_IO_DAVMULTISTATUS_HXX
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory> // for auto_ptr
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class DAVCollection;
|
||||
|
||||
class DAVResource
|
||||
{
|
||||
public:
|
||||
DAVResource(const std::string& url);
|
||||
virtual ~DAVResource() { }
|
||||
|
||||
typedef enum {
|
||||
Unknown = 0,
|
||||
Collection = 1
|
||||
} Type;
|
||||
|
||||
const Type type() const
|
||||
{ return _type; }
|
||||
|
||||
const std::string& url() const
|
||||
{ return _url; }
|
||||
|
||||
std::string name() const;
|
||||
|
||||
/**
|
||||
* SVN servers use this field to expose the head revision
|
||||
* of the resource, which is useful
|
||||
*/
|
||||
const std::string& versionName() const
|
||||
{ return _versionName; }
|
||||
|
||||
void setVersionName(const std::string& aVersion);
|
||||
|
||||
DAVCollection* container() const
|
||||
{ return _container; }
|
||||
|
||||
virtual bool isCollection() const
|
||||
{ return false; }
|
||||
|
||||
void setVersionControlledConfiguration(const std::string& vcc);
|
||||
const std::string& versionControlledConfiguration() const
|
||||
{ return _vcc; }
|
||||
|
||||
void setMD5(const std::string& md5Hex);
|
||||
const std::string& md5() const
|
||||
{ return _md5; }
|
||||
protected:
|
||||
friend class DAVCollection;
|
||||
|
||||
Type _type;
|
||||
std::string _url;
|
||||
std::string _versionName;
|
||||
std::string _vcc;
|
||||
std::string _md5;
|
||||
DAVCollection* _container;
|
||||
};
|
||||
|
||||
typedef std::vector<DAVResource*> DAVResourceList;
|
||||
|
||||
class DAVCollection : public DAVResource
|
||||
{
|
||||
public:
|
||||
DAVCollection(const std::string& url);
|
||||
virtual ~DAVCollection();
|
||||
|
||||
DAVResourceList contents() const;
|
||||
|
||||
void addChild(DAVResource* res);
|
||||
void removeChild(DAVResource* res);
|
||||
|
||||
DAVCollection* createChildCollection(const std::string& name);
|
||||
|
||||
/**
|
||||
* find the collection member with the specified URL, or return NULL
|
||||
* if no such member of this collection exists.
|
||||
*/
|
||||
DAVResource* childWithUrl(const std::string& url) const;
|
||||
|
||||
/**
|
||||
* find the collection member with the specified name, or return NULL
|
||||
*/
|
||||
DAVResource* childWithName(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* wrapper around URL manipulation
|
||||
*/
|
||||
std::string urlForChildWithName(const std::string& name) const;
|
||||
|
||||
virtual bool isCollection() const
|
||||
{ return true; }
|
||||
private:
|
||||
DAVResourceList _contents;
|
||||
};
|
||||
|
||||
class DAVMultiStatus
|
||||
{
|
||||
public:
|
||||
DAVMultiStatus();
|
||||
~DAVMultiStatus();
|
||||
|
||||
// incremental XML parsing
|
||||
void parseXML(const char* data, int size);
|
||||
|
||||
void finishParse();
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
DAVResource* resource();
|
||||
|
||||
class DAVMultiStatusPrivate;
|
||||
private:
|
||||
std::auto_ptr<DAVMultiStatusPrivate> _d;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_IO_DAVMULTISTATUS_HXX
|
||||
@@ -1,400 +0,0 @@
|
||||
|
||||
#include "SVNDirectory.hxx"
|
||||
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/io/HTTPClient.hxx>
|
||||
#include <simgear/io/DAVMultiStatus.hxx>
|
||||
#include <simgear/io/SVNRepository.hxx>
|
||||
#include <simgear/io/sg_file.hxx>
|
||||
#include <simgear/io/SVNReportParser.hxx>
|
||||
#include <simgear/package/md5.h>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using namespace simgear;
|
||||
|
||||
typedef std::vector<HTTP::Request_ptr> RequestVector;
|
||||
typedef std::map<std::string, DAVResource*> DAVResourceMap;
|
||||
|
||||
|
||||
const char* DAV_CACHE_NAME = ".terrasync_cache";
|
||||
const char* CACHE_VERSION_4_TOKEN = "terrasync-cache-4";
|
||||
|
||||
// important: with the Google servers, setting this higher than '1' causes
|
||||
// server internal errors (500, the connection is closed). In other words we
|
||||
// can only specify update report items one level deep at most and no more.
|
||||
// (the root and its direct children, not NOT grand-children)
|
||||
const unsigned int MAX_UPDATE_REPORT_DEPTH = 1;
|
||||
|
||||
enum LineState
|
||||
{
|
||||
LINESTATE_HREF = 0,
|
||||
LINESTATE_VERSIONNAME
|
||||
};
|
||||
|
||||
SVNDirectory::SVNDirectory(SVNRepository *r, const SGPath& path) :
|
||||
localPath(path),
|
||||
dav(NULL),
|
||||
repo(r),
|
||||
_doingUpdateReport(false),
|
||||
_parent(NULL)
|
||||
{
|
||||
if (path.exists()) {
|
||||
parseCache();
|
||||
}
|
||||
|
||||
// don't create dir here, repo might not exist at all
|
||||
}
|
||||
|
||||
SVNDirectory::SVNDirectory(SVNDirectory* pr, DAVCollection* col) :
|
||||
dav(col),
|
||||
repo(pr->repository()),
|
||||
_doingUpdateReport(false),
|
||||
_parent(pr)
|
||||
{
|
||||
assert(col->container());
|
||||
assert(!col->url().empty());
|
||||
assert(_parent);
|
||||
|
||||
localPath = pr->fsDir().file(col->name());
|
||||
if (!localPath.exists()) {
|
||||
Dir d(localPath);
|
||||
d.create(0755);
|
||||
writeCache();
|
||||
} else {
|
||||
parseCache();
|
||||
}
|
||||
}
|
||||
|
||||
SVNDirectory::~SVNDirectory()
|
||||
{
|
||||
// recursive delete our child directories
|
||||
BOOST_FOREACH(SVNDirectory* d, _children) {
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
void SVNDirectory::parseCache()
|
||||
{
|
||||
SGPath p(localPath);
|
||||
p.append(DAV_CACHE_NAME);
|
||||
if (!p.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
char href[1024];
|
||||
char versionName[128];
|
||||
LineState lineState = LINESTATE_HREF;
|
||||
std::ifstream file(p.c_str());
|
||||
if (!file.is_open()) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "unable to open cache file for reading:" << p);
|
||||
return;
|
||||
}
|
||||
bool doneSelf = false;
|
||||
|
||||
file.getline(href, 1024);
|
||||
if (strcmp(CACHE_VERSION_4_TOKEN, href)) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "invalid cache file [missing header token]:" << p << " '" << href << "'");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string vccUrl;
|
||||
file.getline(href, 1024);
|
||||
vccUrl = href;
|
||||
|
||||
while (!file.eof()) {
|
||||
if (lineState == LINESTATE_HREF) {
|
||||
file.getline(href, 1024);
|
||||
lineState = LINESTATE_VERSIONNAME;
|
||||
} else {
|
||||
assert(lineState == LINESTATE_VERSIONNAME);
|
||||
file.getline(versionName, 1024);
|
||||
lineState = LINESTATE_HREF;
|
||||
char* hrefPtr = href;
|
||||
|
||||
if (!doneSelf) {
|
||||
if (!dav) {
|
||||
dav = new DAVCollection(hrefPtr);
|
||||
dav->setVersionName(versionName);
|
||||
} else {
|
||||
assert(string(hrefPtr) == dav->url());
|
||||
}
|
||||
|
||||
if (!vccUrl.empty()) {
|
||||
dav->setVersionControlledConfiguration(vccUrl);
|
||||
}
|
||||
|
||||
_cachedRevision = versionName;
|
||||
doneSelf = true;
|
||||
} else {
|
||||
DAVResource* child = parseChildDirectory(hrefPtr)->collection();
|
||||
string s = strutils::strip(versionName);
|
||||
if (!s.empty()) {
|
||||
child->setVersionName(versionName);
|
||||
}
|
||||
} // of done self test
|
||||
} // of line-state switching
|
||||
} // of file get-line loop
|
||||
}
|
||||
|
||||
void SVNDirectory::writeCache()
|
||||
{
|
||||
SGPath p(localPath);
|
||||
if (!p.exists()) {
|
||||
Dir d(localPath);
|
||||
d.create(0755);
|
||||
}
|
||||
|
||||
p.append(string(DAV_CACHE_NAME) + ".new");
|
||||
|
||||
std::ofstream file(p.c_str(), std::ios::trunc);
|
||||
// first, cache file version header
|
||||
file << CACHE_VERSION_4_TOKEN << '\n';
|
||||
|
||||
// second, the repository VCC url
|
||||
file << dav->versionControlledConfiguration() << '\n';
|
||||
|
||||
// third, our own URL, and version
|
||||
file << dav->url() << '\n' << _cachedRevision << '\n';
|
||||
|
||||
BOOST_FOREACH(DAVResource* child, dav->contents()) {
|
||||
if (child->isCollection()) {
|
||||
file << child->name() << '\n' << child->versionName() << "\n";
|
||||
}
|
||||
} // of child iteration
|
||||
|
||||
file.close();
|
||||
|
||||
// approximately atomic delete + rename operation
|
||||
SGPath cacheName(localPath);
|
||||
cacheName.append(DAV_CACHE_NAME);
|
||||
p.rename(cacheName);
|
||||
}
|
||||
|
||||
void SVNDirectory::setBaseUrl(const string& url)
|
||||
{
|
||||
if (_parent) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "setting base URL on non-root directory " << url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dav && (url == dav->url())) {
|
||||
return;
|
||||
}
|
||||
|
||||
dav = new DAVCollection(url);
|
||||
}
|
||||
|
||||
std::string SVNDirectory::url() const
|
||||
{
|
||||
if (!_parent) {
|
||||
return repo->baseUrl();
|
||||
}
|
||||
|
||||
return _parent->url() + "/" + name();
|
||||
}
|
||||
|
||||
std::string SVNDirectory::name() const
|
||||
{
|
||||
return dav->name();
|
||||
}
|
||||
|
||||
DAVResource*
|
||||
SVNDirectory::addChildFile(const std::string& fileName)
|
||||
{
|
||||
DAVResource* child = NULL;
|
||||
child = new DAVResource(dav->urlForChildWithName(fileName));
|
||||
dav->addChild(child);
|
||||
|
||||
writeCache();
|
||||
return child;
|
||||
}
|
||||
|
||||
SVNDirectory*
|
||||
SVNDirectory::addChildDirectory(const std::string& dirName)
|
||||
{
|
||||
if (dav->childWithName(dirName)) {
|
||||
// existing child, let's remove it
|
||||
deleteChildByName(dirName);
|
||||
}
|
||||
|
||||
DAVCollection* childCol = dav->createChildCollection(dirName);
|
||||
SVNDirectory* child = new SVNDirectory(this, childCol);
|
||||
childCol->setVersionName(child->cachedRevision());
|
||||
_children.push_back(child);
|
||||
writeCache();
|
||||
return child;
|
||||
}
|
||||
|
||||
SVNDirectory*
|
||||
SVNDirectory::parseChildDirectory(const std::string& dirName)
|
||||
{
|
||||
assert(!dav->childWithName(dirName));
|
||||
DAVCollection* childCol = dav->createChildCollection(dirName);
|
||||
SVNDirectory* child = new SVNDirectory(this, childCol);
|
||||
childCol->setVersionName(child->cachedRevision());
|
||||
_children.push_back(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
void SVNDirectory::deleteChildByName(const std::string& nm)
|
||||
{
|
||||
DAVResource* child = dav->childWithName(nm);
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
|
||||
SGPath path = fsDir().file(nm);
|
||||
|
||||
if (child->isCollection()) {
|
||||
Dir d(path);
|
||||
bool ok = d.remove(true);
|
||||
if (!ok) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "SVNDirectory::deleteChildByName: failed to remove dir:"
|
||||
<< nm << " at path:\n\t" << path);
|
||||
}
|
||||
|
||||
DirectoryList::iterator it = findChildDir(nm);
|
||||
if (it != _children.end()) {
|
||||
SVNDirectory* c = *it;
|
||||
delete c;
|
||||
_children.erase(it);
|
||||
}
|
||||
} else {
|
||||
bool ok = path.remove();
|
||||
if (!ok) {
|
||||
SG_LOG(SG_TERRASYNC, SG_ALERT, "SVNDirectory::deleteChildByName: failed to remove path:" << nm
|
||||
<< " at path:\n\t" << path);
|
||||
}
|
||||
}
|
||||
|
||||
dav->removeChild(child);
|
||||
delete child;
|
||||
|
||||
writeCache();
|
||||
}
|
||||
|
||||
bool SVNDirectory::isDoingSync() const
|
||||
{
|
||||
if (_doingUpdateReport) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOST_FOREACH(SVNDirectory* child, _children) {
|
||||
if (child->isDoingSync()) {
|
||||
return true;
|
||||
} // of children
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SVNDirectory::beginUpdateReport()
|
||||
{
|
||||
_doingUpdateReport = true;
|
||||
_cachedRevision.clear();
|
||||
writeCache();
|
||||
}
|
||||
|
||||
void SVNDirectory::updateReportComplete()
|
||||
{
|
||||
_cachedRevision = dav->versionName();
|
||||
_doingUpdateReport = false;
|
||||
writeCache();
|
||||
|
||||
SVNDirectory* pr = parent();
|
||||
if (pr) {
|
||||
pr->writeCache();
|
||||
}
|
||||
}
|
||||
|
||||
SVNRepository* SVNDirectory::repository() const
|
||||
{
|
||||
return repo;
|
||||
}
|
||||
|
||||
void SVNDirectory::mergeUpdateReportDetails(unsigned int depth,
|
||||
string_list& items)
|
||||
{
|
||||
// normal, easy case: we are fully in-sync at a revision
|
||||
if (!_cachedRevision.empty()) {
|
||||
std::ostringstream os;
|
||||
os << "<S:entry rev=\"" << _cachedRevision << "\" depth=\"infinity\">"
|
||||
<< repoPath() << "</S:entry>";
|
||||
items.push_back(os.str());
|
||||
return;
|
||||
}
|
||||
|
||||
Dir d(localPath);
|
||||
if (depth >= MAX_UPDATE_REPORT_DEPTH) {
|
||||
d.removeChildren();
|
||||
return;
|
||||
}
|
||||
|
||||
PathList cs = d.children(Dir::NO_DOT_OR_DOTDOT | Dir::INCLUDE_HIDDEN | Dir::TYPE_DIR);
|
||||
BOOST_FOREACH(SGPath path, cs) {
|
||||
SVNDirectory* c = child(path.file());
|
||||
if (!c) {
|
||||
// ignore this child, if it's an incomplete download,
|
||||
// it will be over-written on the update anyway
|
||||
//std::cerr << "unknown SVN child" << path << std::endl;
|
||||
} else {
|
||||
// recurse down into children
|
||||
c->mergeUpdateReportDetails(depth+1, items);
|
||||
}
|
||||
} // of child dir iteration
|
||||
}
|
||||
|
||||
std::string SVNDirectory::repoPath() const
|
||||
{
|
||||
if (!_parent) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
// find the length of the repository base URL, then
|
||||
// trim that off our repo URL - job done!
|
||||
size_t baseUrlLen = repo->baseUrl().size();
|
||||
return dav->url().substr(baseUrlLen + 1);
|
||||
}
|
||||
|
||||
SVNDirectory* SVNDirectory::parent() const
|
||||
{
|
||||
return _parent;
|
||||
}
|
||||
|
||||
SVNDirectory* SVNDirectory::child(const std::string& dirName) const
|
||||
{
|
||||
BOOST_FOREACH(SVNDirectory* d, _children) {
|
||||
if (d->name() == dirName) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DirectoryList::iterator
|
||||
SVNDirectory::findChildDir(const std::string& dirName)
|
||||
{
|
||||
DirectoryList::iterator it;
|
||||
for (it=_children.begin(); it != _children.end(); ++it) {
|
||||
if ((*it)->name() == dirName) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
simgear::Dir SVNDirectory::fsDir() const
|
||||
{
|
||||
return Dir(localPath);
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
// DAVCollectionMirror.hxx - mirror a DAV collection to the local filesystem
|
||||
//
|
||||
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// 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; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
|
||||
|
||||
#ifndef SG_IO_DAVCOLLECTIONMIRROR_HXX
|
||||
#define SG_IO_DAVCOLLECTIONMIRROR_HXX
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <simgear/misc/sg_path.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/io/DAVMultiStatus.hxx>
|
||||
|
||||
namespace simgear {
|
||||
|
||||
class Dir;
|
||||
namespace HTTP { class Request; }
|
||||
|
||||
// forward decls
|
||||
class DAVMirror;
|
||||
class SVNRepository;
|
||||
class SVNDirectory;
|
||||
|
||||
typedef std::vector<SVNDirectory*> DirectoryList;
|
||||
|
||||
class SVNDirectory
|
||||
{
|
||||
public:
|
||||
// init from local
|
||||
SVNDirectory(SVNRepository *repo, const SGPath& path);
|
||||
~SVNDirectory();
|
||||
|
||||
void setBaseUrl(const std::string& url);
|
||||
|
||||
// init from a collection
|
||||
SVNDirectory(SVNDirectory* pr, DAVCollection* col);
|
||||
|
||||
void beginUpdateReport();
|
||||
void updateReportComplete();
|
||||
|
||||
bool isDoingSync() const;
|
||||
|
||||
std::string url() const;
|
||||
|
||||
std::string name() const;
|
||||
|
||||
DAVResource* addChildFile(const std::string& fileName);
|
||||
SVNDirectory* addChildDirectory(const std::string& dirName);
|
||||
|
||||
// void updateChild(DAVResource* child);
|
||||
void deleteChildByName(const std::string& name);
|
||||
|
||||
SGPath fsPath() const
|
||||
{ return localPath; }
|
||||
|
||||
simgear::Dir fsDir() const;
|
||||
|
||||
std::string repoPath() const;
|
||||
|
||||
SVNRepository* repository() const;
|
||||
DAVCollection* collection() const
|
||||
{ return dav; }
|
||||
|
||||
std::string cachedRevision() const
|
||||
{ return _cachedRevision; }
|
||||
|
||||
void mergeUpdateReportDetails(unsigned int depth, string_list& items);
|
||||
|
||||
SVNDirectory* parent() const;
|
||||
SVNDirectory* child(const std::string& dirName) const;
|
||||
private:
|
||||
|
||||
void parseCache();
|
||||
void writeCache();
|
||||
|
||||
DirectoryList::iterator findChildDir(const std::string& dirName);
|
||||
SVNDirectory* parseChildDirectory(const std::string& dirName);
|
||||
|
||||
SGPath localPath;
|
||||
DAVCollection* dav;
|
||||
SVNRepository* repo;
|
||||
|
||||
std::string _cachedRevision;
|
||||
bool _doingUpdateReport;
|
||||
|
||||
SVNDirectory* _parent;
|
||||
DirectoryList _children;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_IO_DAVCOLLECTIONMIRROR_HXX
|
||||
@@ -1,605 +0,0 @@
|
||||
// SVNReportParser -- parser for SVN report XML data
|
||||
//
|
||||
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// 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; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <simgear_config.h>
|
||||
#endif
|
||||
|
||||
#include "SVNReportParser.hxx"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "simgear/misc/sg_path.hxx"
|
||||
#include "simgear/misc/sg_dir.hxx"
|
||||
#include "simgear/debug/logstream.hxx"
|
||||
#include "simgear/xml/easyxml.hxx"
|
||||
#include "simgear/misc/strutils.hxx"
|
||||
#include "simgear/package/md5.h"
|
||||
|
||||
#ifdef SYSTEM_EXPAT
|
||||
# include <expat.h>
|
||||
#else
|
||||
# include "sg_expat.h"
|
||||
#endif
|
||||
|
||||
#include "SVNDirectory.hxx"
|
||||
#include "SVNRepository.hxx"
|
||||
#include "DAVMultiStatus.hxx"
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
#define DAV_NS "DAV::"
|
||||
#define SVN_NS "svn::"
|
||||
#define SUBVERSION_DAV_NS "http://subversion.tigris.org/xmlns/dav/"
|
||||
|
||||
namespace {
|
||||
|
||||
#define MAX_ENCODED_INT_LEN 10
|
||||
|
||||
static size_t
|
||||
decode_size(unsigned char* &p,
|
||||
const unsigned char *end)
|
||||
{
|
||||
if (p + MAX_ENCODED_INT_LEN < end)
|
||||
end = p + MAX_ENCODED_INT_LEN;
|
||||
/* Decode bytes until we're done. */
|
||||
size_t result = 0;
|
||||
|
||||
while (p < end) {
|
||||
result = (result << 7) | (*p & 0x7f);
|
||||
if (((*p++ >> 7) & 0x1) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool
|
||||
try_decode_size(unsigned char* &p,
|
||||
const unsigned char *end)
|
||||
{
|
||||
if (p + MAX_ENCODED_INT_LEN < end)
|
||||
end = p + MAX_ENCODED_INT_LEN;
|
||||
|
||||
while (p < end) {
|
||||
if (((*p++ >> 7) & 0x1) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// const char* SVN_UPDATE_REPORT_TAG = SVN_NS "update-report";
|
||||
// const char* SVN_TARGET_REVISION_TAG = SVN_NS "target-revision";
|
||||
const char* SVN_OPEN_DIRECTORY_TAG = SVN_NS "open-directory";
|
||||
const char* SVN_OPEN_FILE_TAG = SVN_NS "open-file";
|
||||
const char* SVN_ADD_DIRECTORY_TAG = SVN_NS "add-directory";
|
||||
const char* SVN_ADD_FILE_TAG = SVN_NS "add-file";
|
||||
const char* SVN_TXDELTA_TAG = SVN_NS "txdelta";
|
||||
const char* SVN_SET_PROP_TAG = SVN_NS "set-prop";
|
||||
const char* SVN_PROP_TAG = SVN_NS "prop";
|
||||
const char* SVN_DELETE_ENTRY_TAG = SVN_NS "delete-entry";
|
||||
|
||||
const char* SVN_DAV_MD5_CHECKSUM = SUBVERSION_DAV_NS ":md5-checksum";
|
||||
|
||||
const char* DAV_HREF_TAG = DAV_NS "href";
|
||||
const char* DAV_CHECKED_IN_TAG = DAV_NS "checked-in";
|
||||
|
||||
|
||||
const int svn_txdelta_source = 0;
|
||||
const int svn_txdelta_target = 1;
|
||||
const int svn_txdelta_new = 2;
|
||||
|
||||
const size_t DELTA_HEADER_SIZE = 4;
|
||||
|
||||
/**
|
||||
* helper struct to decode and store the SVN delta header
|
||||
* values
|
||||
*/
|
||||
struct SVNDeltaWindow
|
||||
{
|
||||
public:
|
||||
|
||||
static bool isWindowComplete(unsigned char* buffer, size_t bytes)
|
||||
{
|
||||
unsigned char* p = buffer;
|
||||
unsigned char* pEnd = p + bytes;
|
||||
// if we can't decode five sizes, certainly incomplete
|
||||
for (int i=0; i<5; i++) {
|
||||
if (!try_decode_size(p, pEnd)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
p = buffer;
|
||||
// ignore these three
|
||||
decode_size(p, pEnd);
|
||||
decode_size(p, pEnd);
|
||||
decode_size(p, pEnd);
|
||||
size_t instructionLen = decode_size(p, pEnd);
|
||||
size_t newLength = decode_size(p, pEnd);
|
||||
size_t headerLength = p - buffer;
|
||||
|
||||
return (bytes >= (instructionLen + newLength + headerLength));
|
||||
}
|
||||
|
||||
SVNDeltaWindow(unsigned char* p) :
|
||||
headerLength(0),
|
||||
_ptr(p)
|
||||
{
|
||||
sourceViewOffset = decode_size(p, p+20);
|
||||
sourceViewLength = decode_size(p, p+20);
|
||||
targetViewLength = decode_size(p, p+20);
|
||||
instructionLength = decode_size(p, p+20);
|
||||
newLength = decode_size(p, p+20);
|
||||
|
||||
headerLength = p - _ptr;
|
||||
_ptr = p;
|
||||
}
|
||||
|
||||
bool apply(std::vector<unsigned char>& output, std::istream& source)
|
||||
{
|
||||
unsigned char* pEnd = _ptr + instructionLength;
|
||||
unsigned char* newData = pEnd;
|
||||
|
||||
while (_ptr < pEnd) {
|
||||
int op = ((*_ptr >> 6) & 0x3);
|
||||
if (op >= 3) {
|
||||
SG_LOG(SG_IO, SG_INFO, "SVNDeltaWindow: bad opcode:" << op);
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = *_ptr++ & 0x3f;
|
||||
int offset = 0;
|
||||
|
||||
if (length == 0) {
|
||||
length = decode_size(_ptr, pEnd);
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
SG_LOG(SG_IO, SG_INFO, "SVNDeltaWindow: malformed stream, 0 length" << op);
|
||||
return false;
|
||||
}
|
||||
|
||||
// if op != new, decode another size value
|
||||
if (op != svn_txdelta_new) {
|
||||
offset = decode_size(_ptr, pEnd);
|
||||
}
|
||||
|
||||
if (op == svn_txdelta_target) {
|
||||
// this is inefficent, but ranges can overlap.
|
||||
while (length > 0) {
|
||||
output.push_back(output[offset++]);
|
||||
--length;
|
||||
}
|
||||
} else if (op == svn_txdelta_new) {
|
||||
output.insert(output.end(), newData, newData + length);
|
||||
newData += length;
|
||||
} else if (op == svn_txdelta_source) {
|
||||
source.seekg(offset);
|
||||
char* sourceBuf = (char*) malloc(length);
|
||||
assert(sourceBuf);
|
||||
source.read(sourceBuf, length);
|
||||
output.insert(output.end(), sourceBuf, sourceBuf + length);
|
||||
free(sourceBuf);
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_WARN, "bad opcode logic");
|
||||
return false;
|
||||
}
|
||||
} // of instruction loop
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return headerLength + instructionLength + newLength;
|
||||
}
|
||||
|
||||
unsigned int sourceViewOffset;
|
||||
size_t sourceViewLength,
|
||||
targetViewLength;
|
||||
size_t headerLength,
|
||||
instructionLength,
|
||||
newLength;
|
||||
|
||||
private:
|
||||
unsigned char* _ptr;
|
||||
};
|
||||
|
||||
|
||||
} // of anonymous namespace
|
||||
|
||||
class SVNReportParser::SVNReportParserPrivate
|
||||
{
|
||||
public:
|
||||
SVNReportParserPrivate(SVNRepository* repo) :
|
||||
tree(repo),
|
||||
status(AbstractRepository::REPO_NO_ERROR),
|
||||
parserInited(false),
|
||||
currentPath(repo->fsBase())
|
||||
{
|
||||
inFile = false;
|
||||
currentDir = repo->rootDir();
|
||||
}
|
||||
|
||||
~SVNReportParserPrivate()
|
||||
{
|
||||
}
|
||||
|
||||
void startElement (const char * name, const char** attributes)
|
||||
{
|
||||
if (status != AbstractRepository::REPO_NO_ERROR) {
|
||||
return;
|
||||
}
|
||||
|
||||
ExpatAtts attrs(attributes);
|
||||
tagStack.push_back(name);
|
||||
if (!strcmp(name, SVN_TXDELTA_TAG)) {
|
||||
txDeltaData.clear();
|
||||
} else if (!strcmp(name, SVN_ADD_FILE_TAG)) {
|
||||
string fileName(attrs.getValue("name"));
|
||||
SGPath filePath(currentDir->fsDir().file(fileName));
|
||||
currentPath = filePath;
|
||||
inFile = true;
|
||||
} else if (!strcmp(name, SVN_OPEN_FILE_TAG)) {
|
||||
string fileName(attrs.getValue("name"));
|
||||
SGPath filePath(Dir(currentPath).file(fileName));
|
||||
currentPath = filePath;
|
||||
|
||||
if (!filePath.exists()) {
|
||||
fail(AbstractRepository::REPO_ERROR_FILE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
inFile = true;
|
||||
} else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) {
|
||||
string dirName(attrs.getValue("name"));
|
||||
Dir d(currentDir->fsDir().file(dirName));
|
||||
if (d.exists()) {
|
||||
// policy decision : if we're doing an add, wipe the existing
|
||||
d.remove(true);
|
||||
}
|
||||
|
||||
currentDir = currentDir->addChildDirectory(dirName);
|
||||
currentPath = currentDir->fsPath();
|
||||
currentDir->beginUpdateReport();
|
||||
//cout << "addDir:" << currentPath << endl;
|
||||
} else if (!strcmp(name, SVN_SET_PROP_TAG)) {
|
||||
setPropName = attrs.getValue("name");
|
||||
setPropValue.clear();
|
||||
} else if (!strcmp(name, SVN_DAV_MD5_CHECKSUM)) {
|
||||
md5Sum.clear();
|
||||
} else if (!strcmp(name, SVN_OPEN_DIRECTORY_TAG)) {
|
||||
string dirName;
|
||||
if (attrs.getValue("name")) {
|
||||
dirName = string(attrs.getValue("name"));
|
||||
}
|
||||
openDirectory(dirName);
|
||||
} else if (!strcmp(name, SVN_DELETE_ENTRY_TAG)) {
|
||||
string entryName(attrs.getValue("name"));
|
||||
deleteEntry(entryName);
|
||||
} else if (!strcmp(name, DAV_CHECKED_IN_TAG) ||
|
||||
!strcmp(name, DAV_HREF_TAG) ||
|
||||
!strcmp(name, SVN_PROP_TAG)) {
|
||||
// don't warn on these ones
|
||||
} else {
|
||||
//SG_LOG(SG_IO, SG_WARN, "SVNReportParser: unhandled tag:" << name);
|
||||
}
|
||||
} // of startElement
|
||||
|
||||
void openDirectory(const std::string& dirName)
|
||||
{
|
||||
if (dirName.empty()) {
|
||||
// root directory, we shall assume
|
||||
currentDir = tree->rootDir();
|
||||
} else {
|
||||
assert(currentDir);
|
||||
currentDir = currentDir->child(dirName);
|
||||
}
|
||||
|
||||
assert(currentDir);
|
||||
currentPath = currentDir->fsPath();
|
||||
currentDir->beginUpdateReport();
|
||||
}
|
||||
|
||||
void deleteEntry(const std::string& entryName)
|
||||
{
|
||||
currentDir->deleteChildByName(entryName);
|
||||
}
|
||||
|
||||
bool decodeTextDelta(const SGPath& outputPath)
|
||||
{
|
||||
std::vector<unsigned char> output, decoded;
|
||||
strutils::decodeBase64(txDeltaData, decoded);
|
||||
size_t bytesToDecode = decoded.size();
|
||||
|
||||
unsigned char* p = decoded.data();
|
||||
if (memcmp(p, "SVN\0", DELTA_HEADER_SIZE) != 0) {
|
||||
return false; // bad header
|
||||
}
|
||||
|
||||
bytesToDecode -= DELTA_HEADER_SIZE;
|
||||
p += DELTA_HEADER_SIZE;
|
||||
std::ifstream source;
|
||||
source.open(outputPath.c_str(), std::ios::in | std::ios::binary);
|
||||
|
||||
while (bytesToDecode > 0) {
|
||||
if (!SVNDeltaWindow::isWindowComplete(p, bytesToDecode)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "SVN txdelta broken window");
|
||||
return false;
|
||||
}
|
||||
|
||||
SVNDeltaWindow window(p);
|
||||
assert(bytesToDecode >= window.size());
|
||||
window.apply(output, source);
|
||||
bytesToDecode -= window.size();
|
||||
p += window.size();
|
||||
}
|
||||
|
||||
source.close();
|
||||
|
||||
std::ofstream f;
|
||||
f.open(outputPath.c_str(),
|
||||
std::ios::out | std::ios::trunc | std::ios::binary);
|
||||
f.write((char*) output.data(), output.size());
|
||||
|
||||
// compute MD5 while we have the file in memory
|
||||
memset(&md5Context, 0, sizeof(SG_MD5_CTX));
|
||||
SG_MD5Init(&md5Context);
|
||||
SG_MD5Update(&md5Context, (unsigned char*) output.data(), output.size());
|
||||
unsigned char digest[MD5_DIGEST_LENGTH];
|
||||
SG_MD5Final(digest, &md5Context);
|
||||
decodedFileMd5 = strutils::encodeHex(digest, MD5_DIGEST_LENGTH);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void endElement (const char * name)
|
||||
{
|
||||
if (status != SVNRepository::REPO_NO_ERROR) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(tagStack.back() == name);
|
||||
tagStack.pop_back();
|
||||
|
||||
if (!strcmp(name, SVN_TXDELTA_TAG)) {
|
||||
if (!decodeTextDelta(currentPath)) {
|
||||
fail(SVNRepository::SVN_ERROR_TXDELTA);
|
||||
}
|
||||
} else if (!strcmp(name, SVN_ADD_FILE_TAG)) {
|
||||
finishFile(currentPath);
|
||||
} else if (!strcmp(name, SVN_OPEN_FILE_TAG)) {
|
||||
finishFile(currentPath);
|
||||
} else if (!strcmp(name, SVN_ADD_DIRECTORY_TAG)) {
|
||||
// pop directory
|
||||
currentPath = currentPath.dir();
|
||||
currentDir->updateReportComplete();
|
||||
currentDir = currentDir->parent();
|
||||
} else if (!strcmp(name, SVN_SET_PROP_TAG)) {
|
||||
if (setPropName == "svn:entry:committed-rev") {
|
||||
revision = strutils::to_int(setPropValue);
|
||||
currentVersionName = setPropValue;
|
||||
if (!inFile) {
|
||||
// for directories we have the resource already
|
||||
// for adding files, we might not; we set the version name
|
||||
// above when ending the add/open-file element
|
||||
currentDir->collection()->setVersionName(currentVersionName);
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(name, SVN_DAV_MD5_CHECKSUM)) {
|
||||
// validate against (presumably) just written file
|
||||
if (decodedFileMd5 != md5Sum) {
|
||||
fail(SVNRepository::REPO_ERROR_CHECKSUM);
|
||||
}
|
||||
} else if (!strcmp(name, SVN_OPEN_DIRECTORY_TAG)) {
|
||||
currentDir->updateReportComplete();
|
||||
if (currentDir->parent()) {
|
||||
// pop the collection stack
|
||||
currentDir = currentDir->parent();
|
||||
}
|
||||
|
||||
currentPath = currentDir->fsPath();
|
||||
} else {
|
||||
// std::cout << "element:" << name;
|
||||
}
|
||||
}
|
||||
|
||||
void finishFile(const SGPath& path)
|
||||
{
|
||||
currentPath = path.dir();
|
||||
inFile = false;
|
||||
}
|
||||
|
||||
void data (const char * s, int length)
|
||||
{
|
||||
if (status != SVNRepository::REPO_NO_ERROR) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tagStack.back() == SVN_SET_PROP_TAG) {
|
||||
setPropValue.append(s, length);
|
||||
} else if (tagStack.back() == SVN_TXDELTA_TAG) {
|
||||
txDeltaData.append(s, length);
|
||||
} else if (tagStack.back() == SVN_DAV_MD5_CHECKSUM) {
|
||||
md5Sum.append(s, length);
|
||||
}
|
||||
}
|
||||
|
||||
void pi (const char * target, const char * data) {}
|
||||
|
||||
string tagN(const unsigned int n) const
|
||||
{
|
||||
size_t sz = tagStack.size();
|
||||
if (n >= sz) {
|
||||
return string();
|
||||
}
|
||||
|
||||
return tagStack[sz - (1 + n)];
|
||||
}
|
||||
|
||||
void fail(SVNRepository::ResultCode err)
|
||||
{
|
||||
status = err;
|
||||
}
|
||||
|
||||
SVNRepository* tree;
|
||||
DAVCollection* rootCollection;
|
||||
SVNDirectory* currentDir;
|
||||
SVNRepository::ResultCode status;
|
||||
|
||||
bool parserInited;
|
||||
XML_Parser xmlParser;
|
||||
|
||||
// in-flight data
|
||||
string_list tagStack;
|
||||
string currentVersionName;
|
||||
string txDeltaData;
|
||||
SGPath currentPath;
|
||||
bool inFile;
|
||||
|
||||
unsigned int revision;
|
||||
SG_MD5_CTX md5Context;
|
||||
string md5Sum, decodedFileMd5;
|
||||
std::string setPropName, setPropValue;
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Static callback functions for Expat.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define VISITOR static_cast<SVNReportParser::SVNReportParserPrivate *>(userData)
|
||||
|
||||
static void
|
||||
start_element (void * userData, const char * name, const char ** atts)
|
||||
{
|
||||
VISITOR->startElement(name, atts);
|
||||
}
|
||||
|
||||
static void
|
||||
end_element (void * userData, const char * name)
|
||||
{
|
||||
VISITOR->endElement(name);
|
||||
}
|
||||
|
||||
static void
|
||||
character_data (void * userData, const char * s, int len)
|
||||
{
|
||||
VISITOR->data(s, len);
|
||||
}
|
||||
|
||||
static void
|
||||
processing_instruction (void * userData,
|
||||
const char * target,
|
||||
const char * data)
|
||||
{
|
||||
VISITOR->pi(target, data);
|
||||
}
|
||||
|
||||
#undef VISITOR
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SVNReportParser::SVNReportParser(SVNRepository* repo) :
|
||||
_d(new SVNReportParserPrivate(repo))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SVNReportParser::~SVNReportParser()
|
||||
{
|
||||
}
|
||||
|
||||
SVNRepository::ResultCode
|
||||
SVNReportParser::innerParseXML(const char* data, int size)
|
||||
{
|
||||
if (_d->status != SVNRepository::REPO_NO_ERROR) {
|
||||
return _d->status;
|
||||
}
|
||||
|
||||
bool isEnd = (data == NULL);
|
||||
if (!XML_Parse(_d->xmlParser, data, size, isEnd)) {
|
||||
SG_LOG(SG_IO, SG_INFO, "SVN parse error:" << XML_ErrorString(XML_GetErrorCode(_d->xmlParser))
|
||||
<< " at line:" << XML_GetCurrentLineNumber(_d->xmlParser)
|
||||
<< " column " << XML_GetCurrentColumnNumber(_d->xmlParser));
|
||||
|
||||
XML_ParserFree(_d->xmlParser);
|
||||
_d->parserInited = false;
|
||||
return SVNRepository::SVN_ERROR_XML;
|
||||
} else if (isEnd) {
|
||||
XML_ParserFree(_d->xmlParser);
|
||||
_d->parserInited = false;
|
||||
}
|
||||
|
||||
return _d->status;
|
||||
}
|
||||
|
||||
SVNRepository::ResultCode
|
||||
SVNReportParser::parseXML(const char* data, int size)
|
||||
{
|
||||
if (_d->status != SVNRepository::REPO_NO_ERROR) {
|
||||
return _d->status;
|
||||
}
|
||||
|
||||
if (!_d->parserInited) {
|
||||
_d->xmlParser = XML_ParserCreateNS(0, ':');
|
||||
XML_SetUserData(_d->xmlParser, _d.get());
|
||||
XML_SetElementHandler(_d->xmlParser, start_element, end_element);
|
||||
XML_SetCharacterDataHandler(_d->xmlParser, character_data);
|
||||
XML_SetProcessingInstructionHandler(_d->xmlParser, processing_instruction);
|
||||
_d->parserInited = true;
|
||||
}
|
||||
|
||||
return innerParseXML(data, size);
|
||||
}
|
||||
|
||||
SVNRepository::ResultCode SVNReportParser::finishParse()
|
||||
{
|
||||
if (_d->status != SVNRepository::REPO_NO_ERROR) {
|
||||
return _d->status;
|
||||
}
|
||||
|
||||
return innerParseXML(NULL, 0);
|
||||
}
|
||||
|
||||
std::string SVNReportParser::etagFromRevision(unsigned int revision)
|
||||
{
|
||||
// etags look like W/"7//", hopefully this is stable
|
||||
// across different servers and similar
|
||||
std::ostringstream os;
|
||||
os << "W/\"" << revision << "//";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
// SVNReportParser -- parser for SVN report XML data
|
||||
//
|
||||
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// 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; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
|
||||
|
||||
#ifndef SG_IO_SVNREPORTPARSER_HXX
|
||||
#define SG_IO_SVNREPORTPARSER_HXX
|
||||
|
||||
#include <string>
|
||||
#include <memory> // for auto_ptr
|
||||
|
||||
#include "SVNRepository.hxx"
|
||||
|
||||
class SGPath;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
class SVNRepository;
|
||||
|
||||
class SVNReportParser
|
||||
{
|
||||
public:
|
||||
SVNReportParser(SVNRepository* repo);
|
||||
~SVNReportParser();
|
||||
|
||||
// incremental XML parsing
|
||||
SVNRepository::ResultCode parseXML(const char* data, int size);
|
||||
|
||||
SVNRepository::ResultCode finishParse();
|
||||
|
||||
static std::string etagFromRevision(unsigned int revision);
|
||||
|
||||
class SVNReportParserPrivate;
|
||||
private:
|
||||
SVNRepository::ResultCode innerParseXML(const char* data, int size);
|
||||
|
||||
std::auto_ptr<SVNReportParserPrivate> _d;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_IO_SVNREPORTPARSER_HXX
|
||||
@@ -1,386 +0,0 @@
|
||||
// DAVMirrorTree -- mirror a DAV tree to the local file-system
|
||||
//
|
||||
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// 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; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// 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 "SVNRepository.hxx"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "simgear/debug/logstream.hxx"
|
||||
#include "simgear/misc/strutils.hxx"
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/io/HTTPClient.hxx>
|
||||
#include <simgear/io/DAVMultiStatus.hxx>
|
||||
#include <simgear/io/SVNDirectory.hxx>
|
||||
#include <simgear/io/sg_file.hxx>
|
||||
#include <simgear/io/SVNReportParser.hxx>
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
typedef std::vector<HTTP::Request_ptr> RequestVector;
|
||||
|
||||
class SVNRepoPrivate
|
||||
{
|
||||
public:
|
||||
SVNRepoPrivate(SVNRepository* parent) :
|
||||
p(parent),
|
||||
isUpdating(false),
|
||||
status(SVNRepository::REPO_NO_ERROR)
|
||||
{ ; }
|
||||
|
||||
SVNRepository* p; // link back to outer
|
||||
SVNDirectory* rootCollection;
|
||||
HTTP::Client* http;
|
||||
std::string baseUrl;
|
||||
std::string vccUrl;
|
||||
std::string targetRevision;
|
||||
bool isUpdating;
|
||||
SVNRepository::ResultCode status;
|
||||
|
||||
void svnUpdateDone()
|
||||
{
|
||||
isUpdating = false;
|
||||
}
|
||||
|
||||
void updateFailed(HTTP::Request* req, SVNRepository::ResultCode err)
|
||||
{
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "SVN: failed to update from:" << req->url()
|
||||
<< "\n(repository:" << p->baseUrl() << ")");
|
||||
isUpdating = false;
|
||||
status = err;
|
||||
}
|
||||
|
||||
void propFindComplete(HTTP::Request* req, DAVCollection* col);
|
||||
void propFindFailed(HTTP::Request* req, SVNRepository::ResultCode err);
|
||||
};
|
||||
|
||||
|
||||
namespace { // anonmouse
|
||||
|
||||
string makeAbsoluteUrl(const string& url, const string& base)
|
||||
{
|
||||
if (strutils::starts_with(url, "http://"))
|
||||
return url; // already absolute
|
||||
|
||||
assert(strutils::starts_with(base, "http://"));
|
||||
int schemeEnd = base.find("://");
|
||||
int hostEnd = base.find('/', schemeEnd + 3);
|
||||
if (hostEnd < 0) {
|
||||
return url;
|
||||
}
|
||||
|
||||
return base.substr(0, hostEnd) + url;
|
||||
}
|
||||
|
||||
// keep the responses small by only requesting the properties we actually
|
||||
// care about; the ETag, length and MD5-sum
|
||||
const char* PROPFIND_REQUEST_BODY =
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
|
||||
"<D:propfind xmlns:D=\"DAV:\">"
|
||||
"<D:prop xmlns:R=\"http://subversion.tigris.org/xmlns/dav/\">"
|
||||
"<D:resourcetype/>"
|
||||
"<D:version-name/>"
|
||||
"<D:version-controlled-configuration/>"
|
||||
"</D:prop>"
|
||||
"</D:propfind>";
|
||||
|
||||
class PropFindRequest : public HTTP::Request
|
||||
{
|
||||
public:
|
||||
PropFindRequest(SVNRepoPrivate* repo) :
|
||||
Request(repo->baseUrl, "PROPFIND"),
|
||||
_repo(repo)
|
||||
{
|
||||
assert(repo);
|
||||
requestHeader("Depth") = "0";
|
||||
setBodyData( PROPFIND_REQUEST_BODY,
|
||||
"application/xml; charset=\"utf-8\"" );
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void responseHeadersComplete()
|
||||
{
|
||||
if (responseCode() == 207) {
|
||||
// fine
|
||||
} else if (responseCode() == 404) {
|
||||
_repo->propFindFailed(this, SVNRepository::REPO_ERROR_NOT_FOUND);
|
||||
} else {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "request for:" << url() <<
|
||||
" return code " << responseCode());
|
||||
_repo->propFindFailed(this, SVNRepository::REPO_ERROR_SOCKET);
|
||||
_repo = NULL;
|
||||
}
|
||||
|
||||
Request::responseHeadersComplete();
|
||||
}
|
||||
|
||||
virtual void onDone()
|
||||
{
|
||||
if (responseCode() == 207) {
|
||||
_davStatus.finishParse();
|
||||
if (_davStatus.isValid()) {
|
||||
_repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
|
||||
} else {
|
||||
_repo->propFindFailed(this, SVNRepository::REPO_ERROR_SOCKET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void gotBodyData(const char* s, int n)
|
||||
{
|
||||
if (responseCode() != 207) {
|
||||
return;
|
||||
}
|
||||
_davStatus.parseXML(s, n);
|
||||
}
|
||||
|
||||
virtual void onFail()
|
||||
{
|
||||
HTTP::Request::onFail();
|
||||
if (_repo) {
|
||||
_repo->propFindFailed(this, SVNRepository::REPO_ERROR_SOCKET);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SVNRepoPrivate* _repo;
|
||||
DAVMultiStatus _davStatus;
|
||||
};
|
||||
|
||||
class UpdateReportRequest:
|
||||
public HTTP::Request
|
||||
{
|
||||
public:
|
||||
UpdateReportRequest(SVNRepoPrivate* repo,
|
||||
const std::string& aVersionName,
|
||||
bool startEmpty) :
|
||||
HTTP::Request("", "REPORT"),
|
||||
_parser(repo->p),
|
||||
_repo(repo),
|
||||
_failed(false)
|
||||
{
|
||||
setUrl(repo->vccUrl);
|
||||
std::string request =
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
|
||||
"<S:update-report send-all=\"true\" xmlns:S=\"svn:\">\n"
|
||||
"<S:src-path>" + repo->baseUrl + "</S:src-path>\n"
|
||||
"<S:depth>unknown</S:depth>\n"
|
||||
"<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n";
|
||||
|
||||
if( !startEmpty )
|
||||
{
|
||||
string_list entries;
|
||||
_repo->rootCollection->mergeUpdateReportDetails(0, entries);
|
||||
BOOST_FOREACH(string e, entries)
|
||||
{
|
||||
request += e + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
request += "</S:update-report>";
|
||||
|
||||
setBodyData(request, "application/xml; charset=\"utf-8\"");
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void onDone()
|
||||
{
|
||||
if (_failed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseCode() == 200) {
|
||||
SVNRepository::ResultCode err = _parser.finishParse();
|
||||
if (err) {
|
||||
_repo->updateFailed(this, err);
|
||||
_failed = true;
|
||||
} else {
|
||||
_repo->svnUpdateDone();
|
||||
}
|
||||
} else if (responseCode() == 404) {
|
||||
_repo->updateFailed(this, SVNRepository::REPO_ERROR_NOT_FOUND);
|
||||
_failed = true;
|
||||
} else {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "SVN: request for:" << url() <<
|
||||
" got HTTP status " << responseCode());
|
||||
_repo->updateFailed(this, SVNRepository::REPO_ERROR_HTTP);
|
||||
_failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void gotBodyData(const char* s, int n)
|
||||
{
|
||||
if (_failed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (responseCode() != 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
SVNRepository::ResultCode err = _parser.parseXML(s, n);
|
||||
if (err) {
|
||||
_failed = true;
|
||||
SG_LOG(SG_IO, SG_WARN, this << ": SVN: request for:" << url() << " failed:" << err);
|
||||
_repo->updateFailed(this, err);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void onFail()
|
||||
{
|
||||
HTTP::Request::onFail();
|
||||
if (_repo) {
|
||||
_repo->updateFailed(this, SVNRepository::REPO_ERROR_SOCKET);
|
||||
_repo = NULL;
|
||||
}
|
||||
}
|
||||
private:
|
||||
SVNReportParser _parser;
|
||||
SVNRepoPrivate* _repo;
|
||||
bool _failed;
|
||||
};
|
||||
|
||||
} // anonymous
|
||||
|
||||
SVNRepository::SVNRepository(const SGPath& base, HTTP::Client *cl) :
|
||||
_d(new SVNRepoPrivate(this))
|
||||
{
|
||||
_d->http = cl;
|
||||
_d->rootCollection = new SVNDirectory(this, base);
|
||||
_d->baseUrl = _d->rootCollection->url();
|
||||
}
|
||||
|
||||
SVNRepository::~SVNRepository()
|
||||
{
|
||||
delete _d->rootCollection;
|
||||
}
|
||||
|
||||
void SVNRepository::setBaseUrl(const std::string &url)
|
||||
{
|
||||
_d->baseUrl = url;
|
||||
_d->rootCollection->setBaseUrl(url);
|
||||
}
|
||||
|
||||
std::string SVNRepository::baseUrl() const
|
||||
{
|
||||
return _d->baseUrl;
|
||||
}
|
||||
|
||||
HTTP::Client* SVNRepository::http() const
|
||||
{
|
||||
return _d->http;
|
||||
}
|
||||
|
||||
SGPath SVNRepository::fsBase() const
|
||||
{
|
||||
return _d->rootCollection->fsPath();
|
||||
}
|
||||
|
||||
bool SVNRepository::isBare() const
|
||||
{
|
||||
if (!fsBase().exists() || Dir(fsBase()).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_d->vccUrl.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SVNRepository::update()
|
||||
{
|
||||
_d->status = REPO_NO_ERROR;
|
||||
if (_d->targetRevision.empty() || _d->vccUrl.empty()) {
|
||||
_d->isUpdating = true;
|
||||
PropFindRequest* pfr = new PropFindRequest(_d.get());
|
||||
http()->makeRequest(pfr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_d->targetRevision == rootDir()->cachedRevision()) {
|
||||
SG_LOG(SG_TERRASYNC, SG_DEBUG, baseUrl() << " in sync at version " << _d->targetRevision);
|
||||
_d->isUpdating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_d->isUpdating = true;
|
||||
UpdateReportRequest* urr = new UpdateReportRequest(_d.get(),
|
||||
_d->targetRevision, isBare());
|
||||
http()->makeRequest(urr);
|
||||
}
|
||||
|
||||
bool SVNRepository::isDoingSync() const
|
||||
{
|
||||
if (_d->status != REPO_NO_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _d->isUpdating || _d->rootCollection->isDoingSync();
|
||||
}
|
||||
|
||||
SVNDirectory* SVNRepository::rootDir() const
|
||||
{
|
||||
return _d->rootCollection;
|
||||
}
|
||||
|
||||
SVNRepository::ResultCode
|
||||
SVNRepository::failure() const
|
||||
{
|
||||
return _d->status;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void SVNRepoPrivate::propFindComplete(HTTP::Request* req, DAVCollection* c)
|
||||
{
|
||||
targetRevision = c->versionName();
|
||||
vccUrl = makeAbsoluteUrl(c->versionControlledConfiguration(), baseUrl);
|
||||
rootCollection->collection()->setVersionControlledConfiguration(vccUrl);
|
||||
p->update();
|
||||
}
|
||||
|
||||
void SVNRepoPrivate::propFindFailed(HTTP::Request *req, SVNRepository::ResultCode err)
|
||||
{
|
||||
if (err != SVNRepository::REPO_ERROR_NOT_FOUND) {
|
||||
SG_LOG(SG_TERRASYNC, SG_WARN, "PropFind failed for:" << req->url());
|
||||
}
|
||||
|
||||
isUpdating = false;
|
||||
status = err;
|
||||
}
|
||||
|
||||
} // of namespace simgear
|
||||
@@ -1,59 +0,0 @@
|
||||
// DAVMirrorTree.hxx - mirror a DAV tree to the local file system
|
||||
//
|
||||
// Copyright (C) 2012 James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// 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; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
|
||||
|
||||
#ifndef SG_IO_SVN_REPOSITORY_HXX
|
||||
#define SG_IO_SVN_REPOSITORY_HXX
|
||||
|
||||
#include <simgear/io/AbstractRepository.hxx>
|
||||
#include <memory>
|
||||
|
||||
namespace simgear {
|
||||
|
||||
class SVNDirectory;
|
||||
class SVNRepoPrivate;
|
||||
|
||||
class SVNRepository : public AbstractRepository
|
||||
{
|
||||
public:
|
||||
|
||||
SVNRepository(const SGPath& root, HTTP::Client* cl);
|
||||
virtual ~SVNRepository();
|
||||
|
||||
SVNDirectory* rootDir() const;
|
||||
virtual SGPath fsBase() const;
|
||||
|
||||
virtual void setBaseUrl(const std::string& url);
|
||||
virtual std::string baseUrl() const;
|
||||
|
||||
virtual HTTP::Client* http() const;
|
||||
|
||||
virtual void update();
|
||||
|
||||
virtual bool isDoingSync() const;
|
||||
|
||||
virtual ResultCode failure() const;
|
||||
private:
|
||||
bool isBare() const;
|
||||
|
||||
std::auto_ptr<SVNRepoPrivate> _d;
|
||||
};
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_IO_SVN_REPOSITORY_HXX
|
||||
@@ -1,51 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <signal.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
|
||||
#include <simgear/io/sg_file.hxx>
|
||||
#include <simgear/io/HTTPClient.hxx>
|
||||
#include <simgear/io/HTTPRequest.hxx>
|
||||
#include <simgear/io/sg_netChannel.hxx>
|
||||
#include <simgear/io/DAVMultiStatus.hxx>
|
||||
#include <simgear/io/SVNRepository.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
|
||||
using namespace simgear;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::cerr;
|
||||
using std::string;
|
||||
|
||||
HTTP::Client* httpClient;
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
sglog().setLogLevels( SG_ALL, SG_INFO );
|
||||
HTTP::Client cl;
|
||||
httpClient = &cl;
|
||||
|
||||
|
||||
SGPath p("/Users/jmt/Desktop/traffic");
|
||||
SVNRepository airports(p, &cl);
|
||||
// airports.setBaseUrl("http://svn.goneabitbursar.com/testproject1");
|
||||
// airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Models");
|
||||
|
||||
airports.setBaseUrl("http://fgfs.goneabitbursar.com/fgfsai/trunk/AI/Traffic");
|
||||
|
||||
// airports.setBaseUrl("http://terrascenery.googlecode.com/svn/trunk/data/Scenery/Airports");
|
||||
airports.update();
|
||||
|
||||
while (airports.isDoingSync()) {
|
||||
cl.update(100);
|
||||
}
|
||||
|
||||
cout << "all done!" << endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user