]> git.mxchange.org Git - simgear.git/blob - simgear/io/SVNDirectory.cxx
Introduce SGBinaryFile
[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_TERRASYNC, 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_TERRASYNC, 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 = parseChildDirectory(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     p.rename(cacheName);
181 }
182
183 void SVNDirectory::setBaseUrl(const string& url)
184 {
185     if (_parent) {
186         SG_LOG(SG_TERRASYNC, SG_ALERT, "setting base URL on non-root directory " << url);
187         return;
188     }
189     
190     if (dav && (url == dav->url())) {
191         return;
192     }
193     
194     dav = new DAVCollection(url);
195 }
196
197 std::string SVNDirectory::url() const
198 {
199   if (!_parent) {
200     return repo->baseUrl();
201   }
202   
203   return _parent->url() + "/" + name();
204 }
205
206 std::string SVNDirectory::name() const
207 {
208     return dav->name();
209
210
211 DAVResource*
212 SVNDirectory::addChildFile(const std::string& fileName)
213 {
214     DAVResource* child = NULL;
215     child = new DAVResource(dav->urlForChildWithName(fileName));
216     dav->addChild(child);
217   
218     writeCache();
219     return child;
220 }
221
222 SVNDirectory*
223 SVNDirectory::addChildDirectory(const std::string& dirName)
224 {
225     if (dav->childWithName(dirName)) {
226         // existing child, let's remove it
227         deleteChildByName(dirName);
228     }
229     
230     DAVCollection* childCol = dav->createChildCollection(dirName);
231     SVNDirectory* child = new SVNDirectory(this, childCol);
232     childCol->setVersionName(child->cachedRevision());
233     _children.push_back(child);
234     writeCache();
235     return child;
236 }
237
238 SVNDirectory*
239 SVNDirectory::parseChildDirectory(const std::string& dirName)
240 {
241     assert(!dav->childWithName(dirName));
242     DAVCollection* childCol = dav->createChildCollection(dirName);
243     SVNDirectory* child = new SVNDirectory(this, childCol);
244     childCol->setVersionName(child->cachedRevision());
245     _children.push_back(child);
246     return child;
247 }
248
249 void SVNDirectory::deleteChildByName(const std::string& nm)
250 {
251     DAVResource* child = dav->childWithName(nm);
252     if (!child) {
253         return;
254     }
255
256     SGPath path = fsDir().file(nm);
257     
258     if (child->isCollection()) {
259         Dir d(path);
260         bool ok = d.remove(true);
261         if (!ok) {
262             SG_LOG(SG_TERRASYNC, SG_ALERT, "SVNDirectory::deleteChildByName: failed to remove dir:"
263                    << nm << " at path:\n\t" << path);
264         }
265
266         DirectoryList::iterator it = findChildDir(nm);
267         if (it != _children.end()) {
268             SVNDirectory* c = *it;
269             delete c;
270             _children.erase(it);
271         }
272     } else {
273         bool ok = path.remove();
274         if (!ok) {
275             SG_LOG(SG_TERRASYNC, SG_ALERT, "SVNDirectory::deleteChildByName: failed to remove path:" << nm
276                    << " at path:\n\t" << path);
277         }
278     }
279
280     dav->removeChild(child);
281     delete child;
282
283     writeCache();
284 }
285   
286 bool SVNDirectory::isDoingSync() const
287 {
288   if (_doingUpdateReport) {
289       return true;
290   } 
291
292   BOOST_FOREACH(SVNDirectory* child, _children) {
293       if (child->isDoingSync()) {
294           return true;
295       } // of children
296   }
297     
298   return false;
299 }
300
301 void SVNDirectory::beginUpdateReport()
302 {
303     _doingUpdateReport = true;
304     _cachedRevision.clear();
305     writeCache();
306 }
307
308 void SVNDirectory::updateReportComplete()
309 {
310     _cachedRevision = dav->versionName();
311     _doingUpdateReport = false;
312     writeCache();
313     
314     SVNDirectory* pr = parent();
315     if (pr) {
316         pr->writeCache();
317     }    
318 }
319
320 SVNRepository* SVNDirectory::repository() const
321 {
322     return repo;
323 }
324
325 void SVNDirectory::mergeUpdateReportDetails(unsigned int depth, 
326     string_list& items)
327 {
328     // normal, easy case: we are fully in-sync at a revision
329     if (!_cachedRevision.empty()) {
330         std::ostringstream os;
331         os << "<S:entry rev=\"" << _cachedRevision << "\" depth=\"infinity\">"
332             << repoPath() << "</S:entry>";
333         items.push_back(os.str());
334         return;
335     }
336     
337     Dir d(localPath);
338     if (depth >= MAX_UPDATE_REPORT_DEPTH) {
339         d.removeChildren();
340         return;
341     }
342     
343     PathList cs = d.children(Dir::NO_DOT_OR_DOTDOT | Dir::INCLUDE_HIDDEN | Dir::TYPE_DIR);
344     BOOST_FOREACH(SGPath path, cs) {
345         SVNDirectory* c = child(path.file());
346         if (!c) {
347             // ignore this child, if it's an incomplete download,
348             // it will be over-written on the update anyway
349             //std::cerr << "unknown SVN child" << path << std::endl;
350         } else {
351             // recurse down into children
352             c->mergeUpdateReportDetails(depth+1, items);
353         }
354     } // of child dir iteration
355 }
356
357 std::string SVNDirectory::repoPath() const
358 {
359     if (!_parent) {
360         return "/";
361     }
362     
363     // find the length of the repository base URL, then
364     // trim that off our repo URL - job done!
365     size_t baseUrlLen = repo->baseUrl().size();
366     return dav->url().substr(baseUrlLen + 1);
367 }
368
369 SVNDirectory* SVNDirectory::parent() const
370 {
371     return _parent;
372 }
373
374 SVNDirectory* SVNDirectory::child(const std::string& dirName) const
375 {
376     BOOST_FOREACH(SVNDirectory* d, _children) {
377         if (d->name() == dirName) {
378             return d;
379         }
380     }
381     
382     return NULL;
383 }
384
385 DirectoryList::iterator
386 SVNDirectory::findChildDir(const std::string& dirName)
387 {
388     DirectoryList::iterator it;
389     for (it=_children.begin(); it != _children.end(); ++it) {
390         if ((*it)->name() == dirName) {
391             return it;
392         }
393     }
394     return it;
395 }
396
397 simgear::Dir SVNDirectory::fsDir() const
398 {
399     return Dir(localPath);
400 }