2 #include "SVNDirectory.hxx"
7 #include <boost/foreach.hpp>
9 #include <simgear/debug/logstream.hxx>
10 #include <simgear/misc/strutils.hxx>
11 #include <simgear/misc/sg_dir.hxx>
12 #include <simgear/io/HTTPClient.hxx>
13 #include <simgear/io/DAVMultiStatus.hxx>
14 #include <simgear/io/SVNRepository.hxx>
15 #include <simgear/io/sg_file.hxx>
16 #include <simgear/io/SVNReportParser.hxx>
17 #include <simgear/package/md5.h>
18 #include <simgear/structure/exception.hxx>
23 using namespace simgear;
25 typedef std::vector<HTTP::Request_ptr> RequestVector;
26 typedef std::map<std::string, DAVResource*> DAVResourceMap;
29 const char* DAV_CACHE_NAME = ".terrasync_cache";
30 const char* CACHE_VERSION_4_TOKEN = "terrasync-cache-4";
32 // important: with the Google servers, setting this higher than '1' causes
33 // server internal errors (500, the connection is closed). In other words we
34 // can only specify update report items one level deep at most and no more.
35 // (the root and its direct children, not NOT grand-children)
36 const unsigned int MAX_UPDATE_REPORT_DEPTH = 1;
44 SVNDirectory::SVNDirectory(SVNRepository *r, const SGPath& path) :
48 _doingUpdateReport(false),
55 // don't create dir here, repo might not exist at all
58 SVNDirectory::SVNDirectory(SVNDirectory* pr, DAVCollection* col) :
60 repo(pr->repository()),
61 _doingUpdateReport(false),
64 assert(col->container());
65 assert(!col->url().empty());
68 localPath = pr->fsDir().file(col->name());
69 if (!localPath.exists()) {
78 SVNDirectory::~SVNDirectory()
80 // recursive delete our child directories
81 BOOST_FOREACH(SVNDirectory* d, _children) {
86 void SVNDirectory::parseCache()
89 p.append(DAV_CACHE_NAME);
95 char versionName[128];
96 LineState lineState = LINESTATE_HREF;
97 std::ifstream file(p.c_str());
98 if (!file.is_open()) {
99 SG_LOG(SG_TERRASYNC, SG_WARN, "unable to open cache file for reading:" << p);
102 bool doneSelf = false;
104 file.getline(href, 1024);
105 if (strcmp(CACHE_VERSION_4_TOKEN, href)) {
106 SG_LOG(SG_TERRASYNC, SG_WARN, "invalid cache file [missing header token]:" << p << " '" << href << "'");
111 file.getline(href, 1024);
114 while (!file.eof()) {
115 if (lineState == LINESTATE_HREF) {
116 file.getline(href, 1024);
117 lineState = LINESTATE_VERSIONNAME;
119 assert(lineState == LINESTATE_VERSIONNAME);
120 file.getline(versionName, 1024);
121 lineState = LINESTATE_HREF;
122 char* hrefPtr = href;
126 dav = new DAVCollection(hrefPtr);
127 dav->setVersionName(versionName);
129 assert(string(hrefPtr) == dav->url());
132 if (!vccUrl.empty()) {
133 dav->setVersionControlledConfiguration(vccUrl);
136 _cachedRevision = versionName;
139 DAVResource* child = parseChildDirectory(hrefPtr)->collection();
140 string s = strutils::strip(versionName);
142 child->setVersionName(versionName);
144 } // of done self test
145 } // of line-state switching
146 } // of file get-line loop
149 void SVNDirectory::writeCache()
157 p.append(string(DAV_CACHE_NAME) + ".new");
159 std::ofstream file(p.c_str(), std::ios::trunc);
160 // first, cache file version header
161 file << CACHE_VERSION_4_TOKEN << '\n';
163 // second, the repository VCC url
164 file << dav->versionControlledConfiguration() << '\n';
166 // third, our own URL, and version
167 file << dav->url() << '\n' << _cachedRevision << '\n';
169 BOOST_FOREACH(DAVResource* child, dav->contents()) {
170 if (child->isCollection()) {
171 file << child->name() << '\n' << child->versionName() << "\n";
173 } // of child iteration
177 // approximately atomic delete + rename operation
178 SGPath cacheName(localPath);
179 cacheName.append(DAV_CACHE_NAME);
183 void SVNDirectory::setBaseUrl(const string& url)
186 SG_LOG(SG_TERRASYNC, SG_ALERT, "setting base URL on non-root directory " << url);
190 if (dav && (url == dav->url())) {
194 dav = new DAVCollection(url);
197 std::string SVNDirectory::url() const
200 return repo->baseUrl();
203 return _parent->url() + "/" + name();
206 std::string SVNDirectory::name() const
212 SVNDirectory::addChildFile(const std::string& fileName)
214 DAVResource* child = NULL;
215 child = new DAVResource(dav->urlForChildWithName(fileName));
216 dav->addChild(child);
223 SVNDirectory::addChildDirectory(const std::string& dirName)
225 if (dav->childWithName(dirName)) {
226 // existing child, let's remove it
227 deleteChildByName(dirName);
230 DAVCollection* childCol = dav->createChildCollection(dirName);
231 SVNDirectory* child = new SVNDirectory(this, childCol);
232 childCol->setVersionName(child->cachedRevision());
233 _children.push_back(child);
239 SVNDirectory::parseChildDirectory(const std::string& dirName)
241 assert(!dav->childWithName(dirName));
242 DAVCollection* childCol = dav->createChildCollection(dirName);
243 SVNDirectory* child = new SVNDirectory(this, childCol);
244 childCol->setVersionName(child->cachedRevision());
245 _children.push_back(child);
249 void SVNDirectory::deleteChildByName(const std::string& nm)
251 DAVResource* child = dav->childWithName(nm);
256 SGPath path = fsDir().file(nm);
258 if (child->isCollection()) {
260 bool ok = d.remove(true);
262 SG_LOG(SG_TERRASYNC, SG_ALERT, "SVNDirectory::deleteChildByName: failed to remove dir:"
263 << nm << " at path:\n\t" << path);
266 DirectoryList::iterator it = findChildDir(nm);
267 if (it != _children.end()) {
268 SVNDirectory* c = *it;
273 bool ok = path.remove();
275 SG_LOG(SG_TERRASYNC, SG_ALERT, "SVNDirectory::deleteChildByName: failed to remove path:" << nm
276 << " at path:\n\t" << path);
280 dav->removeChild(child);
286 bool SVNDirectory::isDoingSync() const
288 if (_doingUpdateReport) {
292 BOOST_FOREACH(SVNDirectory* child, _children) {
293 if (child->isDoingSync()) {
301 void SVNDirectory::beginUpdateReport()
303 _doingUpdateReport = true;
304 _cachedRevision.clear();
308 void SVNDirectory::updateReportComplete()
310 _cachedRevision = dav->versionName();
311 _doingUpdateReport = false;
314 SVNDirectory* pr = parent();
320 SVNRepository* SVNDirectory::repository() const
325 void SVNDirectory::mergeUpdateReportDetails(unsigned int depth,
328 // normal, easy case: we are fully in-sync at a revision
329 if (!_cachedRevision.empty()) {
330 std::ostringstream os;
331 os << "<S:entry rev=\"" << _cachedRevision << "\" depth=\"infinity\">"
332 << repoPath() << "</S:entry>";
333 items.push_back(os.str());
338 if (depth >= MAX_UPDATE_REPORT_DEPTH) {
343 PathList cs = d.children(Dir::NO_DOT_OR_DOTDOT | Dir::INCLUDE_HIDDEN | Dir::TYPE_DIR);
344 BOOST_FOREACH(SGPath path, cs) {
345 SVNDirectory* c = child(path.file());
347 // ignore this child, if it's an incomplete download,
348 // it will be over-written on the update anyway
349 //std::cerr << "unknown SVN child" << path << std::endl;
351 // recurse down into children
352 c->mergeUpdateReportDetails(depth+1, items);
354 } // of child dir iteration
357 std::string SVNDirectory::repoPath() const
363 // find the length of the repository base URL, then
364 // trim that off our repo URL - job done!
365 size_t baseUrlLen = repo->baseUrl().size();
366 return dav->url().substr(baseUrlLen + 1);
369 SVNDirectory* SVNDirectory::parent() const
374 SVNDirectory* SVNDirectory::child(const std::string& dirName) const
376 BOOST_FOREACH(SVNDirectory* d, _children) {
377 if (d->name() == dirName) {
385 DirectoryList::iterator
386 SVNDirectory::findChildDir(const std::string& dirName)
388 DirectoryList::iterator it;
389 for (it=_children.begin(); it != _children.end(); ++it) {
390 if ((*it)->name() == dirName) {
397 simgear::Dir SVNDirectory::fsDir() const
399 return Dir(localPath);