3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
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
17 * Full author contact details are available in file CREDITS.
19 * General path-mangling functions
24 #include "support/convert.h"
25 #include "support/environment.h"
26 #include "support/filetools.h"
27 #include "support/fs_extras.h"
28 #include "support/lstrings.h"
29 #include "support/lyxlib.h"
30 #include "support/os.h"
31 #include "support/Package.h"
32 #include "support/Path.h"
33 #include "support/Systemcall.h"
35 // FIXME Interface violation
39 #include <boost/assert.hpp>
40 #include <boost/filesystem/operations.hpp>
41 #include <boost/regex.hpp>
59 #ifdef HAVE_SYS_TYPES_H
60 # include <sys/types.h>
62 #ifdef HAVE_SYS_STAT_H
63 # include <sys/stat.h>
68 #ifdef HAVE_SYS_UTIME_H
69 # include <sys/utime.h>
83 #define WRITEBUFFERSIZE (16384)
84 #define MAXFILENAME (256)
87 #ifndef CXX_GLOBAL_CSTD
96 using std::ostringstream;
100 namespace fs = boost::filesystem;
105 bool isLyXFilename(string const & filename)
107 return suffixIs(ascii_lowercase(filename), ".lyx");
111 bool isSGMLFilename(string const & filename)
113 return suffixIs(ascii_lowercase(filename), ".sgml");
117 bool isValidLaTeXFilename(string const & filename)
119 string const invalid_chars("#$%{}()[]\"^");
120 if (filename.find_first_of(invalid_chars) != string::npos)
127 string const latex_path(string const & original_path,
128 latex_path_extension extension,
129 latex_path_dots dots)
131 // On cygwin, we may need windows or posix style paths.
132 string path = os::latex_path(original_path);
133 path = subst(path, "~", "\\string~");
134 if (path.find(' ') != string::npos) {
135 // We can't use '"' because " is sometimes active (e.g. if
136 // babel is loaded with the "german" option)
137 if (extension == EXCLUDE_EXTENSION) {
138 // ChangeExtension calls os::internal_path internally
139 // so don't use it to remove the extension.
140 string const ext = getExtension(path);
141 string const base = ext.empty() ?
143 path.substr(0, path.length() - ext.length() - 1);
144 // ChangeExtension calls os::internal_path internally
145 // so don't use it to re-add the extension.
146 path = "\\string\"" + base + "\\string\"." + ext;
148 path = "\\string\"" + path + "\\string\"";
152 return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
156 // Substitutes spaces with underscores in filename (and path)
157 string const makeLatexName(string const & file)
159 string name = onlyFilename(file);
160 string const path = onlyPath(file);
162 // ok so we scan through the string twice, but who cares.
163 string const keep = "abcdefghijklmnopqrstuvwxyz"
164 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
165 "@!'()*+,-./0123456789:;<=>?[]`|";
167 string::size_type pos = 0;
168 while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
171 return addName(path, name);
175 string const quoteName(string const & name, quote_style style)
179 // This does not work for filenames containing " (windows)
180 // or ' (all other OSes). This can't be changed easily, since
181 // we would need to adapt the command line parser in
182 // Forkedcall::generateChild. Therefore we don't pass user
183 // filenames to child processes if possible. We store them in
184 // a python script instead, where we don't have these
186 return (os::shell() == os::UNIX) ?
190 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
193 // shut up stupid compiler
198 bool isFileReadable(FileName const & filename)
200 std::string const path = filename.toFilesystemEncoding();
201 return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
205 //returns true: dir writeable
206 // false: not writeable
207 bool isDirWriteable(FileName const & path)
209 LYXERR(Debug::FILES) << "isDirWriteable: " << path << endl;
211 FileName const tmpfl(tempName(path, "lyxwritetest"));
222 // Uses a string of paths separated by ";"s to find a file to open.
223 // Can't cope with pathnames with a ';' in them. Returns full path to file.
224 // If path entry begins with $$LyX/, use system_lyxdir
225 // If path entry begins with $$User/, use user_lyxdir
226 // Example: "$$User/doc;$$LyX/doc"
227 FileName const fileOpenSearch(string const & path, string const & name,
232 bool notfound = true;
233 string tmppath = split(path, path_element, ';');
235 while (notfound && !path_element.empty()) {
236 path_element = os::internal_path(path_element);
237 if (!suffixIs(path_element, '/'))
239 path_element = subst(path_element, "$$LyX",
240 package().system_support().absFilename());
241 path_element = subst(path_element, "$$User",
242 package().user_support().absFilename());
244 real_file = fileSearch(path_element, name, ext);
246 if (real_file.empty()) {
248 tmppath = split(tmppath, path_element, ';');
249 } while (!tmppath.empty() && path_element.empty());
259 /// Returns a vector of all files in directory dir having extension ext.
260 vector<FileName> const dirList(FileName const & dir, string const & ext)
262 // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
263 vector<FileName> dirlist;
265 string const encoded_dir = dir.toFilesystemEncoding();
266 if (!(fs::exists(encoded_dir) && fs::is_directory(encoded_dir))) {
268 << "Directory \"" << dir
269 << "\" does not exist to DirList." << endl;
274 if (!ext.empty() && ext[0] != '.')
278 fs::directory_iterator dit(encoded_dir);
279 fs::directory_iterator end;
280 for (; dit != end; ++dit) {
281 string const & fil = dit->leaf();
282 if (suffixIs(fil, extension))
283 dirlist.push_back(FileName::fromFilesystemEncoding(
284 encoded_dir + '/' + fil));
290 // Returns the real name of file name in directory path, with optional
292 FileName const fileSearch(string const & path, string const & name,
293 string const & ext, search_mode mode)
295 // if `name' is an absolute path, we ignore the setting of `path'
296 // Expand Environmentvariables in 'name'
297 string const tmpname = replaceEnvironmentPath(name);
298 FileName fullname(makeAbsPath(tmpname, path));
299 // search first without extension, then with it.
300 if (isFileReadable(fullname))
304 return mode == allow_unreadable ? fullname : FileName();
305 // Only add the extension if it is not already the extension of
307 if (getExtension(fullname.absFilename()) != ext)
308 fullname = FileName(addExtension(fullname.absFilename(), ext));
309 if (isFileReadable(fullname) || mode == allow_unreadable)
315 // Search the file name.ext in the subdirectory dir of
317 // 2) build_lyxdir (if not empty)
319 FileName const libFileSearch(string const & dir, string const & name,
322 FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
324 if (!fullname.empty())
327 if (!package().build_support().empty())
328 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
330 if (!fullname.empty())
333 return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
337 FileName const i18nLibFileSearch(string const & dir, string const & name,
340 /* The highest priority value is the `LANGUAGE' environment
341 variable. But we don't use the value if the currently
342 selected locale is the C locale. This is a GNU extension.
344 Otherwise, w use a trick to guess what gettext has done:
345 each po file is able to tell us its name. (JMarc)
348 string lang = to_ascii(_("[[Replace with the code of your language]]"));
349 string const language = getEnv("LANGUAGE");
350 if (!lang.empty() && !language.empty())
354 lang = split(lang, l, ':');
357 // First try with the full name
358 tmp = libFileSearch(addPath(dir, l), name, ext);
362 // Then the name without country code
363 string const shortl = token(l, '_', 0);
365 tmp = libFileSearch(addPath(dir, shortl), name, ext);
371 // For compatibility, to be removed later (JMarc)
372 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
375 lyxerr << "i18nLibFileSearch: File `" << tmp
376 << "' has been found by the old method" <<endl;
380 lang = split(lang, l, ':');
383 return libFileSearch(dir, name, ext);
387 string const libScriptSearch(string const & command_in, quote_style style)
389 static string const token_scriptpath = "$$s/";
391 string command = command_in;
392 // Find the starting position of "$$s/"
393 string::size_type const pos1 = command.find(token_scriptpath);
394 if (pos1 == string::npos)
396 // Find the end of the "$$s/some_subdir/some_script" word within
397 // command. Assumes that the script name does not contain spaces.
398 string::size_type const start_script = pos1 + 4;
399 string::size_type const pos2 = command.find(' ', start_script);
400 string::size_type const size_script = pos2 == string::npos?
401 (command.size() - start_script) : pos2 - start_script;
403 // Does this script file exist?
404 string const script =
405 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
407 if (script.empty()) {
408 // Replace "$$s/" with ""
409 command.erase(pos1, 4);
411 // Replace "$$s/foo/some_script" with "<path to>/some_script".
412 string::size_type const size_replace = size_script + 4;
413 command.replace(pos1, size_replace, quoteName(script, style));
422 FileName const createTmpDir(FileName const & tempdir, string const & mask)
425 << "createTmpDir: tempdir=`" << tempdir << "'\n"
426 << "createTmpDir: mask=`" << mask << '\'' << endl;
428 FileName const tmpfl(tempName(tempdir, mask));
429 // lyx::tempName actually creates a file to make sure that it
430 // stays unique. So we have to delete it before we can create
431 // a dir with the same name. Note also that we are not thread
432 // safe because of the gap between unlink and mkdir. (Lgb)
435 if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
436 lyxerr << "LyX could not create the temporary directory '"
437 << tmpfl << "'" << endl;
447 bool destroyDir(FileName const & tmpdir)
450 return fs::remove_all(tmpdir.toFilesystemEncoding()) > 0;
451 } catch (fs::filesystem_error const & fe){
452 lyxerr << "Could not delete " << tmpdir << ". (" << fe.what() << ")" << std::endl;
458 string const createBufferTmpDir()
461 // We are in our own directory. Why bother to mangle name?
462 // In fact I wrote this code to circumvent a problematic behaviour
463 // (bug?) of EMX mkstemp().
465 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
466 convert<string>(count++);
468 if (mkdir(FileName(tmpfl), 0777)) {
469 lyxerr << "LyX could not create the temporary directory '"
470 << tmpfl << "'" << endl;
477 FileName const createLyXTmpDir(FileName const & deflt)
479 if (!deflt.empty() && deflt.absFilename() != "/tmp") {
480 if (mkdir(deflt, 0777)) {
481 if (isDirWriteable(deflt)) {
482 // deflt could not be created because it
483 // did exist already, so let's create our own
485 return createTmpDir(deflt, "lyx_tmpdir");
487 // some other error occured.
488 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
493 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
498 bool createDirectory(FileName const & path, int permission)
500 BOOST_ASSERT(!path.empty());
501 return mkdir(path, permission) == 0;
505 // Strip filename from path name
506 string const onlyPath(string const & filename)
508 // If empty filename, return empty
509 if (filename.empty())
512 // Find last / or start of filename
513 string::size_type j = filename.rfind('/');
514 return j == string::npos ? "./" : filename.substr(0, j + 1);
518 // Convert relative path into absolute path based on a basepath.
519 // If relpath is absolute, just use that.
520 // If basepath is empty, use CWD as base.
521 FileName const makeAbsPath(string const & relPath, string const & basePath)
523 // checks for already absolute path
524 if (os::is_absolute_path(relPath))
525 return FileName(relPath);
527 // Copies given paths
528 string tempRel = os::internal_path(relPath);
529 // Since TempRel is NOT absolute, we can safely replace "//" with "/"
530 tempRel = subst(tempRel, "//", "/");
534 if (os::is_absolute_path(basePath))
537 tempBase = addPath(getcwd().absFilename(), basePath);
539 // Handle /./ at the end of the path
540 while (suffixIs(tempBase, "/./"))
541 tempBase.erase(tempBase.length() - 2);
543 // processes relative path
544 string rTemp = tempRel;
547 while (!rTemp.empty()) {
549 rTemp = split(rTemp, temp, '/');
551 if (temp == ".") continue;
553 // Remove one level of TempBase
554 string::difference_type i = tempBase.length() - 2;
557 while (i > 0 && tempBase[i] != '/')
560 tempBase.erase(i, string::npos);
563 } else if (temp.empty() && !rTemp.empty()) {
564 tempBase = os::current_root() + rTemp;
567 // Add this piece to TempBase
568 if (!suffixIs(tempBase, '/'))
574 // returns absolute path
575 return FileName(os::internal_path(tempBase));
579 // Correctly append filename to the pathname.
580 // If pathname is '.', then don't use pathname.
581 // Chops any path of filename.
582 string const addName(string const & path, string const & fname)
584 string const basename = onlyFilename(fname);
587 if (path != "." && path != "./" && !path.empty()) {
588 buf = os::internal_path(path);
589 if (!suffixIs(path, '/'))
593 return buf + basename;
597 // Strips path from filename
598 string const onlyFilename(string const & fname)
603 string::size_type j = fname.rfind('/');
604 if (j == string::npos) // no '/' in fname
608 return fname.substr(j + 1);
612 /// Returns true is path is absolute
613 bool absolutePath(string const & path)
615 return os::is_absolute_path(path);
619 // Create absolute path. If impossible, don't do anything
620 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
621 string const expandPath(string const & path)
623 // checks for already absolute path
624 string rTemp = replaceEnvironmentPath(path);
625 if (os::is_absolute_path(rTemp))
629 string const copy = rTemp;
632 rTemp = split(rTemp, temp, '/');
635 return getcwd().absFilename() + '/' + rTemp;
638 return package().home_dir().absFilename() + '/' + rTemp;
641 return makeAbsPath(copy).absFilename();
643 // Don't know how to handle this
648 // Normalize a path. Constracts path/../path
649 // Can't handle "../../" or "/../" (Asger)
650 // Also converts paths like /foo//bar ==> /foo/bar
651 string const normalizePath(string const & path)
653 // Normalize paths like /foo//bar ==> /foo/bar
654 static boost::regex regex("/{2,}");
655 string const tmppath = boost::regex_merge(path, regex, "/");
657 fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
659 if (!npath.is_complete())
660 return "./" + npath.string() + '/';
662 return npath.string() + '/';
666 string const getFileContents(FileName const & fname)
668 string const encodedname = fname.toFilesystemEncoding();
669 if (fs::exists(encodedname)) {
670 ifstream ifs(encodedname.c_str());
678 lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
683 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
684 string const replaceEnvironmentPath(string const & path)
686 // ${VAR} is defined as
687 // $\{[A-Za-z_][A-Za-z_0-9]*\}
688 static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
690 // $VAR is defined as:
691 // $\{[A-Za-z_][A-Za-z_0-9]*\}
692 static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
694 static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
695 static boost::regex envvar_re("(.*)" + envvar + "(.*)");
698 string result = path;
700 regex_match(result, what, envvar_br_re);
701 if (!what[0].matched) {
702 regex_match(result, what, envvar_re);
703 if (!what[0].matched)
706 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
712 // Make relative path out of two absolute paths
713 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
714 // Makes relative path out of absolute path. If it is deeper than basepath,
715 // it's easy. If basepath and abspath share something (they are all deeper
716 // than some directory), it'll be rendered using ..'s. If they are completely
717 // different, then the absolute path will be used as relative path.
719 docstring::size_type const abslen = abspath.length();
720 docstring::size_type const baselen = basepath.length();
722 docstring::size_type i = os::common_path(abspath, basepath);
725 // actually no match - cannot make it relative
729 // Count how many dirs there are in basepath above match
730 // and append as many '..''s into relpath
732 docstring::size_type j = i;
733 while (j < baselen) {
734 if (basepath[j] == '/') {
735 if (j + 1 == baselen)
742 // Append relative stuff from common directory to abspath
743 if (abspath[i] == '/')
745 for (; i < abslen; ++i)
748 if (suffixIs(buf, '/'))
749 buf.erase(buf.length() - 1);
750 // Substitute empty with .
757 // Append sub-directory(ies) to a path in an intelligent way
758 string const addPath(string const & path, string const & path_2)
761 string const path2 = os::internal_path(path_2);
763 if (!path.empty() && path != "." && path != "./") {
764 buf = os::internal_path(path);
765 if (path[path.length() - 1] != '/')
769 if (!path2.empty()) {
770 string::size_type const p2start = path2.find_first_not_of('/');
771 string::size_type const p2end = path2.find_last_not_of('/');
772 string const tmp = path2.substr(p2start, p2end - p2start + 1);
779 string const changeExtension(string const & oldname, string const & extension)
781 string::size_type const last_slash = oldname.rfind('/');
782 string::size_type last_dot = oldname.rfind('.');
783 if (last_dot < last_slash && last_slash != string::npos)
784 last_dot = string::npos;
787 // Make sure the extension starts with a dot
788 if (!extension.empty() && extension[0] != '.')
789 ext= '.' + extension;
793 return os::internal_path(oldname.substr(0, last_dot) + ext);
797 string const removeExtension(string const & name)
799 return changeExtension(name, string());
803 string const addExtension(string const & name, string const & extension)
805 if (!extension.empty() && extension[0] != '.')
806 return name + '.' + extension;
807 return name + extension;
811 /// Return the extension of the file (not including the .)
812 string const getExtension(string const & name)
814 string::size_type const last_slash = name.rfind('/');
815 string::size_type const last_dot = name.rfind('.');
816 if (last_dot != string::npos &&
817 (last_slash == string::npos || last_dot > last_slash))
818 return name.substr(last_dot + 1,
819 name.length() - (last_dot + 1));
825 // the different filetypes and what they contain in one of the first lines
826 // (dots are any characters). (Herbert 20020131)
829 // EPS %!PS-Adobe-3.0 EPSF...
836 // PBM P1... or P4 (B/W)
837 // PGM P2... or P5 (Grayscale)
838 // PPM P3... or P6 (color)
839 // PS %!PS-Adobe-2.0 or 1.0, no "EPSF"!
840 // SGI \001\332... (decimal 474)
842 // TIFF II... or MM...
844 // XPM /* XPM */ sometimes missing (f.ex. tgif-export)
845 // ...static char *...
846 // XWD \000\000\000\151 (0x00006900) decimal 105
848 // GZIP \037\213 http://www.ietf.org/rfc/rfc1952.txt
849 // ZIP PK... http://www.halyava.ru/document/ind_arch.htm
850 // Z \037\235 UNIX compress
852 string const getFormatFromContents(FileName const & filename)
855 if (filename.empty() || !isFileReadable(filename))
858 ifstream ifs(filename.toFilesystemEncoding().c_str());
860 // Couldn't open file...
864 static string const gzipStamp = "\037\213";
867 static string const zipStamp = "PK";
870 static string const compressStamp = "\037\235";
872 // Maximum strings to read
873 int const max_count = 50;
878 bool firstLine = true;
879 while ((count++ < max_count) && format.empty()) {
881 LYXERR(Debug::GRAPHICS)
882 << "filetools(getFormatFromContents)\n"
883 << "\tFile type not recognised before EOF!"
889 string const stamp = str.substr(0, 2);
890 if (firstLine && str.size() >= 2) {
891 // at first we check for a zipped file, because this
892 // information is saved in the first bytes of the file!
893 // also some graphic formats which save the information
894 // in the first line, too.
895 if (prefixIs(str, gzipStamp)) {
898 } else if (stamp == zipStamp) {
901 } else if (stamp == compressStamp) {
905 } else if (stamp == "BM") {
908 } else if (stamp == "\001\332") {
912 // Don't need to use str.at(0), str.at(1) because
913 // we already know that str.size() >= 2
914 } else if (str[0] == 'P') {
930 } else if ((stamp == "II") || (stamp == "MM")) {
933 } else if (prefixIs(str,"%TGIF")) {
936 } else if (prefixIs(str,"#FIG")) {
939 } else if (prefixIs(str,"GIF")) {
942 } else if (str.size() > 3) {
943 int const c = ((str[0] << 24) & (str[1] << 16) &
944 (str[2] << 8) & str[3]);
955 else if (contains(str,"EPSF"))
956 // dummy, if we have wrong file description like
957 // %!PS-Adobe-2.0EPSF"
960 else if (contains(str,"Grace"))
963 else if (contains(str,"JFIF"))
966 else if (contains(str,"%PDF"))
969 else if (contains(str,"PNG"))
972 else if (contains(str,"%!PS-Adobe")) {
975 if (contains(str,"EPSF"))
981 else if (contains(str,"_bits[]"))
984 else if (contains(str,"XPM") || contains(str, "static char *"))
987 else if (contains(str,"BITPIX"))
991 if (!format.empty()) {
992 LYXERR(Debug::GRAPHICS)
993 << "Recognised Fileformat: " << format << endl;
997 LYXERR(Debug::GRAPHICS)
998 << "filetools(getFormatFromContents)\n"
999 << "\tCouldn't find a known format!\n";
1004 /// check for zipped file
1005 bool zippedFile(FileName const & name)
1007 string const type = getFormatFromContents(name);
1008 if (contains("gzip zip compress", type) && !type.empty())
1014 string const unzippedFileName(string const & zipped_file)
1016 string const ext = getExtension(zipped_file);
1017 if (ext == "gz" || ext == "z" || ext == "Z")
1018 return changeExtension(zipped_file, string());
1019 return "unzipped_" + zipped_file;
1023 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
1025 FileName const tempfile = FileName(unzipped_file.empty() ?
1026 unzippedFileName(zipped_file.toFilesystemEncoding()) :
1029 string const command = "gunzip -c " +
1030 zipped_file.toFilesystemEncoding() + " > " +
1031 tempfile.toFilesystemEncoding();
1033 one.startscript(Systemcall::Wait, command);
1034 // test that command was executed successfully (anon)
1035 // yes, please do. (Lgb)
1040 docstring const makeDisplayPath(string const & path, unsigned int threshold)
1044 // If file is from LyXDir, display it as if it were relative.
1045 string const system = package().system_support().absFilename();
1046 if (prefixIs(str, system) && str != system)
1047 return from_utf8("[" + str.erase(0, system.length()) + "]");
1049 // replace /home/blah with ~/
1050 string const home = package().home_dir().absFilename();
1051 if (!home.empty() && prefixIs(str, home))
1052 str = subst(str, home, "~");
1054 if (str.length() <= threshold)
1055 return from_utf8(os::external_path(str));
1057 string const prefix = ".../";
1060 while (str.length() > threshold)
1061 str = split(str, temp, '/');
1063 // Did we shorten everything away?
1065 // Yes, filename itself is too long.
1066 // Pick the start and the end of the filename.
1067 str = onlyFilename(path);
1068 string const head = str.substr(0, threshold / 2 - 3);
1070 string::size_type len = str.length();
1072 str.substr(len - threshold / 2 - 2, len - 1);
1073 str = head + "..." + tail;
1076 return from_utf8(os::external_path(prefix + str));
1080 bool readLink(FileName const & file, FileName & link)
1082 #ifdef HAVE_READLINK
1083 char linkbuffer[512];
1084 // Should be PATH_MAX but that needs autconf support
1085 string const encoded = file.toFilesystemEncoding();
1086 int const nRead = ::readlink(encoded.c_str(),
1087 linkbuffer, sizeof(linkbuffer) - 1);
1090 linkbuffer[nRead] = '\0'; // terminator
1091 link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
1099 cmd_ret const runCommand(string const & cmd)
1101 // FIXME: replace all calls to RunCommand with ForkedCall
1102 // (if the output is not needed) or the code in ISpell.cpp
1103 // (if the output is needed).
1105 // One question is if we should use popen or
1106 // create our own popen based on fork, exec, pipe
1107 // of course the best would be to have a
1108 // pstream (process stream), with the
1109 // variants ipstream, opstream
1111 #if defined (HAVE_POPEN)
1112 FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1113 #elif defined (HAVE__POPEN)
1114 FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1116 #error No popen() function.
1119 // (Claus Hentschel) Check if popen was succesful ;-)
1121 lyxerr << "RunCommand:: could not start child process" << endl;
1122 return make_pair(-1, string());
1128 ret += static_cast<char>(c);
1132 #if defined (HAVE_PCLOSE)
1133 int const pret = pclose(inf);
1134 #elif defined (HAVE__PCLOSE)
1135 int const pret = _pclose(inf);
1137 #error No pclose() function.
1141 perror("RunCommand:: could not terminate child process");
1143 return make_pair(pret, ret);
1147 FileName const findtexfile(string const & fil, string const & /*format*/)
1149 /* There is no problem to extend this function too use other
1150 methods to look for files. It could be setup to look
1151 in environment paths and also if wanted as a last resort
1152 to a recursive find. One of the easier extensions would
1153 perhaps be to use the LyX file lookup methods. But! I am
1154 going to implement this until I see some demand for it.
1158 // If the file can be found directly, we just return a
1159 // absolute path version of it.
1160 FileName const absfile(makeAbsPath(fil));
1161 if (fs::exists(absfile.toFilesystemEncoding()))
1164 // No we try to find it using kpsewhich.
1165 // It seems from the kpsewhich manual page that it is safe to use
1166 // kpsewhich without --format: "When the --format option is not
1167 // given, the search path used when looking for a file is inferred
1168 // from the name given, by looking for a known extension. If no
1169 // known extension is found, the search path for TeX source files
1171 // However, we want to take advantage of the format sine almost all
1172 // the different formats has environment variables that can be used
1173 // to controll which paths to search. f.ex. bib looks in
1174 // BIBINPUTS and TEXBIB. Small list follows:
1175 // bib - BIBINPUTS, TEXBIB
1177 // graphic/figure - TEXPICTS, TEXINPUTS
1178 // ist - TEXINDEXSTYLE, INDEXSTYLE
1179 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1181 // tfm - TFMFONTS, TEXFONTS
1182 // This means that to use kpsewhich in the best possible way we
1183 // should help it by setting additional path in the approp. envir.var.
1184 string const kpsecmd = "kpsewhich " + fil;
1186 cmd_ret const c = runCommand(kpsecmd);
1188 LYXERR(Debug::LATEX) << "kpse status = " << c.first << '\n'
1189 << "kpse result = `" << rtrim(c.second, "\n\r")
1192 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
1199 void removeAutosaveFile(string const & filename)
1201 string a = onlyPath(filename);
1203 a += onlyFilename(filename);
1205 FileName const autosave(a);
1206 if (fs::exists(autosave.toFilesystemEncoding()))
1211 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1212 string const & message)
1214 LYXERR(Debug::GRAPHICS) << "[readBB_from_PSFile] "
1215 << message << std::endl;
1216 // FIXME: Why is this func deleting a file? (Lgb)
1222 string const readBB_from_PSFile(FileName const & file)
1224 // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1225 // It seems that every command in the header has an own line,
1226 // getline() should work for all files.
1227 // On the other hand some plot programs write the bb at the
1228 // end of the file. Than we have in the header:
1229 // %%BoundingBox: (atend)
1230 // In this case we must check the end.
1231 bool zipped = zippedFile(file);
1232 FileName const file_ = zipped ? unzipFile(file) : file;
1233 string const format = getFormatFromContents(file_);
1235 if (format != "eps" && format != "ps") {
1236 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1240 static boost::regex bbox_re(
1241 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1242 std::ifstream is(file_.toFilesystemEncoding().c_str());
1247 if (regex_match(s, what, bbox_re)) {
1248 // Our callers expect the tokens in the string
1249 // separated by single spaces.
1250 // FIXME: change return type from string to something
1253 os << what.str(1) << ' ' << what.str(2) << ' '
1254 << what.str(3) << ' ' << what.str(4);
1255 string const bb = os.str();
1256 readBB_lyxerrMessage(file_, zipped, bb);
1260 readBB_lyxerrMessage(file_, zipped, "no bb found");
1265 int compare_timestamps(FileName const & filename1, FileName const & filename2)
1267 // If the original is newer than the copy, then copy the original
1268 // to the new directory.
1270 string const file1 = filename1.toFilesystemEncoding();
1271 string const file2 = filename2.toFilesystemEncoding();
1273 if (fs::exists(file1) && fs::exists(file2)) {
1274 double const tmp = difftime(fs::last_write_time(file1),
1275 fs::last_write_time(file2));
1277 cmp = tmp > 0 ? 1 : -1;
1279 } else if (fs::exists(file1)) {
1281 } else if (fs::exists(file2)) {
1288 // the following is adapted from zlib-1.2.3/contrib/minizip.c
1289 // and miniunz.c, except that
1290 // 1. mkdir, makedir is replaced by lyx' own version
1291 // 2. printf is replaced by lyxerr
1294 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1300 WIN32_FIND_DATA ff32;
1302 hFind = FindFirstFile(f,&ff32);
1303 if (hFind != INVALID_HANDLE_VALUE)
1305 FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal);
1306 FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0);
1316 uLong filetime(const char * f, tm_zip * tmzip, uLong * /*dt*/)
1319 struct stat s; /* results of stat() */
1320 struct tm* filedate;
1323 if (strcmp(f,"-")!=0) {
1324 char name[MAXFILENAME+1];
1325 int len = strlen(f);
1326 if (len > MAXFILENAME)
1329 strncpy(name, f,MAXFILENAME-1);
1330 /* strncpy doesnt append the trailing NULL, of the string is too long. */
1331 name[ MAXFILENAME ] = '\0';
1333 if (name[len - 1] == '/')
1334 name[len - 1] = '\0';
1335 /* not all systems allow stat'ing a file with / appended */
1336 if (stat(name,&s)==0) {
1341 filedate = localtime(&tm_t);
1343 tmzip->tm_sec = filedate->tm_sec;
1344 tmzip->tm_min = filedate->tm_min;
1345 tmzip->tm_hour = filedate->tm_hour;
1346 tmzip->tm_mday = filedate->tm_mday;
1347 tmzip->tm_mon = filedate->tm_mon ;
1348 tmzip->tm_year = filedate->tm_year;
1355 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1362 bool zipFiles(DocFileName const & zipfile, vector<pair<string, string> > const & files)
1369 int size_buf = WRITEBUFFERSIZE;
1370 buf = (void*)malloc(size_buf);
1372 lyxerr << "Error allocating memory" << endl;
1375 string const zfile = zipfile.toFilesystemEncoding();
1376 const char * fname = zfile.c_str();
1378 #ifdef USEWIN32IOAPI
1379 zlib_filefunc_def ffunc;
1380 fill_win32_filefunc(&ffunc);
1381 // false: not append
1382 zf = zipOpen2(fname, false, NULL, &ffunc);
1384 zf = zipOpen(fname, false);
1388 lyxerr << "error opening " << zipfile << endl;
1392 for (vector<pair<string, string> >::const_iterator it = files.begin(); it != files.end(); ++it) {
1396 const char * diskfilename = it->first.c_str();
1397 const char * filenameinzip = it->second.c_str();
1398 unsigned long crcFile=0;
1400 zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour =
1401 zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0;
1405 filetime(filenameinzip, &zi.tmz_date, &zi.dosDate);
1407 err = zipOpenNewFileInZip3(zf, filenameinzip, &zi,
1408 NULL,0,NULL,0,NULL /* comment*/,
1410 Z_DEFAULT_COMPRESSION, // compression level
1412 /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */
1413 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
1417 if (err != ZIP_OK) {
1418 lyxerr << "error in opening " << filenameinzip << " in zipfile" << endl;
1421 fin = fopen(diskfilename, "rb");
1423 lyxerr << "error in opening " << diskfilename << " for reading" << endl;
1429 size_read = (int)fread(buf, 1, size_buf, fin);
1430 if (size_read < size_buf)
1432 lyxerr << "error in reading " << filenameinzip << endl;
1437 err = zipWriteInFileInZip (zf, buf, size_read);
1439 lyxerr << "error in writing " << filenameinzip << " in the zipfile" << endl;
1443 } while ((err == ZIP_OK) && (size_read>0));
1448 err = zipCloseFileInZip(zf);
1449 if (err != ZIP_OK) {
1450 lyxerr << "error in closing " << filenameinzip << "in the zipfile" << endl;
1454 errclose = zipClose(zf, NULL);
1455 if (errclose != ZIP_OK) {
1456 lyxerr << "error in closing " << zipfile << endl;
1463 // adapted from miniunz.c
1465 /* change_file_date : change the date/time of a file
1466 filename : the filename of the file where date/time must be modified
1467 dosdate : the new date at the MSDos format (4 bytes)
1468 tmu_date : the SAME new date at the tm_unz format */
1469 void change_file_date(const char * filename, uLong dosdate, tm_unz tmu_date)
1473 FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite;
1475 hFile = CreateFile(filename,GENERIC_READ | GENERIC_WRITE,
1476 0,NULL,OPEN_EXISTING,0,NULL);
1477 GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite);
1478 DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal);
1479 LocalFileTimeToFileTime(&ftLocal,&ftm);
1480 SetFileTime(hFile,&ftm,&ftLastAcc,&ftm);
1487 newdate.tm_sec = tmu_date.tm_sec;
1488 newdate.tm_min=tmu_date.tm_min;
1489 newdate.tm_hour=tmu_date.tm_hour;
1490 newdate.tm_mday=tmu_date.tm_mday;
1491 newdate.tm_mon=tmu_date.tm_mon;
1492 if (tmu_date.tm_year > 1900)
1493 newdate.tm_year=tmu_date.tm_year - 1900;
1495 newdate.tm_year=tmu_date.tm_year ;
1496 newdate.tm_isdst=-1;
1498 ut.actime=ut.modtime=mktime(&newdate);
1499 utime(filename,&ut);
1505 int do_extract_currentfile(unzFile uf,
1506 const int * popt_extract_without_path,
1507 int * popt_overwrite,
1508 const char * password,
1509 const char * dirname)
1511 char filename_inzip[256];
1512 char* filename_withoutpath;
1519 unz_file_info file_info;
1521 err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
1524 lyxerr << "error " << err << " with zipfile in unzGetCurrentFileInfo" << endl;
1528 size_buf = WRITEBUFFERSIZE;
1529 buf = (void*)malloc(size_buf);
1531 lyxerr << "Error allocating memory" << endl;
1532 return UNZ_INTERNALERROR;
1535 p = filename_withoutpath = filename_inzip;
1536 while ((*p) != '\0') {
1537 if (((*p)=='/') || ((*p)=='\\'))
1538 filename_withoutpath = p+1;
1541 // this is a directory
1542 if ((*filename_withoutpath)=='\0') {
1543 if ((*popt_extract_without_path)==0)
1544 makedir(filename_inzip);
1546 // this is a filename
1548 char write_filename[1024];
1550 strcpy(write_filename, dirname);
1551 int len = strlen(write_filename);
1552 if (write_filename[len-1] != '\\' &&
1553 write_filename[len-1] != '/')
1554 strcat(write_filename, "/");
1556 if ((*popt_extract_without_path)==0)
1557 strcat(write_filename, filename_inzip);
1559 strcat(write_filename, filename_withoutpath);
1561 err = unzOpenCurrentFilePassword(uf,password);
1563 lyxerr << "error " << err << " with zipfile in unzOpenCurrentFilePassword" << endl;
1565 fout=fopen(write_filename, "wb");
1567 /* some zipfile don't contain directory alone before file */
1568 if ((fout==NULL) && ((*popt_extract_without_path)==0) &&
1569 (filename_withoutpath!=(char*)filename_inzip)) {
1570 char c=*(filename_withoutpath-1);
1571 *(filename_withoutpath-1)='\0';
1572 makedir(write_filename);
1573 *(filename_withoutpath-1)=c;
1574 fout=fopen(write_filename,"wb");
1578 lyxerr << "error opening " << write_filename << endl;
1583 LYXERR(Debug::FILES) << " extracting: " << write_filename << endl;
1586 err = unzReadCurrentFile(uf,buf,size_buf);
1588 lyxerr << "error " << err << " with zipfile in unzReadCurrentFile" << endl;
1592 if (fwrite(buf,err,1,fout)!=1) {
1593 lyxerr << "error in writing extracted file" << endl;
1602 change_file_date(write_filename,file_info.dosDate,
1603 file_info.tmu_date);
1607 err = unzCloseCurrentFile (uf);
1609 lyxerr << "error " << err << " with zipfile in unzCloseCurrentFile" << endl;
1613 unzCloseCurrentFile(uf); /* don't lose the error */
1621 bool unzipToDir(string const & zipfile, string const & dirname)
1624 #ifdef USEWIN32IOAPI
1625 zlib_filefunc_def ffunc;
1628 const char * zipfilename = zipfile.c_str();
1630 #ifdef USEWIN32IOAPI
1631 fill_win32_filefunc(&ffunc);
1632 uf = unzOpen2(zipfilename, &ffunc);
1634 uf = unzOpen(zipfilename);
1638 lyxerr << "Cannot open " << zipfile << " or " << zipfile << ".zip" << endl;
1646 int opt_extract_without_path = 0;
1647 int opt_overwrite = 1;
1648 char * password = NULL;
1650 err = unzGetGlobalInfo (uf, &gi);
1651 if (err != UNZ_OK) {
1652 lyxerr << "error " << err << " with zipfile in unzGetGlobalInfo " << endl;
1656 for (i=0; i < gi.number_entry; i++) {
1657 if (do_extract_currentfile(uf, &opt_extract_without_path,
1659 password, dirname.c_str()) != UNZ_OK)
1662 if ((i+1)<gi.number_entry) {
1663 err = unzGoToNextFile(uf);
1664 if (err != UNZ_OK) {
1665 lyxerr << "error " << err << " with zipfile in unzGoToNextFile" << endl;;
1671 unzCloseCurrentFile(uf);
1675 } //namespace support