]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
This commit moves system font initialization and restoration to new support/fontutils...
[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, quote_style style)
137 {
138         switch(style) {
139         case quote_shell:
140                 // This does not work for filenames containing " (windows)
141                 // or ' (all other OSes). This can't be changed easily, since
142                 // we would need to adapt the command line parser in
143                 // Forkedcall::generateChild. Therefore we don't pass user
144                 // filenames to child processes if possible. We store them in
145                 // a python script instead, where we don't have these
146                 // limitations.
147                 return (os::shell() == os::UNIX) ?
148                         '\'' + name + '\'':
149                         '"' + name + '"';
150         case quote_python:
151                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
152                      + "\"";
153         }
154         // shut up stupid compiler
155         return string();
156 }
157
158
159 // Is a file readable ?
160 bool isFileReadable(string const & path)
161 {
162         return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
163 }
164
165
166 //returns true: dir writeable
167 //        false: not writeable
168 bool isDirWriteable(string const & path)
169 {
170         lyxerr[Debug::FILES] << "isDirWriteable: " << path << endl;
171
172         string const tmpfl = tempName(path, "lyxwritetest");
173
174         if (tmpfl.empty())
175                 return false;
176
177         unlink(tmpfl);
178         return true;
179 }
180
181
182 // Uses a string of paths separated by ";"s to find a file to open.
183 // Can't cope with pathnames with a ';' in them. Returns full path to file.
184 // If path entry begins with $$LyX/, use system_lyxdir
185 // If path entry begins with $$User/, use user_lyxdir
186 // Example: "$$User/doc;$$LyX/doc"
187 string const fileOpenSearch(string const & path, string const & name,
188                              string const & ext)
189 {
190         string real_file;
191         string path_element;
192         bool notfound = true;
193         string tmppath = split(path, path_element, ';');
194
195         while (notfound && !path_element.empty()) {
196                 path_element = os::internal_path(path_element);
197                 if (!suffixIs(path_element, '/'))
198                         path_element += '/';
199                 path_element = subst(path_element, "$$LyX",
200                                      package().system_support());
201                 path_element = subst(path_element, "$$User",
202                                      package().user_support());
203
204                 real_file = fileSearch(path_element, name, ext);
205
206                 if (real_file.empty()) {
207                         do {
208                                 tmppath = split(tmppath, path_element, ';');
209                         } while (!tmppath.empty() && path_element.empty());
210                 } else {
211                         notfound = false;
212                 }
213         }
214         return real_file;
215 }
216
217
218 /// Returns a vector of all files in directory dir having extension ext.
219 vector<string> const dirList(string const & dir, string const & ext)
220 {
221         // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
222         vector<string> dirlist;
223
224         if (!(fs::exists(dir) && fs::is_directory(dir))) {
225                 lyxerr[Debug::FILES]
226                         << "Directory \"" << dir
227                         << "\" does not exist to DirList." << endl;
228                 return dirlist;
229         }
230
231         string extension;
232         if (!ext.empty() && ext[0] != '.')
233                 extension += '.';
234         extension += ext;
235
236         fs::directory_iterator dit(dir);
237         fs::directory_iterator end;
238         for (; dit != end; ++dit) {
239                 string const & fil = dit->leaf();
240                 if (suffixIs(fil, extension)) {
241                         dirlist.push_back(fil);
242                 }
243         }
244         return dirlist;
245 }
246
247
248 // Returns the real name of file name in directory path, with optional
249 // extension ext.
250 string const fileSearch(string const & path, string const & name,
251                         string const & ext)
252 {
253         // if `name' is an absolute path, we ignore the setting of `path'
254         // Expand Environmentvariables in 'name'
255         string const tmpname = replaceEnvironmentPath(name);
256         string fullname = makeAbsPath(tmpname, path);
257         // search first without extension, then with it.
258         if (isFileReadable(fullname))
259                 return fullname;
260         if (ext.empty())
261                 return string();
262         // Is it not more reasonable to use ChangeExtension()? (SMiyata)
263         fullname += '.';
264         fullname += ext;
265         return isFileReadable(fullname) ? fullname : string();
266 }
267
268
269 // Search the file name.ext in the subdirectory dir of
270 //   1) user_lyxdir
271 //   2) build_lyxdir (if not empty)
272 //   3) system_lyxdir
273 string const libFileSearch(string const & dir, string const & name,
274                            string const & ext)
275 {
276         string fullname = fileSearch(addPath(package().user_support(), dir),
277                                      name, ext);
278         if (!fullname.empty())
279                 return fullname;
280
281         if (!package().build_support().empty())
282                 fullname = fileSearch(addPath(package().build_support(), dir),
283                                       name, ext);
284         if (!fullname.empty())
285                 return fullname;
286
287         return fileSearch(addPath(package().system_support(), dir), name, ext);
288 }
289
290
291 string const i18nLibFileSearch(string const & dir, string const & name,
292                   string const & ext)
293 {
294         // the following comments are from intl/dcigettext.c. We try
295         // to mimick this behaviour here.
296         /* The highest priority value is the `LANGUAGE' environment
297            variable. But we don't use the value if the currently
298            selected locale is the C locale. This is a GNU extension. */
299         /* [Otherwise] We have to proceed with the POSIX methods of
300            looking to `LC_ALL', `LC_xxx', and `LANG'. */
301
302         string lang = getEnv("LC_ALL");
303         if (lang.empty()) {
304                 lang = getEnv("LC_MESSAGES");
305                 if (lang.empty()) {
306                         lang = getEnv("LANG");
307                         if (lang.empty())
308                                 lang = "C";
309                 }
310         }
311
312         string const language = getEnv("LANGUAGE");
313         if (lang != "C" && lang != "POSIX" && !language.empty())
314                 lang = language;
315
316         string l;
317         lang = split(lang, l, ':');
318         while (!l.empty() && l != "C" && l != "POSIX") {
319                 string const tmp = libFileSearch(dir,
320                                                  token(l, '_', 0) + '_' + name,
321                                                  ext);
322                 if (!tmp.empty())
323                         return tmp;
324                 lang = split(lang, l, ':');
325         }
326
327         return libFileSearch(dir, name, ext);
328 }
329
330
331 string const libScriptSearch(string const & command_in, quote_style style)
332 {
333         static string const token_scriptpath = "$$s/";
334
335         string command = command_in;
336         // Find the starting position of "$$s/"
337         string::size_type const pos1 = command.find(token_scriptpath);
338         if (pos1 == string::npos)
339                 return command;
340         // Find the end of the "$$s/some_subdir/some_script" word within
341         // command. Assumes that the script name does not contain spaces.
342         string::size_type const start_script = pos1 + 4;
343         string::size_type const pos2 = command.find(' ', start_script);
344         string::size_type const size_script = pos2 == string::npos?
345                 (command.size() - start_script) : pos2 - start_script;
346
347         // Does this script file exist?
348         string const script =
349                 libFileSearch(".", command.substr(start_script, size_script));
350
351         if (script.empty()) {
352                 // Replace "$$s/" with ""
353                 command.erase(pos1, 4);
354         } else {
355                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
356                 string::size_type const size_replace = size_script + 4;
357                 command.replace(pos1, size_replace, quoteName(script, style));
358         }
359
360         return command;
361 }
362
363
364 namespace {
365
366 string const createTmpDir(string const & tempdir, string const & mask)
367 {
368         lyxerr[Debug::FILES]
369                 << "createTmpDir: tempdir=`" << tempdir << "'\n"
370                 << "createTmpDir:    mask=`" << mask << '\'' << endl;
371
372         string const tmpfl = tempName(tempdir, mask);
373         // lyx::tempName actually creates a file to make sure that it
374         // stays unique. So we have to delete it before we can create
375         // a dir with the same name. Note also that we are not thread
376         // safe because of the gap between unlink and mkdir. (Lgb)
377         unlink(tmpfl);
378
379         if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
380                 lyxerr << "LyX could not create the temporary directory '"
381                        << tmpfl << "'" << endl;
382                 return string();
383         }
384
385         return makeAbsPath(tmpfl);
386 }
387
388 } // namespace anon
389
390
391 bool destroyDir(string const & tmpdir)
392 {
393         return fs::remove_all(tmpdir) > 0;
394 }
395
396
397 string const createBufferTmpDir()
398 {
399         static int count;
400         // We are in our own directory.  Why bother to mangle name?
401         // In fact I wrote this code to circumvent a problematic behaviour
402         // (bug?) of EMX mkstemp().
403         string const tmpfl =
404                 package().temp_dir() + "/lyx_tmpbuf" +
405                 convert<string>(count++);
406
407         if (mkdir(tmpfl, 0777)) {
408                 lyxerr << "LyX could not create the temporary directory '"
409                        << tmpfl << "'" << endl;
410                 return string();
411         }
412         return tmpfl;
413 }
414
415
416 string const createLyXTmpDir(string const & deflt)
417 {
418         if (!deflt.empty() && deflt != "/tmp") {
419                 if (mkdir(deflt, 0777)) {
420                         if (isDirWriteable(deflt)) {
421                                 // deflt could not be created because it
422                                 // did exist already, so let's create our own
423                                 // dir inside deflt.
424                                 return createTmpDir(deflt, "lyx_tmpdir");
425                         } else {
426                                 // some other error occured.
427                                 return createTmpDir("/tmp", "lyx_tmpdir");
428                         }
429                 } else
430                         return deflt;
431         } else {
432                 return createTmpDir("/tmp", "lyx_tmpdir");
433         }
434 }
435
436
437 bool createDirectory(string const & path, int permission)
438 {
439         string temp = rtrim(os::internal_path(path), "/");
440         BOOST_ASSERT(!temp.empty());
441         return mkdir(temp, permission) == 0;
442 }
443
444
445 // Strip filename from path name
446 string const onlyPath(string const & filename)
447 {
448         // If empty filename, return empty
449         if (filename.empty())
450                 return filename;
451
452         // Find last / or start of filename
453         string::size_type j = filename.rfind('/');
454         return j == string::npos ? "./" : filename.substr(0, j + 1);
455 }
456
457
458 // Convert relative path into absolute path based on a basepath.
459 // If relpath is absolute, just use that.
460 // If basepath is empty, use CWD as base.
461 string const makeAbsPath(string const & relPath, string const & basePath)
462 {
463         // checks for already absolute path
464         if (os::is_absolute_path(relPath))
465                 return relPath;
466
467         // Copies given paths
468         string tempRel = os::internal_path(relPath);
469         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
470         tempRel = subst(tempRel, "//", "/");
471
472         string tempBase;
473
474         if (os::is_absolute_path(basePath))
475                 tempBase = basePath;
476         else
477                 tempBase = addPath(getcwd(), basePath);
478
479         // Handle /./ at the end of the path
480         while (suffixIs(tempBase, "/./"))
481                 tempBase.erase(tempBase.length() - 2);
482
483         // processes relative path
484         string rTemp = tempRel;
485         string temp;
486
487         while (!rTemp.empty()) {
488                 // Split by next /
489                 rTemp = split(rTemp, temp, '/');
490
491                 if (temp == ".") continue;
492                 if (temp == "..") {
493                         // Remove one level of TempBase
494                         string::difference_type i = tempBase.length() - 2;
495                         if (i < 0)
496                                 i = 0;
497                         while (i > 0 && tempBase[i] != '/')
498                                 --i;
499                         if (i > 0)
500                                 tempBase.erase(i, string::npos);
501                         else
502                                 tempBase += '/';
503                 } else if (temp.empty() && !rTemp.empty()) {
504                                 tempBase = os::current_root() + rTemp;
505                                 rTemp.erase();
506                 } else {
507                         // Add this piece to TempBase
508                         if (!suffixIs(tempBase, '/'))
509                                 tempBase += '/';
510                         tempBase += temp;
511                 }
512         }
513
514         // returns absolute path
515         return os::internal_path(tempBase);
516 }
517
518
519 // Correctly append filename to the pathname.
520 // If pathname is '.', then don't use pathname.
521 // Chops any path of filename.
522 string const addName(string const & path, string const & fname)
523 {
524         string const basename = onlyFilename(fname);
525         string buf;
526
527         if (path != "." && path != "./" && !path.empty()) {
528                 buf = os::internal_path(path);
529                 if (!suffixIs(path, '/'))
530                         buf += '/';
531         }
532
533         return buf + basename;
534 }
535
536
537 // Strips path from filename
538 string const onlyFilename(string const & fname)
539 {
540         if (fname.empty())
541                 return fname;
542
543         string::size_type j = fname.rfind('/');
544         if (j == string::npos) // no '/' in fname
545                 return fname;
546
547         // Strip to basename
548         return fname.substr(j + 1);
549 }
550
551
552 /// Returns true is path is absolute
553 bool absolutePath(string const & path)
554 {
555         return os::is_absolute_path(path);
556 }
557
558
559 // Create absolute path. If impossible, don't do anything
560 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
561 string const expandPath(string const & path)
562 {
563         // checks for already absolute path
564         string rTemp = replaceEnvironmentPath(path);
565         if (os::is_absolute_path(rTemp))
566                 return rTemp;
567
568         string temp;
569         string const copy = rTemp;
570
571         // Split by next /
572         rTemp = split(rTemp, temp, '/');
573
574         if (temp == ".")
575                 return getcwd() + '/' + rTemp;
576
577         if (temp == "~")
578                 return package().home_dir() + '/' + rTemp;
579
580         if (temp == "..")
581                 return makeAbsPath(copy);
582
583         // Don't know how to handle this
584         return copy;
585 }
586
587
588 // Normalize a path. Constracts path/../path
589 // Can't handle "../../" or "/../" (Asger)
590 // Also converts paths like /foo//bar ==> /foo/bar
591 string const normalizePath(string const & path)
592 {
593         // Normalize paths like /foo//bar ==> /foo/bar
594         static boost::regex regex("/{2,}");
595         string const tmppath = boost::regex_merge(path, regex, "/");
596
597         fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
598
599         if (!npath.is_complete())
600                 return "./" + npath.string() + '/';
601
602         return npath.string() + '/';
603 }
604
605
606 string const getFileContents(string const & fname)
607 {
608         if (fs::exists(fname)) {
609                 ifstream ifs(fname.c_str());
610                 ostringstream ofs;
611                 if (ifs && ofs) {
612                         ofs << ifs.rdbuf();
613                         ifs.close();
614                         return ofs.str();
615                 }
616         }
617         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
618         return string();
619 }
620
621
622 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
623 string const replaceEnvironmentPath(string const & path)
624 {
625         // ${VAR} is defined as
626         // $\{[A-Za-z_][A-Za-z_0-9]*\}
627         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
628
629         // $VAR is defined as:
630         // $\{[A-Za-z_][A-Za-z_0-9]*\}
631         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
632
633         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
634         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
635         boost::smatch what;
636
637         string result = path;
638         while (1) {
639                 regex_match(result, what, envvar_br_re);
640                 if (!what[0].matched) {
641                         regex_match(result, what, envvar_re);
642                         if (!what[0].matched)
643                                 break;
644                 }
645                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
646         }
647         return result;
648 }
649
650
651 // Make relative path out of two absolute paths
652 string const makeRelPath(string const & abspath, string const & basepath)
653 // Makes relative path out of absolute path. If it is deeper than basepath,
654 // it's easy. If basepath and abspath share something (they are all deeper
655 // than some directory), it'll be rendered using ..'s. If they are completely
656 // different, then the absolute path will be used as relative path.
657 {
658         string::size_type const abslen = abspath.length();
659         string::size_type const baselen = basepath.length();
660
661         string::size_type i = os::common_path(abspath, basepath);
662
663         if (i == 0) {
664                 // actually no match - cannot make it relative
665                 return abspath;
666         }
667
668         // Count how many dirs there are in basepath above match
669         // and append as many '..''s into relpath
670         string buf;
671         string::size_type j = i;
672         while (j < baselen) {
673                 if (basepath[j] == '/') {
674                         if (j + 1 == baselen)
675                                 break;
676                         buf += "../";
677                 }
678                 ++j;
679         }
680
681         // Append relative stuff from common directory to abspath
682         if (abspath[i] == '/')
683                 ++i;
684         for (; i < abslen; ++i)
685                 buf += abspath[i];
686         // Remove trailing /
687         if (suffixIs(buf, '/'))
688                 buf.erase(buf.length() - 1);
689         // Substitute empty with .
690         if (buf.empty())
691                 buf = '.';
692         return buf;
693 }
694
695
696 // Append sub-directory(ies) to a path in an intelligent way
697 string const addPath(string const & path, string const & path_2)
698 {
699         string buf;
700         string const path2 = os::internal_path(path_2);
701
702         if (!path.empty() && path != "." && path != "./") {
703                 buf = os::internal_path(path);
704                 if (path[path.length() - 1] != '/')
705                         buf += '/';
706         }
707
708         if (!path2.empty()) {
709                 string::size_type const p2start = path2.find_first_not_of('/');
710                 string::size_type const p2end = path2.find_last_not_of('/');
711                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
712                 buf += tmp + '/';
713         }
714         return buf;
715 }
716
717
718 /*
719  Change extension of oldname to extension.
720  Strips path off if no_path == true.
721  If no extension on oldname, just appends.
722  */
723 string const changeExtension(string const & oldname, string const & extension)
724 {
725         string::size_type const last_slash = oldname.rfind('/');
726         string::size_type last_dot = oldname.rfind('.');
727         if (last_dot < last_slash && last_slash != string::npos)
728                 last_dot = string::npos;
729
730         string ext;
731         // Make sure the extension starts with a dot
732         if (!extension.empty() && extension[0] != '.')
733                 ext= '.' + extension;
734         else
735                 ext = extension;
736
737         return os::internal_path(oldname.substr(0, last_dot) + ext);
738 }
739
740
741 string const removeExtension(string const & name)
742 {
743         return changeExtension(name, string());
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 docstring 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 lyx::from_utf8(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 lyx::from_utf8(os::external_path(prefix + str));
1005 }
1006
1007
1008 bool readLink(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         static boost::regex bbox_re(
1170                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1171         std::ifstream is(file_.c_str());
1172         while (is) {
1173                 string s;
1174                 getline(is,s);
1175                 boost::smatch what;
1176                 if (regex_match(s, what, bbox_re)) {
1177                         // Our callers expect the tokens in the string
1178                         // separated by single spaces.
1179                         // FIXME: change return type from string to something
1180                         // sensible
1181                         ostringstream os;
1182                         os << what.str(1) << ' ' << what.str(2) << ' '
1183                            << what.str(3) << ' ' << what.str(4);
1184                         string const bb = os.str();
1185                         readBB_lyxerrMessage(file_, zipped, bb);
1186                         return bb;
1187                 }
1188         }
1189         readBB_lyxerrMessage(file_, zipped, "no bb found");
1190         return string();
1191 }
1192
1193
1194 int compare_timestamps(string const & file1, string const & file2)
1195 {
1196         BOOST_ASSERT(absolutePath(file1));
1197         BOOST_ASSERT(absolutePath(file2));
1198
1199         // If the original is newer than the copy, then copy the original
1200         // to the new directory.
1201
1202         int cmp = 0;
1203         if (fs::exists(file1) && fs::exists(file2)) {
1204                 double const tmp = difftime(fs::last_write_time(file1),
1205                                             fs::last_write_time(file2));
1206                 if (tmp != 0)
1207                         cmp = tmp > 0 ? 1 : -1;
1208
1209         } else if (fs::exists(file1)) {
1210                 cmp = 1;
1211         } else if (fs::exists(file2)) {
1212                 cmp = -1;
1213         }
1214
1215         return cmp;
1216 }
1217
1218 } //namespace support
1219 } // namespace lyx