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