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