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