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