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