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