1 // Copyright (C) 2000, 2001, 2002, 2003 Michael Bartl
2 // Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006 Ulf Lorenz
3 // Copyright (C) 2004, 2005, 2006 Andrea Paternesi
4 // Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2014, 2015
5 // 2020 Ben Asselstine
6 // Copyright (C) 2007 Ole Laursen
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 3 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Library General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 // 02110-1301, USA.
22
23 #include <config.h>
24
25 #include <fstream>
26 #include <iostream>
27 #include <string.h>
28 #include <string>
29 #include <glibmm/fileutils.h>
30 #include <glibmm/ustring.h>
31 #include <glibmm/convert.h>
32 #include <sys/types.h>
33 #include <dirent.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36
37 #include "File.h"
38 #include "Configuration.h"
39 #include "defs.h"
40 #include "armyset.h"
41 #include "tileset.h"
42 #include "shieldset.h"
43 #include "cityset.h"
44 #include "file-compat.h"
45 #include "ucompose.hpp"
46 #include "rnd.h"
47
48 #define debug(x) {std::cerr<<__FILE__<<": "<<__LINE__<<": "<<x<<std::endl<<std::flush;}
49 //#define debug(x)
50
51 namespace
52 {
get_files(Glib::ustring path,Glib::ustring ext)53 std::list<Glib::ustring> get_files(Glib::ustring path, Glib::ustring ext)
54 {
55 std::list<Glib::ustring> retlist;
56 Glib::Dir dir(path);
57
58 for (Glib::Dir::iterator i = dir.begin(), end = dir.end(); i != end; ++i)
59 {
60 Glib::ustring entry = *i;
61 Glib::ustring::size_type idx = entry.rfind(ext);
62 if (idx != Glib::ustring::npos &&
63 idx == entry.length() - ext.length())
64 retlist.push_back(Glib::filename_to_utf8(path + entry));
65 }
66 return retlist;
67 }
68 }
69
nameEndsWith(Glib::ustring filename,Glib::ustring extension)70 bool File::nameEndsWith(Glib::ustring filename, Glib::ustring extension)
71 {
72 Glib::ustring::size_type idx = filename.rfind(extension);
73 if (idx == Glib::ustring::npos)
74 return false;
75 if (idx == filename.length() - extension.length())
76 return true;
77 return false;
78 }
79
add_ext_if_necessary(Glib::ustring file,Glib::ustring ext)80 Glib::ustring File::add_ext_if_necessary(Glib::ustring file, Glib::ustring ext)
81 {
82 if (nameEndsWith(file, ext) == true)
83 return file;
84 else
85 return file + ext;
86 }
87
add_slash_if_necessary(Glib::ustring dir)88 Glib::ustring File::add_slash_if_necessary(Glib::ustring dir)
89 {
90 if (dir.c_str()[strlen(dir.c_str())-1] == '/' ||
91 dir.c_str()[strlen(dir.c_str())-1] == '\\')
92 return dir;
93 else
94 {
95 Glib::ustring d = Glib::build_filename (dir, " ");
96 return String::utrim(d);
97 }
98 }
99
getVariousFile(Glib::ustring filename)100 Glib::ustring File::getVariousFile(Glib::ustring filename)
101 {
102 return Glib::build_filename (Configuration::s_dataPath, "various", filename);
103 }
104
getGladeFile(Glib::ustring filename)105 Glib::ustring File::getGladeFile(Glib::ustring filename)
106 {
107 return Glib::build_filename (Configuration::s_dataPath, "glade", filename);
108 }
109
getEditorGladeFile(Glib::ustring filename)110 Glib::ustring File::getEditorGladeFile(Glib::ustring filename)
111 {
112 return Glib::build_filename (Configuration::s_dataPath, "glade", "editor", filename);
113 }
114
getMiscFile(Glib::ustring filename)115 Glib::ustring File::getMiscFile(Glib::ustring filename)
116 {
117 return Glib::build_filename (Configuration::s_dataPath, filename);
118 }
119
getXSLTFile(guint32 type,Glib::ustring old_version,Glib::ustring new_version)120 Glib::ustring File::getXSLTFile(guint32 type, Glib::ustring old_version, Glib::ustring new_version)
121 {
122 FileCompat::Type t = FileCompat::Type(type);
123 Glib::ustring filename = String::ucompose("%1-%2-%3",
124 FileCompat::typeToCode(t),
125 old_version, new_version);
126 Glib::ustring file = getMiscFile(Glib::build_filename("various", "xslt", filename + ".xsl"));
127 if (File::exists(file))
128 return file;
129 else
130 return "";
131 }
132
getUserProfilesDescription()133 Glib::ustring File::getUserProfilesDescription()
134 {
135 return Glib::build_filename (Configuration::s_savePath, PROFILE_LIST);
136 }
137
getUserRecentlyPlayedGamesDescription()138 Glib::ustring File::getUserRecentlyPlayedGamesDescription()
139 {
140 return Glib::build_filename (Configuration::s_savePath, RECENTLY_PLAYED_LIST);
141 }
142
getUserRecentlyHostedGamesDescription()143 Glib::ustring File::getUserRecentlyHostedGamesDescription()
144 {
145 return Glib::build_filename (Configuration::s_savePath, RECENTLY_HOSTED_LIST);
146 }
147
getUserRecentlyAdvertisedGamesDescription()148 Glib::ustring File::getUserRecentlyAdvertisedGamesDescription()
149 {
150 return Glib::build_filename (Configuration::s_savePath, RECENTLY_ADVERTISED_LIST);
151 }
152
getUserRecentlyEditedFilesDescription()153 Glib::ustring File::getUserRecentlyEditedFilesDescription()
154 {
155 return Glib::build_filename (Configuration::s_savePath, RECENTLY_EDITED_LIST);
156 }
157
getItemDescription()158 Glib::ustring File::getItemDescription()
159 {
160 return Glib::build_filename (Configuration::s_dataPath, "various", "items", "items.xml");
161 }
162
getEditorFile(Glib::ustring filename)163 Glib::ustring File::getEditorFile(Glib::ustring filename)
164 {
165 return Glib::build_filename (Configuration::s_dataPath, "various", "editor", filename + ".png");
166 }
167
getMusicFile(Glib::ustring filename)168 Glib::ustring File::getMusicFile(Glib::ustring filename)
169 {
170 return Glib::build_filename (Configuration::s_dataPath, "music", filename);
171 }
172
getDataPath()173 Glib::ustring File::getDataPath()
174 {
175 return add_slash_if_necessary(Configuration::s_dataPath);
176 }
177
getSavePath()178 Glib::ustring File::getSavePath()
179 {
180 return add_slash_if_necessary(Configuration::s_savePath);
181 }
182
getSaveFile(Glib::ustring filename)183 Glib::ustring File::getSaveFile(Glib::ustring filename)
184 {
185 return Glib::build_filename (getSavePath(), filename);
186 }
187
getTempFile(Glib::ustring tmpdir,Glib::ustring filename)188 Glib::ustring File::getTempFile(Glib::ustring tmpdir, Glib::ustring filename)
189 {
190 return Glib::build_filename (tmpdir, filename);
191 }
192
getCacheDir()193 Glib::ustring File::getCacheDir ()
194 {
195 return Glib::build_filename (Glib::get_user_cache_dir (), PACKAGE_NAME);
196 }
197
getUserDataDir()198 Glib::ustring File::getUserDataDir ()
199 {
200 return Glib::build_filename (Glib::get_user_data_dir (), PACKAGE_NAME);
201 }
202
getConfigDir()203 Glib::ustring File::getConfigDir ()
204 {
205 return Glib::build_filename (Glib::get_user_config_dir (), PACKAGE_NAME);
206 }
207
getConfigFile(Glib::ustring filename)208 Glib::ustring File::getConfigFile(Glib::ustring filename)
209 {
210 return Glib::build_filename (File::getConfigDir (), filename);
211 }
212
getTarTempDir(Glib::ustring dir)213 Glib::ustring File::getTarTempDir(Glib::ustring dir)
214 {
215 return Glib::build_filename (File::getCacheDir (),
216 String::ucompose("%1.%2", dir, getpid()));
217 }
218
getUserMapDir()219 Glib::ustring File::getUserMapDir()
220 {
221 return add_slash_if_necessary(Glib::build_filename (add_slash_if_necessary(Configuration::s_savePath), MAPDIR));
222 }
223
getMapDir()224 Glib::ustring File::getMapDir()
225 {
226 return add_slash_if_necessary (Glib::build_filename (add_slash_if_necessary(Configuration::s_dataPath), MAPDIR));
227 }
228
getUserMapFile(Glib::ustring file)229 Glib::ustring File::getUserMapFile(Glib::ustring file)
230 {
231 return Glib::build_filename (getUserMapDir(), file);
232 }
233
getMapFile(Glib::ustring file)234 Glib::ustring File::getMapFile(Glib::ustring file)
235 {
236 return Glib::build_filename (getMapDir(), file);
237 }
238
scanUserMaps()239 std::list<Glib::ustring> File::scanUserMaps()
240 {
241 Glib::ustring path = File::getUserMapDir();
242
243 std::list<Glib::ustring> retlist;
244 Glib::Dir dir(path);
245
246 for (Glib::Dir::iterator i = dir.begin(), end = dir.end(); i != end; ++i)
247 {
248 Glib::ustring entry = *i;
249 Glib::ustring::size_type idx = entry.find(".map");
250 if (idx != Glib::ustring::npos)
251 {
252 if (entry == "random.map")
253 continue;
254 retlist.push_back(Glib::filename_to_utf8(entry));
255 }
256 }
257
258 return retlist;
259 }
260
scanMaps()261 std::list<Glib::ustring> File::scanMaps()
262 {
263 Glib::ustring path = File::getMapDir();
264
265 std::list<Glib::ustring> retlist;
266 Glib::Dir dir(path);
267
268 for (Glib::Dir::iterator i = dir.begin(), end = dir.end(); i != end; ++i)
269 {
270 Glib::ustring entry = *i;
271 Glib::ustring::size_type idx = entry.find(".map");
272 if (idx != Glib::ustring::npos)
273 {
274 retlist.push_back(Glib::filename_to_utf8(entry));
275 }
276 }
277
278 if (retlist.empty())
279 {
280 std::cerr << _("Error: Couldn't find a single map!") << std::endl;
281 std::cerr << String::ucompose(_("Please check the path settings in %1"), File::getConfigFile(DEFAULT_CONFIG_FILENAME)) << std::endl;
282 }
283
284 return retlist;
285 }
286
get_dirname(Glib::ustring path)287 Glib::ustring File::get_dirname(Glib::ustring path)
288 {
289 return Glib::path_get_dirname(path);
290 }
291
get_basename(Glib::ustring path,bool keep_ext)292 Glib::ustring File::get_basename(Glib::ustring path, bool keep_ext)
293 {
294 if (path.empty ())
295 return path;
296 Glib::ustring file;
297 file = Glib::path_get_basename(path);
298 if (keep_ext)
299 return file;
300 //now strip everything past the last dot.
301 const char *tmp = strrchr (file.c_str(), '.');
302 if (!tmp)
303 return file;
304 int npos = tmp - file.c_str() + 1;
305 file = file.substr(0, npos - 1);
306 return file;
307 }
308
309 //copy_file taken from ardour-2.0rc2, gplv2+.
copy(Glib::ustring from,Glib::ustring to)310 bool File::copy (Glib::ustring from, Glib::ustring to)
311 {
312 std::ifstream in;
313 std::ofstream out;
314 in.open(from.c_str(), std::ios::in | std::ios::binary);
315 out.open(to.c_str(), std::ios::out | std::ios::binary);
316
317 if (!in)
318 return false;
319
320 if (!out)
321 return false;
322
323 out << in.rdbuf();
324
325 if (!in || !out)
326 {
327 File::erase(to);
328 return false;
329 }
330
331 return true;
332 }
333
create_dir(Glib::ustring dir)334 bool File::create_dir(Glib::ustring dir)
335 {
336 if (Glib::file_test(dir, Glib::FILE_TEST_IS_DIR) == true)
337 return true;
338 bool retval = false;
339 try
340 {
341 Glib::RefPtr<Gio::File> directory = Gio::File::create_for_path(dir);
342 retval = directory->make_directory_with_parents();
343 }
344 catch (Gio::Error::Exception &ex)
345 {
346 ;
347 }
348 return retval;
349 }
350
is_writable(Glib::ustring file)351 bool File::is_writable(Glib::ustring file)
352 {
353 Glib::RefPtr<Gio::File> f = Gio::File::create_for_path(file);
354 Glib::RefPtr<Gio::FileInfo> info = f->query_info("access::can-write");
355 return info->get_attribute_boolean("access::can-write");
356 }
357
directory_exists(Glib::ustring d)358 bool File::directory_exists(Glib::ustring d)
359 {
360 return Glib::file_test(d, Glib::FILE_TEST_IS_DIR);
361 }
362
exists(Glib::ustring f)363 bool File::exists(Glib::ustring f)
364 {
365 return Glib::file_test(f, Glib::FILE_TEST_EXISTS);
366 }
367
368 //armysets
369
scanForFiles(Glib::ustring dir,Glib::ustring extension)370 std::list<Glib::ustring> File::scanForFiles(Glib::ustring dir, Glib::ustring extension)
371 {
372 std::list<Glib::ustring> files;
373 try
374 {
375 files = get_files (dir, extension);
376 }
377 catch(const Glib::Exception &ex)
378 {
379 return files;
380 }
381 return files;
382 }
383
384 //shieldsets
385
getSetDir(Glib::ustring ext,bool system)386 Glib::ustring File::getSetDir(Glib::ustring ext, bool system)
387 {
388 Glib::ustring dir = add_slash_if_necessary(Configuration::s_dataPath);
389 if (system == false)
390 dir = getSavePath();
391 if (ext == ARMYSET_EXT)
392 return add_slash_if_necessary (Glib::build_filename (dir, ARMYSETDIR));
393 else if (ext == CITYSET_EXT)
394 return add_slash_if_necessary (Glib::build_filename (dir, CITYSETDIR));
395 else if (ext == TILESET_EXT)
396 return add_slash_if_necessary (Glib::build_filename (dir, TILESETDIR));
397 else if (ext == SHIELDSET_EXT)
398 return add_slash_if_necessary (Glib::build_filename (dir, SHIELDSETDIR));
399 return "";
400 }
401
erase(Glib::ustring filename)402 bool File::erase(Glib::ustring filename)
403 {
404 bool success = true;
405 if (File::exists(filename))
406 {
407 Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(filename);
408 try
409 {
410 file->remove();
411 }
412 catch (const Glib::Error &ex)
413 {
414 std::cerr << ex.what() << " " << filename << std::endl;
415 success = false;
416 }
417 }
418 else
419 success = false;
420 return success;
421 }
422
erase_dir(Glib::ustring filename)423 void File::erase_dir(Glib::ustring filename)
424 {
425 if (Glib::file_test(filename, Glib::FILE_TEST_IS_DIR) == true)
426 erase(filename);
427 }
428
clean_dir(Glib::ustring dirname)429 void File::clean_dir(Glib::ustring dirname)
430 {
431 if (File::exists(dirname) == false)
432 return;
433 Glib::Dir dir(dirname);
434 for (Glib::DirIterator it = dir.begin(); it != dir.end(); it++)
435 File::erase(File::add_slash_if_necessary(dirname) + *it);
436 dir.close();
437 File::erase_dir(dirname);
438 }
439
getSetConfigurationFilename(Glib::ustring dir,Glib::ustring subdir,Glib::ustring ext)440 Glib::ustring File::getSetConfigurationFilename(Glib::ustring dir, Glib::ustring subdir, Glib::ustring ext)
441 {
442 return Glib::build_filename (add_slash_if_necessary(dir),
443 subdir, subdir + ext);
444 }
445
_sanify(const char * string)446 char *File::_sanify(const char *string)
447 {
448 char *result = NULL;
449 size_t resultlen = 1;
450 size_t len = strlen(string);
451 result = (char*) malloc (resultlen);
452 result[0] = '\0';
453 for (unsigned int i = 0; i < len; i++)
454 {
455 int letter = tolower(string[i]);
456 if (strchr("abcdefghijklmnopqrstuvwxyz0123456789-", letter) == NULL)
457 continue;
458
459 resultlen++;
460 result = (char *) realloc (result, resultlen);
461 if (result)
462 {
463 result[resultlen-2] = char(letter);
464 result[resultlen-1] = '\0';
465 }
466 }
467 return result;
468 }
469
sanify(Glib::ustring s)470 Glib::ustring File::sanify (Glib::ustring s)
471 {
472 char *s1 = _sanify (s.c_str ());
473 Glib::ustring ret(s1);
474 free (s1);
475 return ret;
476 }
477
get_tmp_file(Glib::ustring ext)478 Glib::ustring File::get_tmp_file(Glib::ustring ext)
479 {
480 Glib::ustring file = "";
481 // fixme, there's a race condition here.
482 while (1)
483 {
484 file = Glib::build_filename (getCacheDir (),
485 "lw." + String::ucompose ("%1", Rnd::rand () % 1000000) + ext);
486 if (File::exists (file) == false)
487 break;
488 }
489 return file;
490 }
491
get_extension(Glib::ustring filename)492 Glib::ustring File::get_extension(Glib::ustring filename)
493 {
494 if (filename.rfind('.') == Glib::ustring::npos)
495 return "";
496 return filename.substr(filename.rfind('.'));
497 }
498
499 //this method is from http://www.cplusplus.com/reference/list/list/sort/
case_insensitive(const Glib::ustring & first,const Glib::ustring & second)500 bool case_insensitive (const Glib::ustring& first, const Glib::ustring& second)
501 {
502 unsigned int i = 0;
503 while (i < first.length () && i < second.length ())
504 {
505 if (tolower (first[i]) < tolower (second[i]))
506 return true;
507 else if (tolower (first[i]) > tolower (second[i]))
508 return false;
509 ++i;
510 }
511 return (first.length() < second.length());
512 }
513
rename(Glib::ustring src,Glib::ustring dest)514 bool File::rename(Glib::ustring src, Glib::ustring dest)
515 {
516 bool result = false;
517 if (File::exists(src) && File::exists(dest) == false)
518 {
519 try
520 {
521 Glib::RefPtr<Gio::File> f = Gio::File::create_for_path(src);
522 result = f->move (Gio::File::create_for_path(dest));
523 }
524 catch (Gio::Error &ex)
525 {
526 ;
527 }
528 }
529 return result;
530 }
531
add_png_if_no_ext(Glib::ustring & filename)532 bool File::add_png_if_no_ext (Glib::ustring &filename)
533 {
534 Glib::ustring f = filename;
535 //in the old days we had filenames without the extensions in our
536 //army/city/shield/tilesets.
537 //now we keep the extensions, but to maintain backwards compatibility
538 //we rejig the filenames as we load them in just in case we have an old
539 //file.
540 //the altenative to this approach is to increment the version numbers on
541 //those files and make xslt templates to give them an upgrade path.
542 if (f != "" && File::get_extension (f) == "")
543 {
544 filename = f + ".png";
545 return true;
546 }
547 return false;
548 }
549