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