1 // DAVMirrorTree -- mirror a DAV tree to the local file-system
3 // Copyright (C) 2012 James Turner <zakalawe@mac.com>
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 #include "SVNRepository.hxx"
30 #include <boost/foreach.hpp>
32 #include "simgear/debug/logstream.hxx"
33 #include "simgear/misc/strutils.hxx"
34 #include <simgear/misc/sg_dir.hxx>
35 #include <simgear/io/HTTPClient.hxx>
36 #include <simgear/io/DAVMultiStatus.hxx>
37 #include <simgear/io/SVNDirectory.hxx>
38 #include <simgear/io/sg_file.hxx>
39 #include <simgear/io/SVNReportParser.hxx>
49 typedef std::vector<HTTP::Request_ptr> RequestVector;
54 SVNRepoPrivate(SVNRepository* parent) :
57 status(SVNRepository::SVN_NO_ERROR)
60 SVNRepository* p; // link back to outer
61 SVNDirectory* rootCollection;
65 std::string targetRevision;
67 SVNRepository::ResultCode status;
74 void updateFailed(HTTP::Request* req, SVNRepository::ResultCode err)
76 SG_LOG(SG_IO, SG_WARN, "SVN: failed to update from:" << req->url());
81 void propFindComplete(HTTP::Request* req, DAVCollection* col);
82 void propFindFailed(HTTP::Request* req, SVNRepository::ResultCode err);
86 namespace { // anonmouse
88 string makeAbsoluteUrl(const string& url, const string& base)
90 if (strutils::starts_with(url, "http://"))
91 return url; // already absolute
93 assert(strutils::starts_with(base, "http://"));
94 int schemeEnd = base.find("://");
95 int hostEnd = base.find('/', schemeEnd + 3);
100 return base.substr(0, hostEnd) + url;
103 // keep the responses small by only requesting the properties we actually
104 // care about; the ETag, length and MD5-sum
105 const char* PROPFIND_REQUEST_BODY =
106 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
107 "<D:propfind xmlns:D=\"DAV:\">"
108 "<D:prop xmlns:R=\"http://subversion.tigris.org/xmlns/dav/\">"
111 "<D:version-controlled-configuration/>"
115 class PropFindRequest : public HTTP::Request
118 PropFindRequest(SVNRepoPrivate* repo) :
119 Request(repo->baseUrl, "PROPFIND"),
124 virtual string_list requestHeaders() const
127 r.push_back("Depth");
131 virtual string header(const string& name) const
133 if (name == "Depth") {
140 virtual string requestBodyType() const
142 return "application/xml; charset=\"utf-8\"";
145 virtual int requestBodyLength() const
147 return strlen(PROPFIND_REQUEST_BODY);
150 virtual int getBodyData(char* buf, int count) const
152 int bodyLen = strlen(PROPFIND_REQUEST_BODY);
153 assert(count >= bodyLen);
154 memcpy(buf, PROPFIND_REQUEST_BODY, bodyLen);
159 virtual void responseHeadersComplete()
161 if (responseCode() == 207) {
163 } else if (responseCode() == 404) {
164 _repo->propFindFailed(this, SVNRepository::SVN_ERROR_NOT_FOUND);
166 SG_LOG(SG_IO, SG_WARN, "request for:" << url() <<
167 " return code " << responseCode());
168 _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
172 virtual void responseComplete()
174 if (responseCode() == 207) {
175 _davStatus.finishParse();
176 if (_davStatus.isValid()) {
177 _repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
179 _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
184 virtual void gotBodyData(const char* s, int n)
186 if (responseCode() != 207) {
189 _davStatus.parseXML(s, n);
192 virtual void failed()
194 HTTP::Request::failed();
195 _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
199 SVNRepoPrivate* _repo;
200 DAVMultiStatus _davStatus;
203 class UpdateReportRequest : public HTTP::Request
206 UpdateReportRequest(SVNRepoPrivate* repo,
207 const std::string& aVersionName,
209 HTTP::Request("", "REPORT"),
215 setUrl(repo->vccUrl);
218 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
219 "<S:update-report send-all=\"true\" xmlns:S=\"svn:\">\n"
220 "<S:src-path>" + repo->baseUrl + "</S:src-path>\n"
221 "<S:depth>unknown</S:depth>\n";
223 _request += "<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n";
227 _repo->rootCollection->mergeUpdateReportDetails(0, entries);
228 BOOST_FOREACH(string e, entries) {
229 _request += e + "\n";
233 _request += "</S:update-report>";
236 virtual string requestBodyType() const
238 return "application/xml; charset=\"utf-8\"";
241 virtual int requestBodyLength() const
243 return _request.size();
246 virtual int getBodyData(char* buf, int count) const
248 int len = std::min(count, requestBodyLength() - _requestSent);
249 memcpy(buf, _request.c_str() + _requestSent, len);
255 virtual void responseHeadersComplete()
260 virtual void responseComplete()
266 if (responseCode() == 200) {
267 SVNRepository::ResultCode err = _parser.finishParse();
269 _repo->updateFailed(this, err);
272 _repo->svnUpdateDone();
274 } else if (responseCode() == 404) {
275 _repo->updateFailed(this, SVNRepository::SVN_ERROR_NOT_FOUND);
278 SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() <<
279 " return code " << responseCode());
280 _repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
285 virtual void gotBodyData(const char* s, int n)
291 if (responseCode() != 200) {
295 SVNRepository::ResultCode err = _parser.parseXML(s, n);
298 SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() << " failed:" << err);
299 _repo->updateFailed(this, err);
303 virtual void failed()
305 HTTP::Request::failed();
306 _repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
310 mutable int _requestSent;
311 SVNReportParser _parser;
312 SVNRepoPrivate* _repo;
318 SVNRepository::SVNRepository(const SGPath& base, HTTP::Client *cl) :
319 _d(new SVNRepoPrivate(this))
322 _d->rootCollection = new SVNDirectory(this, base);
323 _d->baseUrl = _d->rootCollection->url();
326 SVNRepository::~SVNRepository()
328 delete _d->rootCollection;
331 void SVNRepository::setBaseUrl(const std::string &url)
334 _d->rootCollection->setBaseUrl(url);
337 std::string SVNRepository::baseUrl() const
342 HTTP::Client* SVNRepository::http() const
347 SGPath SVNRepository::fsBase() const
349 return _d->rootCollection->fsPath();
352 bool SVNRepository::isBare() const
354 if (!fsBase().exists() || Dir(fsBase()).isEmpty()) {
358 if (_d->vccUrl.empty()) {
365 void SVNRepository::update()
367 _d->status = SVN_NO_ERROR;
368 if (_d->targetRevision.empty() || _d->vccUrl.empty()) {
369 _d->isUpdating = true;
370 PropFindRequest* pfr = new PropFindRequest(_d.get());
371 http()->makeRequest(pfr);
375 if (_d->targetRevision == rootDir()->cachedRevision()) {
376 SG_LOG(SG_IO, SG_DEBUG, baseUrl() << " in sync at version " << _d->targetRevision);
377 _d->isUpdating = false;
381 _d->isUpdating = true;
382 UpdateReportRequest* urr = new UpdateReportRequest(_d.get(),
383 _d->targetRevision, isBare());
384 http()->makeRequest(urr);
387 bool SVNRepository::isDoingSync() const
389 if (_d->status != SVN_NO_ERROR) {
393 return _d->isUpdating || _d->rootCollection->isDoingSync();
396 SVNDirectory* SVNRepository::rootDir() const
398 return _d->rootCollection;
401 SVNRepository::ResultCode
402 SVNRepository::failure() const
407 ///////////////////////////////////////////////////////////////////////////
409 void SVNRepoPrivate::propFindComplete(HTTP::Request* req, DAVCollection* c)
411 targetRevision = c->versionName();
412 vccUrl = makeAbsoluteUrl(c->versionControlledConfiguration(), baseUrl);
413 rootCollection->collection()->setVersionControlledConfiguration(vccUrl);
417 void SVNRepoPrivate::propFindFailed(HTTP::Request *req, SVNRepository::ResultCode err)
419 if (err != SVNRepository::SVN_ERROR_NOT_FOUND) {
420 SG_LOG(SG_IO, SG_WARN, "PropFind failed for:" << req->url());
427 } // of namespace simgear