]> git.mxchange.org Git - simgear.git/blob - simgear/io/SVNDirectory.cxx
Fix #1163
[simgear.git] / simgear / io / SVNDirectory.cxx
1
2 #include "SVNDirectory.hxx"
3
4 #include <cassert>
5 #include <fstream>
6 #include <iostream>
7 #include <boost/foreach.hpp>
8
9 #include <simgear/debug/logstream.hxx>
10 #include <simgear/misc/strutils.hxx>
11 #include <simgear/misc/sg_dir.hxx>
12 #include <simgear/io/HTTPClient.hxx>
13 #include <simgear/io/DAVMultiStatus.hxx>
14 #include <simgear/io/SVNRepository.hxx>
15 #include <simgear/io/sg_file.hxx>
16 #include <simgear/io/SVNReportParser.hxx>
17 #include <simgear/package/md5.h>
18 #include <simgear/structure/exception.hxx>
19
20 using std::string;
21 using std::cout;
22 using std::endl;
23 using namespace simgear;
24
25 typedef std::vector<HTTP::Request_ptr> RequestVector;
26 typedef std::map<std::string, DAVResource*> DAVResourceMap;
27
28
29 const char* DAV_CACHE_NAME = ".terrasync_cache";
30 const char* CACHE_VERSION_4_TOKEN = "terrasync-cache-4";
31
32 // important: with the Google servers, setting this higher than '1' causes
33 // server internal errors (500, the connection is closed). In other words we
34 // can only specify update report items one level deep at most and no more.
35 // (the root and its direct children, not NOT grand-children)
36 const unsigned int MAX_UPDATE_REPORT_DEPTH = 1;
37
38 enum LineState
39 {
40   LINESTATE_HREF = 0,
41   LINESTATE_VERSIONNAME
42 };
43
44 SVNDirectory::SVNDirectory(SVNRepository *r, const SGPath& path) :
45   localPath(path),
46   dav(NULL),
47   repo(r),
48   _doingUpdateReport(false),
49   _parent(NULL)
50 {
51   if (path.exists()) {
52     parseCache();
53   } 
54   
55   // don't create dir here, repo might not exist at all
56 }
57   
58 SVNDirectory::SVNDirectory(SVNDirectory* pr, DAVCollection* col) :
59   dav(col),
60   repo(pr->repository()),
61   _doingUpdateReport(false),
62   _parent(pr)
63 {
64   assert(col->container());
65   assert(!col->url().empty());
66   assert(_parent);
67
68   localPath = pr->fsDir().file(col->name());
69   if (!localPath.exists()) {
70     Dir d(localPath);
71     d.create(0755);
72     writeCache();
73   } else {
74     parseCache();
75   }
76 }
77
78 SVNDirectory::~SVNDirectory()
79 {
80     // recursive delete our child directories
81     BOOST_FOREACH(SVNDirectory* d, _children) {
82         delete d;
83     }
84 }
85
86 void SVNDirectory::parseCache()
87 {
88   SGPath p(localPath);
89   p.append(DAV_CACHE_NAME);
90   if (!p.exists()) {
91     return;
92   }
93     
94   char href[1024];
95   char versionName[128];
96   LineState lineState = LINESTATE_HREF;
97   std::ifstream file(p.c_str());
98     if (!file.is_open()) {
99         SG_LOG(SG_IO, SG_WARN, "unable to open cache file for reading:" << p);
100         return;
101     }
102   bool doneSelf = false;
103     
104   file.getline(href, 1024);
105   if (strcmp(CACHE_VERSION_4_TOKEN, href)) {
106     SG_LOG(SG_IO, SG_WARN, "invalid cache file [missing header token]:" << p << " '" << href << "'");
107     return;
108   }
109     
110     std::string vccUrl;
111     file.getline(href, 1024);
112     vccUrl = href;
113     
114   while (!file.eof()) {
115     if (lineState == LINESTATE_HREF) {
116       file.getline(href, 1024);
117       lineState = LINESTATE_VERSIONNAME;
118     } else {
119       assert(lineState == LINESTATE_VERSIONNAME);
120       file.getline(versionName, 1024);
121       lineState = LINESTATE_HREF;
122       char* hrefPtr = href;
123     
124       if (!doneSelf) {
125         if (!dav) {
126           dav = new DAVCollection(hrefPtr);
127           dav->setVersionName(versionName);
128         } else {
129           assert(string(hrefPtr) == dav->url());
130         }
131         
132         if (!vccUrl.empty()) {
133             dav->setVersionControlledConfiguration(vccUrl);
134         }
135         
136         _cachedRevision = versionName;
137         doneSelf = true;
138       } else {
139         DAVResource* child = addChildDirectory(hrefPtr)->collection();
140           string s = strutils::strip(versionName);
141           if (!s.empty()) {
142               child->setVersionName(versionName);
143           }
144       } // of done self test
145     } // of line-state switching 
146   } // of file get-line loop
147 }
148   
149 void SVNDirectory::writeCache()
150 {
151   SGPath p(localPath);
152   if (!p.exists()) {
153       Dir d(localPath);
154       d.create(0755);
155   }
156   
157   p.append(string(DAV_CACHE_NAME) + ".new");
158     
159   std::ofstream file(p.c_str(), std::ios::trunc);
160 // first, cache file version header
161   file << CACHE_VERSION_4_TOKEN << '\n';
162  
163 // second, the repository VCC url
164   file << dav->versionControlledConfiguration() << '\n';
165       
166 // third, our own URL, and version
167   file << dav->url() << '\n' << _cachedRevision << '\n';
168   
169   BOOST_FOREACH(DAVResource* child, dav->contents()) {
170     if (child->isCollection()) {
171         file << child->name() << '\n' << child->versionName() << "\n";
172     }
173   } // of child iteration
174     
175     file.close();
176     
177 // approximately atomic delete + rename operation
178     SGPath cacheName(localPath);
179     cacheName.append(DAV_CACHE_NAME);
180     if (cacheName.exists()) {
181         cacheName.remove();
182     }
183     p.rename(cacheName);
184 }
185
186 void SVNDirectory::setBaseUrl(const string& url)
187 {
188     if (_parent) {
189         SG_LOG(SG_IO, SG_ALERT, "setting base URL on non-root directory " << url);
190         return;
191     }
192     
193     if (dav && (url == dav->url())) {
194         return;
195     }
196     
197     dav = new DAVCollection(url);
198 }
199
200 std::string SVNDirectory::url() const
201 {
202   if (!_parent) {
203     return repo->baseUrl();
204   }
205   
206   return _parent->url() + "/" + name();
207 }
208
209 std::string SVNDirectory::name() const
210 {
211     return dav->name();
212
213
214 DAVResource*
215 SVNDirectory::addChildFile(const std::string& fileName)
216 {
217     DAVResource* child = NULL;
218     child = new DAVResource(dav->urlForChildWithName(fileName));
219     dav->addChild(child);
220   
221     writeCache();
222     return child;
223 }
224
225 SVNDirectory*
226 SVNDirectory::addChildDirectory(const std::string& dirName)
227 {
228     if (dav->childWithName(dirName)) {
229         // existing child, let's remove it
230         deleteChildByName(dirName);
231     }
232     
233     DAVCollection* childCol = dav->createChildCollection(dirName);
234     SVNDirectory* child = new SVNDirectory(this, childCol);
235     childCol->setVersionName(child->cachedRevision());
236     _children.push_back(child);
237     writeCache();
238     return child;
239 }
240
241 void SVNDirectory::deleteChildByName(const std::string& nm)
242 {
243     DAVResource* child = dav->childWithName(nm);
244     if (!child) {
245 //        std::cerr << "ZZZ: deleteChildByName: unknown:" << nm << std::endl;
246         return;
247     }
248
249     SGPath path = fsDir().file(nm);
250     
251     if (child->isCollection()) {
252         Dir d(path);
253         d.remove(true);
254     
255         DirectoryList::iterator it = findChildDir(nm);
256         if (it != _children.end()) {
257             SVNDirectory* c = *it;
258     //        std::cout << "YYY: deleting SVNDirectory for:" << nm << std::endl;
259             delete c;
260             _children.erase(it);
261         }
262     } else {
263         path.remove();
264     }
265
266     dav->removeChild(child);
267     delete child;
268
269     writeCache();
270 }
271   
272 bool SVNDirectory::isDoingSync() const
273 {
274   if (_doingUpdateReport) {
275       return true;
276   } 
277
278   BOOST_FOREACH(SVNDirectory* child, _children) {
279       if (child->isDoingSync()) {
280           return true;
281       } // of children
282   }
283     
284   return false;
285 }
286
287 void SVNDirectory::beginUpdateReport()
288 {
289     _doingUpdateReport = true;
290     _cachedRevision.clear();
291     writeCache();
292 }
293
294 void SVNDirectory::updateReportComplete()
295 {
296     _cachedRevision = dav->versionName();
297     _doingUpdateReport = false;
298     writeCache();
299     
300     SVNDirectory* pr = parent();
301     if (pr) {
302         pr->writeCache();
303     }    
304 }
305
306 SVNRepository* SVNDirectory::repository() const
307 {
308     return repo;
309 }
310
311 void SVNDirectory::mergeUpdateReportDetails(unsigned int depth, 
312     string_list& items)
313 {
314     // normal, easy case: we are fully in-sync at a revision
315     if (!_cachedRevision.empty()) {
316         std::ostringstream os;
317         os << "<S:entry rev=\"" << _cachedRevision << "\" depth=\"infinity\">"
318             << repoPath() << "</S:entry>";
319         items.push_back(os.str());
320         return;
321     }
322     
323     Dir d(localPath);
324     if (depth >= MAX_UPDATE_REPORT_DEPTH) {
325         SG_LOG(SG_IO, SG_INFO, localPath << "exceeded MAX_UPDATE_REPORT_DEPTH, cleaning");
326         d.removeChildren();
327         return;
328     }
329     
330     PathList cs = d.children(Dir::NO_DOT_OR_DOTDOT | Dir::INCLUDE_HIDDEN | Dir::TYPE_DIR);
331     BOOST_FOREACH(SGPath path, cs) {
332         SVNDirectory* c = child(path.file());
333         if (!c) {
334             // ignore this child, if it's an incomplete download,
335             // it will be over-written on the update anyway
336             //std::cerr << "unknown SVN child" << path << std::endl;
337         } else {
338             // recurse down into children
339             c->mergeUpdateReportDetails(depth+1, items);
340         }
341     } // of child dir iteration
342 }
343
344 std::string SVNDirectory::repoPath() const
345 {
346     if (!_parent) {
347         return "/";
348     }
349     
350     // find the length of the repository base URL, then
351     // trim that off our repo URL - job done!
352     size_t baseUrlLen = repo->baseUrl().size();
353     return dav->url().substr(baseUrlLen + 1);
354 }
355
356 SVNDirectory* SVNDirectory::parent() const
357 {
358     return _parent;
359 }
360
361 SVNDirectory* SVNDirectory::child(const std::string& dirName) const
362 {
363     BOOST_FOREACH(SVNDirectory* d, _children) {
364         if (d->name() == dirName) {
365             return d;
366         }
367     }
368     
369     return NULL;
370 }
371
372 DirectoryList::iterator
373 SVNDirectory::findChildDir(const std::string& dirName)
374 {
375     DirectoryList::iterator it;
376     for (it=_children.begin(); it != _children.end(); ++it) {
377         if ((*it)->name() == dirName) {
378             return it;
379         }
380     }
381     return it;
382 }
383
384 simgear::Dir SVNDirectory::fsDir() const
385 {
386     return Dir(localPath);
387 }