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