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>
53 // FIXME Availability?
54 #include <utime.h> // for utimbuf
62 #ifdef HAVE_SYS_TYPES_H
63 # include <sys/types.h>
65 #ifdef HAVE_SYS_STAT_H
66 # include <sys/stat.h>
71 #ifdef HAVE_SYS_UTIME_H
72 # include <sys/utime.h>
86 #define WRITEBUFFERSIZE (16384)
87 #define MAXFILENAME (256)
90 #ifndef CXX_GLOBAL_CSTD
99 using std::ostringstream;
103 namespace fs = boost::filesystem;
108 bool isLyXFilename(string const & filename)
110 return suffixIs(ascii_lowercase(filename), ".lyx");
114 bool isSGMLFilename(string const & filename)
116 return suffixIs(ascii_lowercase(filename), ".sgml");
120 bool isValidLaTeXFilename(string const & filename)
122 string const invalid_chars("#$%{}()[]\"^");
123 if (filename.find_first_of(invalid_chars) != string::npos)
130 string const latex_path(string const & original_path,
131 latex_path_extension extension,
132 latex_path_dots dots)
134 // On cygwin, we may need windows or posix style paths.
135 string path = os::latex_path(original_path);
136 path = subst(path, "~", "\\string~");
137 if (path.find(' ') != string::npos) {
138 // We can't use '"' because " is sometimes active (e.g. if
139 // babel is loaded with the "german" option)
140 if (extension == EXCLUDE_EXTENSION) {
141 // ChangeExtension calls os::internal_path internally
142 // so don't use it to remove the extension.
143 string const ext = getExtension(path);
144 string const base = ext.empty() ?
146 path.substr(0, path.length() - ext.length() - 1);
147 // ChangeExtension calls os::internal_path internally
148 // so don't use it to re-add the extension.
149 path = "\\string\"" + base + "\\string\"." + ext;
151 path = "\\string\"" + path + "\\string\"";
155 return dots == ESCAPE_DOTS ? subst(path, ".", "\\lyxdot ") : path;
159 // Substitutes spaces with underscores in filename (and path)
160 string const makeLatexName(string const & file)
162 string name = onlyFilename(file);
163 string const path = onlyPath(file);
165 // ok so we scan through the string twice, but who cares.
166 string const keep = "abcdefghijklmnopqrstuvwxyz"
167 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
168 "@!'()*+,-./0123456789:;<=>?[]`|";
170 string::size_type pos = 0;
171 while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
174 return addName(path, name);
178 string const quoteName(string const & name, quote_style style)
182 // This does not work for filenames containing " (windows)
183 // or ' (all other OSes). This can't be changed easily, since
184 // we would need to adapt the command line parser in
185 // Forkedcall::generateChild. Therefore we don't pass user
186 // filenames to child processes if possible. We store them in
187 // a python script instead, where we don't have these
189 return (os::shell() == os::UNIX) ?
193 return "\"" + subst(subst(name, "\\", "\\\\"), "\"", "\\\"")
196 // shut up stupid compiler
201 bool isFileReadable(FileName const & filename)
203 std::string const path = filename.toFilesystemEncoding();
204 return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
208 //returns true: dir writeable
209 // false: not writeable
210 bool isDirWriteable(FileName const & path)
212 LYXERR(Debug::FILES) << "isDirWriteable: " << path << endl;
214 FileName const tmpfl(tempName(path, "lyxwritetest"));
225 // Uses a string of paths separated by ";"s to find a file to open.
226 // Can't cope with pathnames with a ';' in them. Returns full path to file.
227 // If path entry begins with $$LyX/, use system_lyxdir
228 // If path entry begins with $$User/, use user_lyxdir
229 // Example: "$$User/doc;$$LyX/doc"
230 FileName const fileOpenSearch(string const & path, string const & name,
235 bool notfound = true;
236 string tmppath = split(path, path_element, ';');
238 while (notfound && !path_element.empty()) {
239 path_element = os::internal_path(path_element);
240 if (!suffixIs(path_element, '/'))
242 path_element = subst(path_element, "$$LyX",
243 package().system_support().absFilename());
244 path_element = subst(path_element, "$$User",
245 package().user_support().absFilename());
247 real_file = fileSearch(path_element, name, ext);
249 if (real_file.empty()) {
251 tmppath = split(tmppath, path_element, ';');
252 } while (!tmppath.empty() && path_element.empty());
262 /// Returns a vector of all files in directory dir having extension ext.
263 vector<FileName> const dirList(FileName const & dir, string const & ext)
265 // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
266 vector<FileName> dirlist;
268 string const encoded_dir = dir.toFilesystemEncoding();
269 if (!(fs::exists(encoded_dir) && fs::is_directory(encoded_dir))) {
271 << "Directory \"" << dir
272 << "\" does not exist to DirList." << endl;
277 if (!ext.empty() && ext[0] != '.')
281 fs::directory_iterator dit(encoded_dir);
282 fs::directory_iterator end;
283 for (; dit != end; ++dit) {
284 string const & fil = dit->leaf();
285 if (suffixIs(fil, extension))
286 dirlist.push_back(FileName::fromFilesystemEncoding(
287 encoded_dir + '/' + fil));
293 // Returns the real name of file name in directory path, with optional
295 FileName const fileSearch(string const & path, string const & name,
296 string const & ext, search_mode mode)
298 // if `name' is an absolute path, we ignore the setting of `path'
299 // Expand Environmentvariables in 'name'
300 string const tmpname = replaceEnvironmentPath(name);
301 FileName fullname(makeAbsPath(tmpname, path));
302 // search first without extension, then with it.
303 if (isFileReadable(fullname))
307 return mode == allow_unreadable ? fullname : FileName();
308 // Only add the extension if it is not already the extension of
310 if (getExtension(fullname.absFilename()) != ext)
311 fullname = FileName(addExtension(fullname.absFilename(), ext));
312 if (isFileReadable(fullname) || mode == allow_unreadable)
318 // Search the file name.ext in the subdirectory dir of
320 // 2) build_lyxdir (if not empty)
322 FileName const libFileSearch(string const & dir, string const & name,
325 FileName fullname = fileSearch(addPath(package().user_support().absFilename(), dir),
327 if (!fullname.empty())
330 if (!package().build_support().empty())
331 fullname = fileSearch(addPath(package().build_support().absFilename(), dir),
333 if (!fullname.empty())
336 return fileSearch(addPath(package().system_support().absFilename(), dir), name, ext);
340 FileName const i18nLibFileSearch(string const & dir, string const & name,
343 /* The highest priority value is the `LANGUAGE' environment
344 variable. But we don't use the value if the currently
345 selected locale is the C locale. This is a GNU extension.
347 Otherwise, w use a trick to guess what gettext has done:
348 each po file is able to tell us its name. (JMarc)
351 string lang = to_ascii(_("[[Replace with the code of your language]]"));
352 string const language = getEnv("LANGUAGE");
353 if (!lang.empty() && !language.empty())
357 lang = split(lang, l, ':');
360 // First try with the full name
361 tmp = libFileSearch(addPath(dir, l), name, ext);
365 // Then the name without country code
366 string const shortl = token(l, '_', 0);
368 tmp = libFileSearch(addPath(dir, shortl), name, ext);
374 // For compatibility, to be removed later (JMarc)
375 tmp = libFileSearch(dir, token(l, '_', 0) + '_' + name,
378 lyxerr << "i18nLibFileSearch: File `" << tmp
379 << "' has been found by the old method" <<endl;
383 lang = split(lang, l, ':');
386 return libFileSearch(dir, name, ext);
390 string const libScriptSearch(string const & command_in, quote_style style)
392 static string const token_scriptpath = "$$s/";
394 string command = command_in;
395 // Find the starting position of "$$s/"
396 string::size_type const pos1 = command.find(token_scriptpath);
397 if (pos1 == string::npos)
399 // Find the end of the "$$s/some_subdir/some_script" word within
400 // command. Assumes that the script name does not contain spaces.
401 string::size_type const start_script = pos1 + 4;
402 string::size_type const pos2 = command.find(' ', start_script);
403 string::size_type const size_script = pos2 == string::npos?
404 (command.size() - start_script) : pos2 - start_script;
406 // Does this script file exist?
407 string const script =
408 libFileSearch(".", command.substr(start_script, size_script)).absFilename();
410 if (script.empty()) {
411 // Replace "$$s/" with ""
412 command.erase(pos1, 4);
414 // Replace "$$s/foo/some_script" with "<path to>/some_script".
415 string::size_type const size_replace = size_script + 4;
416 command.replace(pos1, size_replace, quoteName(script, style));
425 FileName const createTmpDir(FileName const & tempdir, string const & mask)
428 << "createTmpDir: tempdir=`" << tempdir << "'\n"
429 << "createTmpDir: mask=`" << mask << '\'' << endl;
431 FileName const tmpfl(tempName(tempdir, mask));
432 // lyx::tempName actually creates a file to make sure that it
433 // stays unique. So we have to delete it before we can create
434 // a dir with the same name. Note also that we are not thread
435 // safe because of the gap between unlink and mkdir. (Lgb)
438 if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
439 lyxerr << "LyX could not create the temporary directory '"
440 << tmpfl << "'" << endl;
450 bool destroyDir(FileName const & tmpdir)
453 return fs::remove_all(tmpdir.toFilesystemEncoding()) > 0;
454 } catch (fs::filesystem_error const & fe){
455 lyxerr << "Could not delete " << tmpdir << ". (" << fe.what() << ")" << std::endl;
461 string const createBufferTmpDir()
464 // We are in our own directory. Why bother to mangle name?
465 // In fact I wrote this code to circumvent a problematic behaviour
466 // (bug?) of EMX mkstemp().
468 package().temp_dir().absFilename() + "/lyx_tmpbuf" +
469 convert<string>(count++);
471 if (mkdir(FileName(tmpfl), 0777)) {
472 lyxerr << "LyX could not create the temporary directory '"
473 << tmpfl << "'" << endl;
480 FileName const createLyXTmpDir(FileName const & deflt)
482 if (!deflt.empty() && deflt.absFilename() != "/tmp") {
483 if (mkdir(deflt, 0777)) {
484 if (isDirWriteable(deflt)) {
485 // deflt could not be created because it
486 // did exist already, so let's create our own
488 return createTmpDir(deflt, "lyx_tmpdir");
490 // some other error occured.
491 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
496 return createTmpDir(FileName("/tmp"), "lyx_tmpdir");
501 bool createDirectory(FileName const & path, int permission)
503 BOOST_ASSERT(!path.empty());
504 return mkdir(path, permission) == 0;
508 // Strip filename from path name
509 string const onlyPath(string const & filename)
511 // If empty filename, return empty
512 if (filename.empty())
515 // Find last / or start of filename
516 string::size_type j = filename.rfind('/');
517 return j == string::npos ? "./" : filename.substr(0, j + 1);
521 // Convert relative path into absolute path based on a basepath.
522 // If relpath is absolute, just use that.
523 // If basepath is empty, use CWD as base.
524 FileName const makeAbsPath(string const & relPath, string const & basePath)
526 // checks for already absolute path
527 if (os::is_absolute_path(relPath))
528 return FileName(relPath);
530 // Copies given paths
531 string tempRel = os::internal_path(relPath);
532 // Since TempRel is NOT absolute, we can safely replace "//" with "/"
533 tempRel = subst(tempRel, "//", "/");
537 if (os::is_absolute_path(basePath))
540 tempBase = addPath(getcwd().absFilename(), basePath);
542 // Handle /./ at the end of the path
543 while (suffixIs(tempBase, "/./"))
544 tempBase.erase(tempBase.length() - 2);
546 // processes relative path
547 string rTemp = tempRel;
550 while (!rTemp.empty()) {
552 rTemp = split(rTemp, temp, '/');
554 if (temp == ".") continue;
556 // Remove one level of TempBase
557 string::difference_type i = tempBase.length() - 2;
560 while (i > 0 && tempBase[i] != '/')
563 tempBase.erase(i, string::npos);
566 } else if (temp.empty() && !rTemp.empty()) {
567 tempBase = os::current_root() + rTemp;
570 // Add this piece to TempBase
571 if (!suffixIs(tempBase, '/'))
577 // returns absolute path
578 return FileName(os::internal_path(tempBase));
582 // Correctly append filename to the pathname.
583 // If pathname is '.', then don't use pathname.
584 // Chops any path of filename.
585 string const addName(string const & path, string const & fname)
587 string const basename = onlyFilename(fname);
590 if (path != "." && path != "./" && !path.empty()) {
591 buf = os::internal_path(path);
592 if (!suffixIs(path, '/'))
596 return buf + basename;
600 // Strips path from filename
601 string const onlyFilename(string const & fname)
606 string::size_type j = fname.rfind('/');
607 if (j == string::npos) // no '/' in fname
611 return fname.substr(j + 1);
615 /// Returns true is path is absolute
616 bool absolutePath(string const & path)
618 return os::is_absolute_path(path);
622 // Create absolute path. If impossible, don't do anything
623 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
624 string const expandPath(string const & path)
626 // checks for already absolute path
627 string rTemp = replaceEnvironmentPath(path);
628 if (os::is_absolute_path(rTemp))
632 string const copy = rTemp;
635 rTemp = split(rTemp, temp, '/');
638 return getcwd().absFilename() + '/' + rTemp;
641 return package().home_dir().absFilename() + '/' + rTemp;
644 return makeAbsPath(copy).absFilename();
646 // Don't know how to handle this
651 // Normalize a path. Constracts path/../path
652 // Can't handle "../../" or "/../" (Asger)
653 // Also converts paths like /foo//bar ==> /foo/bar
654 string const normalizePath(string const & path)
656 // Normalize paths like /foo//bar ==> /foo/bar
657 static boost::regex regex("/{2,}");
658 string const tmppath = boost::regex_merge(path, regex, "/");
660 fs::path const npath = fs::path(tmppath, fs::no_check).normalize();
662 if (!npath.is_complete())
663 return "./" + npath.string() + '/';
665 return npath.string() + '/';
669 string const getFileContents(FileName const & fname)
671 string const encodedname = fname.toFilesystemEncoding();
672 if (fs::exists(encodedname)) {
673 ifstream ifs(encodedname.c_str());
681 lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
686 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
687 string const replaceEnvironmentPath(string const & path)
689 // ${VAR} is defined as
690 // $\{[A-Za-z_][A-Za-z_0-9]*\}
691 static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
693 // $VAR is defined as:
694 // $\{[A-Za-z_][A-Za-z_0-9]*\}
695 static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
697 static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
698 static boost::regex envvar_re("(.*)" + envvar + "(.*)");
701 string result = path;
703 regex_match(result, what, envvar_br_re);
704 if (!what[0].matched) {
705 regex_match(result, what, envvar_re);
706 if (!what[0].matched)
709 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
715 // Make relative path out of two absolute paths
716 docstring const makeRelPath(docstring const & abspath, docstring const & basepath)
717 // Makes relative path out of absolute path. If it is deeper than basepath,
718 // it's easy. If basepath and abspath share something (they are all deeper
719 // than some directory), it'll be rendered using ..'s. If they are completely
720 // different, then the absolute path will be used as relative path.
722 docstring::size_type const abslen = abspath.length();
723 docstring::size_type const baselen = basepath.length();
725 docstring::size_type i = os::common_path(abspath, basepath);
728 // actually no match - cannot make it relative
732 // Count how many dirs there are in basepath above match
733 // and append as many '..''s into relpath
735 docstring::size_type j = i;
736 while (j < baselen) {
737 if (basepath[j] == '/') {
738 if (j + 1 == baselen)
745 // Append relative stuff from common directory to abspath
746 if (abspath[i] == '/')
748 for (; i < abslen; ++i)
751 if (suffixIs(buf, '/'))
752 buf.erase(buf.length() - 1);
753 // Substitute empty with .
760 // Append sub-directory(ies) to a path in an intelligent way
761 string const addPath(string const & path, string const & path_2)
764 string const path2 = os::internal_path(path_2);
766 if (!path.empty() && path != "." && path != "./") {
767 buf = os::internal_path(path);
768 if (path[path.length() - 1] != '/')
772 if (!path2.empty()) {
773 string::size_type const p2start = path2.find_first_not_of('/');
774 string::size_type const p2end = path2.find_last_not_of('/');
775 string const tmp = path2.substr(p2start, p2end - p2start + 1);
782 string const changeExtension(string const & oldname, string const & extension)
784 string::size_type const last_slash = oldname.rfind('/');
785 string::size_type last_dot = oldname.rfind('.');
786 if (last_dot < last_slash && last_slash != string::npos)
787 last_dot = string::npos;
790 // Make sure the extension starts with a dot
791 if (!extension.empty() && extension[0] != '.')
792 ext= '.' + extension;
796 return os::internal_path(oldname.substr(0, last_dot) + ext);
800 string const removeExtension(string const & name)
802 return changeExtension(name, string());
806 string const addExtension(string const & name, string const & extension)
808 if (!extension.empty() && extension[0] != '.')
809 return name + '.' + extension;
810 return name + extension;
814 /// Return the extension of the file (not including the .)
815 string const getExtension(string const & name)
817 string::size_type const last_slash = name.rfind('/');
818 string::size_type const last_dot = name.rfind('.');
819 if (last_dot != string::npos &&
820 (last_slash == string::npos || last_dot > last_slash))
821 return name.substr(last_dot + 1,
822 name.length() - (last_dot + 1));
828 // the different filetypes and what they contain in one of the first lines
829 // (dots are any characters). (Herbert 20020131)
832 // EPS %!PS-Adobe-3.0 EPSF...
839 // PBM P1... or P4 (B/W)
840 // PGM P2... or P5 (Grayscale)
841 // PPM P3... or P6 (color)
842 // PS %!PS-Adobe-2.0 or 1.0, no "EPSF"!
843 // SGI \001\332... (decimal 474)
845 // TIFF II... or MM...
847 // XPM /* XPM */ sometimes missing (f.ex. tgif-export)
848 // ...static char *...
849 // XWD \000\000\000\151 (0x00006900) decimal 105
851 // GZIP \037\213 http://www.ietf.org/rfc/rfc1952.txt
852 // ZIP PK... http://www.halyava.ru/document/ind_arch.htm
853 // Z \037\235 UNIX compress
855 string const getFormatFromContents(FileName const & filename)
858 if (filename.empty() || !isFileReadable(filename))
861 ifstream ifs(filename.toFilesystemEncoding().c_str());
863 // Couldn't open file...
867 static string const gzipStamp = "\037\213";
870 static string const zipStamp = "PK";
873 static string const compressStamp = "\037\235";
875 // Maximum strings to read
876 int const max_count = 50;
881 bool firstLine = true;
882 while ((count++ < max_count) && format.empty()) {
884 LYXERR(Debug::GRAPHICS)
885 << "filetools(getFormatFromContents)\n"
886 << "\tFile type not recognised before EOF!"
892 string const stamp = str.substr(0, 2);
893 if (firstLine && str.size() >= 2) {
894 // at first we check for a zipped file, because this
895 // information is saved in the first bytes of the file!
896 // also some graphic formats which save the information
897 // in the first line, too.
898 if (prefixIs(str, gzipStamp)) {
901 } else if (stamp == zipStamp) {
904 } else if (stamp == compressStamp) {
908 } else if (stamp == "BM") {
911 } else if (stamp == "\001\332") {
915 // Don't need to use str.at(0), str.at(1) because
916 // we already know that str.size() >= 2
917 } else if (str[0] == 'P') {
933 } else if ((stamp == "II") || (stamp == "MM")) {
936 } else if (prefixIs(str,"%TGIF")) {
939 } else if (prefixIs(str,"#FIG")) {
942 } else if (prefixIs(str,"GIF")) {
945 } else if (str.size() > 3) {
946 int const c = ((str[0] << 24) & (str[1] << 16) &
947 (str[2] << 8) & str[3]);
958 else if (contains(str,"EPSF"))
959 // dummy, if we have wrong file description like
960 // %!PS-Adobe-2.0EPSF"
963 else if (contains(str,"Grace"))
966 else if (contains(str,"JFIF"))
969 else if (contains(str,"%PDF"))
972 else if (contains(str,"PNG"))
975 else if (contains(str,"%!PS-Adobe")) {
978 if (contains(str,"EPSF"))
984 else if (contains(str,"_bits[]"))
987 else if (contains(str,"XPM") || contains(str, "static char *"))
990 else if (contains(str,"BITPIX"))
994 if (!format.empty()) {
995 LYXERR(Debug::GRAPHICS)
996 << "Recognised Fileformat: " << format << endl;
1000 LYXERR(Debug::GRAPHICS)
1001 << "filetools(getFormatFromContents)\n"
1002 << "\tCouldn't find a known format!\n";
1007 /// check for zipped file
1008 bool zippedFile(FileName const & name)
1010 string const type = getFormatFromContents(name);
1011 if (contains("gzip zip compress", type) && !type.empty())
1017 string const unzippedFileName(string const & zipped_file)
1019 string const ext = getExtension(zipped_file);
1020 if (ext == "gz" || ext == "z" || ext == "Z")
1021 return changeExtension(zipped_file, string());
1022 return "unzipped_" + zipped_file;
1026 FileName const unzipFile(FileName const & zipped_file, string const & unzipped_file)
1028 FileName const tempfile = FileName(unzipped_file.empty() ?
1029 unzippedFileName(zipped_file.toFilesystemEncoding()) :
1032 string const command = "gunzip -c " +
1033 zipped_file.toFilesystemEncoding() + " > " +
1034 tempfile.toFilesystemEncoding();
1036 one.startscript(Systemcall::Wait, command);
1037 // test that command was executed successfully (anon)
1038 // yes, please do. (Lgb)
1043 docstring const makeDisplayPath(string const & path, unsigned int threshold)
1047 // If file is from LyXDir, display it as if it were relative.
1048 string const system = package().system_support().absFilename();
1049 if (prefixIs(str, system) && str != system)
1050 return from_utf8("[" + str.erase(0, system.length()) + "]");
1052 // replace /home/blah with ~/
1053 string const home = package().home_dir().absFilename();
1054 if (!home.empty() && prefixIs(str, home))
1055 str = subst(str, home, "~");
1057 if (str.length() <= threshold)
1058 return from_utf8(os::external_path(str));
1060 string const prefix = ".../";
1063 while (str.length() > threshold)
1064 str = split(str, temp, '/');
1066 // Did we shorten everything away?
1068 // Yes, filename itself is too long.
1069 // Pick the start and the end of the filename.
1070 str = onlyFilename(path);
1071 string const head = str.substr(0, threshold / 2 - 3);
1073 string::size_type len = str.length();
1075 str.substr(len - threshold / 2 - 2, len - 1);
1076 str = head + "..." + tail;
1079 return from_utf8(os::external_path(prefix + str));
1083 bool readLink(FileName const & file, FileName & link)
1085 #ifdef HAVE_READLINK
1086 char linkbuffer[512];
1087 // Should be PATH_MAX but that needs autconf support
1088 string const encoded = file.toFilesystemEncoding();
1089 int const nRead = ::readlink(encoded.c_str(),
1090 linkbuffer, sizeof(linkbuffer) - 1);
1093 linkbuffer[nRead] = '\0'; // terminator
1094 link = makeAbsPath(linkbuffer, onlyPath(file.absFilename()));
1102 cmd_ret const runCommand(string const & cmd)
1104 // FIXME: replace all calls to RunCommand with ForkedCall
1105 // (if the output is not needed) or the code in ISpell.cpp
1106 // (if the output is needed).
1108 // One question is if we should use popen or
1109 // create our own popen based on fork, exec, pipe
1110 // of course the best would be to have a
1111 // pstream (process stream), with the
1112 // variants ipstream, opstream
1114 #if defined (HAVE_POPEN)
1115 FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1116 #elif defined (HAVE__POPEN)
1117 FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1119 #error No popen() function.
1122 // (Claus Hentschel) Check if popen was succesful ;-)
1124 lyxerr << "RunCommand:: could not start child process" << endl;
1125 return make_pair(-1, string());
1131 ret += static_cast<char>(c);
1135 #if defined (HAVE_PCLOSE)
1136 int const pret = pclose(inf);
1137 #elif defined (HAVE__PCLOSE)
1138 int const pret = _pclose(inf);
1140 #error No pclose() function.
1144 perror("RunCommand:: could not terminate child process");
1146 return make_pair(pret, ret);
1150 FileName const findtexfile(string const & fil, string const & /*format*/)
1152 /* There is no problem to extend this function too use other
1153 methods to look for files. It could be setup to look
1154 in environment paths and also if wanted as a last resort
1155 to a recursive find. One of the easier extensions would
1156 perhaps be to use the LyX file lookup methods. But! I am
1157 going to implement this until I see some demand for it.
1161 // If the file can be found directly, we just return a
1162 // absolute path version of it.
1163 FileName const absfile(makeAbsPath(fil));
1164 if (fs::exists(absfile.toFilesystemEncoding()))
1167 // No we try to find it using kpsewhich.
1168 // It seems from the kpsewhich manual page that it is safe to use
1169 // kpsewhich without --format: "When the --format option is not
1170 // given, the search path used when looking for a file is inferred
1171 // from the name given, by looking for a known extension. If no
1172 // known extension is found, the search path for TeX source files
1174 // However, we want to take advantage of the format sine almost all
1175 // the different formats has environment variables that can be used
1176 // to controll which paths to search. f.ex. bib looks in
1177 // BIBINPUTS and TEXBIB. Small list follows:
1178 // bib - BIBINPUTS, TEXBIB
1180 // graphic/figure - TEXPICTS, TEXINPUTS
1181 // ist - TEXINDEXSTYLE, INDEXSTYLE
1182 // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1184 // tfm - TFMFONTS, TEXFONTS
1185 // This means that to use kpsewhich in the best possible way we
1186 // should help it by setting additional path in the approp. envir.var.
1187 string const kpsecmd = "kpsewhich " + fil;
1189 cmd_ret const c = runCommand(kpsecmd);
1191 LYXERR(Debug::LATEX) << "kpse status = " << c.first << '\n'
1192 << "kpse result = `" << rtrim(c.second, "\n\r")
1195 return FileName(os::internal_path(rtrim(to_utf8(from_filesystem8bit(c.second)),
1202 void removeAutosaveFile(string const & filename)
1204 string a = onlyPath(filename);
1206 a += onlyFilename(filename);
1208 FileName const autosave(a);
1209 if (fs::exists(autosave.toFilesystemEncoding()))
1214 void readBB_lyxerrMessage(FileName const & file, bool & zipped,
1215 string const & message)
1217 LYXERR(Debug::GRAPHICS) << "[readBB_from_PSFile] "
1218 << message << std::endl;
1219 // FIXME: Why is this func deleting a file? (Lgb)
1225 string const readBB_from_PSFile(FileName const & file)
1227 // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1228 // It seems that every command in the header has an own line,
1229 // getline() should work for all files.
1230 // On the other hand some plot programs write the bb at the
1231 // end of the file. Than we have in the header:
1232 // %%BoundingBox: (atend)
1233 // In this case we must check the end.
1234 bool zipped = zippedFile(file);
1235 FileName const file_ = zipped ? unzipFile(file) : file;
1236 string const format = getFormatFromContents(file_);
1238 if (format != "eps" && format != "ps") {
1239 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1243 static boost::regex bbox_re(
1244 "^%%BoundingBox:\\s*([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)\\s+([[:digit:]]+)");
1245 std::ifstream is(file_.toFilesystemEncoding().c_str());
1250 if (regex_match(s, what, bbox_re)) {
1251 // Our callers expect the tokens in the string
1252 // separated by single spaces.
1253 // FIXME: change return type from string to something
1256 os << what.str(1) << ' ' << what.str(2) << ' '
1257 << what.str(3) << ' ' << what.str(4);
1258 string const bb = os.str();
1259 readBB_lyxerrMessage(file_, zipped, bb);
1263 readBB_lyxerrMessage(file_, zipped, "no bb found");
1268 int compare_timestamps(FileName const & filename1, FileName const & filename2)
1270 // If the original is newer than the copy, then copy the original
1271 // to the new directory.
1273 string const file1 = filename1.toFilesystemEncoding();
1274 string const file2 = filename2.toFilesystemEncoding();
1276 if (fs::exists(file1) && fs::exists(file2)) {
1277 double const tmp = difftime(fs::last_write_time(file1),
1278 fs::last_write_time(file2));
1280 cmp = tmp > 0 ? 1 : -1;
1282 } else if (fs::exists(file1)) {
1284 } else if (fs::exists(file2)) {
1291 // the following is adapted from zlib-1.2.3/contrib/minizip.c
1292 // and miniunz.c, except that
1293 // 1. mkdir, makedir is replaced by lyx' own version
1294 // 2. printf is replaced by lyxerr
1297 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1303 WIN32_FIND_DATA ff32;
1305 hFind = FindFirstFile(f,&ff32);
1306 if (hFind != INVALID_HANDLE_VALUE)
1308 FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal);
1309 FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0);
1319 uLong filetime(const char * f, tm_zip * tmzip, uLong * /*dt*/)
1322 struct stat s; /* results of stat() */
1323 struct tm* filedate;
1326 if (strcmp(f,"-")!=0) {
1327 char name[MAXFILENAME+1];
1328 int len = strlen(f);
1329 if (len > MAXFILENAME)
1332 strncpy(name, f,MAXFILENAME-1);
1333 /* strncpy doesnt append the trailing NULL, of the string is too long. */
1334 name[ MAXFILENAME ] = '\0';
1336 if (name[len - 1] == '/')
1337 name[len - 1] = '\0';
1338 /* not all systems allow stat'ing a file with / appended */
1339 if (stat(name,&s)==0) {
1344 filedate = localtime(&tm_t);
1346 tmzip->tm_sec = filedate->tm_sec;
1347 tmzip->tm_min = filedate->tm_min;
1348 tmzip->tm_hour = filedate->tm_hour;
1349 tmzip->tm_mday = filedate->tm_mday;
1350 tmzip->tm_mon = filedate->tm_mon ;
1351 tmzip->tm_year = filedate->tm_year;
1358 uLong filetime(const char * f, tm_zip * tmzip, uLong * dt)
1365 bool zipFiles(DocFileName const & zipfile, vector<pair<string, string> > const & files)
1372 int size_buf = WRITEBUFFERSIZE;
1373 buf = (void*)malloc(size_buf);
1375 lyxerr << "Error allocating memory" << endl;
1378 string const zfile = zipfile.toFilesystemEncoding();
1379 const char * fname = zfile.c_str();
1381 #ifdef USEWIN32IOAPI
1382 zlib_filefunc_def ffunc;
1383 fill_win32_filefunc(&ffunc);
1384 // false: not append
1385 zf = zipOpen2(fname, false, NULL, &ffunc);
1387 zf = zipOpen(fname, false);
1391 lyxerr << "error opening " << zipfile << endl;
1395 for (vector<pair<string, string> >::const_iterator it = files.begin(); it != files.end(); ++it) {
1399 const char * diskfilename = it->first.c_str();
1400 const char * filenameinzip = it->second.c_str();
1401 unsigned long crcFile=0;
1403 zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour =
1404 zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0;
1408 filetime(filenameinzip, &zi.tmz_date, &zi.dosDate);
1410 err = zipOpenNewFileInZip3(zf, filenameinzip, &zi,
1411 NULL,0,NULL,0,NULL /* comment*/,
1413 Z_DEFAULT_COMPRESSION, // compression level
1415 /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */
1416 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
1420 if (err != ZIP_OK) {
1421 lyxerr << "error in opening " << filenameinzip << " in zipfile" << endl;
1424 fin = fopen(diskfilename, "rb");
1426 lyxerr << "error in opening " << diskfilename << " for reading" << endl;
1432 size_read = (int)fread(buf, 1, size_buf, fin);
1433 if (size_read < size_buf)
1435 lyxerr << "error in reading " << filenameinzip << endl;
1440 err = zipWriteInFileInZip (zf, buf, size_read);
1442 lyxerr << "error in writing " << filenameinzip << " in the zipfile" << endl;
1446 } while ((err == ZIP_OK) && (size_read>0));
1451 err = zipCloseFileInZip(zf);
1452 if (err != ZIP_OK) {
1453 lyxerr << "error in closing " << filenameinzip << "in the zipfile" << endl;
1457 errclose = zipClose(zf, NULL);
1458 if (errclose != ZIP_OK) {
1459 lyxerr << "error in closing " << zipfile << endl;
1466 // adapted from miniunz.c
1468 /* change_file_date : change the date/time of a file
1469 filename : the filename of the file where date/time must be modified
1470 dosdate : the new date at the MSDos format (4 bytes)
1471 tmu_date : the SAME new date at the tm_unz format */
1472 void change_file_date(const char * filename, uLong dosdate, tm_unz tmu_date)
1476 FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite;
1478 hFile = CreateFile(filename,GENERIC_READ | GENERIC_WRITE,
1479 0,NULL,OPEN_EXISTING,0,NULL);
1480 GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite);
1481 DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal);
1482 LocalFileTimeToFileTime(&ftLocal,&ftm);
1483 SetFileTime(hFile,&ftm,&ftLastAcc,&ftm);
1490 newdate.tm_sec = tmu_date.tm_sec;
1491 newdate.tm_min=tmu_date.tm_min;
1492 newdate.tm_hour=tmu_date.tm_hour;
1493 newdate.tm_mday=tmu_date.tm_mday;
1494 newdate.tm_mon=tmu_date.tm_mon;
1495 if (tmu_date.tm_year > 1900)
1496 newdate.tm_year=tmu_date.tm_year - 1900;
1498 newdate.tm_year=tmu_date.tm_year ;
1499 newdate.tm_isdst=-1;
1501 ut.actime=ut.modtime=mktime(&newdate);
1502 utime(filename,&ut);
1508 int do_extract_currentfile(unzFile uf,
1509 const int * popt_extract_without_path,
1510 int * popt_overwrite,
1511 const char * password,
1512 const char * dirname)
1514 char filename_inzip[256];
1515 char* filename_withoutpath;
1522 unz_file_info file_info;
1524 err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
1527 lyxerr << "error " << err << " with zipfile in unzGetCurrentFileInfo" << endl;
1531 size_buf = WRITEBUFFERSIZE;
1532 buf = (void*)malloc(size_buf);
1534 lyxerr << "Error allocating memory" << endl;
1535 return UNZ_INTERNALERROR;
1538 p = filename_withoutpath = filename_inzip;
1539 while ((*p) != '\0') {
1540 if (((*p)=='/') || ((*p)=='\\'))
1541 filename_withoutpath = p+1;
1544 // this is a directory
1545 if ((*filename_withoutpath)=='\0') {
1546 if ((*popt_extract_without_path)==0)
1547 makedir(filename_inzip);
1549 // this is a filename
1551 char write_filename[1024];
1553 strcpy(write_filename, dirname);
1554 int len = strlen(write_filename);
1555 if (write_filename[len-1] != '\\' &&
1556 write_filename[len-1] != '/')
1557 strcat(write_filename, "/");
1559 if ((*popt_extract_without_path)==0)
1560 strcat(write_filename, filename_inzip);
1562 strcat(write_filename, filename_withoutpath);
1564 err = unzOpenCurrentFilePassword(uf,password);
1566 lyxerr << "error " << err << " with zipfile in unzOpenCurrentFilePassword" << endl;
1568 fout=fopen(write_filename, "wb");
1570 /* some zipfile don't contain directory alone before file */
1571 if ((fout==NULL) && ((*popt_extract_without_path)==0) &&
1572 (filename_withoutpath!=(char*)filename_inzip)) {
1573 char c=*(filename_withoutpath-1);
1574 *(filename_withoutpath-1)='\0';
1575 makedir(write_filename);
1576 *(filename_withoutpath-1)=c;
1577 fout=fopen(write_filename,"wb");
1581 lyxerr << "error opening " << write_filename << endl;
1586 LYXERR(Debug::FILES) << " extracting: " << write_filename << endl;
1589 err = unzReadCurrentFile(uf,buf,size_buf);
1591 lyxerr << "error " << err << " with zipfile in unzReadCurrentFile" << endl;
1595 if (fwrite(buf,err,1,fout)!=1) {
1596 lyxerr << "error in writing extracted file" << endl;
1605 change_file_date(write_filename,file_info.dosDate,
1606 file_info.tmu_date);
1610 err = unzCloseCurrentFile (uf);
1612 lyxerr << "error " << err << " with zipfile in unzCloseCurrentFile" << endl;
1616 unzCloseCurrentFile(uf); /* don't lose the error */
1624 bool unzipToDir(string const & zipfile, string const & dirname)
1627 #ifdef USEWIN32IOAPI
1628 zlib_filefunc_def ffunc;
1631 const char * zipfilename = zipfile.c_str();
1633 #ifdef USEWIN32IOAPI
1634 fill_win32_filefunc(&ffunc);
1635 uf = unzOpen2(zipfilename, &ffunc);
1637 uf = unzOpen(zipfilename);
1641 lyxerr << "Cannot open " << zipfile << " or " << zipfile << ".zip" << endl;
1649 int opt_extract_without_path = 0;
1650 int opt_overwrite = 1;
1651 char * password = NULL;
1653 err = unzGetGlobalInfo (uf, &gi);
1654 if (err != UNZ_OK) {
1655 lyxerr << "error " << err << " with zipfile in unzGetGlobalInfo " << endl;
1659 for (i=0; i < gi.number_entry; i++) {
1660 if (do_extract_currentfile(uf, &opt_extract_without_path,
1662 password, dirname.c_str()) != UNZ_OK)
1665 if ((i+1)<gi.number_entry) {
1666 err = unzGoToNextFile(uf);
1667 if (err != UNZ_OK) {
1668 lyxerr << "error " << err << " with zipfile in unzGoToNextFile" << endl;;
1674 unzCloseCurrentFile(uf);
1678 } //namespace support