]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
remove redundant lyxerr.debugging checks; macro LYXERR already checks whether the...
[lyx.git] / src / support / filetools.C
1 /**
2  * \file filetools.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7  *
8  * \author Ivan Schreter
9  * \author Dirk Niggemann
10  * \author Asger Alstrup
11  * \author Lars Gullik Bjønnes
12  * \author Jean-Marc Lasgouttes
13  * \author Angus Leeming
14  * \author John Levon
15  * \author Herbert Voß
16  *
17  * Full author contact details are available in file CREDITS.
18  *
19  * General path-mangling functions
20  */
21
22 #include <config.h>
23
24 #include "support/convert.h"
25 #include "support/environment.h"
26 #include "support/filetools.h"
27 #include "support/fs_extras.h"
28 #include "support/lstrings.h"
29 #include "support/lyxlib.h"
30 #include "support/os.h"
31 #include "support/package.h"
32 #include "support/path.h"
33 #include "support/systemcall.h"
34
35 // FIXME Interface violation
36 #include "gettext.h"
37 #include "debug.h"
38
39 #include <boost/assert.hpp>
40 #include <boost/filesystem/operations.hpp>
41 #include <boost/regex.hpp>
42
43 #include <fcntl.h>
44
45 #include <cctype>
46 #include <cerrno>
47 #include <cstdlib>
48 #include <cstdio>
49
50 #include <utility>
51 #include <fstream>
52 #include <sstream>
53
54 #ifndef CXX_GLOBAL_CSTD
55 using std::fgetc;
56 using std::isalnum;
57 using std::isalpha;
58 #endif
59
60 using std::endl;
61 using std::getline;
62 using std::make_pair;
63 using std::string;
64 using std::ifstream;
65 using std::ostringstream;
66 using std::vector;
67
68 namespace fs = boost::filesystem;
69
70 namespace lyx {
71 namespace support {
72
73 bool isLyXFilename(string const & filename)
74 {
75         return suffixIs(ascii_lowercase(filename), ".lyx");
76 }
77
78
79 bool isSGMLFilename(string const & filename)
80 {
81         return suffixIs(ascii_lowercase(filename), ".sgml");
82 }
83
84
85 string const latex_path(string const & original_path,
86                 latex_path_extension extension,
87                 latex_path_dots dots)
88 {
89         // On cygwin, we may need windows or posix style paths.
90         string path = os::latex_path(original_path);
91         path = subst(path, "~", "\\string~");
92         if (path.find(' ') != string::npos) {
93                 // We can't use '"' because " is sometimes active (e.g. if
94                 // babel is loaded with the "german" option)
95                 if (extension == EXCLUDE_EXTENSION) {
96                         // ChangeExtension calls os::internal_path internally
97                         // so don't use it to remove the extension.
98                         string const ext = getExtension(path);
99                         string const base = ext.empty() ?
100                                 path :
101                                 path.substr(0, path.length() - ext.length() - 1);
102                         // ChangeExtension calls os::internal_path internally
103                         // so don't use it to re-add the extension.
104                         path = "\\string\"" + base + "\\string\"." + ext;
105                 } else {
106                         path = "\\string\"" + path + "\\string\"";
107                 }
108         }
109
110         return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
111 }
112
113
114 // Substitutes spaces with underscores in filename (and path)
115 string const makeLatexName(string const & file)
116 {
117         string name = onlyFilename(file);
118         string const path = onlyPath(file);
119
120         // ok so we scan through the string twice, but who cares.
121         string const keep = "abcdefghijklmnopqrstuvwxyz"
122                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
123                 "@!'()*+,-./0123456789:;<=>?[]`|";
124
125         string::size_type pos = 0;
126         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
127                 name[pos++] = '_';
128
129         return addName(path, name);
130 }
131
132
133 string const quoteName(string const & name, quote_style style)
134 {
135         switch(style) {
136         case quote_shell:
137                 // This does not work for filenames containing " (windows)
138                 // or ' (all other OSes). This can't be changed easily, since
139                 // we would need to adapt the command line parser in
140                 // Forkedcall::generateChild. Therefore we don't pass user
141                 // filenames to child processes if possible. We store them in
142                 // a python script instead, where we don't have these
143                 // limitations.
144                 return (os::shell() == os::UNIX) ?
145                         '\'' + name + '\'':
146                         '"' + name + '"';
147         case quote_python:
148                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
149                      + "\"";
150         }
151         // shut up stupid compiler
152         return string();
153 }
154
155
156 bool isFileReadable(FileName const & filename)
157 {
158         std::string const path = filename.toFilesystemEncoding();
159         return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
160 }
161
162
163 //returns true: dir writeable
164 //        false: not writeable
165 bool isDirWriteable(FileName const & path)
166 {
167         LYXERR(Debug::FILES) << "isDirWriteable: " << path << endl;
168
169         FileName const tmpfl(tempName(path, "lyxwritetest"));
170
171         if (tmpfl.empty())
172                 return false;
173
174         unlink(tmpfl);
175         return true;
176 }
177
178
179 #if 0
180 // Uses a string of paths separated by ";"s to find a file to open.
181 // Can't cope with pathnames with a ';' in them. Returns full path to file.
182 // If path entry begins with $$LyX/, use system_lyxdir
183 // If path entry begins with $$User/, use user_lyxdir
184 // Example: "$$User/doc;$$LyX/doc"
185 FileName const fileOpenSearch(string const & path, string const & name,
186                              string const & ext)
187 {
188         FileName real_file;
189         string path_element;
190         bool notfound = true;
191         string tmppath = split(path, path_element, ';');
192
193         while (notfound && !path_element.empty()) {
194                 path_element = os::internal_path(path_element);
195                 if (!suffixIs(path_element, '/'))
196                         path_element += '/';
197                 path_element = subst(path_element, "$$LyX",
198                                      package().system_support());
199                 path_element = subst(path_element, "$$User",
200                                      package().user_support());
201
202                 real_file = fileSearch(path_element, name, ext);
203
204                 if (real_file.empty()) {
205                         do {
206                                 tmppath = split(tmppath, path_element, ';');
207                         } while (!tmppath.empty() && path_element.empty());
208                 } else {
209                         notfound = false;
210                 }
211         }
212         return real_file;
213 }
214 #endif
215
216
217 /// Returns a vector of all files in directory dir having extension ext.
218 vector<FileName> const dirList(FileName const & dir, string const & ext)
219 {
220         // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
221         vector<FileName> dirlist;
222
223         string const encoded_dir = dir.toFilesystemEncoding();
224         if (!(fs::exists(encoded_dir) && fs::is_directory(encoded_dir))) {
225                 LYXERR(Debug::FILES)
226                         << "Directory \"" << dir
227                         << "\" does not exist to DirList." << endl;
228                 return dirlist;
229         }
230
231         string extension;
232         if (!ext.empty() && ext[0] != '.')
233                 extension += '.';
234         extension += ext;
235
236         fs::directory_iterator dit(encoded_dir);
237         fs::directory_iterator end;
238         for (; dit != end; ++dit) {
239                 string const & fil = dit->leaf();
240                 if (suffixIs(fil, extension))
241                         dirlist.push_back(FileName::fromFilesystemEncoding(
242                                         encoded_dir + '/' + fil));
243         }
244         return dirlist;
245 }
246
247
248 // Returns the real name of file name in directory path, with optional
249 // extension ext.
250 FileName const fileSearch(string const & path, string const & name,
251                           string const & ext, search_mode mode)
252 {
253         // if `name' is an absolute path, we ignore the setting of `path'
254         // Expand Environmentvariables in 'name'
255         string const tmpname = replaceEnvironmentPath(name);
256         FileName fullname(makeAbsPath(tmpname, path));
257         // search first without extension, then with it.
258         if (isFileReadable(fullname))
259                 return fullname;
260         if (ext.empty())
261                 // We are done.
262                 return mode == allow_unreadable ? fullname : FileName();
263         // Only add the extension if it is not already the extension of
264         // fullname.
265         if (getExtension(fullname.absFilename()) != ext)
266                 fullname = FileName(addExtension(fullname.absFilename(), ext));
267         if (isFileReadable(fullname) || mode == allow_unreadable)
268                 return fullname;
269         return FileName();
270 }
271
272
273 // Search the file name.ext in the subdirectory dir of
274 //   1) user_lyxdir
275 //   2) build_lyxdir (if not empty)
276 //   3) system_lyxdir
277 FileName const libFileSearch(string const & dir, string const & name,
278                            string const & ext)
279 {
280         FileName fullname = fileSearch(addPath(package().user_support(), dir),
281                                      name, ext);
282         if (!fullname.empty())
283                 return fullname;
284
285         if (!package().build_support().empty())
286                 fullname = fileSearch(addPath(package().build_support(), dir),
287                                       name, ext);
288         if (!fullname.empty())
289                 return fullname;
290
291         return fileSearch(addPath(package().system_support(), dir), name, ext);
292 }
293
294
295 FileName const i18nLibFileSearch(string const & dir, string const & name,
296                   string const & ext)
297 {
298         // the following comments are from intl/dcigettext.c. We try
299         // to mimick this behaviour here.
300         /* The highest priority value is the `LANGUAGE' environment
301            variable. But we don't use the value if the currently
302            selected locale is the C locale. This is a GNU extension. */
303         /* [Otherwise] We have to proceed with the POSIX methods of
304            looking to `LC_ALL', `LC_xxx', and `LANG'. */
305
306         string lang = getEnv("LC_ALL");
307         if (lang.empty()) {
308                 lang = getEnv("LC_MESSAGES");
309                 if (lang.empty()) {
310                         lang = getEnv("LANG");
311                         if (lang.empty())
312                                 lang = "C";
313                 }
314         }
315
316         string const language = getEnv("LANGUAGE");
317         if (lang != "C" && lang != "POSIX" && !language.empty())
318                 lang = language;
319
320         string l;
321         lang = split(lang, l, ':');
322         while (!l.empty() && l != "C" && l != "POSIX") {
323                 FileName const tmp = libFileSearch(addPath(dir, token(l, '_', 0)),
324                                                       name, ext);
325                 if (!tmp.empty())
326                         return tmp;
327 #if 1
328                 // to be removed later (JMarc)
329                 FileName const tmpold = libFileSearch(dir,
330                                                  token(l, '_', 0) + '_' + name,
331                                                  ext);
332                 if (!tmpold.empty()) {
333                         lyxerr << "i18nLibFileSearch: File `" << tmpold 
334                                << "' has been found by the old method" <<endl;
335                         return tmpold;
336                 }
337 #endif
338                 lang = split(lang, l, ':');
339         }
340
341         return libFileSearch(dir, name, ext);
342 }
343
344
345 string const libScriptSearch(string const & command_in, quote_style style)
346 {
347         static string const token_scriptpath = "$$s/";
348
349         string command = command_in;
350         // Find the starting position of "$$s/"
351         string::size_type const pos1 = command.find(token_scriptpath);
352         if (pos1 == string::npos)
353                 return command;
354         // Find the end of the "$$s/some_subdir/some_script" word within
355         // command. Assumes that the script name does not contain spaces.
356         string::size_type const start_script = pos1 + 4;
357         string::size_type const pos2 = command.find(' ', start_script);
358         string::size_type const size_script = pos2 == string::npos?
359                 (command.size() - start_script) : pos2 - start_script;
360
361         // Does this script file exist?
362         string const script = 
363                 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
364
365         if (script.empty()) {
366                 // Replace "$$s/" with ""
367                 command.erase(pos1, 4);
368         } else {
369                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
370                 string::size_type const size_replace = size_script + 4;
371                 command.replace(pos1, size_replace, quoteName(script, style));
372         }
373
374         return command;
375 }
376
377
378 namespace {
379
380 FileName const createTmpDir(FileName const & tempdir, string const & mask)
381 {
382         LYXERR(Debug::FILES)
383                 << "createTmpDir: tempdir=`" << tempdir << "'\n"
384                 << "createTmpDir:    mask=`" << mask << '\'' << endl;
385
386         FileName const tmpfl(tempName(tempdir, mask));
387         // lyx::tempName actually creates a file to make sure that it
388         // stays unique. So we have to delete it before we can create
389         // a dir with the same name. Note also that we are not thread
390         // safe because of the gap between unlink and mkdir. (Lgb)
391         unlink(tmpfl);
392
393         if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
394                 lyxerr << "LyX could not create the temporary directory '"
395                        << tmpfl << "'" << endl;
396                 return FileName();
397         }
398
399         return tmpfl;
400 }
401
402 } // namespace anon
403
404
405 bool destroyDir(FileName const & tmpdir)
406 {
407         try {
408                 return fs::remove_all(tmpdir.toFilesystemEncoding()) > 0;
409         } catch (fs::filesystem_error const & fe){
410                 lyxerr << "Could not delete " << tmpdir << ". (" << fe.what() << ")" << std::endl;
411                 return false;
412         }
413 }
414
415
416 string const createBufferTmpDir()
417 {
418         static int count;
419         // We are in our own directory.  Why bother to mangle name?
420         // In fact I wrote this code to circumvent a problematic behaviour
421         // (bug?) of EMX mkstemp().
422         string const tmpfl =
423                 package().temp_dir() + "/lyx_tmpbuf" +
424                 convert<string>(count++);
425
426         if (mkdir(FileName(tmpfl), 0777)) {
427                 lyxerr << "LyX could not create the temporary directory '"
428                        << tmpfl << "'" << endl;
429                 return string();
430         }
431         return tmpfl;
432 }
433
434
435 FileName const createLyXTmpDir(FileName const & deflt)
436 {
437         if (!deflt.empty() && deflt.absFilename() != "/tmp") {
438                 if (mkdir(deflt, 0777)) {
439                         if (isDirWriteable(deflt)) {
440                                 // deflt could not be created because it
441                                 // did exist already, so let's create our own
442                                 // dir inside deflt.
443                                 return createTmpDir(deflt, "lyx_tmpdir");
444                         } else {
445                                 // some other error occured.
446                                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
447                         }
448                 } else
449                         return deflt;
450         } else {
451                 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
452         }
453 }
454
455
456 bool createDirectory(string const & path, int permission)
457 {
458         string temp = rtrim(os::internal_path(path), "/");
459         BOOST_ASSERT(!temp.empty());
460         return mkdir(FileName(temp), permission) == 0;
461 }
462
463
464 // Strip filename from path name
465 string const onlyPath(string const & filename)
466 {
467         // If empty filename, return empty
468         if (filename.empty())
469                 return filename;
470
471         // Find last / or start of filename
472         string::size_type j = filename.rfind('/');
473         return j == string::npos ? "./" : filename.substr(0, j + 1);
474 }
475
476
477 // Convert relative path into absolute path based on a basepath.
478 // If relpath is absolute, just use that.
479 // If basepath is empty, use CWD as base.
480 FileName const makeAbsPath(string const & relPath, string const & basePath)
481 {
482         // checks for already absolute path
483         if (os::is_absolute_path(relPath))
484                 return FileName(relPath);
485
486         // Copies given paths
487         string tempRel = os::internal_path(relPath);
488         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
489         tempRel = subst(tempRel, "//", "/");
490
491         string tempBase;
492
493         if (os::is_absolute_path(basePath))
494                 tempBase = basePath;
495         else
496                 tempBase = addPath(getcwd().absFilename(), basePath);
497
498         // Handle /./ at the end of the path
499         while (suffixIs(tempBase, "/./"))
500                 tempBase.erase(tempBase.length() - 2);
501
502         // processes relative path
503         string rTemp = tempRel;
504         string temp;
505
506         while (!rTemp.empty()) {
507                 // Split by next /
508                 rTemp = split(rTemp, temp, '/');
509
510                 if (temp == ".") continue;
511                 if (temp == "..") {
512                         // Remove one level of TempBase
513                         string::difference_type i = tempBase.length() - 2;
514                         if (i < 0)
515                                 i = 0;
516                         while (i > 0 && tempBase[i] != '/')
517                                 --i;
518                         if (i > 0)
519                                 tempBase.erase(i, string::npos);
520                         else
521                                 tempBase += '/';
522                 } else if (temp.empty() && !rTemp.empty()) {
523                                 tempBase = os::current_root() + rTemp;
524                                 rTemp.erase();
525                 } else {
526                         // Add this piece to TempBase
527                         if (!suffixIs(tempBase, '/'))
528                                 tempBase += '/';
529                         tempBase += temp;
530                 }
531         }
532
533         // returns absolute path
534         return FileName(os::internal_path(tempBase));
535 }
536
537
538 // Correctly append filename to the pathname.
539 // If pathname is '.', then don't use pathname.
540 // Chops any path of filename.
541 string const addName(string const & path, string const & fname)
542 {
543         string const basename = onlyFilename(fname);
544         string buf;
545
546         if (path != "." && path != "./" && !path.empty()) {
547                 buf = os::internal_path(path);
548                 if (!suffixIs(path, '/'))
549                         buf += '/';
550         }
551
552         return buf + basename;
553 }
554
555
556 // Strips path from filename
557 string const onlyFilename(string const & fname)
558 {
559         if (fname.empty())
560                 return fname;
561
562         string::size_type j = fname.rfind('/');
563         if (j == string::npos) // no '/' in fname
564                 return fname;
565
566         // Strip to basename
567         return fname.substr(j + 1);
568 }
569
570
571 /// Returns true is path is absolute
572 bool absolutePath(string const & path)
573 {
574         return os::is_absolute_path(path);
575 }
576
577
578 // Create absolute path. If impossible, don't do anything
579 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
580 string const expandPath(string const & path)
581 {
582         // checks for already absolute path
583         string rTemp = replaceEnvironmentPath(path);
584         if (os::is_absolute_path(rTemp))
585                 return rTemp;
586
587         string temp;
588         string const copy = rTemp;
589
590         // Split by next /
591         rTemp = split(rTemp, temp, '/');
592
593         if (temp == ".")
594                 return getcwd().absFilename() + '/' + rTemp;
595
596         if (temp == "~")
597                 return package().home_dir() + '/' + rTemp;
598
599         if (temp == "..")
600                 return makeAbsPath(copy).absFilename();
601
602         // Don't know how to handle this
603         return copy;
604 }
605
606
607 // Normalize a path. Constracts path/../path
608 // Can't handle "../../" or "/../" (Asger)
609 // Also converts paths like /foo//bar ==> /foo/bar
610 string const normalizePath(string const & path)
611 {
612         // Normalize paths like /foo//bar ==> /foo/bar
613         static boost::regex regex("/{2,}");
614         string const tmppath = boost::regex_merge(path, regex, "/");
615
616         fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
617
618         if (!npath.is_complete())
619                 return "./" + npath.string() + '/';
620
621         return npath.string() + '/';
622 }
623
624
625 string const getFileContents(FileName const & fname)
626 {
627         string const encodedname = fname.toFilesystemEncoding();
628         if (fs::exists(encodedname)) {
629                 ifstream ifs(encodedname.c_str());
630                 ostringstream ofs;
631                 if (ifs && ofs) {
632                         ofs << ifs.rdbuf();
633                         ifs.close();
634                         return ofs.str();
635                 }
636         }
637         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
638         return string();
639 }
640
641
642 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
643 string const replaceEnvironmentPath(string const & path)
644 {
645         // ${VAR} is defined as
646         // $\{[A-Za-z_][A-Za-z_0-9]*\}
647         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
648
649         // $VAR is defined as:
650         // $\{[A-Za-z_][A-Za-z_0-9]*\}
651         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
652
653         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
654         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
655         boost::smatch what;
656
657         string result = path;
658         while (1) {
659                 regex_match(result, what, envvar_br_re);
660                 if (!what[0].matched) {
661                         regex_match(result, what, envvar_re);
662                         if (!what[0].matched)
663                                 break;
664                 }
665                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
666         }
667         return result;
668 }
669
670
671 // Make relative path out of two absolute paths
672 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
673 // Makes relative path out of absolute path. If it is deeper than basepath,
674 // it's easy. If basepath and abspath share something (they are all deeper
675 // than some directory), it'll be rendered using ..'s. If they are completely
676 // different, then the absolute path will be used as relative path.
677 {
678         docstring::size_type const abslen = abspath.length();
679         docstring::size_type const baselen = basepath.length();
680
681         docstring::size_type i = os::common_path(abspath, basepath);
682
683         if (i == 0) {
684                 // actually no match - cannot make it relative
685                 return abspath;
686         }
687
688         // Count how many dirs there are in basepath above match
689         // and append as many '..''s into relpath
690         docstring buf;
691         docstring::size_type j = i;
692         while (j < baselen) {
693                 if (basepath[j] == '/') {
694                         if (j + 1 == baselen)
695                                 break;
696                         buf += "../";
697                 }
698                 ++j;
699         }
700
701         // Append relative stuff from common directory to abspath
702         if (abspath[i] == '/')
703                 ++i;
704         for (; i < abslen; ++i)
705                 buf += abspath[i];
706         // Remove trailing /
707         if (suffixIs(buf, '/'))
708                 buf.erase(buf.length() - 1);
709         // Substitute empty with .
710         if (buf.empty())
711                 buf = '.';
712         return buf;
713 }
714
715
716 // Append sub-directory(ies) to a path in an intelligent way
717 string const addPath(string const & path, string const & path_2)
718 {
719         string buf;
720         string const path2 = os::internal_path(path_2);
721
722         if (!path.empty() && path != "." && path != "./") {
723                 buf = os::internal_path(path);
724                 if (path[path.length() - 1] != '/')
725                         buf += '/';
726         }
727
728         if (!path2.empty()) {
729                 string::size_type const p2start = path2.find_first_not_of('/');
730                 string::size_type const p2end = path2.find_last_not_of('/');
731                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
732                 buf += tmp + '/';
733         }
734         return buf;
735 }
736
737
738 string const changeExtension(string const & oldname, string const & extension)
739 {
740         string::size_type const last_slash = oldname.rfind('/');
741         string::size_type last_dot = oldname.rfind('.');
742         if (last_dot < last_slash && last_slash != string::npos)
743                 last_dot = string::npos;
744
745         string ext;
746         // Make sure the extension starts with a dot
747         if (!extension.empty() && extension[0] != '.')
748                 ext= '.' + extension;
749         else
750                 ext = extension;
751
752         return os::internal_path(oldname.substr(0, last_dot) + ext);
753 }
754
755
756 string const removeExtension(string const & name)
757 {
758         return changeExtension(name, string());
759 }
760
761
762 string const addExtension(string const & name, string const & extension)
763 {
764         if (!extension.empty() && extension[0] != '.')
765                 return name + '.' + extension;
766         return name + extension;
767 }
768
769
770 /// Return the extension of the file (not including the .)
771 string const getExtension(string const & name)
772 {
773         string::size_type const last_slash = name.rfind('/');
774         string::size_type const last_dot = name.rfind('.');
775         if (last_dot != string::npos &&
776             (last_slash == string::npos || last_dot > last_slash))
777                 return name.substr(last_dot + 1,
778                                    name.length() - (last_dot + 1));
779         else
780                 return string();
781 }
782
783
784 // the different filetypes and what they contain in one of the first lines
785 // (dots are any characters).           (Herbert 20020131)
786 // AGR  Grace...
787 // BMP  BM...
788 // EPS  %!PS-Adobe-3.0 EPSF...
789 // FIG  #FIG...
790 // FITS ...BITPIX...
791 // GIF  GIF...
792 // JPG  JFIF
793 // PDF  %PDF-...
794 // PNG  .PNG...
795 // PBM  P1... or P4     (B/W)
796 // PGM  P2... or P5     (Grayscale)
797 // PPM  P3... or P6     (color)
798 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
799 // SGI  \001\332...     (decimal 474)
800 // TGIF %TGIF...
801 // TIFF II... or MM...
802 // XBM  ..._bits[]...
803 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
804 //      ...static char *...
805 // XWD  \000\000\000\151        (0x00006900) decimal 105
806 //
807 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
808 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
809 // Z    \037\235                UNIX compress
810
811 string const getFormatFromContents(FileName const & filename)
812 {
813         // paranoia check
814         if (filename.empty() || !isFileReadable(filename))
815                 return string();
816
817         ifstream ifs(filename.toFilesystemEncoding().c_str());
818         if (!ifs)
819                 // Couldn't open file...
820                 return string();
821
822         // gnuzip
823         static string const gzipStamp = "\037\213";
824
825         // PKZIP
826         static string const zipStamp = "PK";
827
828         // compress
829         static string const compressStamp = "\037\235";
830
831         // Maximum strings to read
832         int const max_count = 50;
833         int count = 0;
834
835         string str;
836         string format;
837         bool firstLine = true;
838         while ((count++ < max_count) && format.empty()) {
839                 if (ifs.eof()) {
840                         LYXERR(Debug::GRAPHICS)
841                                 << "filetools(getFormatFromContents)\n"
842                                 << "\tFile type not recognised before EOF!"
843                                 << endl;
844                         break;
845                 }
846
847                 getline(ifs, str);
848                 string const stamp = str.substr(0, 2);
849                 if (firstLine && str.size() >= 2) {
850                         // at first we check for a zipped file, because this
851                         // information is saved in the first bytes of the file!
852                         // also some graphic formats which save the information
853                         // in the first line, too.
854                         if (prefixIs(str, gzipStamp)) {
855                                 format =  "gzip";
856
857                         } else if (stamp == zipStamp) {
858                                 format =  "zip";
859
860                         } else if (stamp == compressStamp) {
861                                 format =  "compress";
862
863                         // the graphics part
864                         } else if (stamp == "BM") {
865                                 format =  "bmp";
866
867                         } else if (stamp == "\001\332") {
868                                 format =  "sgi";
869
870                         // PBM family
871                         // Don't need to use str.at(0), str.at(1) because
872                         // we already know that str.size() >= 2
873                         } else if (str[0] == 'P') {
874                                 switch (str[1]) {
875                                 case '1':
876                                 case '4':
877                                         format =  "pbm";
878                                     break;
879                                 case '2':
880                                 case '5':
881                                         format =  "pgm";
882                                     break;
883                                 case '3':
884                                 case '6':
885                                         format =  "ppm";
886                                 }
887                                 break;
888
889                         } else if ((stamp == "II") || (stamp == "MM")) {
890                                 format =  "tiff";
891
892                         } else if (prefixIs(str,"%TGIF")) {
893                                 format =  "tgif";
894
895                         } else if (prefixIs(str,"#FIG")) {
896                                 format =  "fig";
897
898                         } else if (prefixIs(str,"GIF")) {
899                                 format =  "gif";
900
901                         } else if (str.size() > 3) {
902                                 int const c = ((str[0] << 24) & (str[1] << 16) &
903                                                (str[2] << 8)  & str[3]);
904                                 if (c == 105) {
905                                         format =  "xwd";
906                                 }
907                         }
908
909                         firstLine = false;
910                 }
911
912                 if (!format.empty())
913                     break;
914                 else if (contains(str,"EPSF"))
915                         // dummy, if we have wrong file description like
916                         // %!PS-Adobe-2.0EPSF"
917                         format =  "eps";
918
919                 else if (contains(str,"Grace"))
920                         format =  "agr";
921
922                 else if (contains(str,"JFIF"))
923                         format =  "jpg";
924
925                 else if (contains(str,"%PDF"))
926                         format =  "pdf";
927
928                 else if (contains(str,"PNG"))
929                         format =  "png";
930
931                 else if (contains(str,"%!PS-Adobe")) {
932                         // eps or ps
933                         ifs >> str;
934                         if (contains(str,"EPSF"))
935                                 format = "eps";
936                         else
937                             format = "ps";
938                 }
939
940                 else if (contains(str,"_bits[]"))
941                         format = "xbm";
942
943                 else if (contains(str,"XPM") || contains(str, "static char *"))
944                         format = "xpm";
945
946                 else if (contains(str,"BITPIX"))
947                         format = "fits";
948         }
949
950         if (!format.empty()) {
951                 LYXERR(Debug::GRAPHICS)
952                         << "Recognised Fileformat: " << format << endl;
953                 return format;
954         }
955
956         LYXERR(Debug::GRAPHICS)
957                 << "filetools(getFormatFromContents)\n"
958                 << "\tCouldn't find a known format!\n";
959         return string();
960 }
961
962
963 /// check for zipped file
964 bool zippedFile(FileName const & name)
965 {
966         string const type = getFormatFromContents(name);
967         if (contains("gzip zip compress", type) && !type.empty())
968                 return true;
969         return false;
970 }
971
972
973 string const unzippedFileName(string const & zipped_file)
974 {
975         string const ext = getExtension(zipped_file);
976         if (ext == "gz" || ext == "z" || ext == "Z")
977                 return changeExtension(zipped_file, string());
978         return "unzipped_" + zipped_file;
979 }
980
981
982 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
983 {
984         FileName const tempfile = FileName(unzipped_file.empty() ?
985                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
986                 unzipped_file);
987         // Run gunzip
988         string const command = "gunzip -c " +
989                 zipped_file.toFilesystemEncoding() + " > " +
990                 tempfile.toFilesystemEncoding();
991         Systemcall one;
992         one.startscript(Systemcall::Wait, command);
993         // test that command was executed successfully (anon)
994         // yes, please do. (Lgb)
995         return tempfile;
996 }
997
998
999 docstring const makeDisplayPath(string const & path, unsigned int threshold)
1000 {
1001         string str = path;
1002
1003         // If file is from LyXDir, display it as if it were relative.
1004         string const system = package().system_support();
1005         if (prefixIs(str, system) && str != system)
1006                 return from_utf8("[" + str.erase(0, system.length()) + "]");    
1007
1008         // replace /home/blah with ~/
1009         string const home = package().home_dir();
1010         if (!home.empty() && prefixIs(str, home))
1011                 str = subst(str, home, "~");
1012
1013         if (str.length() <= threshold)
1014                 return from_utf8(os::external_path(str));
1015
1016         string const prefix = ".../";
1017         string temp;
1018
1019         while (str.length() > threshold)
1020                 str = split(str, temp, '/');
1021
1022         // Did we shorten everything away?
1023         if (str.empty()) {
1024                 // Yes, filename itself is too long.
1025                 // Pick the start and the end of the filename.
1026                 str = onlyFilename(path);
1027                 string const head = str.substr(0, threshold / 2 - 3);
1028
1029                 string::size_type len = str.length();
1030                 string const tail =
1031                         str.substr(len - threshold / 2 - 2, len - 1);
1032                 str = head + "..." + tail;
1033         }
1034
1035         return from_utf8(os::external_path(prefix + str));
1036 }
1037
1038
1039 bool readLink(string const & file, string & link, bool resolve)
1040 {
1041 #ifdef HAVE_READLINK
1042         char linkbuffer[512];
1043         // Should be PATH_MAX but that needs autconf support
1044         int const nRead = ::readlink(file.c_str(),
1045                                      linkbuffer, sizeof(linkbuffer) - 1);
1046         if (nRead <= 0)
1047                 return false;
1048         linkbuffer[nRead] = '\0'; // terminator
1049         if (resolve)
1050                 link = makeAbsPath(linkbuffer, onlyPath(file)).absFilename();
1051         else
1052                 link = linkbuffer;
1053         return true;
1054 #else
1055         return false;
1056 #endif
1057 }
1058
1059
1060 cmd_ret const runCommand(string const & cmd)
1061 {
1062         // FIXME: replace all calls to RunCommand with ForkedCall
1063         // (if the output is not needed) or the code in ispell.C
1064         // (if the output is needed).
1065
1066         // One question is if we should use popen or
1067         // create our own popen based on fork, exec, pipe
1068         // of course the best would be to have a
1069         // pstream (process stream), with the
1070         // variants ipstream, opstream
1071
1072 #if defined (HAVE_POPEN)
1073         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1074 #elif defined (HAVE__POPEN)
1075         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1076 #else
1077 #error No popen() function.
1078 #endif
1079
1080         // (Claus Hentschel) Check if popen was succesful ;-)
1081         if (!inf) {
1082                 lyxerr << "RunCommand:: could not start child process" << endl;
1083                 return make_pair(-1, string());
1084         }
1085
1086         string ret;
1087         int c = fgetc(inf);
1088         while (c != EOF) {
1089                 ret += static_cast<char>(c);
1090                 c = fgetc(inf);
1091         }
1092
1093 #if defined (HAVE_PCLOSE)
1094         int const pret = pclose(inf);
1095 #elif defined (HAVE__PCLOSE)
1096         int const pret = _pclose(inf);
1097 #else
1098 #error No pclose() function.
1099 #endif
1100
1101         if (pret == -1)
1102                 perror("RunCommand:: could not terminate child process");
1103
1104         return make_pair(pret, ret);
1105 }
1106
1107
1108 FileName const findtexfile(string const & fil, string const & /*format*/)
1109 {
1110         /* There is no problem to extend this function too use other
1111            methods to look for files. It could be setup to look
1112            in environment paths and also if wanted as a last resort
1113            to a recursive find. One of the easier extensions would
1114            perhaps be to use the LyX file lookup methods. But! I am
1115            going to implement this until I see some demand for it.
1116            Lgb
1117         */
1118
1119         // If the file can be found directly, we just return a
1120         // absolute path version of it.
1121         FileName const absfile(makeAbsPath(fil));
1122         if (fs::exists(absfile.toFilesystemEncoding()))
1123                 return absfile;
1124
1125         // No we try to find it using kpsewhich.
1126         // It seems from the kpsewhich manual page that it is safe to use
1127         // kpsewhich without --format: "When the --format option is not
1128         // given, the search path used when looking for a file is inferred
1129         // from the name given, by looking for a known extension. If no
1130         // known extension is found, the search path for TeX source files
1131         // is used."
1132         // However, we want to take advantage of the format sine almost all
1133         // the different formats has environment variables that can be used
1134         // to controll which paths to search. f.ex. bib looks in
1135         // BIBINPUTS and TEXBIB. Small list follows:
1136         // bib - BIBINPUTS, TEXBIB
1137         // bst - BSTINPUTS
1138         // graphic/figure - TEXPICTS, TEXINPUTS
1139         // ist - TEXINDEXSTYLE, INDEXSTYLE
1140         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1141         // tex - TEXINPUTS
1142         // tfm - TFMFONTS, TEXFONTS
1143         // This means that to use kpsewhich in the best possible way we
1144         // should help it by setting additional path in the approp. envir.var.
1145         string const kpsecmd = "kpsewhich " + fil;
1146
1147         cmd_ret const c = runCommand(kpsecmd);
1148
1149         LYXERR(Debug::LATEX) << "kpse status = " << c.first << '\n'
1150                  << "kpse result = `" << rtrim(c.second, "\n\r")
1151                  << '\'' << endl;
1152         if (c.first != -1)
1153                 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
1154                                                         "\n\r")));
1155         else
1156                 return FileName();
1157 }
1158
1159
1160 void removeAutosaveFile(string const & filename)
1161 {
1162         string a = onlyPath(filename);
1163         a += '#';
1164         a += onlyFilename(filename);
1165         a += '#';
1166         FileName const autosave(a);
1167         if (fs::exists(autosave.toFilesystemEncoding()))
1168                 unlink(autosave);
1169 }
1170
1171
1172 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1173         string const & message)
1174 {
1175         LYXERR(Debug::GRAPHICS) << "[readBB_from_PSFile] "
1176                 << message << std::endl;
1177 #ifdef WITH_WARNINGS
1178 #warning Why is this func deleting a file? (Lgb)
1179 #endif
1180         if (zipped)
1181                 unlink(file);
1182 }
1183
1184
1185 string const readBB_from_PSFile(FileName const & file)
1186 {
1187         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1188         // It seems that every command in the header has an own line,
1189         // getline() should work for all files.
1190         // On the other hand some plot programs write the bb at the
1191         // end of the file. Than we have in the header:
1192         // %%BoundingBox: (atend)
1193         // In this case we must check the end.
1194         bool zipped = zippedFile(file);
1195         FileName const file_ = zipped ? unzipFile(file) : file;
1196         string const format = getFormatFromContents(file_);
1197
1198         if (format != "eps" && format != "ps") {
1199                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1200                 return string();
1201         }
1202
1203         static boost::regex bbox_re(
1204                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1205         std::ifstream is(file_.toFilesystemEncoding().c_str());
1206         while (is) {
1207                 string s;
1208                 getline(is,s);
1209                 boost::smatch what;
1210                 if (regex_match(s, what, bbox_re)) {
1211                         // Our callers expect the tokens in the string
1212                         // separated by single spaces.
1213                         // FIXME: change return type from string to something
1214                         // sensible
1215                         ostringstream os;
1216                         os << what.str(1) << ' ' << what.str(2) << ' '
1217                            << what.str(3) << ' ' << what.str(4);
1218                         string const bb = os.str();
1219                         readBB_lyxerrMessage(file_, zipped, bb);
1220                         return bb;
1221                 }
1222         }
1223         readBB_lyxerrMessage(file_, zipped, "no bb found");
1224         return string();
1225 }
1226
1227
1228 int compare_timestamps(FileName const & filename1, FileName const & filename2)
1229 {
1230         // If the original is newer than the copy, then copy the original
1231         // to the new directory.
1232
1233         string const file1 = filename1.toFilesystemEncoding();
1234         string const file2 = filename2.toFilesystemEncoding();
1235         int cmp = 0;
1236         if (fs::exists(file1) && fs::exists(file2)) {
1237                 double const tmp = difftime(fs::last_write_time(file1),
1238                                             fs::last_write_time(file2));
1239                 if (tmp != 0)
1240                         cmp = tmp > 0 ? 1 : -1;
1241
1242         } else if (fs::exists(file1)) {
1243                 cmp = 1;
1244         } else if (fs::exists(file2)) {
1245                 cmp = -1;
1246         }
1247
1248         return cmp;
1249 }
1250
1251 } //namespace support
1252 } // namespace lyx