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