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