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