1 // Copyright (C) 2011, 2014 Ben Asselstine
2 //
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 // 02110-1301, USA.
17
18 #include <sigc++/functors/mem_fun.h>
19
20 #include <cstdio>
21 #include <limits.h>
22 #include <fstream>
23 #include <iostream>
24 #include <libxml/xmlmemory.h>
25 #include <libxslt/xslt.h>
26 #include <libxslt/transform.h>
27 #include <libxslt/xsltutils.h>
28
29 #include "armyset.h"
30 #include "tileset.h"
31 #include "shieldset.h"
32 #include "cityset.h"
33 #include "xmlhelper.h"
34 #include "Configuration.h"
35 #include "defs.h"
36 #include "File.h"
37 #include "file-compat.h"
38 #include "tarhelper.h"
39 #include "GameScenario.h"
40 #include "ucompose.hpp"
41 #include "Itemlist.h"
42
43 //#define debug(x) {std::cerr<<__FILE__<<": "<<__LINE__<<": "<<x<<std::endl<<std::flush;}
44 #define debug(x)
45
46 FileCompat* FileCompat::s_instance = 0;
47
getInstance()48 FileCompat* FileCompat::getInstance()
49 {
50 if (s_instance == 0)
51 s_instance = new FileCompat();
52
53 return s_instance;
54 }
55
deleteInstance()56 void FileCompat::deleteInstance()
57 {
58 if (s_instance)
59 delete s_instance;
60
61 s_instance = 0;
62 }
63
contains(FileCompat::Type type) const64 bool FileCompat::contains(FileCompat::Type type) const
65 {
66 for (const_iterator i = begin(); i != end(); i++)
67 if ((*i).type == type)
68 return true;
69 return false;
70 }
71
support_backward_compatibility_for_common_files()72 void FileCompat::support_backward_compatibility_for_common_files()
73 {
74 if (FileCompat::getInstance()->contains(CONFIGURATION) == false)
75 Configuration::support_backward_compatibility();
76 if (FileCompat::getInstance()->contains(ITEMLIST) == false)
77 Itemlist::support_backward_compatibility();
78 if (FileCompat::getInstance()->contains(ARMYSET) == false)
79 Armyset::support_backward_compatibility();
80 if (FileCompat::getInstance()->contains(TILESET) == false)
81 Tileset::support_backward_compatibility();
82 if (FileCompat::getInstance()->contains(CITYSET) == false)
83 Cityset::support_backward_compatibility();
84 if (FileCompat::getInstance()->contains(SHIELDSET) == false)
85 Shieldset::support_backward_compatibility();
86 if (FileCompat::getInstance()->contains(GAMESCENARIO) == false)
87 GameScenario::support_backward_compatibility();
88 }
89
FileCompat()90 FileCompat::FileCompat()
91 {
92 }
93
getType(Glib::ustring filename) const94 FileCompat::Type FileCompat::getType(Glib::ustring filename) const
95 {
96 if (File::exists(filename) == false)
97 return UNKNOWN;
98 for (const_iterator i = begin(); i != end(); i++)
99 {
100 if (File::nameEndsWith(filename, (*i).file_extension) == true &&
101 (*i).file_extension != ".xml")
102 {
103 return FileCompat::Type((*i).type);
104 }
105 }
106 bool tar = false;
107 return getTypeByFileInspection(filename, tar);
108 }
109
getTypeByTarFileInspection(Glib::ustring filename) const110 FileCompat::Type FileCompat::getTypeByTarFileInspection(Glib::ustring filename) const
111 {
112 bool broken = false;
113 Tar_Helper t(filename, std::ios::in, broken);
114 if (broken)
115 return UNKNOWN;
116
117 std::list<Glib::ustring> files = t.getFilenames();
118 t.Close();
119 std::list<FileDetails> details;
120 //whittle down the files it can't be
121 for (std::list<Glib::ustring>::iterator i = files.begin(); i != files.end();
122 i++)
123 {
124 bool found = false;
125 for (const_iterator j = begin(); j != end(); j++)
126 {
127 if (File::nameEndsWith(*i, (*j).file_extension) == true)
128 {
129 if ((*j).type == GAMESCENARIO)
130 return GAMESCENARIO;
131 details.push_back(*j);
132 found = true;
133 break;
134 }
135 }
136 if (!found)
137 {
138 i = files.erase(i);
139 continue;
140 }
141 }
142
143 if (details.size() == 0)
144 return UNKNOWN;
145 else
146 return FileCompat::Type(details.front().type);
147 }
148
getTypeByXmlFileInspection(Glib::ustring filename) const149 FileCompat::Type FileCompat::getTypeByXmlFileInspection(Glib::ustring filename) const
150 {
151 Glib::ustring tag = XML_Helper::get_top_tag(filename);
152
153 if (tag == "")
154 return UNKNOWN;
155 for (const_iterator i = begin(); i != end(); i++)
156 {
157 if (tag == (*i).tag)
158 return FileCompat::Type((*i).type);
159 }
160 return UNKNOWN;
161 }
162
getTypeByFileInspection(Glib::ustring filename,bool & tar) const163 FileCompat::Type FileCompat::getTypeByFileInspection(Glib::ustring filename, bool &tar) const
164 {
165 Type type = getTypeByTarFileInspection(filename);
166 tar = type != UNKNOWN;
167 if (type == UNKNOWN)
168 return getTypeByXmlFileInspection(filename);
169 else
170 return type;
171 }
172
get_tag_and_version_from_file(Glib::ustring filename,FileCompat::Type type,Glib::ustring & tag,Glib::ustring & version) const173 bool FileCompat::get_tag_and_version_from_file(Glib::ustring filename, FileCompat::Type type, Glib::ustring &tag, Glib::ustring &version) const
174 {
175 bool broken = false;
176 if (isTarFile(type) == true)
177 {
178 std::list<Glib::ustring> ext = getFileExtensions(type);
179 if (ext.empty() == true)
180 return false;
181 Tar_Helper t(filename, std::ios::in, broken);
182 if (!broken)
183 {
184 Glib::ustring tmpfile = "";
185 for (std::list<Glib::ustring>::iterator i = ext.begin();
186 i != ext.end(); ++i)
187 {
188 tmpfile = t.getFirstFile(*i, broken);
189 if (!broken && tmpfile.empty() == false)
190 {
191 XML_Helper helper(tmpfile, std::ios::in);
192 tag = XML_Helper::get_top_tag(tmpfile);
193 VersionLoader l(tmpfile, tag, version, broken);
194 t.Close();
195 File::erase(tmpfile);
196 return !broken;
197 }
198 }
199 }
200 }
201 else
202 {
203 tag = XML_Helper::get_top_tag(filename);
204 VersionLoader l(filename, tag, version, broken);
205 }
206 return !broken;
207 }
208
isTarFile(FileCompat::Type type) const209 bool FileCompat::isTarFile(FileCompat::Type type) const
210 {
211 for (const_iterator i = begin(); i != end(); i++)
212 if ((*i).type == type)
213 return (*i).tar;
214 return false;
215 }
216
getFileExtension(FileCompat::Type type) const217 Glib::ustring FileCompat::getFileExtension(FileCompat::Type type) const
218 {
219 for (const_iterator i = begin(); i != end(); i++)
220 if ((*i).type == type)
221 return (*i).file_extension;
222 return "";
223 }
224
getTag(FileCompat::Type type) const225 Glib::ustring FileCompat::getTag(FileCompat::Type type) const
226 {
227 for (const_iterator i = begin(); i != end(); i++)
228 if ((*i).type == type)
229 return (*i).tag;
230 return "";
231 }
232
upgrade(Glib::ustring filename,bool & same) const233 bool FileCompat::upgrade(Glib::ustring filename, bool &same) const
234 {
235 Glib::ustring tag, version;
236 bool upgraded = false;
237 if (File::exists(filename) == false)
238 return false;
239 Type type = getType(filename);
240 if (type == UNKNOWN)
241 return false;
242 if (get_tag_and_version_from_file (filename, type, tag, version) == false)
243 return false;
244
245 //can we get there from here?
246 Slot slot;
247 Glib::ustring next_version;
248 if (get_upgrade_method(type, version, next_version, slot) == false)
249 {
250 same = can_upgrade_to(type, version);
251 return false;
252 }
253
254 bool broken = false;
255 // ride the upgrade train as far as we can.
256 while (1)
257 {
258 if (get_upgrade_method(type, version, next_version, slot))
259 {
260 upgraded = (slot)(filename, version, next_version);
261 version = next_version;
262 if (upgraded == false)
263 {
264 broken = true;
265 break;
266 }
267 }
268 else
269 break;
270 }
271 return !broken;
272 }
273
can_upgrade_to(FileCompat::Type type,Glib::ustring version) const274 bool FileCompat::can_upgrade_to(FileCompat::Type type, Glib::ustring version) const
275 {
276 for (std::list<UpgradeDetails>::const_iterator i = versions[type].begin();
277 i != versions[type].end(); i++)
278 {
279 if ((*i).to_version == version)
280 return true;
281 }
282 return false;
283 }
284
get_upgrade_method(FileCompat::Type type,Glib::ustring version,Glib::ustring & next_version,FileCompat::Slot & slot) const285 bool FileCompat::get_upgrade_method(FileCompat::Type type, Glib::ustring version, Glib::ustring &next_version, FileCompat::Slot &slot) const
286 {
287 for (std::list<UpgradeDetails>::const_iterator i = versions[type].begin();
288 i != versions[type].end(); i++)
289 {
290 if ((*i).from_version == version)
291 {
292 next_version = (*i).to_version;
293 slot = (*i).slot;
294 return true;
295 }
296 }
297 return false;
298 }
299
rewrite_with_updated_version(Glib::ustring filename,FileCompat::Type type,Glib::ustring tag,Glib::ustring version) const300 bool FileCompat::rewrite_with_updated_version(Glib::ustring filename, FileCompat::Type type, Glib::ustring tag, Glib::ustring version) const
301 {
302 bool broken = false;
303 bool upgraded = false;
304 if (isTarFile(type) && type != GAMESCENARIO)
305 {
306 Glib::ustring ext = getFileExtension(type);
307
308 Tar_Helper t(filename, std::ios::in, broken);
309 if (broken == false)
310 {
311 Glib::ustring tmpfile = t.getFirstFile(ext, broken);
312 if (broken == false && version != "")
313 upgraded = XML_Helper::rewrite_version(tmpfile, tag, version);
314 if (upgraded)
315 {
316 Glib::ustring n = t.getFirstFilename(ext);
317 t.replaceFile (n, tmpfile, n);
318 }
319 t.Close();
320 if (tmpfile != "")
321 File::erase(tmpfile);
322 }
323 }
324 else if (isTarFile(type) && type == GAMESCENARIO)
325 {
326 bool upgraded_armyset = false, upgraded_tileset = false,
327 upgraded_cityset = false, upgraded_shieldset = false;
328 return upgradeGameScenario(filename, version, upgraded_armyset,
329 upgraded_tileset, upgraded_cityset,
330 upgraded_shieldset);
331 }
332 else if (isTarFile(type) == false)
333 {
334 if (version != "")
335 upgraded = XML_Helper::rewrite_version(filename, tag, version);
336 }
337 return upgraded;
338 }
339
getTypeByFileExtension(Glib::ustring ext) const340 FileCompat::Type FileCompat::getTypeByFileExtension(Glib::ustring ext) const
341 {
342 for (const_iterator i = begin(); i != end(); i++)
343 if ((*i).file_extension == ext)
344 return FileCompat::Type((*i).type);
345 return UNKNOWN;
346 }
347
upgradeGameScenario(Glib::ustring filename,Glib::ustring version,bool & upgraded_armyset,bool & upgraded_tileset,bool & upgraded_cityset,bool & upgraded_shieldset) const348 bool FileCompat::upgradeGameScenario(Glib::ustring filename, Glib::ustring version, bool& upgraded_armyset, bool& upgraded_tileset, bool& upgraded_cityset, bool& upgraded_shieldset) const
349 {
350 Glib::ustring ext = File::get_extension(filename);
351 if (ext == "")
352 return false;
353 if (getTypeByFileExtension(ext) != GAMESCENARIO)
354 return false;
355 bool upgraded = false;
356 bool broken = false;
357 Tar_Helper t(filename, std::ios::in, broken);
358 if (!broken)
359 {
360 Glib::ustring tmpfile = t.getFirstFile(ext, broken);
361 if (broken == false)
362 upgraded = XML_Helper::rewrite_version(tmpfile, getTag(GAMESCENARIO),
363 version);
364 std::list<Glib::ustring> delfiles;
365 delfiles.push_back(tmpfile);
366 if (upgraded)
367 {
368 bool same;
369 Glib::ustring n = t.getFirstFilename(ext);
370 t.replaceFile (n, tmpfile, n);
371 //now we need to upgrade the other files.
372 Glib::ustring f = t.getFirstFilename (getFileExtension(ARMYSET));
373 tmpfile = t.getFile(f, broken);
374 if (tmpfile != "")
375 {
376 same = false;
377 if (upgrade(tmpfile, same))
378 {
379 if (!same)
380 {
381 upgraded_armyset = true;
382 t.replaceFile (f, tmpfile, f);
383 }
384 }
385 delfiles.push_back(tmpfile);
386 }
387 tmpfile = t.getFirstFile(getFileExtension(TILESET), broken);
388 if (tmpfile != "")
389 {
390 same = false;
391 if (upgrade(tmpfile, same))
392 {
393 if (!same)
394 {
395 upgraded_tileset = true;
396 n = t.getFirstFilename (getFileExtension(TILESET));
397 t.replaceFile (n, tmpfile, n);
398 }
399 }
400 delfiles.push_back(tmpfile);
401 }
402 tmpfile = t.getFirstFile(getFileExtension(CITYSET), broken);
403 if (tmpfile != "")
404 {
405 same = false;
406 if (upgrade(tmpfile, same))
407 {
408 if (!same)
409 {
410 upgraded_cityset = true;
411 n = t.getFirstFilename (getFileExtension(CITYSET));
412 t.replaceFile (n, tmpfile, n);
413 }
414 }
415 delfiles.push_back(tmpfile);
416 }
417 tmpfile = t.getFirstFile(getFileExtension(SHIELDSET), broken);
418 if (tmpfile != "")
419 {
420 same = false;
421 if (upgrade(tmpfile, same))
422 {
423 if (!same)
424 {
425 upgraded_shieldset = true;
426 n = t.getFirstFilename (getFileExtension(SHIELDSET));
427 t.replaceFile (n, tmpfile, n);
428 }
429 }
430 delfiles.push_back(tmpfile);
431 }
432 }
433 t.Close();
434 for (std::list<Glib::ustring>::iterator i = delfiles.begin(); i != delfiles.end(); i++)
435 File::erase(*i);
436 }
437 return upgraded;
438 }
439
initialize()440 void FileCompat::initialize()
441 {
442 bool same;
443 if (contains(GAMELIST))
444 upgrade(File::getUserRecentlyAdvertisedGamesDescription(), same);
445 if (contains(GAMELIST))
446 upgrade(File::getUserRecentlyHostedGamesDescription(), same);
447 if (contains(PROFILELIST))
448 upgrade(File::getUserProfilesDescription(), same);
449 if (contains(RECENTLYPLAYEDGAMELIST))
450 upgrade(File::getUserRecentlyPlayedGamesDescription(), same);
451 }
452
typeToString(const FileCompat::Type type)453 Glib::ustring FileCompat::typeToString(const FileCompat::Type type)
454 {
455 switch (type)
456 {
457 case UNKNOWN: return _("unknown file");
458 case CONFIGURATION: return _("primary configuration file");
459 case ITEMLIST: return _("item description file");
460 case PROFILELIST: return _("profiles file");
461 case RECENTLYPLAYEDGAMELIST: return _("recently played games file");
462 case GAMELIST: return _("recently hosted or recently advertised games file");
463 case ARMYSET: return _("armyset file");
464 case TILESET: return _("tileset file");
465 case CITYSET: return _("cityset file");
466 case SHIELDSET: return _("shieldset file");
467 case GAMESCENARIO: return _("map or saved-game file");
468 }
469 return _("unknown file");
470 }
471
typeToCode(const FileCompat::Type type)472 Glib::ustring FileCompat::typeToCode(const FileCompat::Type type)
473 {
474 switch (type)
475 {
476 case UNKNOWN: return "";
477 case CONFIGURATION: return "c";
478 case ITEMLIST: return "il";
479 case PROFILELIST: return "pl";
480 case RECENTLYPLAYEDGAMELIST: return "rpg";
481 case GAMELIST: return "gl";
482 case ARMYSET: return "as";
483 case TILESET: return "ts";
484 case CITYSET: return "cs";
485 case SHIELDSET: return "ss";
486 case GAMESCENARIO: return "gs";
487 }
488 return "";
489 }
490
support_version(guint32 k,Glib::ustring from,Glib::ustring to,FileCompat::Slot slot)491 void FileCompat::support_version(guint32 k, Glib::ustring from, Glib::ustring to, FileCompat::Slot slot)
492 {
493 versions[FileCompat::Type(k)].push_back(UpgradeDetails(from, to, slot));
494 }
495
rewrite_with_xslt(Glib::ustring filename,FileCompat::Type type,Glib::ustring xsl_file) const496 bool FileCompat::rewrite_with_xslt(Glib::ustring filename, FileCompat::Type type, Glib::ustring xsl_file) const
497 {
498 bool broken = false;
499 bool upgraded = false;
500 if (isTarFile(type) && type != GAMESCENARIO)
501 {
502 Glib::ustring ext = getFileExtension(type);
503
504 Tar_Helper t(filename, std::ios::in, broken);
505 if (broken == false)
506 {
507 Glib::ustring tmpfile = t.getFirstFile(ext, broken);
508 Glib::ustring n = t.getFirstFilename(ext);
509 if (broken == false)
510 upgraded = xsl_transform(tmpfile, xsl_file);
511 if (upgraded)
512 t.replaceFile (n, tmpfile, n);
513 t.Close();
514 if (tmpfile != "")
515 File::erase(tmpfile);
516 }
517 }
518 else if (isTarFile(type) && type == GAMESCENARIO)
519 {
520 bool armyset_upgraded, tileset_upgraded, cityset_upgraded,
521 shieldset_upgraded = false;
522 return upgradeGameScenarioWithXslt(filename, xsl_file, armyset_upgraded,
523 tileset_upgraded, cityset_upgraded,
524 shieldset_upgraded);
525 }
526 else if (isTarFile(type) == false)
527 {
528 upgraded = xsl_transform(filename, xsl_file);
529 }
530 return upgraded;
531 }
532
upgradeGameScenarioWithXslt(Glib::ustring filename,Glib::ustring xsl_file,bool & armyset_upgraded,bool & tileset_upgraded,bool & cityset_upgraded,bool & shieldset_upgraded) const533 bool FileCompat::upgradeGameScenarioWithXslt(Glib::ustring filename, Glib::ustring xsl_file, bool& armyset_upgraded, bool& tileset_upgraded, bool& cityset_upgraded, bool &shieldset_upgraded) const
534 {
535 Glib::ustring ext = File::get_extension(filename);
536 if (ext == "")
537 return false;
538 if (getTypeByFileExtension(ext) != GAMESCENARIO)
539 return false;
540 bool upgraded = false;
541 bool broken = false;
542 Tar_Helper t(filename, std::ios::in, broken);
543 if (!broken)
544 {
545 Glib::ustring tmpfile = t.getFirstFile(ext, broken);
546 if (broken == false)
547 upgraded = xsl_transform(tmpfile, xsl_file);
548
549 std::list<Glib::ustring> delfiles;
550 delfiles.push_back(tmpfile);
551 if (upgraded)
552 {
553 bool same;
554 Glib::ustring n = t.getFirstFilename(ext);
555 t.replaceFile (n, tmpfile, n);
556 //now we need to upgrade the other files.
557 Glib::ustring f = t.getFirstFilename (getFileExtension(ARMYSET));
558 tmpfile = t.getFile(f, broken);
559 if (tmpfile != "")
560 {
561 same = false;
562 if (upgrade(tmpfile, same))
563 {
564 if (!same)
565 {
566 armyset_upgraded = true;
567 t.replaceFile (f, tmpfile, f);
568 }
569 }
570 delfiles.push_back(tmpfile);
571 }
572 tmpfile = t.getFirstFile(getFileExtension(TILESET), broken);
573 if (tmpfile != "")
574 {
575 same = false;
576 if (upgrade(tmpfile, same))
577 {
578 if (!same)
579 {
580 tileset_upgraded = true;
581 n = t.getFirstFilename (getFileExtension(TILESET));
582 t.replaceFile (n, tmpfile, n);
583 }
584 }
585 delfiles.push_back(tmpfile);
586 }
587 tmpfile = t.getFirstFile(getFileExtension(CITYSET), broken);
588 if (tmpfile != "")
589 {
590 same = false;
591 if (upgrade(tmpfile, same))
592 {
593 if (!same)
594 {
595 cityset_upgraded = true;
596 n = t.getFirstFilename (getFileExtension(CITYSET));
597 t.replaceFile (n, tmpfile, n);
598 }
599 }
600 delfiles.push_back(tmpfile);
601 }
602 tmpfile = t.getFirstFile(getFileExtension(SHIELDSET), broken);
603 if (tmpfile != "")
604 {
605 same = false;
606 if (upgrade(tmpfile, same))
607 {
608 if (!same)
609 {
610 shieldset_upgraded = true;
611 n = t.getFirstFilename (getFileExtension(SHIELDSET));
612 t.replaceFile (n, tmpfile, n);
613 }
614 }
615 delfiles.push_back(tmpfile);
616 }
617 }
618 for (std::list<Glib::ustring>::iterator i = delfiles.begin(); i != delfiles.end(); i++)
619 File::erase(*i);
620 t.Close();
621 }
622 return upgraded;
623 }
624
625
xsl_transform(Glib::ustring filename,Glib::ustring xsl_file) const626 bool FileCompat::xsl_transform(Glib::ustring filename, Glib::ustring xsl_file) const
627 {
628 const char *params[16 + 1];
629 //int nbparams = 0;
630 memset (params, 0, sizeof (params));
631 xsltStylesheetPtr cur = NULL;
632 xmlDocPtr doc, res;
633
634 xmlChar *xsl = xmlCharStrdup(xsl_file.c_str());
635 cur = xsltParseStylesheetFile(xsl);
636 if (cur == NULL)
637 return false;
638 doc = xmlParseFile(filename.c_str());
639 if (doc == NULL)
640 return false;
641 res = xsltApplyStylesheet(cur, doc, params);
642 if (res == NULL)
643 return false;
644
645 Glib::ustring tmpfile = File::get_tmp_file();
646
647 xmlChar *out = xmlCharStrdup(tmpfile.c_str());
648 xsltSaveResultToFilename(tmpfile.c_str(), res, cur, 0);
649 xsltFreeStylesheet(cur);
650 xmlFreeDoc(res);
651 xmlFreeDoc(doc);
652 free (xsl);
653 free (out);
654
655 xsltCleanupGlobals();
656 xmlCleanupParser();
657 File::erase(filename);
658 File::rename(tmpfile, filename);
659 return true;
660 }
661
upgrade(Glib::ustring filename,Glib::ustring old_version,Glib::ustring new_version,FileCompat::Type type,Glib::ustring tag) const662 bool FileCompat::upgrade(Glib::ustring filename, Glib::ustring old_version, Glib::ustring new_version, FileCompat::Type type, Glib::ustring tag) const
663 {
664 Glib::ustring xsl_filename = File::getXSLTFile(type, old_version, new_version);
665 if (xsl_filename != "")
666 return rewrite_with_xslt (filename, type, xsl_filename);
667 else
668 return rewrite_with_updated_version (filename, type, tag, new_version);
669 }
670
getFileExtensions(FileCompat::Type type) const671 std::list<Glib::ustring> FileCompat::getFileExtensions(FileCompat::Type type) const
672 {
673 std::list<Glib::ustring> ext;
674 for (const_iterator i = begin(); i != end(); i++)
675 if ((*i).type == type)
676 ext.push_back((*i).file_extension);
677 return ext;
678 }
679
680 // End of file
681