]> git.mxchange.org Git - simgear.git/blob - simgear/io/SVNRepository.cxx
Terrasync: implement HTTP service lookup via DNS
[simgear.git] / simgear / io / SVNRepository.cxx
1 // DAVMirrorTree -- mirror a DAV tree to the local file-system
2 //
3 // Copyright (C) 2012  James Turner <zakalawe@mac.com>
4 //
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.
9 //
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.
14 //
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.
18
19 #include "SVNRepository.hxx"
20
21 #include <iostream>
22 #include <cstring>
23 #include <cassert>
24 #include <algorithm>
25 #include <sstream>
26 #include <map>
27 #include <set>
28 #include <fstream>
29 #include <memory>
30
31 #include <boost/foreach.hpp>
32
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>
41
42 using std::cout;
43 using std::cerr;
44 using std::endl;
45 using std::string;
46
47 namespace simgear
48 {
49
50 typedef std::vector<HTTP::Request_ptr> RequestVector;
51
52 class SVNRepoPrivate
53 {
54 public:
55     SVNRepoPrivate(SVNRepository* parent) :
56         p(parent),
57         isUpdating(false),
58         status(SVNRepository::REPO_NO_ERROR)
59     { ; }
60
61     SVNRepository* p; // link back to outer
62     SVNDirectory* rootCollection;
63     HTTP::Client* http;
64     std::string baseUrl;
65     std::string vccUrl;
66     std::string targetRevision;
67     bool isUpdating;
68     SVNRepository::ResultCode status;
69
70     void svnUpdateDone()
71     {
72         isUpdating = false;
73     }
74
75     void updateFailed(HTTP::Request* req, SVNRepository::ResultCode err)
76     {
77         SG_LOG(SG_TERRASYNC, SG_WARN, "SVN: failed to update from:" << req->url()
78             << "\n(repository:" << p->baseUrl() << ")");
79         isUpdating = false;
80         status = err;
81     }
82
83     void propFindComplete(HTTP::Request* req, DAVCollection* col);
84     void propFindFailed(HTTP::Request* req, SVNRepository::ResultCode err);
85 };
86
87
88 namespace { // anonmouse
89
90     string makeAbsoluteUrl(const string& url, const string& base)
91     {
92       if (strutils::starts_with(url, "http://"))
93         return url; // already absolute
94
95       assert(strutils::starts_with(base, "http://"));
96       int schemeEnd = base.find("://");
97       int hostEnd = base.find('/', schemeEnd + 3);
98       if (hostEnd < 0) {
99         return url;
100       }
101
102       return base.substr(0, hostEnd) + url;
103     }
104
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/\">"
111       "<D:resourcetype/>"
112       "<D:version-name/>"
113       "<D:version-controlled-configuration/>"
114       "</D:prop>"
115       "</D:propfind>";
116
117     class PropFindRequest : public HTTP::Request
118     {
119     public:
120       PropFindRequest(SVNRepoPrivate* repo) :
121         Request(repo->baseUrl, "PROPFIND"),
122         _repo(repo)
123       {
124         assert(repo);
125         requestHeader("Depth") = "0";
126         setBodyData( PROPFIND_REQUEST_BODY,
127                      "application/xml; charset=\"utf-8\"" );
128       }
129
130     protected:
131       virtual void responseHeadersComplete()
132       {
133         if (responseCode() == 207) {
134             // fine
135         } else if (responseCode() == 404) {
136             _repo->propFindFailed(this, SVNRepository::REPO_ERROR_NOT_FOUND);
137         } else {
138             SG_LOG(SG_TERRASYNC, SG_WARN, "request for:" << url() <<
139                 " return code " << responseCode());
140             _repo->propFindFailed(this, SVNRepository::REPO_ERROR_SOCKET);
141             _repo = NULL;
142         }
143
144         Request::responseHeadersComplete();
145       }
146
147       virtual void onDone()
148       {
149         if (responseCode() == 207) {
150           _davStatus.finishParse();
151           if (_davStatus.isValid()) {
152               _repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
153           } else {
154               _repo->propFindFailed(this, SVNRepository::REPO_ERROR_SOCKET);
155           }
156         }
157       }
158
159       virtual void gotBodyData(const char* s, int n)
160       {
161         if (responseCode() != 207) {
162           return;
163         }
164         _davStatus.parseXML(s, n);
165       }
166
167         virtual void onFail()
168         {
169             HTTP::Request::onFail();
170                         if (_repo) {
171                                 _repo->propFindFailed(this, SVNRepository::REPO_ERROR_SOCKET);
172                                 _repo = NULL;
173                         }
174         }
175
176     private:
177       SVNRepoPrivate* _repo;
178       DAVMultiStatus _davStatus;
179     };
180
181 class UpdateReportRequest:
182   public HTTP::Request
183 {
184 public:
185   UpdateReportRequest(SVNRepoPrivate* repo,
186       const std::string& aVersionName,
187       bool startEmpty) :
188     HTTP::Request("", "REPORT"),
189     _parser(repo->p),
190     _repo(repo),
191     _failed(false)
192   {
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";
200
201     if( !startEmpty )
202     {
203       string_list entries;
204       _repo->rootCollection->mergeUpdateReportDetails(0, entries);
205       BOOST_FOREACH(string e, entries)
206       {
207         request += e + "\n";
208       }
209     }
210
211     request += "</S:update-report>";
212
213     setBodyData(request, "application/xml; charset=\"utf-8\"");
214   }
215
216 protected:
217   virtual void onDone()
218   {
219       if (_failed) {
220           return;
221       }
222
223     if (responseCode() == 200) {
224           SVNRepository::ResultCode err = _parser.finishParse();
225           if (err) {
226               _repo->updateFailed(this, err);
227               _failed = true;
228           } else {
229               _repo->svnUpdateDone();
230           }
231     } else if (responseCode() == 404) {
232         _repo->updateFailed(this, SVNRepository::REPO_ERROR_NOT_FOUND);
233         _failed = true;
234     } else {
235         SG_LOG(SG_TERRASYNC, SG_WARN, "SVN: request for:" << url() <<
236         " got HTTP status " << responseCode());
237         _repo->updateFailed(this, SVNRepository::REPO_ERROR_HTTP);
238         _failed = true;
239     }
240   }
241
242   virtual void gotBodyData(const char* s, int n)
243   {
244       if (_failed) {
245           return;
246       }
247
248     if (responseCode() != 200) {
249         return;
250     }
251
252     SVNRepository::ResultCode err = _parser.parseXML(s, n);
253     if (err) {
254         _failed = true;
255         SG_LOG(SG_IO, SG_WARN, this << ": SVN: request for:" << url() << " failed:" << err);
256         _repo->updateFailed(this, err);
257         _repo = NULL;
258     }
259   }
260
261     virtual void onFail()
262     {
263         HTTP::Request::onFail();
264         if (_repo) {
265             _repo->updateFailed(this, SVNRepository::REPO_ERROR_SOCKET);
266             _repo = NULL;
267         }
268     }
269 private:
270   SVNReportParser _parser;
271   SVNRepoPrivate* _repo;
272   bool _failed;
273 };
274
275 } // anonymous
276
277 SVNRepository::SVNRepository(const SGPath& base, HTTP::Client *cl) :
278     _d(new SVNRepoPrivate(this))
279 {
280   _d->http = cl;
281   _d->rootCollection = new SVNDirectory(this, base);
282   _d->baseUrl = _d->rootCollection->url();
283 }
284
285 SVNRepository::~SVNRepository()
286 {
287     delete _d->rootCollection;
288 }
289
290 void SVNRepository::setBaseUrl(const std::string &url)
291 {
292   _d->baseUrl = url;
293   _d->rootCollection->setBaseUrl(url);
294 }
295
296 std::string SVNRepository::baseUrl() const
297 {
298   return _d->baseUrl;
299 }
300
301 HTTP::Client* SVNRepository::http() const
302 {
303   return _d->http;
304 }
305
306 SGPath SVNRepository::fsBase() const
307 {
308   return _d->rootCollection->fsPath();
309 }
310
311 bool SVNRepository::isBare() const
312 {
313     if (!fsBase().exists() || Dir(fsBase()).isEmpty()) {
314         return true;
315     }
316
317     if (_d->vccUrl.empty()) {
318         return true;
319     }
320
321     return false;
322 }
323
324 void SVNRepository::update()
325 {
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);
331         return;
332     }
333
334     if (_d->targetRevision == rootDir()->cachedRevision()) {
335         SG_LOG(SG_TERRASYNC, SG_DEBUG, baseUrl() << " in sync at version " << _d->targetRevision);
336         _d->isUpdating = false;
337         return;
338     }
339
340     _d->isUpdating = true;
341     UpdateReportRequest* urr = new UpdateReportRequest(_d.get(),
342         _d->targetRevision, isBare());
343     http()->makeRequest(urr);
344 }
345
346 bool SVNRepository::isDoingSync() const
347 {
348     if (_d->status != REPO_NO_ERROR) {
349         return false;
350     }
351
352     return _d->isUpdating || _d->rootCollection->isDoingSync();
353 }
354
355 SVNDirectory* SVNRepository::rootDir() const
356 {
357     return _d->rootCollection;
358 }
359
360 SVNRepository::ResultCode
361 SVNRepository::failure() const
362 {
363     return _d->status;
364 }
365
366 ///////////////////////////////////////////////////////////////////////////
367
368 void SVNRepoPrivate::propFindComplete(HTTP::Request* req, DAVCollection* c)
369 {
370     targetRevision = c->versionName();
371     vccUrl = makeAbsoluteUrl(c->versionControlledConfiguration(), baseUrl);
372     rootCollection->collection()->setVersionControlledConfiguration(vccUrl);
373     p->update();
374 }
375
376 void SVNRepoPrivate::propFindFailed(HTTP::Request *req, SVNRepository::ResultCode err)
377 {
378     if (err != SVNRepository::REPO_ERROR_NOT_FOUND) {
379         SG_LOG(SG_TERRASYNC, SG_WARN, "PropFind failed for:" << req->url());
380     }
381
382     isUpdating = false;
383     status = err;
384 }
385
386 } // of namespace simgear