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