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"
31 #include <boost/foreach.hpp>
33 #include "simgear/debug/logstream.hxx"
34 #include "simgear/misc/strutils.hxx"
35 #include <simgear/misc/sg_dir.hxx>
36 #include <simgear/io/HTTPClient.hxx>
37 #include <simgear/io/DAVMultiStatus.hxx>
38 #include <simgear/io/SVNDirectory.hxx>
39 #include <simgear/io/sg_file.hxx>
40 #include <simgear/io/SVNReportParser.hxx>
50 typedef std::vector<HTTP::Request_ptr> RequestVector;
55 SVNRepoPrivate(SVNRepository* parent) :
58 status(SVNRepository::REPO_NO_ERROR)
61 SVNRepository* p; // link back to outer
62 SVNDirectory* rootCollection;
66 std::string targetRevision;
68 SVNRepository::ResultCode status;
75 void updateFailed(HTTP::Request* req, SVNRepository::ResultCode err)
77 SG_LOG(SG_TERRASYNC, SG_WARN, "SVN: failed to update from:" << req->url()
78 << "\n(repository:" << p->baseUrl() << ")");
83 void propFindComplete(HTTP::Request* req, DAVCollection* col);
84 void propFindFailed(HTTP::Request* req, SVNRepository::ResultCode err);
88 namespace { // anonmouse
90 string makeAbsoluteUrl(const string& url, const string& base)
92 if (strutils::starts_with(url, "http://"))
93 return url; // already absolute
95 assert(strutils::starts_with(base, "http://"));
96 int schemeEnd = base.find("://");
97 int hostEnd = base.find('/', schemeEnd + 3);
102 return base.substr(0, hostEnd) + url;
105 // keep the responses small by only requesting the properties we actually
106 // care about; the ETag, length and MD5-sum
107 const char* PROPFIND_REQUEST_BODY =
108 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
109 "<D:propfind xmlns:D=\"DAV:\">"
110 "<D:prop xmlns:R=\"http://subversion.tigris.org/xmlns/dav/\">"
113 "<D:version-controlled-configuration/>"
117 class PropFindRequest : public HTTP::Request
120 PropFindRequest(SVNRepoPrivate* repo) :
121 Request(repo->baseUrl, "PROPFIND"),
125 requestHeader("Depth") = "0";
126 setBodyData( PROPFIND_REQUEST_BODY,
127 "application/xml; charset=\"utf-8\"" );
131 virtual void responseHeadersComplete()
133 if (responseCode() == 207) {
135 } else if (responseCode() == 404) {
136 _repo->propFindFailed(this, SVNRepository::REPO_ERROR_NOT_FOUND);
138 SG_LOG(SG_TERRASYNC, SG_WARN, "request for:" << url() <<
139 " return code " << responseCode());
140 _repo->propFindFailed(this, SVNRepository::REPO_ERROR_SOCKET);
144 Request::responseHeadersComplete();
147 virtual void onDone()
149 if (responseCode() == 207) {
150 _davStatus.finishParse();
151 if (_davStatus.isValid()) {
152 _repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
154 _repo->propFindFailed(this, SVNRepository::REPO_ERROR_SOCKET);
159 virtual void gotBodyData(const char* s, int n)
161 if (responseCode() != 207) {
164 _davStatus.parseXML(s, n);
167 virtual void onFail()
169 HTTP::Request::onFail();
171 _repo->propFindFailed(this, SVNRepository::REPO_ERROR_SOCKET);
177 SVNRepoPrivate* _repo;
178 DAVMultiStatus _davStatus;
181 class UpdateReportRequest:
185 UpdateReportRequest(SVNRepoPrivate* repo,
186 const std::string& aVersionName,
188 HTTP::Request("", "REPORT"),
193 setUrl(repo->vccUrl);
194 std::string request =
195 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
196 "<S:update-report send-all=\"true\" xmlns:S=\"svn:\">\n"
197 "<S:src-path>" + repo->baseUrl + "</S:src-path>\n"
198 "<S:depth>unknown</S:depth>\n"
199 "<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n";
204 _repo->rootCollection->mergeUpdateReportDetails(0, entries);
205 BOOST_FOREACH(string e, entries)
211 request += "</S:update-report>";
213 setBodyData(request, "application/xml; charset=\"utf-8\"");
217 virtual void onDone()
223 if (responseCode() == 200) {
224 SVNRepository::ResultCode err = _parser.finishParse();
226 _repo->updateFailed(this, err);
229 _repo->svnUpdateDone();
231 } else if (responseCode() == 404) {
232 _repo->updateFailed(this, SVNRepository::REPO_ERROR_NOT_FOUND);
235 SG_LOG(SG_TERRASYNC, SG_WARN, "SVN: request for:" << url() <<
236 " got HTTP status " << responseCode());
237 _repo->updateFailed(this, SVNRepository::REPO_ERROR_HTTP);
242 virtual void gotBodyData(const char* s, int n)
248 if (responseCode() != 200) {
252 SVNRepository::ResultCode err = _parser.parseXML(s, n);
255 SG_LOG(SG_IO, SG_WARN, this << ": SVN: request for:" << url() << " failed:" << err);
256 _repo->updateFailed(this, err);
261 virtual void onFail()
263 HTTP::Request::onFail();
265 _repo->updateFailed(this, SVNRepository::REPO_ERROR_SOCKET);
270 SVNReportParser _parser;
271 SVNRepoPrivate* _repo;
277 SVNRepository::SVNRepository(const SGPath& base, HTTP::Client *cl) :
278 _d(new SVNRepoPrivate(this))
281 _d->rootCollection = new SVNDirectory(this, base);
282 _d->baseUrl = _d->rootCollection->url();
285 SVNRepository::~SVNRepository()
287 delete _d->rootCollection;
290 void SVNRepository::setBaseUrl(const std::string &url)
293 _d->rootCollection->setBaseUrl(url);
296 std::string SVNRepository::baseUrl() const
301 HTTP::Client* SVNRepository::http() const
306 SGPath SVNRepository::fsBase() const
308 return _d->rootCollection->fsPath();
311 bool SVNRepository::isBare() const
313 if (!fsBase().exists() || Dir(fsBase()).isEmpty()) {
317 if (_d->vccUrl.empty()) {
324 void SVNRepository::update()
326 _d->status = REPO_NO_ERROR;
327 if (_d->targetRevision.empty() || _d->vccUrl.empty()) {
328 _d->isUpdating = true;
329 PropFindRequest* pfr = new PropFindRequest(_d.get());
330 http()->makeRequest(pfr);
334 if (_d->targetRevision == rootDir()->cachedRevision()) {
335 SG_LOG(SG_TERRASYNC, SG_DEBUG, baseUrl() << " in sync at version " << _d->targetRevision);
336 _d->isUpdating = false;
340 _d->isUpdating = true;
341 UpdateReportRequest* urr = new UpdateReportRequest(_d.get(),
342 _d->targetRevision, isBare());
343 http()->makeRequest(urr);
346 bool SVNRepository::isDoingSync() const
348 if (_d->status != REPO_NO_ERROR) {
352 return _d->isUpdating || _d->rootCollection->isDoingSync();
355 SVNDirectory* SVNRepository::rootDir() const
357 return _d->rootCollection;
360 SVNRepository::ResultCode
361 SVNRepository::failure() const
366 ///////////////////////////////////////////////////////////////////////////
368 void SVNRepoPrivate::propFindComplete(HTTP::Request* req, DAVCollection* c)
370 targetRevision = c->versionName();
371 vccUrl = makeAbsoluteUrl(c->versionControlledConfiguration(), baseUrl);
372 rootCollection->collection()->setVersionControlledConfiguration(vccUrl);
376 void SVNRepoPrivate::propFindFailed(HTTP::Request *req, SVNRepository::ResultCode err)
378 if (err != SVNRepository::REPO_ERROR_NOT_FOUND) {
379 SG_LOG(SG_TERRASYNC, SG_WARN, "PropFind failed for:" << req->url());
386 } // of namespace simgear