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