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