]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
bb531b807ca5d9cd0d1351e5cc9680d8b3250327
[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 "LyXRC.h"
25
26 #include "support/filetools.h"
27
28 #include "support/debug.h"
29 #include "support/environment.h"
30 #include "support/gettext.h"
31 #include "support/lstrings.h"
32 #include "support/os.h"
33 #include "support/Package.h"
34 #include "support/Path.h"
35 #include "support/Systemcall.h"
36 #include "support/qstring_helpers.h"
37
38 #include <QDir>
39
40 #include "support/lassert.h"
41 #include "support/regex.h"
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 #include <vector>
53
54 #if defined (_WIN32)
55 #include <io.h>
56 #include <windows.h>
57 #endif
58
59 using namespace std;
60
61 #define USE_QPROCESS
62
63 namespace lyx {
64 namespace support {
65
66 bool isLyXFileName(string const & filename)
67 {
68         return suffixIs(ascii_lowercase(filename), ".lyx");
69 }
70
71
72 bool isSGMLFileName(string const & filename)
73 {
74         return suffixIs(ascii_lowercase(filename), ".sgml");
75 }
76
77
78 bool isValidLaTeXFileName(string const & filename)
79 {
80         string const invalid_chars("#%\"");
81         return filename.find_first_of(invalid_chars) == string::npos;
82 }
83
84
85 bool isValidDVIFileName(string const & filename)
86 {
87         string const invalid_chars("${}()[]^");
88         return filename.find_first_of(invalid_chars) == string::npos;
89 }
90
91
92 string const latex_path(string const & original_path,
93                 latex_path_extension extension,
94                 latex_path_dots dots)
95 {
96         // On cygwin, we may need windows or posix style paths.
97         string path = os::latex_path(original_path);
98         path = subst(path, "~", "\\string~");
99         if (path.find(' ') != string::npos) {
100                 // We can't use '"' because " is sometimes active (e.g. if
101                 // babel is loaded with the "german" option)
102                 if (extension == EXCLUDE_EXTENSION) {
103                         // changeExtension calls os::internal_path internally
104                         // so don't use it to remove the extension.
105                         string const ext = getExtension(path);
106                         string const base = ext.empty() ?
107                                 path :
108                                 path.substr(0, path.length() - ext.length() - 1);
109                         // changeExtension calls os::internal_path internally
110                         // so don't use it to re-add the extension.
111                         path = "\\string\"" + base + "\\string\"." + ext;
112                 } else {
113                         path = "\\string\"" + path + "\\string\"";
114                 }
115         }
116
117         if (dots != ESCAPE_DOTS)
118                 return path;
119
120         // Replace dots with the lyxdot macro, but only in the file name,
121         // not the directory part.
122         // addName etc call os::internal_path internally
123         // so don't use them for path manipulation
124         // The directory separator is always '/' for LaTeX.
125         string::size_type pos = path.rfind('/');
126         if (pos == string::npos)
127                 return subst(path, ".", "\\lyxdot ");
128         return path.substr(0, pos) + subst(path.substr(pos), ".", "\\lyxdot ");
129 }
130
131
132 // Substitutes spaces with underscores in filename (and path)
133 FileName const makeLatexName(FileName const & file)
134 {
135         string name = file.onlyFileName();
136         string const path = file.onlyPath().absFileName() + "/";
137
138         // ok so we scan through the string twice, but who cares.
139         // FIXME: in Unicode time this will break for sure! There is
140         // a non-latin world out there...
141         string const keep = "abcdefghijklmnopqrstuvwxyz"
142                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
143                 "@!'()*+,-./0123456789:;<=>?[]`|";
144
145         string::size_type pos = 0;
146         while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
147                 name[pos++] = '_';
148
149         FileName latex_name(path + name);
150         latex_name.changeExtension(".tex");
151         return latex_name;
152 }
153
154
155 string const quoteName(string const & name, quote_style style)
156 {
157         switch(style) {
158         case quote_shell:
159                 // This does not work on native Windows for filenames
160                 // containing the following characters < > : " / \ | ? *
161                 // Moreover, it can't be made to work, as, according to
162                 // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
163                 // those are reserved characters, and thus are forbidden.
164                 // Please, also note that the command-line parser in
165                 // ForkedCall::generateChild cannot deal with filenames
166                 // containing " or ', therefore we don't pass user filenames
167                 // to child processes if possible. We store them in a python
168                 // script instead, where we don't have these limitations.
169 #ifndef USE_QPROCESS
170                 return (os::shell() == os::UNIX) ?
171                         '\'' + subst(name, "'", "\'\\\'\'") + '\'' :
172                         '"' + name + '"';
173 #else
174                 // According to the QProcess parser, a single double
175                 // quote is represented by three consecutive ones.
176                 // Here we simply escape the double quote and let our
177                 // simple parser in Systemcall.cpp do the substitution.
178                 return '"' + subst(name, "\"", "\\\"") + '"';
179 #endif
180         case quote_python:
181                 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
182                      + "\"";
183         }
184         // shut up stupid compiler
185         return string();
186 }
187
188
189 #if 0
190 // Uses a string of paths separated by ";"s to find a file to open.
191 // Can't cope with pathnames with a ';' in them. Returns full path to file.
192 // If path entry begins with $$LyX/, use system_lyxdir
193 // If path entry begins with $$User/, use user_lyxdir
194 // Example: "$$User/doc;$$LyX/doc"
195 FileName const fileOpenSearch(string const & path, string const & name,
196                              string const & ext)
197 {
198         FileName real_file;
199         string path_element;
200         bool notfound = true;
201         string tmppath = split(path, path_element, ';');
202
203         while (notfound && !path_element.empty()) {
204                 path_element = os::internal_path(path_element);
205                 if (!suffixIs(path_element, '/'))
206                         path_element += '/';
207                 path_element = subst(path_element, "$$LyX",
208                                      package().system_support().absFileName());
209                 path_element = subst(path_element, "$$User",
210                                      package().user_support().absFileName());
211
212                 real_file = fileSearch(path_element, name, ext);
213
214                 if (real_file.empty()) {
215                         do {
216                                 tmppath = split(tmppath, path_element, ';');
217                         } while (!tmppath.empty() && path_element.empty());
218                 } else {
219                         notfound = false;
220                 }
221         }
222         return real_file;
223 }
224 #endif
225
226
227 // Returns the real name of file name in directory path, with optional
228 // extension ext.
229 FileName const fileSearch(string const & path, string const & name,
230                           string const & ext, search_mode mode)
231 {
232         // if `name' is an absolute path, we ignore the setting of `path'
233         // Expand Environmentvariables in 'name'
234         string const tmpname = replaceEnvironmentPath(name);
235         FileName fullname(makeAbsPath(tmpname, path));
236         // search first without extension, then with it.
237         if (fullname.isReadableFile())
238                 return fullname;
239         if (ext.empty())
240                 // We are done.
241                 return mode == may_not_exist ? fullname : FileName();
242         // Only add the extension if it is not already the extension of
243         // fullname.
244         if (getExtension(fullname.absFileName()) != ext)
245                 fullname = FileName(addExtension(fullname.absFileName(), ext));
246         if (fullname.isReadableFile() || mode == may_not_exist)
247                 return fullname;
248         return FileName();
249 }
250
251
252 // Search the file name.ext in the subdirectory dir of
253 //   1) user_lyxdir
254 //   2) build_lyxdir (if not empty)
255 //   3) system_lyxdir
256 FileName const libFileSearch(string const & dir, string const & name,
257                            string const & ext)
258 {
259         FileName fullname = fileSearch(addPath(package().user_support().absFileName(), dir),
260                                      name, ext);
261         if (!fullname.empty())
262                 return fullname;
263
264         if (!package().build_support().empty())
265                 fullname = fileSearch(addPath(package().build_support().absFileName(), dir),
266                                       name, ext);
267         if (!fullname.empty())
268                 return fullname;
269
270         return fileSearch(addPath(package().system_support().absFileName(), dir), name, ext);
271 }
272
273
274 FileName const i18nLibFileSearch(string const & dir, string const & name,
275                   string const & ext)
276 {
277         /* The highest priority value is the `LANGUAGE' environment
278            variable. But we don't use the value if the currently
279            selected locale is the C locale. This is a GNU extension.
280
281            Otherwise, w use a trick to guess what support/gettext.has done:
282            each po file is able to tell us its name. (JMarc)
283         */
284
285         string lang = to_ascii(_(languageTestString()));
286         string const language = getEnv("LANGUAGE");
287         if (!lang.empty() && !language.empty())
288                 lang = language;
289
290         string l;
291         lang = split(lang, l, ':');
292         while (!l.empty()) {
293                 FileName tmp;
294                 // First try with the full name
295                 tmp = libFileSearch(addPath(dir, l), name, ext);
296                 if (!tmp.empty())
297                         return tmp;
298
299                 // Then the name without country code
300                 string const shortl = token(l, '_', 0);
301                 if (shortl != l) {
302                         tmp = libFileSearch(addPath(dir, shortl), name, ext);
303                         if (!tmp.empty())
304                                 return tmp;
305                 }
306
307 #if 1
308                 // For compatibility, to be removed later (JMarc)
309                 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
310                                     ext);
311                 if (!tmp.empty()) {
312                         lyxerr << "i18nLibFileSearch: File `" << tmp
313                                << "' has been found by the old method" <<endl;
314                         return tmp;
315                 }
316 #endif
317                 lang = split(lang, l, ':');
318         }
319
320         return libFileSearch(dir, name, ext);
321 }
322
323
324 FileName const imageLibFileSearch(string & dir, string const & name,
325                   string const & ext)
326 {
327         if (!lyx::lyxrc.icon_set.empty()) {
328                 string const imagedir = addPath(dir, lyx::lyxrc.icon_set);
329                 FileName const fn = libFileSearch(imagedir, name, ext);
330                 if (fn.exists()) {
331                         dir = imagedir;
332                         return fn;
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 static FileName createTmpDir(FileName const & tempdir, string const & mask)
373 {
374         LYXERR(Debug::FILES, "createTmpDir: tempdir=`" << tempdir << "'\n"
375                 << "createTmpDir:    mask=`" << mask << '\'');
376
377         FileName const tmpfl = FileName::tempName(tempdir, mask);
378
379         if (tmpfl.empty() || !tmpfl.createDirectory(0700)) {
380                 LYXERR0("LyX could not create temporary directory in " << tempdir
381                         << "'");
382                 return FileName();
383         }
384
385         return tmpfl;
386 }
387
388
389 FileName const createLyXTmpDir(FileName const & deflt)
390 {
391         if (deflt.empty() || deflt == package().system_temp_dir())
392                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
393
394         if (deflt.createDirectory(0777))
395                 return deflt;
396
397         if (deflt.isDirWritable()) {
398                 // deflt could not be created because it
399                 // did exist already, so let's create our own
400                 // dir inside deflt.
401                 return createTmpDir(deflt, "lyx_tmpdir");
402         } else {
403                 // some other error occured.
404                 return createTmpDir(package().system_temp_dir(), "lyx_tmpdir");
405         }
406 }
407
408
409 // Strip filename from path name
410 string const onlyPath(string const & filename)
411 {
412         // If empty filename, return empty
413         if (filename.empty())
414                 return filename;
415
416         // Find last / or start of filename
417         size_t j = filename.rfind('/');
418         return j == string::npos ? "./" : filename.substr(0, j + 1);
419 }
420
421
422 // Convert relative path into absolute path based on a basepath.
423 // If relpath is absolute, just use that.
424 // If basepath is empty, use CWD as base.
425 // Note that basePath can be a relative path, in the sense that it may
426 // not begin with "/" (e.g.), but it should NOT contain such constructs
427 // as "/../".
428 // FIXME It might be nice if the code didn't simply assume that.
429 FileName const makeAbsPath(string const & relPath, string const & basePath)
430 {
431         // checks for already absolute path
432         if (FileName::isAbsolute(relPath))
433                 return FileName(relPath);
434
435         // Copies given paths
436         string tempRel = os::internal_path(relPath);
437         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
438         tempRel = subst(tempRel, "//", "/");
439
440         string tempBase;
441
442         if (FileName::isAbsolute(basePath))
443                 tempBase = basePath;
444         else
445                 tempBase = addPath(FileName::getcwd().absFileName(), basePath);
446
447         // Handle /./ at the end of the path
448         while (suffixIs(tempBase, "/./"))
449                 tempBase.erase(tempBase.length() - 2);
450
451         // processes relative path
452         string rTemp = tempRel;
453         string temp;
454
455         // Check for a leading "~"
456         // Split by first /
457         rTemp = split(rTemp, temp, '/');
458         if (temp == "~") {
459                 tempBase = Package::get_home_dir().absFileName();
460                 tempRel = rTemp;
461         }
462
463         rTemp = tempRel;
464         while (!rTemp.empty()) {
465                 // Split by next /
466                 rTemp = split(rTemp, temp, '/');
467
468                 if (temp == ".") continue;
469                 if (temp == "..") {
470                         // Remove one level of TempBase
471                         if (tempBase.length() <= 1) {
472                                 //this is supposed to be an absolute path, so...
473                                 tempBase = "/";
474                                 continue;
475                         }
476                         //erase a trailing slash if there is one
477                         if (suffixIs(tempBase, "/"))
478                                 tempBase.erase(tempBase.length() - 1, string::npos);
479
480                         string::size_type i = tempBase.length() - 1;
481                         while (i > 0 && tempBase[i] != '/')
482                                 --i;
483                         if (i > 0)
484                                 tempBase.erase(i, string::npos);
485                         else
486                                 tempBase = '/';
487                 } else if (temp.empty() && !rTemp.empty()) {
488                                 tempBase = os::current_root() + rTemp;
489                                 rTemp.erase();
490                 } else {
491                         // Add this piece to TempBase
492                         if (!suffixIs(tempBase, '/'))
493                                 tempBase += '/';
494                         tempBase += temp;
495                 }
496         }
497
498         // returns absolute path
499         return FileName(tempBase);
500 }
501
502
503 // Correctly append filename to the pathname.
504 // If pathname is '.', then don't use pathname.
505 // Chops any path of filename.
506 string const addName(string const & path, string const & fname)
507 {
508         string const basename = onlyFileName(fname);
509         string buf;
510
511         if (path != "." && path != "./" && !path.empty()) {
512                 buf = os::internal_path(path);
513                 if (!suffixIs(path, '/'))
514                         buf += '/';
515         }
516
517         return buf + basename;
518 }
519
520
521 // Strips path from filename
522 string const onlyFileName(string const & fname)
523 {
524         if (fname.empty())
525                 return fname;
526
527         string::size_type j = fname.rfind('/');
528         if (j == string::npos) // no '/' in fname
529                 return fname;
530
531         // Strip to basename
532         return fname.substr(j + 1);
533 }
534
535
536 // Create absolute path. If impossible, don't do anything
537 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
538 string const expandPath(string const & path)
539 {
540         // checks for already absolute path
541         string rTemp = replaceEnvironmentPath(path);
542         if (FileName::isAbsolute(rTemp))
543                 return rTemp;
544
545         string temp;
546         string const copy = rTemp;
547
548         // Split by next /
549         rTemp = split(rTemp, temp, '/');
550
551         if (temp == ".")
552                 return FileName::getcwd().absFileName() + '/' + rTemp;
553
554         if (temp == "~")
555                 return Package::get_home_dir().absFileName() + '/' + rTemp;
556
557         if (temp == "..")
558                 return makeAbsPath(copy).absFileName();
559
560         // Don't know how to handle this
561         return copy;
562 }
563
564
565 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
566 string const replaceEnvironmentPath(string const & path)
567 {
568         // ${VAR} is defined as
569         // $\{[A-Za-z_][A-Za-z_0-9]*\}
570         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
571
572         // $VAR is defined as:
573         // $[A-Za-z_][A-Za-z_0-9]*
574         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
575
576         static regex envvar_br_re("(.*)" + envvar_br + "(.*)");
577         static regex envvar_re("(.*)" + envvar + "(.*)");
578         string result = path;
579         while (1) {
580                 smatch what;
581                 if (!regex_match(result, what, envvar_br_re)) {
582                         if (!regex_match(result, what, envvar_re))
583                                 break;
584                 }
585                 string env_var = getEnv(what.str(2));
586                 result = what.str(1) + env_var + what.str(3);
587         }
588         return result;
589 }
590
591
592 // Return a command prefix for setting the environment of the TeX engine.
593 string latexEnvCmdPrefix(string const & path)
594 {
595         if (path.empty() || lyxrc.texinputs_prefix.empty())
596                 return string();
597
598         string const texinputs_prefix = os::latex_path_list(
599                         replaceCurdirPath(path, lyxrc.texinputs_prefix));
600         string const sep = string(1, os::path_separator(os::TEXENGINE));
601         string const texinputs = getEnv("TEXINPUTS");
602
603         if (os::shell() == os::UNIX)
604                 return "env TEXINPUTS=\"." + sep + texinputs_prefix
605                                           + sep + texinputs + "\" ";
606         else
607                 return "cmd /d /c set TEXINPUTS=." + sep + texinputs_prefix
608                                                    + sep + texinputs + "&";
609 }
610
611
612 // Replace current directory in all elements of a path list with a given path.
613 string const replaceCurdirPath(string const & path, string const & pathlist)
614 {
615         string const oldpathlist = replaceEnvironmentPath(pathlist);
616         char const sep = os::path_separator();
617         string newpathlist;
618
619         for (size_t i = 0, k = 0; i != string::npos; k = i) {
620                 i = oldpathlist.find(sep, i);
621                 string p = oldpathlist.substr(k, i - k);
622                 if (FileName::isAbsolute(p)) {
623                         newpathlist += p;
624                 } else if (i > k) {
625                         size_t offset = 0;
626                         if (p == ".") {
627                                 offset = 1;
628                         } else if (prefixIs(p, "./")) {
629                                 offset = 2;
630                                 while (p[offset] == '/')
631                                         ++offset;
632                         }
633                         newpathlist += addPath(path, p.substr(offset));
634                         if (suffixIs(p, "//"))
635                                 newpathlist += '/';
636                 }
637                 if (i != string::npos) {
638                         newpathlist += sep;
639                         // Stop here if the last element is empty 
640                         if (++i == oldpathlist.length())
641                                 break;
642                 }
643         }
644         return newpathlist;
645 }
646
647
648 // Make relative path out of two absolute paths
649 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
650 // Makes relative path out of absolute path. If it is deeper than basepath,
651 // it's easy. If basepath and abspath share something (they are all deeper
652 // than some directory), it'll be rendered using ..'s. If they are completely
653 // different, then the absolute path will be used as relative path.
654 {
655         docstring::size_type const abslen = abspath.length();
656         docstring::size_type const baselen = basepath.length();
657
658         docstring::size_type i = os::common_path(abspath, basepath);
659
660         if (i == 0) {
661                 // actually no match - cannot make it relative
662                 return abspath;
663         }
664
665         // Count how many dirs there are in basepath above match
666         // and append as many '..''s into relpath
667         docstring buf;
668         docstring::size_type j = i;
669         while (j < baselen) {
670                 if (basepath[j] == '/') {
671                         if (j + 1 == baselen)
672                                 break;
673                         buf += "../";
674                 }
675                 ++j;
676         }
677
678         // Append relative stuff from common directory to abspath
679         if (abspath[i] == '/')
680                 ++i;
681         for (; i < abslen; ++i)
682                 buf += abspath[i];
683         // Remove trailing /
684         if (suffixIs(buf, '/'))
685                 buf.erase(buf.length() - 1);
686         // Substitute empty with .
687         if (buf.empty())
688                 buf = '.';
689         return buf;
690 }
691
692
693 // Append sub-directory(ies) to a path in an intelligent way
694 string const addPath(string const & path, string const & path_2)
695 {
696         string buf;
697         string const path2 = os::internal_path(path_2);
698
699         if (!path.empty() && path != "." && path != "./") {
700                 buf = os::internal_path(path);
701                 if (path[path.length() - 1] != '/')
702                         buf += '/';
703         }
704
705         if (!path2.empty()) {
706                 string::size_type const p2start = path2.find_first_not_of('/');
707                 string::size_type const p2end = path2.find_last_not_of('/');
708                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
709                 buf += tmp + '/';
710         }
711         return buf;
712 }
713
714
715 string const changeExtension(string const & oldname, string const & extension)
716 {
717         string::size_type const last_slash = oldname.rfind('/');
718         string::size_type last_dot = oldname.rfind('.');
719         if (last_dot < last_slash && last_slash != string::npos)
720                 last_dot = string::npos;
721
722         string ext;
723         // Make sure the extension starts with a dot
724         if (!extension.empty() && extension[0] != '.')
725                 ext= '.' + extension;
726         else
727                 ext = extension;
728
729         return os::internal_path(oldname.substr(0, last_dot) + ext);
730 }
731
732
733 string const removeExtension(string const & name)
734 {
735         return changeExtension(name, string());
736 }
737
738
739 string const addExtension(string const & name, string const & extension)
740 {
741         if (!extension.empty() && extension[0] != '.')
742                 return name + '.' + extension;
743         return name + extension;
744 }
745
746
747 /// Return the extension of the file (not including the .)
748 string const getExtension(string const & name)
749 {
750         string::size_type const last_slash = name.rfind('/');
751         string::size_type const last_dot = name.rfind('.');
752         if (last_dot != string::npos &&
753             (last_slash == string::npos || last_dot > last_slash))
754                 return name.substr(last_dot + 1,
755                                    name.length() - (last_dot + 1));
756         else
757                 return string();
758 }
759
760
761 string const unzippedFileName(string const & zipped_file)
762 {
763         string const ext = getExtension(zipped_file);
764         if (ext == "gz" || ext == "z" || ext == "Z")
765                 return changeExtension(zipped_file, string());
766         return onlyPath(zipped_file) + "unzipped_" + onlyFileName(zipped_file);
767 }
768
769
770 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
771 {
772         FileName const tempfile = FileName(unzipped_file.empty() ?
773                 unzippedFileName(zipped_file.toFilesystemEncoding()) :
774                 unzipped_file);
775         // Run gunzip
776         string const command = "gunzip -c " +
777                 zipped_file.toFilesystemEncoding() + " > " +
778                 tempfile.toFilesystemEncoding();
779         Systemcall one;
780         one.startscript(Systemcall::Wait, command);
781         // test that command was executed successfully (anon)
782         // yes, please do. (Lgb)
783         return tempfile;
784 }
785
786
787 docstring const makeDisplayPath(string const & path, unsigned int threshold)
788 {
789         string str = path;
790
791         // If file is from LyXDir, display it as if it were relative.
792         string const system = package().system_support().absFileName();
793         if (prefixIs(str, system) && str != system)
794                 return from_utf8("[" + str.erase(0, system.length()) + "]");
795
796         // replace /home/blah with ~/
797         string const home = Package::get_home_dir().absFileName();
798         if (!home.empty() && prefixIs(str, home))
799                 str = subst(str, home, "~");
800
801         if (str.length() <= threshold)
802                 return from_utf8(os::external_path(str));
803
804         string const prefix = ".../";
805         string temp;
806
807         while (str.length() > threshold)
808                 str = split(str, temp, '/');
809
810         // Did we shorten everything away?
811         if (str.empty()) {
812                 // Yes, filename itself is too long.
813                 // Pick the start and the end of the filename.
814                 str = onlyFileName(path);
815                 string const head = str.substr(0, threshold / 2 - 3);
816
817                 string::size_type len = str.length();
818                 string const tail =
819                         str.substr(len - threshold / 2 - 2, len - 1);
820                 str = head + "..." + tail;
821         }
822
823         return from_utf8(os::external_path(prefix + str));
824 }
825
826
827 #ifdef HAVE_READLINK
828 bool readLink(FileName const & file, FileName & link)
829 {
830         string const encoded = file.toFilesystemEncoding();
831 #ifdef HAVE_DEF_PATH_MAX
832         char linkbuffer[PATH_MAX + 1];
833         int const nRead = ::readlink(encoded.c_str(),
834                                      linkbuffer, sizeof(linkbuffer) - 1);
835         if (nRead <= 0)
836                 return false;
837         linkbuffer[nRead] = '\0'; // terminator
838 #else
839         vector<char> buf(1024);
840         int nRead = -1;
841
842         while (true) {
843                 nRead = ::readlink(encoded.c_str(), &buf[0], buf.size() - 1);
844                 if (nRead < 0) {
845                         return false;
846                 }
847                 if (nRead < buf.size() - 1) {
848                         break;
849                 }
850                 buf.resize(buf.size() * 2);
851         }
852         buf[nRead] = '\0'; // terminator
853         const char * linkbuffer = &buf[0];
854 #endif
855         link = makeAbsPath(linkbuffer, onlyPath(file.absFileName()));
856         return true;
857 }
858 #else
859 bool readLink(FileName const &, FileName &)
860 {
861         return false;
862 }
863 #endif
864
865
866 cmd_ret const runCommand(string const & cmd)
867 {
868         // FIXME: replace all calls to RunCommand with ForkedCall
869         // (if the output is not needed) or the code in ISpell.cpp
870         // (if the output is needed).
871
872         // One question is if we should use popen or
873         // create our own popen based on fork, exec, pipe
874         // of course the best would be to have a
875         // pstream (process stream), with the
876         // variants ipstream, opstream
877
878 #if defined (_WIN32)
879         int fno;
880         STARTUPINFO startup;
881         PROCESS_INFORMATION process;
882         SECURITY_ATTRIBUTES security;
883         HANDLE in, out;
884         FILE * inf = 0;
885         bool err2out = false;
886         string command;
887         string const infile = trim(split(cmd, command, '<'), " \"");
888         command = rtrim(command);
889         if (suffixIs(command, "2>&1")) {
890                 command = rtrim(command, "2>&1");
891                 err2out = true;
892         }
893         string const cmdarg = "/d /c " + command;
894         string const comspec = getEnv("COMSPEC");
895
896         security.nLength = sizeof(SECURITY_ATTRIBUTES);
897         security.bInheritHandle = TRUE;
898         security.lpSecurityDescriptor = NULL;
899
900         if (CreatePipe(&in, &out, &security, 0)) {
901                 memset(&startup, 0, sizeof(STARTUPINFO));
902                 memset(&process, 0, sizeof(PROCESS_INFORMATION));
903
904                 startup.cb = sizeof(STARTUPINFO);
905                 startup.dwFlags = STARTF_USESTDHANDLES;
906
907                 startup.hStdError = err2out ? out : GetStdHandle(STD_ERROR_HANDLE);
908                 startup.hStdInput = infile.empty()
909                         ? GetStdHandle(STD_INPUT_HANDLE)
910                         : CreateFile(infile.c_str(), GENERIC_READ,
911                                 FILE_SHARE_READ, &security, OPEN_EXISTING,
912                                 FILE_ATTRIBUTE_NORMAL, NULL);
913                 startup.hStdOutput = out;
914
915                 if (startup.hStdInput != INVALID_HANDLE_VALUE &&
916                         CreateProcess(comspec.c_str(), (LPTSTR)cmdarg.c_str(),
917                                 &security, &security, TRUE, CREATE_NO_WINDOW,
918                                 0, 0, &startup, &process)) {
919
920                         CloseHandle(process.hThread);
921                         fno = _open_osfhandle((long)in, _O_RDONLY);
922                         CloseHandle(out);
923                         inf = _fdopen(fno, "r");
924                 }
925         }
926 #elif defined (HAVE_POPEN)
927         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
928 #elif defined (HAVE__POPEN)
929         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
930 #else
931 #error No popen() function.
932 #endif
933
934         // (Claus Hentschel) Check if popen was successful ;-)
935         if (!inf) {
936                 lyxerr << "RunCommand:: could not start child process" << endl;
937                 return make_pair(-1, string());
938         }
939
940         string ret;
941         int c = fgetc(inf);
942         while (c != EOF) {
943                 ret += static_cast<char>(c);
944                 c = fgetc(inf);
945         }
946
947 #if defined (_WIN32)
948         WaitForSingleObject(process.hProcess, INFINITE);
949         if (!infile.empty())
950                 CloseHandle(startup.hStdInput);
951         CloseHandle(process.hProcess);
952         int const pret = fclose(inf);
953 #elif defined (HAVE_PCLOSE)
954         int const pret = pclose(inf);
955 #elif defined (HAVE__PCLOSE)
956         int const pret = _pclose(inf);
957 #else
958 #error No pclose() function.
959 #endif
960
961         if (pret == -1)
962                 perror("RunCommand:: could not terminate child process");
963
964         return make_pair(pret, ret);
965 }
966
967
968 FileName const findtexfile(string const & fil, string const & /*format*/)
969 {
970         /* There is no problem to extend this function too use other
971            methods to look for files. It could be setup to look
972            in environment paths and also if wanted as a last resort
973            to a recursive find. One of the easier extensions would
974            perhaps be to use the LyX file lookup methods. But! I am
975            going to implement this until I see some demand for it.
976            Lgb
977         */
978
979         // If the file can be found directly, we just return a
980         // absolute path version of it.
981         FileName const absfile(makeAbsPath(fil));
982         if (absfile.exists())
983                 return absfile;
984
985         // Now we try to find it using kpsewhich.
986         // It seems from the kpsewhich manual page that it is safe to use
987         // kpsewhich without --format: "When the --format option is not
988         // given, the search path used when looking for a file is inferred
989         // from the name given, by looking for a known extension. If no
990         // known extension is found, the search path for TeX source files
991         // is used."
992         // However, we want to take advantage of the format sine almost all
993         // the different formats has environment variables that can be used
994         // to controll which paths to search. f.ex. bib looks in
995         // BIBINPUTS and TEXBIB. Small list follows:
996         // bib - BIBINPUTS, TEXBIB
997         // bst - BSTINPUTS
998         // graphic/figure - TEXPICTS, TEXINPUTS
999         // ist - TEXINDEXSTYLE, INDEXSTYLE
1000         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1001         // tex - TEXINPUTS
1002         // tfm - TFMFONTS, TEXFONTS
1003         // This means that to use kpsewhich in the best possible way we
1004         // should help it by setting additional path in the approp. envir.var.
1005         string const kpsecmd = "kpsewhich " + fil;
1006
1007         cmd_ret const c = runCommand(kpsecmd);
1008
1009         LYXERR(Debug::LATEX, "kpse status = " << c.first << '\n'
1010                  << "kpse result = `" << rtrim(c.second, "\n\r") << '\'');
1011         if (c.first != -1)
1012                 return FileName(rtrim(to_utf8(from_filesystem8bit(c.second)), "\n\r"));
1013         else
1014                 return FileName();
1015 }
1016
1017
1018 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1019         string const & message)
1020 {
1021         LYXERR(Debug::GRAPHICS, "[readBB_from_PSFile] " << message);
1022         // FIXME: Why is this func deleting a file? (Lgb)
1023         if (zipped)
1024                 file.removeFile();
1025 }
1026
1027
1028 string const readBB_from_PSFile(FileName const & file)
1029 {
1030         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1031         // It seems that every command in the header has an own line,
1032         // getline() should work for all files.
1033         // On the other hand some plot programs write the bb at the
1034         // end of the file. Than we have in the header:
1035         // %%BoundingBox: (atend)
1036         // In this case we must check the end.
1037         bool zipped = file.isZippedFile();
1038         FileName const file_ = zipped ? unzipFile(file) : file;
1039         string const format = file_.guessFormatFromContents();
1040
1041         if (format != "eps" && format != "ps") {
1042                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1043                 return string();
1044         }
1045
1046         static lyx::regex bbox_re(
1047                 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1048         ifstream is(file_.toFilesystemEncoding().c_str());
1049         while (is) {
1050                 string s;
1051                 getline(is,s);
1052                 lyx::smatch what;
1053                 if (regex_match(s, what, bbox_re)) {
1054                         // Our callers expect the tokens in the string
1055                         // separated by single spaces.
1056                         // FIXME: change return type from string to something
1057                         // sensible
1058                         ostringstream os;
1059                         os << what.str(1) << ' ' << what.str(2) << ' '
1060                            << what.str(3) << ' ' << what.str(4);
1061                         string const bb = os.str();
1062                         readBB_lyxerrMessage(file_, zipped, bb);
1063                         return bb;
1064                 }
1065         }
1066         readBB_lyxerrMessage(file_, zipped, "no bb found");
1067         return string();
1068 }
1069
1070
1071 int compare_timestamps(FileName const & file1, FileName const & file2)
1072 {
1073         // If the original is newer than the copy, then copy the original
1074         // to the new directory.
1075
1076         int cmp = 0;
1077         if (file1.exists() && file2.exists()) {
1078                 double const tmp = difftime(file1.lastModified(), file2.lastModified());
1079                 if (tmp != 0)
1080                         cmp = tmp > 0 ? 1 : -1;
1081
1082         } else if (file1.exists()) {
1083                 cmp = 1;
1084         } else if (file2.exists()) {
1085                 cmp = -1;
1086         }
1087
1088         return cmp;
1089 }
1090
1091
1092 bool prefs2prefs(FileName const & filename, FileName const & tempfile, bool lfuns)
1093 {
1094         FileName const script = libFileSearch("scripts", "prefs2prefs.py");
1095         if (script.empty()) {
1096                 LYXERR0("Could not find bind file conversion "
1097                                 "script prefs2prefs.py.");
1098                 return false;
1099         }
1100
1101         ostringstream command;
1102         command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
1103           << ' ' << (lfuns ? "-l" : "-p") << ' '
1104                 << quoteName(filename.toFilesystemEncoding())
1105                 << ' ' << quoteName(tempfile.toFilesystemEncoding());
1106         string const command_str = command.str();
1107
1108         LYXERR(Debug::FILES, "Running `" << command_str << '\'');
1109
1110         cmd_ret const ret = runCommand(command_str);
1111         if (ret.first != 0) {
1112                 LYXERR0("Could not run file conversion script prefs2prefs.py.");
1113                 return false;
1114         }
1115         return true;
1116 }
1117
1118
1119 } //namespace support
1120 } // namespace lyx