]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
Fix latex_path on cygwin:
[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 /// Return the extension of the file (not including the .)
748 string const GetExtension(string const & name)
749 {
750         string::size_type const last_slash = name.rfind('/');
751         string::size_type const last_dot = name.rfind('.');
752         if (last_dot != string::npos &&
753             (last_slash == string::npos || last_dot > last_slash))
754                 return name.substr(last_dot + 1,
755                                    name.length() - (last_dot + 1));
756         else
757                 return string();
758 }
759
760
761 // the different filetypes and what they contain in one of the first lines
762 // (dots are any characters).           (Herbert 20020131)
763 // AGR  Grace...
764 // BMP  BM...
765 // EPS  %!PS-Adobe-3.0 EPSF...
766 // FIG  #FIG...
767 // FITS ...BITPIX...
768 // GIF  GIF...
769 // JPG  JFIF
770 // PDF  %PDF-...
771 // PNG  .PNG...
772 // PBM  P1... or P4     (B/W)
773 // PGM  P2... or P5     (Grayscale)
774 // PPM  P3... or P6     (color)
775 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
776 // SGI  \001\332...     (decimal 474)
777 // TGIF %TGIF...
778 // TIFF II... or MM...
779 // XBM  ..._bits[]...
780 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
781 //      ...static char *...
782 // XWD  \000\000\000\151        (0x00006900) decimal 105
783 //
784 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
785 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
786 // Z    \037\235                UNIX compress
787
788 string const getFormatFromContents(string const & filename)
789 {
790         // paranoia check
791         if (filename.empty() || !IsFileReadable(filename))
792                 return string();
793
794         ifstream ifs(filename.c_str());
795         if (!ifs)
796                 // Couldn't open file...
797                 return string();
798
799         // gnuzip
800         static string const gzipStamp = "\037\213";
801
802         // PKZIP
803         static string const zipStamp = "PK";
804
805         // compress
806         static string const compressStamp = "\037\235";
807
808         // Maximum strings to read
809         int const max_count = 50;
810         int count = 0;
811
812         string str;
813         string format;
814         bool firstLine = true;
815         while ((count++ < max_count) && format.empty()) {
816                 if (ifs.eof()) {
817                         lyxerr[Debug::GRAPHICS]
818                                 << "filetools(getFormatFromContents)\n"
819                                 << "\tFile type not recognised before EOF!"
820                                 << endl;
821                         break;
822                 }
823
824                 getline(ifs, str);
825                 string const stamp = str.substr(0, 2);
826                 if (firstLine && str.size() >= 2) {
827                         // at first we check for a zipped file, because this
828                         // information is saved in the first bytes of the file!
829                         // also some graphic formats which save the information
830                         // in the first line, too.
831                         if (prefixIs(str, gzipStamp)) {
832                                 format =  "gzip";
833
834                         } else if (stamp == zipStamp) {
835                                 format =  "zip";
836
837                         } else if (stamp == compressStamp) {
838                                 format =  "compress";
839
840                         // the graphics part
841                         } else if (stamp == "BM") {
842                                 format =  "bmp";
843
844                         } else if (stamp == "\001\332") {
845                                 format =  "sgi";
846
847                         // PBM family
848                         // Don't need to use str.at(0), str.at(1) because
849                         // we already know that str.size() >= 2
850                         } else if (str[0] == 'P') {
851                                 switch (str[1]) {
852                                 case '1':
853                                 case '4':
854                                         format =  "pbm";
855                                     break;
856                                 case '2':
857                                 case '5':
858                                         format =  "pgm";
859                                     break;
860                                 case '3':
861                                 case '6':
862                                         format =  "ppm";
863                                 }
864                                 break;
865
866                         } else if ((stamp == "II") || (stamp == "MM")) {
867                                 format =  "tiff";
868
869                         } else if (prefixIs(str,"%TGIF")) {
870                                 format =  "tgif";
871
872                         } else if (prefixIs(str,"#FIG")) {
873                                 format =  "fig";
874
875                         } else if (prefixIs(str,"GIF")) {
876                                 format =  "gif";
877
878                         } else if (str.size() > 3) {
879                                 int const c = ((str[0] << 24) & (str[1] << 16) &
880                                                (str[2] << 8)  & str[3]);
881                                 if (c == 105) {
882                                         format =  "xwd";
883                                 }
884                         }
885
886                         firstLine = false;
887                 }
888
889                 if (!format.empty())
890                     break;
891                 else if (contains(str,"EPSF"))
892                         // dummy, if we have wrong file description like
893                         // %!PS-Adobe-2.0EPSF"
894                         format =  "eps";
895
896                 else if (contains(str,"Grace"))
897                         format =  "agr";
898
899                 else if (contains(str,"JFIF"))
900                         format =  "jpg";
901
902                 else if (contains(str,"%PDF"))
903                         format =  "pdf";
904
905                 else if (contains(str,"PNG"))
906                         format =  "png";
907
908                 else if (contains(str,"%!PS-Adobe")) {
909                         // eps or ps
910                         ifs >> str;
911                         if (contains(str,"EPSF"))
912                                 format = "eps";
913                         else
914                             format = "ps";
915                 }
916
917                 else if (contains(str,"_bits[]"))
918                         format = "xbm";
919
920                 else if (contains(str,"XPM") || contains(str, "static char *"))
921                         format = "xpm";
922
923                 else if (contains(str,"BITPIX"))
924                         format = "fits";
925         }
926
927         if (!format.empty()) {
928                 lyxerr[Debug::GRAPHICS]
929                         << "Recognised Fileformat: " << format << endl;
930                 return format;
931         }
932
933         lyxerr[Debug::GRAPHICS]
934                 << "filetools(getFormatFromContents)\n"
935                 << "\tCouldn't find a known format!\n";
936         return string();
937 }
938
939
940 /// check for zipped file
941 bool zippedFile(string const & name)
942 {
943         string const type = getFormatFromContents(name);
944         if (contains("gzip zip compress", type) && !type.empty())
945                 return true;
946         return false;
947 }
948
949
950 string const unzippedFileName(string const & zipped_file)
951 {
952         string const ext = GetExtension(zipped_file);
953         if (ext == "gz" || ext == "z" || ext == "Z")
954                 return ChangeExtension(zipped_file, string());
955         return "unzipped_" + zipped_file;
956 }
957
958
959 string const unzipFile(string const & zipped_file, string const & unzipped_file)
960 {
961         string const tempfile = unzipped_file.empty() ?
962                 unzippedFileName(zipped_file) : unzipped_file;
963         // Run gunzip
964         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
965         Systemcall one;
966         one.startscript(Systemcall::Wait, command);
967         // test that command was executed successfully (anon)
968         // yes, please do. (Lgb)
969         return tempfile;
970 }
971
972
973 string const MakeDisplayPath(string const & path, unsigned int threshold)
974 {
975         string str = path;
976         string const home = package().home_dir();
977
978         // replace /home/blah with ~/
979         if (!home.empty() && prefixIs(str, home))
980                 str = subst(str, home, "~");
981
982         if (str.length() <= threshold)
983                 return os::external_path(str);
984
985         string const prefix = ".../";
986         string temp;
987
988         while (str.length() > threshold)
989                 str = split(str, temp, '/');
990
991         // Did we shorten everything away?
992         if (str.empty()) {
993                 // Yes, filename itself is too long.
994                 // Pick the start and the end of the filename.
995                 str = OnlyFilename(path);
996                 string const head = str.substr(0, threshold / 2 - 3);
997
998                 string::size_type len = str.length();
999                 string const tail =
1000                         str.substr(len - threshold / 2 - 2, len - 1);
1001                 str = head + "..." + tail;
1002         }
1003
1004         return os::external_path(prefix + str);
1005 }
1006
1007
1008 bool LyXReadLink(string const & file, string & link, bool resolve)
1009 {
1010 #ifdef HAVE_READLINK
1011         char linkbuffer[512];
1012         // Should be PATH_MAX but that needs autconf support
1013         int const nRead = ::readlink(file.c_str(),
1014                                      linkbuffer, sizeof(linkbuffer) - 1);
1015         if (nRead <= 0)
1016                 return false;
1017         linkbuffer[nRead] = '\0'; // terminator
1018         if (resolve)
1019                 link = MakeAbsPath(linkbuffer, OnlyPath(file));
1020         else
1021                 link = linkbuffer;
1022         return true;
1023 #else
1024         return false;
1025 #endif
1026 }
1027
1028
1029 cmd_ret const RunCommand(string const & cmd)
1030 {
1031         // FIXME: replace all calls to RunCommand with ForkedCall
1032         // (if the output is not needed) or the code in ispell.C
1033         // (if the output is needed).
1034
1035         // One question is if we should use popen or
1036         // create our own popen based on fork, exec, pipe
1037         // of course the best would be to have a
1038         // pstream (process stream), with the
1039         // variants ipstream, opstream
1040
1041 #if defined (HAVE_POPEN)
1042         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1043 #elif defined (HAVE__POPEN)
1044         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1045 #else
1046 #error No popen() function.
1047 #endif
1048
1049         // (Claus Hentschel) Check if popen was succesful ;-)
1050         if (!inf) {
1051                 lyxerr << "RunCommand:: could not start child process" << endl;
1052                 return make_pair(-1, string());
1053         }
1054
1055         string ret;
1056         int c = fgetc(inf);
1057         while (c != EOF) {
1058                 ret += static_cast<char>(c);
1059                 c = fgetc(inf);
1060         }
1061
1062 #if defined (HAVE_PCLOSE)
1063         int const pret = pclose(inf);
1064 #elif defined (HAVE__PCLOSE)
1065         int const pret = _pclose(inf);
1066 #else
1067 #error No pclose() function.
1068 #endif
1069
1070         if (pret == -1)
1071                 perror("RunCommand:: could not terminate child process");
1072
1073         return make_pair(pret, ret);
1074 }
1075
1076
1077 string const findtexfile(string const & fil, string const & /*format*/)
1078 {
1079         /* There is no problem to extend this function too use other
1080            methods to look for files. It could be setup to look
1081            in environment paths and also if wanted as a last resort
1082            to a recursive find. One of the easier extensions would
1083            perhaps be to use the LyX file lookup methods. But! I am
1084            going to implement this until I see some demand for it.
1085            Lgb
1086         */
1087
1088         // If the file can be found directly, we just return a
1089         // absolute path version of it.
1090         if (fs::exists(fil))
1091                 return MakeAbsPath(fil);
1092
1093         // No we try to find it using kpsewhich.
1094         // It seems from the kpsewhich manual page that it is safe to use
1095         // kpsewhich without --format: "When the --format option is not
1096         // given, the search path used when looking for a file is inferred
1097         // from the name given, by looking for a known extension. If no
1098         // known extension is found, the search path for TeX source files
1099         // is used."
1100         // However, we want to take advantage of the format sine almost all
1101         // the different formats has environment variables that can be used
1102         // to controll which paths to search. f.ex. bib looks in
1103         // BIBINPUTS and TEXBIB. Small list follows:
1104         // bib - BIBINPUTS, TEXBIB
1105         // bst - BSTINPUTS
1106         // graphic/figure - TEXPICTS, TEXINPUTS
1107         // ist - TEXINDEXSTYLE, INDEXSTYLE
1108         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1109         // tex - TEXINPUTS
1110         // tfm - TFMFONTS, TEXFONTS
1111         // This means that to use kpsewhich in the best possible way we
1112         // should help it by setting additional path in the approp. envir.var.
1113         string const kpsecmd = "kpsewhich " + fil;
1114
1115         cmd_ret const c = RunCommand(kpsecmd);
1116
1117         lyxerr[Debug::LATEX] << "kpse status = " << c.first << '\n'
1118                  << "kpse result = `" << rtrim(c.second, "\n")
1119                  << '\'' << endl;
1120         if (c.first != -1)
1121                 return os::internal_path(rtrim(c.second, "\n\r"));
1122         else
1123                 return string();
1124 }
1125
1126
1127 void removeAutosaveFile(string const & filename)
1128 {
1129         string a = OnlyPath(filename);
1130         a += '#';
1131         a += OnlyFilename(filename);
1132         a += '#';
1133         if (fs::exists(a))
1134                 unlink(a);
1135 }
1136
1137
1138 void readBB_lyxerrMessage(string const & file, bool & zipped,
1139         string const & message)
1140 {
1141         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] "
1142                 << message << std::endl;
1143 #ifdef WITH_WARNINGS
1144 #warning Why is this func deleting a file? (Lgb)
1145 #endif
1146         if (zipped)
1147                 unlink(file);
1148 }
1149
1150
1151 string const readBB_from_PSFile(string const & file)
1152 {
1153         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1154         // It seems that every command in the header has an own line,
1155         // getline() should work for all files.
1156         // On the other hand some plot programs write the bb at the
1157         // end of the file. Than we have in the header:
1158         // %%BoundingBox: (atend)
1159         // In this case we must check the end.
1160         bool zipped = zippedFile(file);
1161         string const file_ = zipped ?  unzipFile(file) : file;
1162         string const format = getFormatFromContents(file_);
1163
1164         if (format != "eps" && format != "ps") {
1165                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1166                 return string();
1167         }
1168
1169         std::ifstream is(file_.c_str());
1170         while (is) {
1171                 string s;
1172                 getline(is,s);
1173                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend")) {
1174                         string const bb = ltrim(s.substr(14));
1175                         readBB_lyxerrMessage(file_, zipped, bb);
1176                         return bb;
1177                 }
1178         }
1179         readBB_lyxerrMessage(file_, zipped, "no bb found");
1180         return string();
1181 }
1182
1183
1184 int compare_timestamps(string const & file1, string const & file2)
1185 {
1186         BOOST_ASSERT(AbsolutePath(file1));
1187         BOOST_ASSERT(AbsolutePath(file2));
1188
1189         // If the original is newer than the copy, then copy the original
1190         // to the new directory.
1191
1192         int cmp = 0;
1193         if (fs::exists(file1) && fs::exists(file2)) {
1194                 double const tmp = difftime(fs::last_write_time(file1),
1195                                             fs::last_write_time(file2));
1196                 if (tmp != 0)
1197                         cmp = tmp > 0 ? 1 : -1;
1198
1199         } else if (fs::exists(file1)) {
1200                 cmp = 1;
1201         } else if (fs::exists(file2)) {
1202                 cmp = -1;
1203         }
1204
1205         return cmp;
1206 }
1207
1208 } //namespace support
1209 } // namespace lyx