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