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_IO, 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_IO, 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_IO, 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);
242 // std::cerr << "ZZZ: deleteChildByName: unknown:" << nm << std::endl;
246 SGPath path = fsDir().file(nm);
248 if (child->isCollection()) {
252 DirectoryList::iterator it = findChildDir(nm);
253 if (it != _children.end()) {
254 SVNDirectory* c = *it;
255 // std::cout << "YYY: deleting SVNDirectory for:" << nm << std::endl;
263 dav->removeChild(child);
269 bool SVNDirectory::isDoingSync() const
271 if (_doingUpdateReport) {
275 BOOST_FOREACH(SVNDirectory* child, _children) {
276 if (child->isDoingSync()) {
284 void SVNDirectory::beginUpdateReport()
286 _doingUpdateReport = true;
287 _cachedRevision.clear();
291 void SVNDirectory::updateReportComplete()
293 _cachedRevision = dav->versionName();
294 _doingUpdateReport = false;
297 SVNDirectory* pr = parent();
303 SVNRepository* SVNDirectory::repository() const
308 void SVNDirectory::mergeUpdateReportDetails(unsigned int depth,
311 // normal, easy case: we are fully in-sync at a revision
312 if (!_cachedRevision.empty()) {
313 std::ostringstream os;
314 os << "<S:entry rev=\"" << _cachedRevision << "\" depth=\"infinity\">"
315 << repoPath() << "</S:entry>";
316 items.push_back(os.str());
321 if (depth >= MAX_UPDATE_REPORT_DEPTH) {
322 SG_LOG(SG_IO, SG_INFO, localPath << "exceeded MAX_UPDATE_REPORT_DEPTH, cleaning");
327 PathList cs = d.children(Dir::NO_DOT_OR_DOTDOT | Dir::INCLUDE_HIDDEN | Dir::TYPE_DIR);
328 BOOST_FOREACH(SGPath path, cs) {
329 SVNDirectory* c = child(path.file());
331 // ignore this child, if it's an incomplete download,
332 // it will be over-written on the update anyway
333 //std::cerr << "unknown SVN child" << path << std::endl;
335 // recurse down into children
336 c->mergeUpdateReportDetails(depth+1, items);
338 } // of child dir iteration
341 std::string SVNDirectory::repoPath() const
347 // find the length of the repository base URL, then
348 // trim that off our repo URL - job done!
349 size_t baseUrlLen = repo->baseUrl().size();
350 return dav->url().substr(baseUrlLen + 1);
353 SVNDirectory* SVNDirectory::parent() const
358 SVNDirectory* SVNDirectory::child(const std::string& dirName) const
360 BOOST_FOREACH(SVNDirectory* d, _children) {
361 if (d->name() == dirName) {
369 DirectoryList::iterator
370 SVNDirectory::findChildDir(const std::string& dirName)
372 DirectoryList::iterator it;
373 for (it=_children.begin(); it != _children.end(); ++it) {
374 if ((*it)->name() == dirName) {
381 simgear::Dir SVNDirectory::fsDir() const
383 return Dir(localPath);