]> git.mxchange.org Git - simgear.git/blob - simgear/io/SVNRepository.cxx
Check for DAV status parse failures.
[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         isUpdating = false;
78         status = err;
79     }
80       
81     void propFindComplete(HTTP::Request* req, DAVCollection* col);
82     void propFindFailed(HTTP::Request* req, SVNRepository::ResultCode err);
83 };
84
85
86 namespace { // anonmouse
87     
88     string makeAbsoluteUrl(const string& url, const string& base)
89     {
90       if (strutils::starts_with(url, "http://"))
91         return url; // already absolute
92   
93       assert(strutils::starts_with(base, "http://"));
94       int schemeEnd = base.find("://");
95       int hostEnd = base.find('/', schemeEnd + 3);
96       if (hostEnd < 0) {
97         return url;
98       }
99   
100       return base.substr(0, hostEnd) + url;
101     }
102     
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/\">"
109       "<D:resourcetype/>"
110       "<D:version-name/>"
111       "<D:version-controlled-configuration/>"
112       "</D:prop>"
113       "</D:propfind>";
114
115     class PropFindRequest : public HTTP::Request
116     {
117     public:
118       PropFindRequest(SVNRepoPrivate* repo) :
119         Request(repo->baseUrl, "PROPFIND"),
120         _repo(repo)
121       {
122       }
123   
124       virtual string_list requestHeaders() const
125       {
126         string_list r;
127         r.push_back("Depth");
128         return r;
129       }
130   
131       virtual string header(const string& name) const
132       {
133           if (name == "Depth") {
134               return "0";
135           }
136           
137           return string();
138       }
139   
140       virtual string requestBodyType() const
141       {
142           return "application/xml; charset=\"utf-8\"";
143       }
144   
145       virtual int requestBodyLength() const
146       {
147         return strlen(PROPFIND_REQUEST_BODY);
148       }
149   
150       virtual int getBodyData(char* buf, int count) const
151       {
152         int bodyLen = strlen(PROPFIND_REQUEST_BODY);
153         assert(count >= bodyLen);
154         memcpy(buf, PROPFIND_REQUEST_BODY, bodyLen);
155         return bodyLen;
156       }
157
158     protected:
159       virtual void responseHeadersComplete()
160       {
161         if (responseCode() == 207) {
162             // fine
163         } else if (responseCode() == 404) {
164             _repo->propFindFailed(this, SVNRepository::SVN_ERROR_NOT_FOUND);
165         } else {
166             SG_LOG(SG_IO, SG_WARN, "request for:" << url() << 
167                 " return code " << responseCode());
168             _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
169         }
170       }
171   
172       virtual void responseComplete()
173       {
174         if (responseCode() == 207) {
175           _davStatus.finishParse();
176           if (_davStatus.isValid()) {
177               _repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
178           } else {
179               _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
180           }
181         }
182       }
183   
184       virtual void gotBodyData(const char* s, int n)
185       {
186         if (responseCode() != 207) {
187           return;
188         }
189         _davStatus.parseXML(s, n);
190       }
191         
192         virtual void failed()
193         {
194             HTTP::Request::failed();
195             _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
196         }
197         
198     private:
199       SVNRepoPrivate* _repo;
200       DAVMultiStatus _davStatus;
201     };
202
203 class UpdateReportRequest : public HTTP::Request
204 {
205 public:
206   UpdateReportRequest(SVNRepoPrivate* repo, 
207       const std::string& aVersionName,
208       bool startEmpty) :
209     HTTP::Request("", "REPORT"),
210     _requestSent(0),
211     _parser(repo->p),
212     _repo(repo),
213     _failed(false)
214   {       
215     setUrl(repo->vccUrl);
216
217     _request =
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";
222
223     _request += "<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n";
224      
225     if (!startEmpty) {
226         string_list entries;
227         _repo->rootCollection->mergeUpdateReportDetails(0, entries);
228         BOOST_FOREACH(string e, entries) {
229             _request += e + "\n";
230         }
231     }
232
233     _request += "</S:update-report>";   
234   }
235
236   virtual string requestBodyType() const
237   {
238     return "application/xml; charset=\"utf-8\"";
239   }
240
241   virtual int requestBodyLength() const
242   {
243     return _request.size();
244   }
245
246   virtual int getBodyData(char* buf, int count) const
247   {
248     int len = std::min(count, requestBodyLength() - _requestSent);
249     memcpy(buf, _request.c_str() + _requestSent, len);
250     _requestSent += len;
251     return len;
252   }
253
254 protected:
255   virtual void responseHeadersComplete()
256   {
257
258   }
259
260   virtual void responseComplete()
261   {
262       if (_failed) {
263           return;
264       }
265       
266     if (responseCode() == 200) {
267           SVNRepository::ResultCode err = _parser.finishParse();
268           if (err) {
269               _repo->updateFailed(this, err);
270               _failed = true;
271           } else {
272               _repo->svnUpdateDone();
273           }
274     } else if (responseCode() == 404) {
275         _repo->updateFailed(this, SVNRepository::SVN_ERROR_NOT_FOUND);
276         _failed = true;
277     } else {
278         SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() <<
279         " return code " << responseCode());
280         _repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
281         _failed = true;
282     }
283   }
284
285   virtual void gotBodyData(const char* s, int n)
286   {    
287       if (_failed) {
288           return;
289       }
290       
291     if (responseCode() != 200) {
292         return;
293     }
294     
295     SVNRepository::ResultCode err = _parser.parseXML(s, n);
296     if (err) {
297         _failed = true;
298         SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() << " failed:" << err);
299         _repo->updateFailed(this, err);
300     }
301   }
302
303     virtual void failed()
304     {
305         HTTP::Request::failed();
306         _repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
307     }
308 private:
309   string _request;
310   mutable int _requestSent;
311   SVNReportParser _parser;
312   SVNRepoPrivate* _repo;
313   bool _failed;
314 };
315         
316 } // anonymous 
317
318 SVNRepository::SVNRepository(const SGPath& base, HTTP::Client *cl) :
319   _d(new SVNRepoPrivate(this))
320 {
321   _d->http = cl;
322   _d->rootCollection = new SVNDirectory(this, base);
323   _d->baseUrl = _d->rootCollection->url();  
324 }
325
326 SVNRepository::~SVNRepository()
327 {
328     delete _d->rootCollection;
329 }
330
331 void SVNRepository::setBaseUrl(const std::string &url)
332 {
333   _d->baseUrl = url;
334   _d->rootCollection->setBaseUrl(url);
335 }
336
337 std::string SVNRepository::baseUrl() const
338 {
339   return _d->baseUrl;
340 }
341
342 HTTP::Client* SVNRepository::http() const
343 {
344   return _d->http;
345 }
346
347 SGPath SVNRepository::fsBase() const
348 {
349   return _d->rootCollection->fsPath();
350 }
351
352 bool SVNRepository::isBare() const
353 {
354     if (!fsBase().exists() || Dir(fsBase()).isEmpty()) {
355         return true;
356     }
357     
358     if (_d->vccUrl.empty()) {
359         return true;
360     }
361     
362     return false;
363 }
364
365 void SVNRepository::update()
366 {  
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);
372         return;
373     }
374         
375     if (_d->targetRevision == rootDir()->cachedRevision()) {
376         SG_LOG(SG_IO, SG_DEBUG, baseUrl() << " in sync at version " << _d->targetRevision);
377         _d->isUpdating = false;
378         return;
379     }
380     
381     _d->isUpdating = true;
382     UpdateReportRequest* urr = new UpdateReportRequest(_d.get(), 
383         _d->targetRevision, isBare());
384     http()->makeRequest(urr);
385 }
386   
387 bool SVNRepository::isDoingSync() const
388 {
389     if (_d->status != SVN_NO_ERROR) {
390         return false;
391     }
392     
393     return _d->isUpdating || _d->rootCollection->isDoingSync();
394 }
395
396 SVNDirectory* SVNRepository::rootDir() const
397 {
398     return _d->rootCollection;
399 }
400
401 SVNRepository::ResultCode
402 SVNRepository::failure() const
403 {
404     return _d->status;
405 }
406
407 ///////////////////////////////////////////////////////////////////////////
408
409 void SVNRepoPrivate::propFindComplete(HTTP::Request* req, DAVCollection* c)
410 {
411     targetRevision = c->versionName();
412     vccUrl = makeAbsoluteUrl(c->versionControlledConfiguration(), baseUrl);
413     rootCollection->collection()->setVersionControlledConfiguration(vccUrl);    
414     p->update();
415 }
416   
417 void SVNRepoPrivate::propFindFailed(HTTP::Request *req, SVNRepository::ResultCode err)
418 {
419     if (err != SVNRepository::SVN_ERROR_NOT_FOUND) {
420         SG_LOG(SG_IO, SG_WARN, "PropFind failed for:" << req->url());
421     }
422     
423     isUpdating = false;
424     status = err;
425 }
426
427 } // of namespace simgear