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