]> git.mxchange.org Git - simgear.git/blob - simgear/io/SVNDirectory.cxx
cc572fc11da25dff410a0125f1b544e9db760923
[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     dav->removeChild(child);
251     delete child;
252     
253     if (child->isCollection()) {
254         Dir d(path);
255         d.remove(true);
256     
257         DirectoryList::iterator it = findChildDir(nm);
258         if (it != _children.end()) {
259             SVNDirectory* c = *it;
260     //        std::cout << "YYY: deleting SVNDirectory for:" << nm << std::endl;
261             delete c;
262             _children.erase(it);
263         }
264     } else {
265         path.remove();
266     }
267     
268     writeCache();
269 }
270   
271 bool SVNDirectory::isDoingSync() const
272 {
273   if (_doingUpdateReport) {
274       return true;
275   } 
276
277   BOOST_FOREACH(SVNDirectory* child, _children) {
278       if (child->isDoingSync()) {
279           return true;
280       } // of children
281   }
282     
283   return false;
284 }
285
286 void SVNDirectory::beginUpdateReport()
287 {
288     _doingUpdateReport = true;
289     _cachedRevision.clear();
290     writeCache();
291 }
292
293 void SVNDirectory::updateReportComplete()
294 {
295     _cachedRevision = dav->versionName();
296     _doingUpdateReport = false;
297     writeCache();
298     
299     SVNDirectory* pr = parent();
300     if (pr) {
301         pr->writeCache();
302     }    
303 }
304
305 SVNRepository* SVNDirectory::repository() const
306 {
307     return repo;
308 }
309
310 void SVNDirectory::mergeUpdateReportDetails(unsigned int depth, 
311     string_list& items)
312 {
313     // normal, easy case: we are fully in-sync at a revision
314     if (!_cachedRevision.empty()) {
315         std::ostringstream os;
316         os << "<S:entry rev=\"" << _cachedRevision << "\" depth=\"infinity\">"
317             << repoPath() << "</S:entry>";
318         items.push_back(os.str());
319         return;
320     }
321     
322     Dir d(localPath);
323     if (depth >= MAX_UPDATE_REPORT_DEPTH) {
324         SG_LOG(SG_IO, SG_INFO, localPath << "exceeded MAX_UPDATE_REPORT_DEPTH, cleaning");
325         d.removeChildren();
326         return;
327     }
328     
329     PathList cs = d.children(Dir::NO_DOT_OR_DOTDOT | Dir::INCLUDE_HIDDEN | Dir::TYPE_DIR);
330     BOOST_FOREACH(SGPath path, cs) {
331         SVNDirectory* c = child(path.file());
332         if (!c) {
333             // ignore this child, if it's an incomplete download,
334             // it will be over-written on the update anyway
335             //std::cerr << "unknown SVN child" << path << std::endl;
336         } else {
337             // recurse down into children
338             c->mergeUpdateReportDetails(depth+1, items);
339         }
340     } // of child dir iteration
341 }
342
343 std::string SVNDirectory::repoPath() const
344 {
345     if (!_parent) {
346         return "/";
347     }
348     
349     // find the length of the repository base URL, then
350     // trim that off our repo URL - job done!
351     size_t baseUrlLen = repo->baseUrl().size();
352     return dav->url().substr(baseUrlLen + 1);
353 }
354
355 SVNDirectory* SVNDirectory::parent() const
356 {
357     return _parent;
358 }
359
360 SVNDirectory* SVNDirectory::child(const std::string& dirName) const
361 {
362     BOOST_FOREACH(SVNDirectory* d, _children) {
363         if (d->name() == dirName) {
364             return d;
365         }
366     }
367     
368     return NULL;
369 }
370
371 DirectoryList::iterator
372 SVNDirectory::findChildDir(const std::string& dirName)
373 {
374     DirectoryList::iterator it;
375     for (it=_children.begin(); it != _children.end(); ++it) {
376         if ((*it)->name() == dirName) {
377             return it;
378         }
379     }
380     return it;
381 }
382
383 simgear::Dir SVNDirectory::fsDir() const
384 {
385     return Dir(localPath);
386 }