Files
simgear/simgear/io/SVNDirectory.cxx
T

390 lines
9.2 KiB
C++

#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 = addChildDirectory(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;
}
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);
}