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