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