]> git.lyx.org Git - lyx.git/blob - src/support/filetools.cpp
Work around qt bug that prevents the glyph LATIN CAPITAL LETTER SHARP S from being...
[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         docstring dstr = from_utf8(str);
806         docstring temp;
807
808         while (dstr.length() > threshold)
809                 dstr = split(dstr, temp, '/');
810
811         // Did we shorten everything away?
812         if (dstr.empty()) {
813                 // Yes, filename itself is too long.
814                 // Pick the start and the end of the filename.
815                 dstr = from_utf8(onlyFileName(path));
816                 docstring const head = dstr.substr(0, threshold / 2 - 3);
817
818                 docstring::size_type len = dstr.length();
819                 docstring const tail =
820                         dstr.substr(len - threshold / 2 - 2, len - 1);
821                 dstr = head + from_ascii("...") + tail;
822         }
823
824         return from_utf8(os::external_path(prefix + to_utf8(dstr)));
825 }
826
827
828 #ifdef HAVE_READLINK
829 bool readLink(FileName const & file, FileName & link)
830 {
831         string const encoded = file.toFilesystemEncoding();
832 #ifdef HAVE_DEF_PATH_MAX
833         char linkbuffer[PATH_MAX + 1];
834         int const nRead = ::readlink(encoded.c_str(),
835                                      linkbuffer, sizeof(linkbuffer) - 1);
836         if (nRead <= 0)
837                 return false;
838         linkbuffer[nRead] = '\0'; // terminator
839 #else
840         vector<char> buf(1024);
841         int nRead = -1;
842
843         while (true) {
844                 nRead = ::readlink(encoded.c_str(), &buf[0], buf.size() - 1);
845                 if (nRead < 0) {
846                         return false;
847                 }
848                 if (nRead < buf.size() - 1) {
849                         break;
850                 }
851                 buf.resize(buf.size() * 2);
852         }
853         buf[nRead] = '\0'; // terminator
854         const char * linkbuffer = &buf[0];
855 #endif
856         link = makeAbsPath(linkbuffer, onlyPath(file.absFileName()));
857         return true;
858 }
859 #else
860 bool readLink(FileName const &, FileName &)
861 {
862         return false;
863 }
864 #endif
865
866
867 cmd_ret const runCommand(string const & cmd)
868 {
869         // FIXME: replace all calls to RunCommand with ForkedCall
870         // (if the output is not needed) or the code in ISpell.cpp
871         // (if the output is needed).
872
873         // One question is if we should use popen or
874         // create our own popen based on fork, exec, pipe
875         // of course the best would be to have a
876         // pstream (process stream), with the
877         // variants ipstream, opstream
878
879 #if defined (_WIN32)
880         int fno;
881         STARTUPINFO startup;
882         PROCESS_INFORMATION process;
883         SECURITY_ATTRIBUTES security;
884         HANDLE in, out;
885         FILE * inf = 0;
886         bool err2out = false;
887         string command;
888         string const infile = trim(split(cmd, command, '<'), " \"");
889         command = rtrim(command);
890         if (suffixIs(command, "2>&1")) {
891                 command = rtrim(command, "2>&1");
892                 err2out = true;
893         }
894         string const cmdarg = "/d /c " + command;
895         string const comspec = getEnv("COMSPEC");
896
897         security.nLength = sizeof(SECURITY_ATTRIBUTES);
898         security.bInheritHandle = TRUE;
899         security.lpSecurityDescriptor = NULL;
900
901         if (CreatePipe(&in, &out, &security, 0)) {
902                 memset(&startup, 0, sizeof(STARTUPINFO));
903                 memset(&process, 0, sizeof(PROCESS_INFORMATION));
904
905                 startup.cb = sizeof(STARTUPINFO);
906                 startup.dwFlags = STARTF_USESTDHANDLES;
907
908                 startup.hStdError = err2out ? out : GetStdHandle(STD_ERROR_HANDLE);
909                 startup.hStdInput = infile.empty()
910                         ? GetStdHandle(STD_INPUT_HANDLE)
911                         : CreateFile(infile.c_str(), GENERIC_READ,
912                                 FILE_SHARE_READ, &security, OPEN_EXISTING,
913                                 FILE_ATTRIBUTE_NORMAL, NULL);
914                 startup.hStdOutput = out;
915
916                 if (startup.hStdInput != INVALID_HANDLE_VALUE &&
917                         CreateProcess(comspec.c_str(), (LPTSTR)cmdarg.c_str(),
918                                 &security, &security, TRUE, CREATE_NO_WINDOW,
919                                 0, 0, &startup, &process)) {
920
921                         CloseHandle(process.hThread);
922                         fno = _open_osfhandle((long)in, _O_RDONLY);
923                         CloseHandle(out);
924                         inf = _fdopen(fno, "r");
925                 }
926         }
927 #elif defined (HAVE_POPEN)
928         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
929 #elif defined (HAVE__POPEN)
930         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
931 #else
932 #error No popen() function.
933 #endif
934
935         // (Claus Hentschel) Check if popen was successful ;-)
936         if (!inf) {
937                 lyxerr << "RunCommand:: could not start child process" << endl;
938                 return make_pair(-1, string());
939         }
940
941         string ret;
942         int c = fgetc(inf);
943         while (c != EOF) {
944                 ret += static_cast<char>(c);
945                 c = fgetc(inf);
946         }
947
948 #if defined (_WIN32)
949         WaitForSingleObject(process.hProcess, INFINITE);
950         if (!infile.empty())
951                 CloseHandle(startup.hStdInput);
952         CloseHandle(process.hProcess);
953         int const pret = fclose(inf);
954 #elif defined (HAVE_PCLOSE)
955         int const pret = pclose(inf);
956 #elif defined (HAVE__PCLOSE)
957         int const pret = _pclose(inf);
958 #else
959 #error No pclose() function.
960 #endif
961
962         if (pret == -1)
963                 perror("RunCommand:: could not terminate child process");
964
965         return make_pair(pret, ret);
966 }
967
968
969 FileName const findtexfile(string const & fil, string const & /*format*/)
970 {
971         /* There is no problem to extend this function too use other
972            methods to look for files. It could be setup to look
973            in environment paths and also if wanted as a last resort
974            to a recursive find. One of the easier extensions would
975            perhaps be to use the LyX file lookup methods. But! I am
976            going to implement this until I see some demand for it.
977            Lgb
978         */
979
980         // If the file can be found directly, we just return a
981         // absolute path version of it.
982         FileName const absfile(makeAbsPath(fil));
983         if (absfile.exists())
984                 return absfile;
985
986         // Now we try to find it using kpsewhich.
987         // It seems from the kpsewhich manual page that it is safe to use
988         // kpsewhich without --format: "When the --format option is not
989         // given, the search path used when looking for a file is inferred
990         // from the name given, by looking for a known extension. If no
991         // known extension is found, the search path for TeX source files
992         // is used."
993         // However, we want to take advantage of the format sine almost all
994         // the different formats has environment variables that can be used
995         // to controll which paths to search. f.ex. bib looks in
996         // BIBINPUTS and TEXBIB. Small list follows:
997         // bib - BIBINPUTS, TEXBIB
998         // bst - BSTINPUTS
999         // graphic/figure - TEXPICTS, TEXINPUTS
1000         // ist - TEXINDEXSTYLE, INDEXSTYLE
1001         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1002         // tex - TEXINPUTS
1003         // tfm - TFMFONTS, TEXFONTS
1004         // This means that to use kpsewhich in the best possible way we
1005         // should help it by setting additional path in the approp. envir.var.
1006         string const kpsecmd = "kpsewhich " + fil;
1007
1008         cmd_ret const c = runCommand(kpsecmd);
1009
1010         LYXERR(Debug::LATEX, "kpse status = " << c.first << '\n'
1011                  << "kpse result = `" << rtrim(c.second, "\n\r") << '\'');
1012         if (c.first != -1)
1013                 return FileName(rtrim(to_utf8(from_filesystem8bit(c.second)), "\n\r"));
1014         else
1015                 return FileName();
1016 }
1017
1018
1019 int compare_timestamps(FileName const & file1, FileName const & file2)
1020 {
1021         // If the original is newer than the copy, then copy the original
1022         // to the new directory.
1023
1024         int cmp = 0;
1025         if (file1.exists() && file2.exists()) {
1026                 double const tmp = difftime(file1.lastModified(), file2.lastModified());
1027                 if (tmp != 0)
1028                         cmp = tmp > 0 ? 1 : -1;
1029
1030         } else if (file1.exists()) {
1031                 cmp = 1;
1032         } else if (file2.exists()) {
1033                 cmp = -1;
1034         }
1035
1036         return cmp;
1037 }
1038
1039
1040 bool prefs2prefs(FileName const & filename, FileName const & tempfile, bool lfuns)
1041 {
1042         FileName const script = libFileSearch("scripts", "prefs2prefs.py");
1043         if (script.empty()) {
1044                 LYXERR0("Could not find bind file conversion "
1045                                 "script prefs2prefs.py.");
1046                 return false;
1047         }
1048
1049         ostringstream command;
1050         command << os::python() << ' ' << quoteName(script.toFilesystemEncoding())
1051           << ' ' << (lfuns ? "-l" : "-p") << ' '
1052                 << quoteName(filename.toFilesystemEncoding())
1053                 << ' ' << quoteName(tempfile.toFilesystemEncoding());
1054         string const command_str = command.str();
1055
1056         LYXERR(Debug::FILES, "Running `" << command_str << '\'');
1057
1058         cmd_ret const ret = runCommand(command_str);
1059         if (ret.first != 0) {
1060                 LYXERR0("Could not run file conversion script prefs2prefs.py.");
1061                 return false;
1062         }
1063         return true;
1064 }
1065
1066
1067 } //namespace support
1068 } // namespace lyx