]> git.mxchange.org Git - simgear.git/blob - simgear/io/SVNRepository.cxx
Fix VS2010 lack of fminf
[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         requestHeader("Depth") = "0";
124         setBodyData( PROPFIND_REQUEST_BODY,
125                      "application/xml; charset=\"utf-8\"" );
126       }
127
128     protected:
129       virtual void responseHeadersComplete()
130       {
131         if (responseCode() == 207) {
132             // fine
133         } else if (responseCode() == 404) {
134             _repo->propFindFailed(this, SVNRepository::SVN_ERROR_NOT_FOUND);
135         } else {
136             SG_LOG(SG_IO, SG_WARN, "request for:" << url() << 
137                 " return code " << responseCode());
138             _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
139             _repo = NULL;
140         }
141       }
142   
143       virtual void onDone()
144       {
145         if (responseCode() == 207) {
146           _davStatus.finishParse();
147           if (_davStatus.isValid()) {
148               _repo->propFindComplete(this, (DAVCollection*) _davStatus.resource());
149           } else {
150               _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
151           }
152         }
153       }
154   
155       virtual void gotBodyData(const char* s, int n)
156       {
157         if (responseCode() != 207) {
158           return;
159         }
160         _davStatus.parseXML(s, n);
161       }
162         
163         virtual void onFail()
164         {
165             HTTP::Request::onFail();
166                         if (_repo) {
167                                 _repo->propFindFailed(this, SVNRepository::SVN_ERROR_SOCKET);
168                                 _repo = NULL;
169                         }
170         }
171         
172     private:
173       SVNRepoPrivate* _repo;
174       DAVMultiStatus _davStatus;
175     };
176
177 class UpdateReportRequest:
178   public HTTP::Request
179 {
180 public:
181   UpdateReportRequest(SVNRepoPrivate* repo, 
182       const std::string& aVersionName,
183       bool startEmpty) :
184     HTTP::Request("", "REPORT"),
185     _parser(repo->p),
186     _repo(repo),
187     _failed(false)
188   {       
189     setUrl(repo->vccUrl);
190     std::string request =
191     "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
192     "<S:update-report send-all=\"true\" xmlns:S=\"svn:\">\n"
193     "<S:src-path>" + repo->baseUrl + "</S:src-path>\n"
194     "<S:depth>unknown</S:depth>\n"
195     "<S:entry rev=\"" + aVersionName + "\" depth=\"infinity\" start-empty=\"true\"/>\n";
196
197     if( !startEmpty )
198     {
199       string_list entries;
200       _repo->rootCollection->mergeUpdateReportDetails(0, entries);
201       BOOST_FOREACH(string e, entries)
202       {
203         request += e + "\n";
204       }
205     }
206
207     request += "</S:update-report>";
208
209     setBodyData(request, "application/xml; charset=\"utf-8\"");
210   }
211
212 protected:
213   virtual void onDone()
214   {
215       if (_failed) {
216           return;
217       }
218       
219     if (responseCode() == 200) {
220           SVNRepository::ResultCode err = _parser.finishParse();
221           if (err) {
222               _repo->updateFailed(this, err);
223               _failed = true;
224           } else {
225               _repo->svnUpdateDone();
226           }
227     } else if (responseCode() == 404) {
228         _repo->updateFailed(this, SVNRepository::SVN_ERROR_NOT_FOUND);
229         _failed = true;
230     } else {
231         SG_LOG(SG_IO, SG_WARN, "SVN: request for:" << url() <<
232         " got HTTP status " << responseCode());
233         _repo->updateFailed(this, SVNRepository::SVN_ERROR_HTTP);
234         _failed = true;
235     }
236   }
237
238   virtual void gotBodyData(const char* s, int n)
239   {    
240       if (_failed) {
241           return;
242       }
243       
244     if (responseCode() != 200) {
245         return;
246     }
247     
248     SVNRepository::ResultCode err = _parser.parseXML(s, n);
249     if (err) {
250         _failed = true;
251         SG_LOG(SG_IO, SG_WARN, this << ": SVN: request for:" << url() << " failed:" << err);
252         _repo->updateFailed(this, err);
253         _repo = NULL;
254     }
255   }
256
257     virtual void onFail()
258     {
259         HTTP::Request::onFail();
260         if (_repo) {
261             _repo->updateFailed(this, SVNRepository::SVN_ERROR_SOCKET);
262             _repo = NULL;
263         }
264     }
265 private:
266   SVNReportParser _parser;
267   SVNRepoPrivate* _repo;
268   bool _failed;
269 };
270         
271 } // anonymous 
272
273 SVNRepository::SVNRepository(const SGPath& base, HTTP::Client *cl) :
274   _d(new SVNRepoPrivate(this))
275 {
276   _d->http = cl;
277   _d->rootCollection = new SVNDirectory(this, base);
278   _d->baseUrl = _d->rootCollection->url();  
279 }
280
281 SVNRepository::~SVNRepository()
282 {
283     delete _d->rootCollection;
284 }
285
286 void SVNRepository::setBaseUrl(const std::string &url)
287 {
288   _d->baseUrl = url;
289   _d->rootCollection->setBaseUrl(url);
290 }
291
292 std::string SVNRepository::baseUrl() const
293 {
294   return _d->baseUrl;
295 }
296
297 HTTP::Client* SVNRepository::http() const
298 {
299   return _d->http;
300 }
301
302 SGPath SVNRepository::fsBase() const
303 {
304   return _d->rootCollection->fsPath();
305 }
306
307 bool SVNRepository::isBare() const
308 {
309     if (!fsBase().exists() || Dir(fsBase()).isEmpty()) {
310         return true;
311     }
312     
313     if (_d->vccUrl.empty()) {
314         return true;
315     }
316     
317     return false;
318 }
319
320 void SVNRepository::update()
321 {  
322     _d->status = SVN_NO_ERROR;
323     if (_d->targetRevision.empty() || _d->vccUrl.empty()) {        
324         _d->isUpdating = true;        
325         PropFindRequest* pfr = new PropFindRequest(_d.get());
326         http()->makeRequest(pfr);
327         return;
328     }
329         
330     if (_d->targetRevision == rootDir()->cachedRevision()) {
331         SG_LOG(SG_IO, SG_DEBUG, baseUrl() << " in sync at version " << _d->targetRevision);
332         _d->isUpdating = false;
333         return;
334     }
335     
336     _d->isUpdating = true;
337     UpdateReportRequest* urr = new UpdateReportRequest(_d.get(), 
338         _d->targetRevision, isBare());
339     http()->makeRequest(urr);
340 }
341   
342 bool SVNRepository::isDoingSync() const
343 {
344     if (_d->status != SVN_NO_ERROR) {
345         return false;
346     }
347     
348     return _d->isUpdating || _d->rootCollection->isDoingSync();
349 }
350
351 SVNDirectory* SVNRepository::rootDir() const
352 {
353     return _d->rootCollection;
354 }
355
356 SVNRepository::ResultCode
357 SVNRepository::failure() const
358 {
359     return _d->status;
360 }
361
362 ///////////////////////////////////////////////////////////////////////////
363
364 void SVNRepoPrivate::propFindComplete(HTTP::Request* req, DAVCollection* c)
365 {
366     targetRevision = c->versionName();
367     vccUrl = makeAbsoluteUrl(c->versionControlledConfiguration(), baseUrl);
368     rootCollection->collection()->setVersionControlledConfiguration(vccUrl);    
369     p->update();
370 }
371   
372 void SVNRepoPrivate::propFindFailed(HTTP::Request *req, SVNRepository::ResultCode err)
373 {
374     if (err != SVNRepository::SVN_ERROR_NOT_FOUND) {
375         SG_LOG(SG_IO, SG_WARN, "PropFind failed for:" << req->url());
376     }
377     
378     isUpdating = false;
379     status = err;
380 }
381
382 } // of namespace simgear