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