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