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