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 = addChildDirectory(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);
238 void SVNDirectory::deleteChildByName(const std::string& nm)
240 DAVResource* child = dav->childWithName(nm);
245 SGPath path = fsDir().file(nm);
247 if (child->isCollection()) {
249 bool ok = d.remove(true);
251 SG_LOG(SG_TERRASYNC, SG_ALERT, "SVNDirectory::deleteChildByName: failed to remove dir:"
252 << nm << " at path:\n\t" << path);
255 DirectoryList::iterator it = findChildDir(nm);
256 if (it != _children.end()) {
257 SVNDirectory* c = *it;
262 bool ok = path.remove();
264 SG_LOG(SG_TERRASYNC, SG_ALERT, "SVNDirectory::deleteChildByName: failed to remove path:" << nm
265 << " at path:\n\t" << path);
269 dav->removeChild(child);
275 bool SVNDirectory::isDoingSync() const
277 if (_doingUpdateReport) {
281 BOOST_FOREACH(SVNDirectory* child, _children) {
282 if (child->isDoingSync()) {
290 void SVNDirectory::beginUpdateReport()
292 _doingUpdateReport = true;
293 _cachedRevision.clear();
297 void SVNDirectory::updateReportComplete()
299 _cachedRevision = dav->versionName();
300 _doingUpdateReport = false;
303 SVNDirectory* pr = parent();
309 SVNRepository* SVNDirectory::repository() const
314 void SVNDirectory::mergeUpdateReportDetails(unsigned int depth,
317 // normal, easy case: we are fully in-sync at a revision
318 if (!_cachedRevision.empty()) {
319 std::ostringstream os;
320 os << "<S:entry rev=\"" << _cachedRevision << "\" depth=\"infinity\">"
321 << repoPath() << "</S:entry>";
322 items.push_back(os.str());
327 if (depth >= MAX_UPDATE_REPORT_DEPTH) {
332 PathList cs = d.children(Dir::NO_DOT_OR_DOTDOT | Dir::INCLUDE_HIDDEN | Dir::TYPE_DIR);
333 BOOST_FOREACH(SGPath path, cs) {
334 SVNDirectory* c = child(path.file());
336 // ignore this child, if it's an incomplete download,
337 // it will be over-written on the update anyway
338 //std::cerr << "unknown SVN child" << path << std::endl;
340 // recurse down into children
341 c->mergeUpdateReportDetails(depth+1, items);
343 } // of child dir iteration
346 std::string SVNDirectory::repoPath() const
352 // find the length of the repository base URL, then
353 // trim that off our repo URL - job done!
354 size_t baseUrlLen = repo->baseUrl().size();
355 return dav->url().substr(baseUrlLen + 1);
358 SVNDirectory* SVNDirectory::parent() const
363 SVNDirectory* SVNDirectory::child(const std::string& dirName) const
365 BOOST_FOREACH(SVNDirectory* d, _children) {
366 if (d->name() == dirName) {
374 DirectoryList::iterator
375 SVNDirectory::findChildDir(const std::string& dirName)
377 DirectoryList::iterator it;
378 for (it=_children.begin(); it != _children.end(); ++it) {
379 if ((*it)->name() == dirName) {
386 simgear::Dir SVNDirectory::fsDir() const
388 return Dir(localPath);